diff --git a/backend/UniversalAdmin.Api/Controllers/DocumentController.cs b/backend/UniversalAdmin.Api/Controllers/DocumentController.cs index 4d75cb890e3fe83142bb30a04caec1e558607acb..c1c632c892fb92af7a3a6162dcfcb50c5378022a 100644 --- a/backend/UniversalAdmin.Api/Controllers/DocumentController.cs +++ b/backend/UniversalAdmin.Api/Controllers/DocumentController.cs @@ -51,12 +51,12 @@ public class DocumentController : ControllerBase } [HttpPost("upload")] - public async Task UploadDocument([FromForm] IFormFile file) + public async Task UploadDocument([FromForm] IFormFile file, [FromForm] string? fileName = null) { try { var userId = GetCurrentUserId(); - var document = await _documentService.UploadDocumentAsync(userId, file); + var document = await _documentService.UploadDocumentAsync(userId, file, fileName); return CreatedAtAction(nameof(GetDocumentById), new { id = document.Id }, document); } catch (Exception ex) @@ -73,16 +73,6 @@ public class DocumentController : ControllerBase var userId = GetCurrentUserId(); Console.WriteLine($"用户 {userId} 尝试删除文档 {id}"); - // 先检查文档是否存在 - var document = await _documentService.GetDocumentByIdAsync(userId, id); - if (document == null) - { - Console.WriteLine($"文档 {id} 不存在或用户 {userId} 无权限访问"); - return NotFound(new { message = "文档不存在或无权限删除" }); - } - - Console.WriteLine($"找到文档: {document.Title}, 开始删除..."); - var success = await _documentService.DeleteDocumentAsync(userId, id); if (!success) { @@ -96,7 +86,16 @@ public class DocumentController : ControllerBase catch (Exception ex) { Console.WriteLine($"删除文档时发生异常: {ex.Message}"); + Console.WriteLine($"异常类型: {ex.GetType().Name}"); Console.WriteLine($"异常堆栈: {ex.StackTrace}"); + + // 如果是数据库相关异常,提供更具体的错误信息 + if (ex.InnerException != null) + { + Console.WriteLine($"内部异常: {ex.InnerException.Message}"); + return BadRequest(new { message = $"删除失败: {ex.InnerException.Message}" }); + } + return BadRequest(new { message = $"删除失败: {ex.Message}" }); } } @@ -133,46 +132,7 @@ public class DocumentController : ControllerBase } } - [HttpPost("{id}/test-delete")] - public async Task TestDeleteDocument(Guid id) - { - try - { - var userId = GetCurrentUserId(); - Console.WriteLine($"测试删除文档 {id},用户 {userId}"); - // 检查文档是否存在 - var document = await _documentService.GetDocumentByIdAsync(userId, id); - if (document == null) - { - return NotFound(new { message = "文档不存在", documentId = id, userId = userId }); - } - - // 检查文档块数量 - var chunks = await _documentService.GetDocumentChunksAsync(userId, id, 1, 1000); - var chunkCount = chunks.TotalCount; - - Console.WriteLine($"文档信息: ID={id}, 标题={document.Title}, 块数量={chunkCount}"); - - return Ok(new - { - message = "文档存在,可以删除", - document = new - { - id = document.Id, - title = document.Title, - uploadedBy = document.UploadedBy - }, - chunkCount = chunkCount, - userId = userId - }); - } - catch (Exception ex) - { - Console.WriteLine($"测试删除时发生异常: {ex.Message}"); - return BadRequest(new { message = ex.Message, documentId = id }); - } - } private Guid GetCurrentUserId() { diff --git a/backend/UniversalAdmin.Api/Program.cs b/backend/UniversalAdmin.Api/Program.cs index 1772a52a1aa282721fada1b4a2b2da3aced62b0f..5e8c568a8bbd6253687e3de8c792484b838d2066 100644 --- a/backend/UniversalAdmin.Api/Program.cs +++ b/backend/UniversalAdmin.Api/Program.cs @@ -216,7 +216,7 @@ builder.Services.ConfigureSwagger(); builder.Services.AddCors(options => options.AddPolicy("AllowFrontend", policy => { - policy.WithOrigins( "http://localhost:5173", + policy.WithOrigins("http://localhost:5173", "http://localhost:3000", "http://localhost:8080", "http://localhost:4173", @@ -225,11 +225,11 @@ builder.Services.AddCors(options => "http://admin-zjp.beiweijierui.xyz:5173", "http://admin-yzy.wudkmao.top:5173", "http://47.122.49.186:5173", - "http://admin-zjp-two.beiweijierui.xyz", - "http://admin-yzy-two.wudkmao.top", - "http://admin-hcx-two.axuege.xyz", - "http://rag.zzaisx.top" - ) + "http://admin-zjp-two.beiweijierui.xyz:3000", + "http://admin-yzy-two.wudkmao.top:3000", + "http://admin-hcx-two.axuege.xyz:3000", + "http://rag.zzaisx.top:3000" + ) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); diff --git a/backend/UniversalAdmin.Application/Services/IDocumentService.cs b/backend/UniversalAdmin.Application/Services/IDocumentService.cs index 88ba8d47f1db466e051aba5b80b8550bbc3d3a02..65357633ec393074c7a9d2cbc8367e78ee88f945 100644 --- a/backend/UniversalAdmin.Application/Services/IDocumentService.cs +++ b/backend/UniversalAdmin.Application/Services/IDocumentService.cs @@ -6,7 +6,7 @@ namespace UniversalAdmin.Application.Services; public interface IDocumentService { - Task UploadDocumentAsync(Guid userId, IFormFile file); + Task UploadDocumentAsync(Guid userId, IFormFile file, string? fileName = null); Task> GetAllDocumentsAsync(Guid userId, int page = 1, int pageSize = 10, string? keyword = null); Task GetDocumentByIdAsync(Guid userId, Guid id); Task DeleteDocumentAsync(Guid userId, Guid id); diff --git a/backend/UniversalAdmin.Infrastructure/Repositories/DocumentChunkRepository.cs b/backend/UniversalAdmin.Infrastructure/Repositories/DocumentChunkRepository.cs index 3e42bdecacd7893da04e5b8f3910c16a23e4470b..ba01269c21b57fbbc850f8dbc7ae889b8b762157 100644 --- a/backend/UniversalAdmin.Infrastructure/Repositories/DocumentChunkRepository.cs +++ b/backend/UniversalAdmin.Infrastructure/Repositories/DocumentChunkRepository.cs @@ -14,7 +14,7 @@ public class DocumentChunkRepository : Repository, IDocumentChunk public async Task> GetByDocumentIdAsync(Guid documentId) { return await _context.DocumentChunks - .Where(dc => dc.DocumentId == documentId && !dc.IsDeleted) + .Where(dc => dc.DocumentId == documentId) .ToListAsync(); } diff --git a/backend/UniversalAdmin.Infrastructure/Repositories/DocumentRepository.cs b/backend/UniversalAdmin.Infrastructure/Repositories/DocumentRepository.cs index ba3d171923d3ba861e6bd54614280de791af8c21..813d232d3774261ffaf20ba8a7f999c4050462af 100644 --- a/backend/UniversalAdmin.Infrastructure/Repositories/DocumentRepository.cs +++ b/backend/UniversalAdmin.Infrastructure/Repositories/DocumentRepository.cs @@ -14,8 +14,8 @@ public class DocumentRepository : Repository, IDocumentRepository public async Task GetByIdWithChunksAsync(Guid id) { return await _context.Documents - .Include(d => d.Chunks.Where(c => !c.IsDeleted)) - .FirstOrDefaultAsync(d => d.Id == id && !d.IsDeleted); + .Include(d => d.Chunks) + .FirstOrDefaultAsync(d => d.Id == id); } public async Task> GetAllWithChunksAsync() @@ -39,6 +39,6 @@ public class DocumentRepository : Repository, IDocumentRepository public IQueryable GetQueryable() { - return _context.Documents.Where(d => !d.IsDeleted).AsQueryable(); + return _context.Documents.AsQueryable(); } } \ No newline at end of file diff --git a/backend/UniversalAdmin.Infrastructure/Services/ChatService.cs b/backend/UniversalAdmin.Infrastructure/Services/ChatService.cs index 1ff5355a4e12481a1898ca1fc6bdbf2123d1e124..be0c095e26d37215a0bbdffc3cc3d523766d36bb 100644 --- a/backend/UniversalAdmin.Infrastructure/Services/ChatService.cs +++ b/backend/UniversalAdmin.Infrastructure/Services/ChatService.cs @@ -141,13 +141,13 @@ public class ChatService : IChatService { var messages = await _messageRepository.GetByConversationIdOrderedAsync(conversationId); var messageDtos = _mapper.Map>(messages); - + // 为每个消息添加空的sources列表(历史消息可能没有保存sources信息) foreach (var messageDto in messageDtos) { messageDto.Sources = new List(); } - + return messageDtos; } @@ -391,7 +391,7 @@ public class ChatService : IChatService // 使用AI服务基于上下文生成智能回答 var aiAnswer = await CallAIWithContextAsync(question, context); Console.WriteLine($"✅ AI服务返回答案: {aiAnswer?.Substring(0, Math.Min(100, aiAnswer?.Length ?? 0))}..."); - return aiAnswer; + return aiAnswer ?? "抱歉,我现在无法基于文档内容回答这个问题。"; } catch (Exception ex) { @@ -487,6 +487,10 @@ public class ChatService : IChatService if (response.IsSuccessStatusCode) { Console.WriteLine("✅ AI服务调用成功,解析响应..."); + if (string.IsNullOrEmpty(responseContent)) + { + throw new Exception("AI服务返回了空的响应内容"); + } var jsonResponse = System.Text.Json.JsonDocument.Parse(responseContent); var aiAnswer = jsonResponse.RootElement .GetProperty("choices")[0] diff --git a/backend/UniversalAdmin.Infrastructure/Services/DocumentService.cs b/backend/UniversalAdmin.Infrastructure/Services/DocumentService.cs index 28f8f9e2947ea2de287f066d1969d58ef47a4640..92f5a25ab519137d5a35a6beffbdfd5e5fbfb12f 100644 --- a/backend/UniversalAdmin.Infrastructure/Services/DocumentService.cs +++ b/backend/UniversalAdmin.Infrastructure/Services/DocumentService.cs @@ -29,15 +29,22 @@ public class DocumentService : IDocumentService _mapper = mapper; } - public async Task UploadDocumentAsync(Guid userId, IFormFile file) + public async Task UploadDocumentAsync(Guid userId, IFormFile file, string? fileName = null) { try { + // 优先使用传入的fileName,如果没有则使用file.FileName + var documentTitle = !string.IsNullOrEmpty(fileName) + ? Path.GetFileNameWithoutExtension(fileName) + : Path.GetFileNameWithoutExtension(file.FileName); + + var documentSource = !string.IsNullOrEmpty(fileName) ? fileName : file.FileName; + var document = new Document { - Title = Path.GetFileNameWithoutExtension(file.FileName), + Title = documentTitle, Content = await ReadFileContentAsync(file), - Source = file.FileName, + Source = documentSource, UploadedBy = userId, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, @@ -76,8 +83,10 @@ public class DocumentService : IDocumentService public async Task> GetAllDocumentsAsync(Guid userId, int page = 1, int pageSize = 10, string? keyword = null) { - var query = _documentRepository.GetQueryable() - .Where(d => d.UploadedBy == userId && !d.IsDeleted); + // 直接查询数据库,不检查软删除状态 + var context = (ApplicationDbContext)_documentRepository.GetContext(); + var query = context.Documents + .Where(d => d.UploadedBy == userId); if (!string.IsNullOrWhiteSpace(keyword)) { @@ -103,7 +112,7 @@ public class DocumentService : IDocumentService public async Task GetDocumentByIdAsync(Guid userId, Guid id) { var document = await _documentRepository.GetByIdWithChunksAsync(id); - if (document == null || document.UploadedBy != userId || document.IsDeleted) + if (document == null || document.UploadedBy != userId) return null; return _mapper.Map(document); @@ -113,53 +122,86 @@ public class DocumentService : IDocumentService { try { - var document = await _documentRepository.GetByIdAsync(id); - if (document == null || document.UploadedBy != userId) - return false; + Console.WriteLine($"开始删除文档,用户ID: {userId}, 文档ID: {id}"); - // 获取数据库上下文以使用事务 + // 直接查询数据库,不检查软删除状态 var context = (ApplicationDbContext)_documentRepository.GetContext(); + var document = await context.Documents + .FirstOrDefaultAsync(d => d.Id == id); + + if (document == null) + { + Console.WriteLine($"文档 {id} 不存在"); + return false; + } + + if (document.UploadedBy != userId) + { + Console.WriteLine($"用户 {userId} 无权限删除文档 {id},文档属于用户 {document.UploadedBy}"); + return false; + } + + Console.WriteLine($"找到文档: {document.Title}, 开始删除..."); - using var transaction = await context.Database.BeginTransactionAsync(); try { - // 软删除相关的文档块 - var chunks = await _documentChunkRepository.GetByDocumentIdAsync(id); - if (chunks.Any()) + // 先检查文档块数量 + var chunkCount = await context.DocumentChunks + .Where(dc => dc.DocumentId == id) + .CountAsync(); + Console.WriteLine($"文档 {id} 有 {chunkCount} 个文档块"); + + // 使用原生SQL删除,避免EF Core的复杂性 + using var transaction = await context.Database.BeginTransactionAsync(); + try { - foreach (var chunk in chunks) - { - chunk.IsDeleted = true; - chunk.UpdatedAt = DateTime.UtcNow; - chunk.UpdateBy = userId.ToString(); - } - context.DocumentChunks.UpdateRange(chunks); - } + // 先删除文档块 + var chunksDeleted = await context.Database.ExecuteSqlRawAsync( + "DELETE FROM document_chunks WHERE \"DocumentId\" = {0}", id); + Console.WriteLine($"删除了 {chunksDeleted} 个文档块"); - // 软删除文档 - document.IsDeleted = true; - document.UpdatedAt = DateTime.UtcNow; - document.UpdateBy = userId.ToString(); - context.Documents.Update(document); + // 再删除文档 + var documentsDeleted = await context.Database.ExecuteSqlRawAsync( + "DELETE FROM documents WHERE \"Id\" = {0} AND \"UploadedBy\" = {1}", id, userId); + Console.WriteLine($"删除了 {documentsDeleted} 个文档"); - // 保存更改 - await context.SaveChangesAsync(); + if (documentsDeleted == 0) + { + Console.WriteLine("没有删除任何文档,可能是权限问题"); + await transaction.RollbackAsync(); + return false; + } - // 提交事务 - await transaction.CommitAsync(); - return true; + await transaction.CommitAsync(); + Console.WriteLine($"文档 {id} 删除成功"); + return true; + } + catch (Exception) + { + await transaction.RollbackAsync(); + throw; + } } catch (Exception ex) { - // 回滚事务 - await transaction.RollbackAsync(); Console.WriteLine($"删除文档失败: {ex.Message}"); + Console.WriteLine($"异常类型: {ex.GetType().Name}"); + Console.WriteLine($"异常堆栈: {ex.StackTrace}"); + + // 如果是数据库约束错误,提供更详细的信息 + if (ex.InnerException != null) + { + Console.WriteLine($"内部异常: {ex.InnerException.Message}"); + } + return false; } } catch (Exception ex) { Console.WriteLine($"删除文档时发生异常: {ex.Message}"); + Console.WriteLine($"异常类型: {ex.GetType().Name}"); + Console.WriteLine($"异常堆栈: {ex.StackTrace}"); return false; } } @@ -172,7 +214,10 @@ public class DocumentService : IDocumentService var chunks = await _documentChunkRepository.GetByDocumentIdAsync(documentId); var totalItems = chunks.Count(); - var pagedChunks = chunks.Skip((page - 1) * pageSize).Take(pageSize); + var pagedChunks = chunks + .OrderBy(c => c.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize); return new PagedResultDto { diff --git "a/frontend/uniapp/README_\344\270\212\344\274\240\347\273\204\344\273\266.md" "b/frontend/uniapp/README_\344\270\212\344\274\240\347\273\204\344\273\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..b5cd4e77155bdda042623689490950c7ae7873c0 --- /dev/null +++ "b/frontend/uniapp/README_\344\270\212\344\274\240\347\273\204\344\273\266.md" @@ -0,0 +1,165 @@ +# 文档上传组件使用说明 + +## 概述 + +为了解决 H5 端和微信小程序端代码冲突的问题,我们创建了三个专门的上传组件: + +1. **DocumentUploadH5.vue** - 专门用于 H5 端的文档上传 +2. **DocumentUploadWeixin.vue** - 专门用于微信小程序端的文档上传 +3. **DocumentUploadSmart.vue** - 智能组件,自动检测平台并选择合适的组件 + +## 组件特点 + +### DocumentUploadH5.vue + +- ✅ **专门为 H5 端优化**:使用原生 HTML5 File API +- ✅ **严格按照后端接口**:完全匹配 `DocumentController.cs` 的上传接口 +- ✅ **FormData 正确构建**:确保文件大小和内容正确传递 +- ✅ **详细调试信息**:完整的日志记录,便于问题排查 +- ✅ **文件验证增强**:严格防止 0KB 文件上传 + +### DocumentUploadWeixin.vue + +- ✅ **专门为微信小程序端优化**:使用 `uni.chooseMessageFile` 和 `uni.uploadFile` +- ✅ **严格按照后端接口**:完全匹配 `DocumentController.cs` 的上传接口 +- ✅ **微信小程序兼容性**:支持多种文件选择方式 +- ✅ **错误处理完善**:详细的错误信息和用户提示 + +### DocumentUploadSmart.vue + +- ✅ **智能平台检测**:自动识别 H5、微信小程序、APP 等平台 +- ✅ **动态组件渲染**:根据平台自动选择合适的上传组件 +- ✅ **统一接口**:提供一致的 API 接口,便于父组件使用 +- ✅ **避免代码冲突**:完全分离不同平台的实现逻辑 + +## 使用方法 + +### 1. 在文档页面中使用智能组件 + +```vue + + + +``` + +### 2. 直接使用特定平台组件 + +如果需要针对特定平台进行定制,可以直接使用对应的组件: + +```vue + + + + + +``` + +## 后端接口对应 + +所有组件都严格按照后端 `DocumentController.cs` 的接口实现: + +```csharp +[HttpPost("upload")] +public async Task UploadDocument([FromForm] IFormFile file, [FromForm] string? fileName = null) +``` + +### H5 端实现 + +```javascript +const formData = new FormData(); +formData.append("file", file.originalFile, file.name); // 对应 [FromForm] IFormFile file +formData.append("fileName", file.name); // 对应 [FromForm] string? fileName = null +``` + +### 微信小程序端实现 + +```javascript +uni.uploadFile({ + url: `${API_BASE_URL}/Document/upload`, + filePath: file.path, + name: "file", // 对应 [FromForm] IFormFile file + formData: { + fileName: file.name, // 对应 [FromForm] string? fileName = null + }, +}); +``` + +## 主要优势 + +1. **代码分离**:H5 端和微信小程序端完全独立,避免冲突 +2. **接口一致**:所有组件都严格按照后端接口实现 +3. **平台优化**:针对不同平台使用最适合的 API +4. **错误处理**:完善的错误处理和用户提示 +5. **调试友好**:详细的日志记录,便于问题排查 +6. **易于维护**:模块化设计,便于后续维护和扩展 + +## 注意事项 + +1. **H5 端**:确保浏览器支持 File API,文件大小验证更严格 +2. **微信小程序端**:支持的文件类型和大小可能受平台限制 +3. **API 地址**:确保 `VITE_API_BASE_URL` 环境变量正确配置 +4. **认证 Token**:确保用户已登录,token 有效 +5. **文件类型**:支持 PDF、Word、TXT、MD 等文档格式 + +## 故障排除 + +### H5 端文件大小为 0KB + +- 检查 FormData 构建是否正确 +- 确认文件对象是否完整 +- 查看浏览器控制台的详细日志 + +### 微信小程序端上传失败 + +- 检查文件路径是否有效 +- 确认 API 地址是否正确 +- 验证 token 是否有效 + +### 通用问题 + +- 查看控制台日志信息 +- 确认网络连接正常 +- 验证后端服务是否正常运行 diff --git a/frontend/uniapp/src/App.vue b/frontend/uniapp/src/App.vue index 49dad18250c94c15296f6e29c4756478e2d0c094..7bc3d8d15452937cc6e5cdffe7ca0fe0b95cd092 100644 --- a/frontend/uniapp/src/App.vue +++ b/frontend/uniapp/src/App.vue @@ -2,7 +2,7 @@ - + @@ -43,6 +43,12 @@ export default { onHide: function () { console.log("App Hide"); }, + onError: function (err) { + console.error("App Error:", err); + }, + onUnhandledRejection: function (res) { + console.error("App Unhandled Rejection:", res); + } }; @@ -55,6 +61,51 @@ page { sans-serif; } +#app { + width: 100%; + height: 100%; +} + +.app-container { + width: 100%; + height: 100%; +} + +/* 全局加载样式 */ +.global-loading { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.loading-spinner { + width: 60rpx; + height: 60rpx; + border: 4rpx solid rgba(255, 255, 255, 0.3); + border-top: 4rpx solid white; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 20rpx; +} + +.loading-text { + color: white; + font-size: 28rpx; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + /* 通用样式类 */ .flex-center { display: flex; diff --git a/frontend/uniapp/src/api/index.js b/frontend/uniapp/src/api/index.js index 580343326d98434b2adb47d0c3ac5038552a3afe..0a977faf6736b5fa31d5e2f65f100bfb40ed9951 100644 --- a/frontend/uniapp/src/api/index.js +++ b/frontend/uniapp/src/api/index.js @@ -16,6 +16,17 @@ const clearToken = () => { uni.removeStorageSync('token'); }; +// 构建查询字符串(兼容小程序) +const buildQueryString = (params) => { + const queryParts = []; + for (const key in params) { + if (params[key] !== undefined && params[key] !== null && params[key] !== '') { + queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`); + } + } + return queryParts.length > 0 ? `?${queryParts.join('&')}` : ''; +}; + // 通用请求方法 const request = async (url, options = {}) => { const token = getToken(); @@ -88,102 +99,162 @@ export const authAPI = { // 文档管理API export const documentAPI = { - // 上传文档 - upload: (file, onProgress) => { + // 上传文档 - 完全按照后端DocumentController接口设计 + upload: (file, progressCallback = null, fileName = null) => { return new Promise((resolve, reject) => { - // H5环境使用FormData直接上传 - if (process.env.UNI_PLATFORM === 'h5') { - // 获取token - const token = getToken(); + const token = getToken(); + + if (!token) { + reject(new Error('未找到认证token,请先登录')); + return; + } + + // #ifdef H5 + // H5端:使用FormData和fetch + if (file.originalFile && file.originalFile instanceof File) { + // 验证文件 + if (file.originalFile.size === 0) { + reject(new Error('文件大小为0,请选择有效的文件')); + return; + } + + // 验证是否为文件夹 + if (file.originalFile.size === 0 && !file.originalFile.name.includes('.')) { + reject(new Error('不能上传文件夹,请选择文件')); + return; + } - // 创建FormData const formData = new FormData(); - formData.append('file', file.file || file, file.name); + formData.append('file', file.originalFile); - // 使用fetch上传 + // 如果提供了fileName,添加到FormData中 + if (fileName) { + formData.append('fileName', fileName); + } + + console.log('H5端上传 - 文件:', file.originalFile); + console.log('H5端上传 - 文件名:', fileName || file.name); + console.log('H5端上传 - FormData内容:'); + for (let [key, value] of formData.entries()) { + console.log(`${key}:`, value); + } + + // H5端模拟进度(因为fetch不支持进度) + if (progressCallback) { + let progress = 0; + const progressInterval = setInterval(() => { + progress += Math.random() * 20; + if (progress >= 100) { + progress = 100; + clearInterval(progressInterval); + } + progressCallback(progress); + }, 100); + } + fetch(`${API_BASE_URL}/document/upload`, { method: 'POST', headers: { - 'Authorization': `Bearer ${token}` + 'Authorization': `Bearer ${token}`, + // 不要手动设置Content-Type,让浏览器自动设置multipart/form-data的boundary }, - body: formData + body: formData, }) - .then(response => response.json()) - .then(data => { - if (data.success || data.code === 200 || data.code === 201 || data.id) { - resolve(data); + .then(response => { + console.log('上传响应状态:', response.status); + console.log('上传响应头:', response.headers); + + if (response.status === 201) { + // 成功:201 Created + return response.json(); + } else if (response.ok) { + // 其他成功状态码 + return response.json(); } else { - reject(new Error(data.message || data.error || '上传失败')); + // 失败状态码 + return response.text().then(text => { + throw new Error(`上传失败 (${response.status}): ${text}`); + }); } }) + .then(data => { + console.log('上传成功,返回数据:', data); + if (progressCallback) progressCallback(100); + resolve(data); + }) .catch(error => { - reject(new Error(error.message || '网络错误')); + console.error('H5端上传失败:', error); + reject(error); }); return; } + // #endif - const token = getToken(); - - // 获取文件路径(兼容不同平台) + // 非H5端或没有originalFile的情况:使用uni.uploadFile let filePath = file.path || file.tempFilePath || file.tempPath; - // 调试日志 - console.log('上传文件信息:', { - file, - filePath, - hasPath: !!file.path, - hasTempFilePath: !!file.tempFilePath, - hasTempPath: !!file.tempPath, - platform: process.env.UNI_PLATFORM - }); - if (!filePath) { - // 尝试从其他属性获取路径 - if (file.filePath) { - filePath = file.filePath; - } else if (typeof file === 'string') { - filePath = file; - } else { - reject(new Error(`文件路径无效,请检查文件选择方式。平台:${process.env.UNI_PLATFORM}`)); - return; - } + reject(new Error(`文件路径无效,请检查文件选择方式。平台:${process.env.UNI_PLATFORM}`)); + return; } + console.log('非H5端上传 - 文件路径:', filePath); + console.log('非H5端上传 - 文件名:', fileName || file.name); + const uploadTask = uni.uploadFile({ url: `${API_BASE_URL}/document/upload`, filePath: filePath, name: 'file', + formData: { + // 如果提供了fileName,添加到formData中 + ...(fileName && { fileName: fileName }) + }, header: { 'Authorization': `Bearer ${token}`, - 'Content-Type': 'multipart/form-data' + // 不要手动设置Content-Type,让uni-app自动设置 }, success: (res) => { - if (res.statusCode === 200 || res.statusCode === 201) { + console.log('uni.uploadFile响应:', res); + + if (res.statusCode === 201) { + // 成功:201 Created + try { + const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; + if (progressCallback) progressCallback(100); + resolve(data); + } catch (e) { + reject(new Error('服务器响应格式错误')); + } + } else if (res.statusCode >= 200 && res.statusCode < 300) { + // 其他成功状态码 try { const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; + if (progressCallback) progressCallback(100); resolve(data); } catch (e) { reject(new Error('服务器响应格式错误')); } } else { + // 失败状态码 try { const errorData = JSON.parse(res.data); - reject(new Error(errorData.message || '上传失败')); + reject(new Error(errorData.message || `上传失败 (${res.statusCode})`)); } catch (e) { - reject(new Error('上传失败')); + reject(new Error(`上传失败 (${res.statusCode}): ${res.data}`)); } } }, fail: (error) => { - console.error('上传失败:', error); + console.error('uni.uploadFile失败:', error); reject(new Error(error.errMsg || '网络错误')); } }); - // 监听上传进度 - if (onProgress && uploadTask) { + // 监听上传进度(微信小程序端) + if (uploadTask && uploadTask.onProgressUpdate && progressCallback) { uploadTask.onProgressUpdate((res) => { - onProgress(res.progress); + console.log('上传进度:', res.progress + '%'); + progressCallback(res.progress); }); } }); @@ -191,13 +262,13 @@ export const documentAPI = { // 获取文档列表 getList: (page = 1, pageSize = 10, keyword = '') => { - const params = new URLSearchParams({ + const params = { page: page.toString(), pageSize: pageSize.toString() - }); - if (keyword) params.append('keyword', keyword); + }; + if (keyword) params.keyword = keyword; - return request(`/document?${params.toString()}`); + return request(`/document${buildQueryString(params)}`); }, // 获取文档详情 @@ -221,6 +292,11 @@ export const documentAPI = { if (res.statusCode === 204) { // 204 No Content 表示删除成功 resolve({ success: true }); + } else if (res.statusCode === 401) { + // token过期,跳转登录 + clearToken(); + uni.reLaunch({ url: '/pages/login/index' }); + reject(new Error('认证失败,请重新登录')); } else if (res.statusCode === 404) { reject(new Error('文档不存在或无权限删除')); } else if (res.statusCode === 400) { @@ -240,12 +316,12 @@ export const documentAPI = { // 获取文档分块信息 getChunks: (id, page = 1, pageSize = 10) => { - const params = new URLSearchParams({ + const params = { page: page.toString(), pageSize: pageSize.toString() - }); + }; - return request(`/document/${id}/chunks?${params.toString()}`); + return request(`/document/${id}/chunks${buildQueryString(params)}`); }, // 重新索引文档 @@ -266,12 +342,12 @@ export const chatAPI = { // 获取对话列表 getConversations: (page = 1, pageSize = 10) => { - const params = new URLSearchParams({ + const params = { page: page.toString(), pageSize: pageSize.toString() - }); + }; - return request(`/chat/conversations?${params.toString()}`); + return request(`/chat/conversations${buildQueryString(params)}`); }, // 发送问题 @@ -303,6 +379,11 @@ export const chatAPI = { if (res.statusCode === 204 || res.statusCode === 200) { // 204 No Content 或 200 OK 都表示删除成功 resolve({ success: true }); + } else if (res.statusCode === 401) { + // token过期,跳转登录 + clearToken(); + uni.reLaunch({ url: '/pages/login/index' }); + reject(new Error('认证失败,请重新登录')); } else if (res.statusCode === 404) { reject(new Error('对话不存在或无权限删除')); } else if (res.statusCode === 400) { diff --git a/frontend/uniapp/src/components/DocumentUpload.vue b/frontend/uniapp/src/components/DocumentUploadH5.vue similarity index 49% rename from frontend/uniapp/src/components/DocumentUpload.vue rename to frontend/uniapp/src/components/DocumentUploadH5.vue index 7ff8f4333b2910379827810703eee53a222d65bc..752f255d0a1895dee36ad3af2a522dd0684f5da6 100644 --- a/frontend/uniapp/src/components/DocumentUpload.vue +++ b/frontend/uniapp/src/components/DocumentUploadH5.vue @@ -31,9 +31,8 @@ + + diff --git a/frontend/uniapp/src/components/DocumentUploadWeixin.vue b/frontend/uniapp/src/components/DocumentUploadWeixin.vue new file mode 100644 index 0000000000000000000000000000000000000000..fce8c738326bcfb466f4383532d43a0ecf704abf --- /dev/null +++ b/frontend/uniapp/src/components/DocumentUploadWeixin.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/frontend/uniapp/src/main.js b/frontend/uniapp/src/main.js index 3f2962088b8997822e12040880f879f269384cba..c1eb17a804d2be5e4dc39e56741c59cb8f0dd3cd 100644 --- a/frontend/uniapp/src/main.js +++ b/frontend/uniapp/src/main.js @@ -6,12 +6,22 @@ export function createApp() { // 全局错误处理 app.config.errorHandler = (err, vm, info) => { - console.error('Vue Error:', err, info) + console.error('Vue Error:', err) + console.error('Error Info:', info) + console.error('Component:', vm) } // 全局警告处理 app.config.warnHandler = (msg, vm, trace) => { - console.warn('Vue Warning:', msg, trace) + console.warn('Vue Warning:', msg) + console.warn('Warning Trace:', trace) + } + + // 添加全局属性 + app.config.globalProperties.$platform = { + isH5: process.env.VUE_APP_PLATFORM === 'h5', + isWeixin: process.env.VUE_APP_PLATFORM === 'mp-weixin', + isApp: process.env.VUE_APP_PLATFORM === 'app-plus' } return { diff --git a/frontend/uniapp/src/manifest.json b/frontend/uniapp/src/manifest.json index ecbdcc988b02c36e51acfc396d2ad9183a5ae343..ea9c3535a202b951db24e354f5acf9a95c49ded6 100644 --- a/frontend/uniapp/src/manifest.json +++ b/frontend/uniapp/src/manifest.json @@ -15,7 +15,12 @@ "autoclose": true, "delay": 0 }, - "modules": {}, + "modules": { + "VideoPlayer": {}, + "OAuth": {}, + "Payment": {}, + "Share": {} + }, "distribute": { "android": { "permissions": [ @@ -31,16 +36,40 @@ "", "", "", - "" - ] + "", + "", + "", + "", + "", + "", + "" + ], + "abiFilters": ["armeabi-v7a", "arm64-v8a"], + "minSdkVersion": 21, + "targetSdkVersion": 30 + }, + "ios": { + "privacyDescription": { + "NSPhotoLibraryUsageDescription": "此应用需要访问相册来选择图片", + "NSCameraUsageDescription": "此应用需要访问相机来拍照", + "NSMicrophoneUsageDescription": "此应用需要访问麦克风来录音", + "NSLocationWhenInUseUsageDescription": "此应用需要访问位置信息" + } }, - "ios": {}, - "sdkConfigs": {} + "sdkConfigs": { + "ad": {}, + "maps": {}, + "oauth": {}, + "payment": {}, + "share": {}, + "speech": {}, + "video": {} + } } }, "quickapp": {}, "mp-weixin": { - "appid": "", + "appid": "wxb2217ad686192c30", "setting": { "urlCheck": false, "es6": true, @@ -79,7 +108,15 @@ "showES6CompileOption": false, "useCompilerPlugins": false }, - "usingComponents": true + "usingComponents": true, + "permission": { + "scope.userLocation": { + "desc": "你的位置信息将用于小程序位置接口的效果展示" + } + }, + "requiredPrivateInfos": [ + "chooseLocation" + ] }, "mp-alipay": { "usingComponents": true diff --git a/frontend/uniapp/src/pages.json b/frontend/uniapp/src/pages.json index da10044f17bf0b74bf8d90f2ec630a95aebc2a80..1187c25ad0809daae064fef5783428eb80b267ba 100644 --- a/frontend/uniapp/src/pages.json +++ b/frontend/uniapp/src/pages.json @@ -55,19 +55,5 @@ "navigationBarTitleText": "VKG AI", "navigationBarBackgroundColor": "#000000", "backgroundColor": "#f8f8f8" - }, - "tabBar": { - "color": "#7A7E83", - "selectedColor": "#3cc51f", - "borderStyle": "black", - "backgroundColor": "#ffffff", - "list": [ - { - "pagePath": "pages/index/index", - "iconPath": "static/VK_MDY_lightmode.png", - "selectedIconPath": "static/VK_MDY_lightmode.png", - "text": "AI对话" - } - ] } } diff --git a/frontend/uniapp/src/pages/documents/detail.vue b/frontend/uniapp/src/pages/documents/detail.vue index a466c60bb0f336777f8f5e1d10e27a58e3b71f66..c4f04cb22c737a856b69b394339badbca1ee2a26 100644 --- a/frontend/uniapp/src/pages/documents/detail.vue +++ b/frontend/uniapp/src/pages/documents/detail.vue @@ -189,20 +189,25 @@ const loadDocumentDetail = async () => { uni.showLoading({ title: '加载中...' }) const response = await documentAPI.getDetail(props.id) + console.log('文档详情API响应:', response) + // 检查不同的响应格式 const documentData = response.data || response if (documentData && documentData.id) { - document.value = { + const processedDocument = { ...documentData, - fileName: documentData.title, + fileName: documentData.title || documentData.Title, fileSize: documentData.content ? documentData.content.length : 0, - uploadedAt: documentData.createdAt, + uploadedAt: documentData.createdAt || documentData.CreatedAt, status: documentData.errorMessage ? 'error' : 'success', - chunkCount: documentData.chunksCount || 0, + chunkCount: documentData.chunksCount || documentData.ChunksCount || 0, formattedSize: formatFileSize(documentData.content ? documentData.content.length : 0), - formattedDate: formatDateTime(documentData.createdAt) + formattedDate: formatDateTime(documentData.createdAt || documentData.CreatedAt) } + + console.log('处理后的文档数据:', processedDocument) + document.value = processedDocument } } catch (error) { console.error('加载文档详情失败:', error) @@ -228,10 +233,12 @@ const loadChunks = async (refresh = false) => { loading.value = true const response = await documentAPI.getChunks(props.id, page.value, pageSize.value) + console.log('分块API响应:', response) + // 检查不同的响应格式 const chunksData = response.data || response - const items = chunksData.items || chunksData.Items || [] - const totalCount = chunksData.totalCount || chunksData.TotalCount || 0 + const items = chunksData.items || chunksData.Items || chunksData.data || [] + const totalCount = chunksData.totalCount || chunksData.TotalCount || chunksData.total || 0 if (items.length > 0 || totalCount > 0) { const formattedItems = items.map(chunk => ({ diff --git a/frontend/uniapp/src/pages/documents/index.vue b/frontend/uniapp/src/pages/documents/index.vue index 2724bae70653d471b3bd506de08744163b6ca71c..cb3a9a6e8014226501c0d747b912d50ee5e611aa 100644 --- a/frontend/uniapp/src/pages/documents/index.vue +++ b/frontend/uniapp/src/pages/documents/index.vue @@ -64,7 +64,7 @@ {{ - truncateText(doc.title, 20) + truncateText(doc.title, 15) }} {{ formatFileSize(doc.content ? doc.content.length : 0) @@ -106,21 +106,15 @@ - - {{ - selectedFile.name - }} - + + @@ -205,6 +199,7 @@ import { formatDateTime, truncateText, } from "@/utils/document.js"; +import DocumentUploadSmart from "@/components/DocumentUploadSmart.vue"; // 响应式数据 const documents = ref([]); @@ -337,114 +332,26 @@ const hideUploadDialog = () => { uploadProgress.value = 0; }; -const selectFile = async () => { - try { - // #ifdef H5 - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".pdf,.doc,.docx,.txt,.md"; - input.onchange = (e) => { - if (e.target.files && e.target.files.length > 0) { - const file = e.target.files[0]; - // H5环境下需要创建兼容的对象 - const compatibleFile = { - name: file.name, - size: file.size, - type: file.type, - path: file.name, // H5使用文件名作为标识 - file: file, // 保存原始文件对象,供API使用 - }; - console.log("H5文件选择结果:", compatibleFile); - selectedFile.value = compatibleFile; - } - }; - input.click(); - // #endif - - // #ifdef MP-WEIXIN || APP-PLUS - const res = await uni.chooseFile({ - count: 1, - type: "all", - extension: [".pdf", ".doc", ".docx", ".txt", ".md"], - }); - if (res.tempFiles && res.tempFiles.length > 0) { - selectedFile.value = res.tempFiles[0]; - } - // #endif - } catch (error) { - console.error("选择文件失败:", error); - } +const handleUploadSuccess = (file) => { + console.log("上传成功:", file); + uni.showToast({ + title: "上传成功", + icon: "success", + }); + hideUploadDialog(); + loadDocuments(true); }; -const uploadFile = async () => { - if (!selectedFile.value) return; - - try { - uploading.value = true; - uploadProgress.value = 0; - - console.log("开始上传文件:", selectedFile.value); - - // #ifdef H5 - // H5环境使用FormData直接上传 - const formData = new FormData(); - formData.append( - "file", - selectedFile.value.file || selectedFile.value, - selectedFile.value.name - ); - - const token = uni.getStorageSync("token"); - const API_BASE_URL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:5292/api/v1"; - - const fetchResponse = await fetch(`${API_BASE_URL}/document/upload`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - }, - body: formData, - }); +const handleUploadError = (error) => { + console.error("上传失败:", error); + uni.showToast({ + title: `上传失败: ${error.message}`, + icon: "none", + }); +}; - const response = await fetchResponse.json(); - if (!fetchResponse.ok) { - throw new Error(response.message || response.error || "上传失败"); - } - // #endif - - // #ifndef H5 - // 小程序/APP使用原生上传 - const response = await documentAPI.upload(selectedFile.value); - // #endif - - console.log("上传响应:", response); - - // 检查不同的成功响应格式 - // 如果响应包含文档ID,说明上传成功 - if ( - response.success || - response.code === 200 || - response.code === 201 || - response.id - ) { - uni.showToast({ - title: "上传成功", - icon: "success", - }); - hideUploadDialog(); - loadDocuments(true); - } else { - throw new Error(response.message || response.error || "上传失败"); - } - } catch (error) { - console.error("上传失败:", error); - uni.showToast({ - title: `上传失败: ${error.message}`, - icon: "none", - }); - } finally { - uploading.value = false; - } +const handleUploadProgress = (progress) => { + uploadProgress.value = progress; }; // 文档操作 @@ -588,10 +495,13 @@ const deleteDocument = async () => { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; flex-direction: column; + /* 微信小程序兼容性 */ + box-sizing: border-box; + overflow-x: hidden; } .header { - padding: 40rpx 40rpx 20rpx; + padding: 40rpx 20rpx 20rpx; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(20rpx); } @@ -636,7 +546,7 @@ const deleteDocument = async () => { } .search-section { - padding: 20rpx 40rpx; + padding: 20rpx 20rpx; } .search-bar { @@ -674,7 +584,7 @@ const deleteDocument = async () => { .documents-list { flex: 1; - padding: 0 40rpx; + padding: 0 20rpx; } .empty-state { @@ -707,15 +617,15 @@ const deleteDocument = async () => { .documents-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300rpx, 1fr)); - gap: 30rpx; - padding-bottom: 40rpx; + grid-template-columns: repeat(auto-fill, minmax(280rpx, 1fr)); + gap: 20rpx; + padding: 20rpx 100rpx 40rpx 100rpx; } .document-card { background: rgba(255, 255, 255, 0.2); border-radius: 20rpx; - padding: 30rpx; + padding: 20rpx; position: relative; backdrop-filter: blur(10rpx); transition: all 0.3s ease; @@ -744,6 +654,14 @@ const deleteDocument = async () => { font-weight: bold; color: white; margin-bottom: 10rpx; + word-break: break-all; + line-height: 1.3; + max-height: 2.6em; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } .document-size, @@ -752,6 +670,9 @@ const deleteDocument = async () => { font-size: 24rpx; color: rgba(255, 255, 255, 0.7); margin-bottom: 5rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .document-actions { @@ -838,7 +759,7 @@ const deleteDocument = async () => { .detail-dialog { background: white; border-radius: 20rpx; - width: 80%; + width: 90%; max-width: 600rpx; max-height: 80vh; overflow-y: auto; @@ -956,4 +877,75 @@ const deleteDocument = async () => { color: #666; flex: 1; } + +/* 微信小程序特定优化 */ +@media screen and (max-width: 750rpx) { + .documents-grid { + grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr)); + gap: 15rpx; + } + + .document-card { + padding: 15rpx; + } + + .header { + padding: 30rpx 15rpx 15rpx; + } + + .search-section { + padding: 15rpx 15rpx; + } + + .documents-list { + padding: 0 15rpx; + } + + .document-name { + font-size: 26rpx; + max-height: 2.4em; + } +} + +/* 超小屏幕优化 */ +@media screen and (max-width: 600rpx) { + .documents-grid { + grid-template-columns: repeat(auto-fill, minmax(200rpx, 1fr)); + gap: 10rpx; + } + + .document-card { + padding: 12rpx; + } + + .document-name { + font-size: 26rpx; + } + + .document-size, + .document-date { + font-size: 22rpx; + } +} + +/* 微信小程序特定样式 */ +.upload-dialog, +.actions-dialog, +.detail-dialog { + /* 确保在微信小程序中正确显示 */ + transform: translateZ(0); + -webkit-transform: translateZ(0); +} + +/* 防止微信小程序中的滚动问题 */ +.documents-list { + -webkit-overflow-scrolling: touch; +} + +/* 优化微信小程序中的按钮点击效果 */ +.upload-btn, +.action-btn, +.close-btn { + -webkit-tap-highlight-color: transparent; +} diff --git a/frontend/uniapp/src/pages/index/index.vue b/frontend/uniapp/src/pages/index/index.vue index 70c8f7728a2255c18dcf6eecbc9d2eca97966db0..2b3b4aa4cc794f70821e7e9676dc897aa2482f96 100644 --- a/frontend/uniapp/src/pages/index/index.vue +++ b/frontend/uniapp/src/pages/index/index.vue @@ -4,16 +4,22 @@ - + + + VKG AI @@ -30,22 +36,43 @@ - 💬 + + + 开始对话 与AI助手进行智能问答 - 📚 + + + + + + + 文档管理 管理您的文档和查看分析结果 - 🗂️ + + + 对话历史 查看之前的对话记录 + + + + + + + + + 上传文档 + 上传文档进行智能分析 + @@ -235,6 +262,13 @@ import { ref, reactive, onMounted, nextTick } from "vue"; import { chatAPI, documentAPI } from "@/api/index"; import { useUserStore } from "@/stores/user"; +// API基础URL - 确保URL格式完全正确 +const API_BASE_URL = "http://localhost:5292/api/v1"; + +// 调试:打印API基础URL +console.log("API_BASE_URL:", API_BASE_URL); +console.log("环境变量:", import.meta.env); + // 响应式数据 const showChat = ref(false); const loading = ref(false); @@ -259,26 +293,26 @@ onMounted(() => { }); // 检查认证状态 - const checkAuth = () => { - // 检查是否为游客模式 - if (userStore.isGuestMode()) { - const guestUser = userStore.getGuestInfo(); - if (guestUser) { - userInfo.value = { - username: guestUser.username, - isGuest: true, - }; - - // 显示游客模式提示框 - uni.showModal({ - title: '游客模式', - content: '当前为游客模式,如果要使用对话功能,请点击右上角退出注册账号', - showCancel: false, - confirmText: '我知道了' - }); - } - return; +const checkAuth = () => { + // 检查是否为游客模式 + if (userStore.isGuestMode()) { + const guestUser = userStore.getGuestInfo(); + if (guestUser) { + userInfo.value = { + username: guestUser.username, + isGuest: true, + }; + + // 显示游客模式提示框 + uni.showModal({ + title: "游客模式", + content: "当前为游客模式,如果要使用对话功能,请点击右上角退出注册账号", + showCancel: false, + confirmText: "我知道了", + }); } + return; + } // 检查正式登录状态 if (!userStore.isLoggedIn()) { @@ -451,50 +485,204 @@ const selectFile = async () => { input.accept = ".pdf,.doc,.docx,.txt,.md"; input.onchange = (e) => { if (e.target.files && e.target.files.length > 0) { - selectedFile.value = e.target.files[0]; + const file = e.target.files[0]; + console.log("H5文件选择结果:", { + name: file.name, + size: file.size, + type: file.type, + // 不要使用file作为path,这可能导致错误的URL生成 + path: file.name, // 只使用文件名作为路径 + tempFilePath: file.name, + // 添加原始File对象引用 + originalFile: file, + }); + + // 为H5端创建兼容的文件对象 + selectedFile.value = { + name: file.name, + size: file.size, + type: file.type, + // 使用文件名作为路径,避免生成错误的URL + path: file.name, + tempFilePath: file.name, + // 添加原始File对象引用 + originalFile: file, + }; } }; input.click(); // #endif // #ifdef MP-WEIXIN || APP-PLUS - const res = await uni.chooseFile({ - count: 1, - type: "all", - extension: [".pdf", ".doc", ".docx", ".txt", ".md"], - }); - if (res.tempFiles && res.tempFiles.length > 0) { - selectedFile.value = res.tempFiles[0]; + try { + const res = await uni.chooseMessageFile({ + count: 1, + type: "file", + extension: ["pdf", "doc", "docx", "txt", "md"], + }); + if (res.tempFiles && res.tempFiles.length > 0) { + const file = res.tempFiles[0]; + console.log("微信小程序文件选择结果:", file); + + // 为微信小程序端创建兼容的文件对象 + // 微信小程序的文件对象需要特殊处理,确保有正确的文件名 + selectedFile.value = { + name: + file.name || file.path?.split("/").pop() || `文件_${Date.now()}`, + size: file.size, + type: file.type || "application/octet-stream", + path: file.path, + tempFilePath: file.path, + // 微信小程序特有的属性 + originalFile: file, + }; + + console.log("处理后的文件对象:", selectedFile.value); + } + } catch (chooseError) { + console.log("chooseMessageFile失败,尝试chooseFile:", chooseError); + // 如果chooseMessageFile失败,尝试使用chooseFile + const res = await uni.chooseFile({ + count: 1, + type: "all", + extension: [".pdf", ".doc", ".docx", ".txt", ".md"], + }); + if (res.tempFiles && res.tempFiles.length > 0) { + const file = res.tempFiles[0]; + console.log("微信小程序chooseFile结果:", file); + + // 为微信小程序端创建兼容的文件对象 + selectedFile.value = { + name: + file.name || file.path?.split("/").pop() || `文件_${Date.now()}`, + size: file.size, + type: file.type || "application/octet-stream", + path: file.path, + tempFilePath: file.path, + // 微信小程序特有的属性 + originalFile: file, + }; + + console.log("处理后的文件对象:", selectedFile.value); + } } // #endif } catch (error) { console.error("选择文件失败:", error); + uni.showToast({ + title: "选择文件失败", + icon: "none", + }); } }; -// 上传文件 +// 上传文件 - 完全按照后端DocumentController接口设计 const uploadFile = async () => { if (!selectedFile.value) return; try { uploading.value = true; + console.log("开始上传文件:", selectedFile.value); + + // 检查用户登录状态 + if (!userStore.isLoggedIn()) { + uploading.value = false; + uni.showModal({ + title: "需要登录", + content: "上传文档需要登录账号,请先登录", + confirmText: "去登录", + cancelText: "取消", + success: (res) => { + if (res.confirm) { + uni.navigateTo({ + url: "/pages/login/index", + }); + } + }, + }); + return; + } - const response = await documentAPI.upload(selectedFile.value); + // 检查token是否存在 + const token = uni.getStorageSync("token"); + if (!token) { + uploading.value = false; + uni.showToast({ + title: "认证失败,请重新登录", + icon: "none", + }); + return; + } + + console.log("用户已登录,Token:", token); + console.log("用户信息:", userInfo.value); + console.log("准备上传文件:", { + name: selectedFile.value.name, + size: selectedFile.value.size, + type: selectedFile.value.type, + hasOriginalFile: !!selectedFile.value.originalFile, + }); + + // 调用documentAPI.upload方法 + // 传递文件名作为第二个参数(对应后端的fileName参数) + // 确保文件名正确,微信小程序端可能需要特殊处理 + const fileName = + selectedFile.value.name || + selectedFile.value.path?.split("/").pop() || + `文件_${Date.now()}`; + console.log("最终使用的文件名:", fileName); + + const response = await documentAPI.upload(selectedFile.value, fileName); + + console.log("上传成功,返回结果:", response); + + // 检查返回结果 - 后端返回的是文档对象,包含id字段 + if (response && (response.id || response.Id)) { + uni.showToast({ + title: "上传成功", + icon: "success", + duration: 2000, + }); - if (response.success) { + // 延迟关闭对话框,让用户看到成功提示 + setTimeout(() => { + hideUploadDialog(); + selectedFile.value = null; + }, 1500); + } else { + console.warn("返回数据结构不符合预期:", response); + // 即使数据结构不符合预期,如果API调用成功,仍然认为上传成功 uni.showToast({ title: "上传成功", icon: "success", + duration: 2000, }); - // 关闭对话框 - hideUploadDialog(); + setTimeout(() => { + hideUploadDialog(); + selectedFile.value = null; + }, 1500); } } catch (error) { console.error("上传失败:", error); + + let errorTitle = "上传失败,请重试"; + + if (typeof error === "string") { + errorTitle = error; + } else if (error && error.message) { + errorTitle = error.message.replace(/^Error:\s*/i, ""); + } + + // 限制错误信息长度 + if (errorTitle.length > 50) { + errorTitle = errorTitle.substring(0, 50) + "..."; + } + uni.showToast({ - title: "上传失败,请重试", + title: errorTitle, icon: "none", + duration: 3000, }); } finally { uploading.value = false; @@ -516,6 +704,7 @@ const formatTime = (time) => {