Compare commits
No commits in common. "29d0950c6f95c3d4d4ae4d5fde9b3e621db10e34" and "442e3a2e5714d871104f53e19af04b59c9f5edbc" have entirely different histories.
29d0950c6f
...
442e3a2e57
|
|
@ -1,8 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<annotationProcessing>
|
|
||||||
<profile name="Maven default annotation processors profile" enabled="true">
|
|
||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
|
||||||
<outputRelativeToContentRoot value="true" />
|
|
||||||
<module name="medical-info-system" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
|
||||||
</component>
|
|
||||||
<component name="JavacSettings">
|
|
||||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
|
||||||
<module name="medical-info-system" options="-parameters" />
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding">
|
|
||||||
<file url="file://$PROJECT_DIR$/backend/src/main/java" charset="UTF-8" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RemoteRepositoriesConfiguration">
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Central Repository" />
|
|
||||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Maven Central repository" />
|
|
||||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="jboss.community" />
|
|
||||||
<option name="name" value="JBoss Community repository" />
|
|
||||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
|
||||||
</remote-repository>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="MavenProjectsManager">
|
|
||||||
<option name="originalFiles">
|
|
||||||
<list>
|
|
||||||
<option value="$PROJECT_DIR$/backend/pom.xml" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/Ipsen.iml" filepath="$PROJECT_DIR$/.idea/Ipsen.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package com.ipsen.medical.config;
|
|
||||||
|
|
||||||
import com.ipsen.medical.entity.User;
|
|
||||||
import com.ipsen.medical.repository.UserRepository;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DataInitializer {
|
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
|
||||||
|
|
||||||
@Value("${APP_ADMIN_USERNAME:admin}")
|
|
||||||
private String defaultAdminUsername;
|
|
||||||
|
|
||||||
@Value("${APP_ADMIN_PASSWORD:admin123}")
|
|
||||||
private String defaultAdminPassword;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public CommandLineRunner ensureDefaultAdminUser() {
|
|
||||||
return args -> {
|
|
||||||
User admin = userRepository.findByUsername(defaultAdminUsername).orElse(null);
|
|
||||||
if (admin == null) {
|
|
||||||
admin = new User();
|
|
||||||
admin.setUsername(defaultAdminUsername);
|
|
||||||
admin.setFullName("系统管理员");
|
|
||||||
admin.setEmail("admin@ipsen.com");
|
|
||||||
admin.setRole(User.UserRole.ADMIN);
|
|
||||||
admin.setEnabled(true);
|
|
||||||
admin.setCreatedAt(LocalDateTime.now());
|
|
||||||
admin.setPassword(defaultAdminPassword);
|
|
||||||
userRepository.save(admin);
|
|
||||||
} else {
|
|
||||||
// If password looks like a placeholder bcrypt string, reset to default plain text
|
|
||||||
String existing = admin.getPassword() == null ? "" : admin.getPassword();
|
|
||||||
boolean looksBcrypt = existing.startsWith("$2a$") || existing.startsWith("$2b$") || existing.startsWith("$2y$");
|
|
||||||
if (looksBcrypt) {
|
|
||||||
admin.setPassword(defaultAdminPassword);
|
|
||||||
userRepository.save(admin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,6 +4,8 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
|
@ -13,8 +15,6 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.ipsen.medical.security.PlainTextPasswordEncoder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security配置
|
* Spring Security配置
|
||||||
*/
|
*/
|
||||||
|
|
@ -27,7 +27,9 @@ public class SecurityConfig {
|
||||||
http
|
http
|
||||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
.csrf(csrf -> csrf.disable())
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(authz -> authz
|
.authorizeHttpRequests(authz -> authz
|
||||||
|
// 允许所有请求
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
.httpBasic(httpBasic -> httpBasic.disable())
|
.httpBasic(httpBasic -> httpBasic.disable())
|
||||||
|
|
@ -51,7 +53,7 @@ public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new PlainTextPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
@ -59,5 +61,4 @@ public class SecurityConfig {
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)); // 10MB buffer
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)); // 10MB buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
package com.ipsen.medical.controller;
|
|
||||||
|
|
||||||
import com.ipsen.medical.dto.ApiResponse;
|
|
||||||
import com.ipsen.medical.entity.User;
|
|
||||||
import com.ipsen.medical.repository.UserRepository;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/auth")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@CrossOrigin(origins = "*")
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
|
||||||
private final PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ResponseEntity<ApiResponse<AuthResponse>> login(@RequestBody LoginRequest request) {
|
|
||||||
Optional<User> optional = userRepository.findByUsername(request.getUsername());
|
|
||||||
if (!optional.isPresent()) {
|
|
||||||
return ResponseEntity.status(401).body(ApiResponse.error("用户不存在"));
|
|
||||||
}
|
|
||||||
User user = optional.get();
|
|
||||||
if (!Boolean.TRUE.equals(user.getEnabled())) {
|
|
||||||
return ResponseEntity.status(403).body(ApiResponse.error("用户已禁用"));
|
|
||||||
}
|
|
||||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
|
||||||
return ResponseEntity.status(401).body(ApiResponse.error("用户名或密码错误"));
|
|
||||||
}
|
|
||||||
user.setLastLoginAt(LocalDateTime.now());
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
AuthResponse resp = new AuthResponse();
|
|
||||||
resp.setToken("");
|
|
||||||
resp.setUsername(user.getUsername());
|
|
||||||
resp.setFullName(user.getFullName());
|
|
||||||
resp.setRole(user.getRole().name());
|
|
||||||
return ResponseEntity.ok(ApiResponse.success(resp));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class LoginRequest {
|
|
||||||
private String username;
|
|
||||||
private String password;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class AuthResponse {
|
|
||||||
private String token;
|
|
||||||
private String username;
|
|
||||||
private String fullName;
|
|
||||||
private String role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,7 +73,3 @@ public class DownloadTaskController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,17 +205,6 @@ public class InquiryController {
|
||||||
InquiryRequestDTO result = autoSearchService.executeFullWorkflow(id);
|
InquiryRequestDTO result = autoSearchService.executeFullWorkflow(id);
|
||||||
return ResponseEntity.ok(ApiResponse.success(result));
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据选中的检索结果生成下载任务
|
|
||||||
*/
|
|
||||||
@PostMapping("/{id}/downloads")
|
|
||||||
public ResponseEntity<ApiResponse<InquiryRequestDTO>> generateDownloadTasks(
|
|
||||||
@PathVariable Long id,
|
|
||||||
@RequestBody List<Long> searchResultItemIds) {
|
|
||||||
InquiryRequestDTO result = inquiryService.generateDownloadTasks(id, searchResultItemIds);
|
|
||||||
return ResponseEntity.ok(ApiResponse.success(result));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,3 @@ public class SearchResultController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,3 @@ public class ClinicalTrialDTO {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,3 @@ public class ClinicalTrialsSearchResult {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,3 @@ public class DataSourceSelectionDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,3 @@ public class KeywordConfirmationDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,3 @@ public class ResponseGenerationDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,3 @@ public class SearchResultItemDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,3 @@ public class UserDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,14 @@ public class InquiryRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RequestStatus {
|
public enum RequestStatus {
|
||||||
CREATED, // 已创建
|
PENDING, // 待处理
|
||||||
SEARCHING, // 检索中(提取关键词成功后进入)
|
KEYWORD_EXTRACTED, // 关键词已提取
|
||||||
SEARCHED, // 已检索(有检索结果)
|
SEARCHING, // 检索中
|
||||||
RESPONDING, // 回复中(生成回复后)
|
SEARCH_COMPLETED, // 检索完成
|
||||||
COMPLETED // 已完成(邮件发送后人工标记)
|
UNDER_REVIEW, // 审核中
|
||||||
|
DOWNLOADING, // 下载文献中
|
||||||
|
COMPLETED, // 已完成
|
||||||
|
REJECTED // 已拒绝
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,3 @@ public class SearchResultItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,3 @@ public interface ClinicalTrialRepository extends JpaRepository<ClinicalTrial, Lo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,3 @@ public interface SearchResultItemRepository extends JpaRepository<SearchResultIt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package com.ipsen.medical.security;
|
|
||||||
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
|
|
||||||
public class PlainTextPasswordEncoder implements PasswordEncoder {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encode(CharSequence rawPassword) {
|
|
||||||
return rawPassword == null ? null : rawPassword.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
|
||||||
String raw = rawPassword == null ? null : rawPassword.toString();
|
|
||||||
return raw == null ? encodedPassword == null : raw.equals(encodedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean upgradeEncoding(String encodedPassword) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,7 +52,3 @@ public interface ClinicalTrialsService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,6 @@ public interface InquiryService {
|
||||||
* 完成查询请求
|
* 完成查询请求
|
||||||
*/
|
*/
|
||||||
InquiryRequestDTO completeInquiry(Long id);
|
InquiryRequestDTO completeInquiry(Long id);
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据选中的检索结果生成下载任务,并更新状态为DOWNLOADING
|
|
||||||
*/
|
|
||||||
InquiryRequestDTO generateDownloadTasks(Long id, List<Long> searchResultItemIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,3 @@ public interface MultiSourceSearchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,3 @@ public interface SearchResultService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,3 @@ public interface UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ public class AutoSearchServiceImpl implements AutoSearchService {
|
||||||
|
|
||||||
// 5. 更新查询请求
|
// 5. 更新查询请求
|
||||||
inquiry.setSearchResults(searchResultsJson);
|
inquiry.setSearchResults(searchResultsJson);
|
||||||
inquiry.setStatus(InquiryRequest.RequestStatus.SEARCHED);
|
inquiry.setStatus(InquiryRequest.RequestStatus.SEARCH_COMPLETED);
|
||||||
inquiry.setUpdatedAt(LocalDateTime.now());
|
inquiry.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedInquiry = inquiryRequestRepository.save(inquiry);
|
InquiryRequest savedInquiry = inquiryRequestRepository.save(inquiry);
|
||||||
|
|
@ -114,7 +114,7 @@ public class AutoSearchServiceImpl implements AutoSearchService {
|
||||||
KeywordExtractionResult keywords = difyService.extractKeywordsStructured(inquiry.getInquiryContent());
|
KeywordExtractionResult keywords = difyService.extractKeywordsStructured(inquiry.getInquiryContent());
|
||||||
String keywordsJson = objectMapper.writeValueAsString(keywords);
|
String keywordsJson = objectMapper.writeValueAsString(keywords);
|
||||||
inquiry.setKeywords(keywordsJson);
|
inquiry.setKeywords(keywordsJson);
|
||||||
inquiry.setStatus(InquiryRequest.RequestStatus.SEARCHING);
|
inquiry.setStatus(InquiryRequest.RequestStatus.KEYWORD_EXTRACTED);
|
||||||
inquiry.setUpdatedAt(LocalDateTime.now());
|
inquiry.setUpdatedAt(LocalDateTime.now());
|
||||||
inquiryRequestRepository.save(inquiry);
|
inquiryRequestRepository.save(inquiry);
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ public class AutoSearchServiceImpl implements AutoSearchService {
|
||||||
inquiry.getSearchResults()
|
inquiry.getSearchResults()
|
||||||
);
|
);
|
||||||
inquiry.setResponseContent(response);
|
inquiry.setResponseContent(response);
|
||||||
inquiry.setStatus(InquiryRequest.RequestStatus.RESPONDING);
|
inquiry.setStatus(InquiryRequest.RequestStatus.UNDER_REVIEW);
|
||||||
inquiry.setUpdatedAt(LocalDateTime.now());
|
inquiry.setUpdatedAt(LocalDateTime.now());
|
||||||
inquiryRequestRepository.save(inquiry);
|
inquiryRequestRepository.save(inquiry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -30,9 +31,6 @@ public class DifyServiceImpl implements DifyService {
|
||||||
@Value("${app.dify.api-key}")
|
@Value("${app.dify.api-key}")
|
||||||
private String difyApiKey;
|
private String difyApiKey;
|
||||||
|
|
||||||
@Value("${app.dify.app-type:workflow}")
|
|
||||||
private String difyAppType;
|
|
||||||
|
|
||||||
private final WebClient webClient;
|
private final WebClient webClient;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
|
@ -48,79 +46,50 @@ public class DifyServiceImpl implements DifyService {
|
||||||
public KeywordExtractionResult extractKeywordsStructured(String content) {
|
public KeywordExtractionResult extractKeywordsStructured(String content) {
|
||||||
log.info("Extracting keywords structured from content");
|
log.info("Extracting keywords structured from content");
|
||||||
|
|
||||||
|
// 检查API Key配置
|
||||||
|
if (difyApiKey == null || difyApiKey.equals("your-dify-api-key-here") || difyApiKey.trim().isEmpty()) {
|
||||||
|
log.error("Dify API Key not configured properly. Current value: {}",
|
||||||
|
difyApiKey != null ? difyApiKey.substring(0, Math.min(20, difyApiKey.length())) + "..." : "null");
|
||||||
|
throw new RuntimeException("Dify API Key未配置。请设置环境变量DIFY_API_KEY或修改application.yml中的配置。");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String response = callDifyAPI(content, "extract_keywords");
|
String response = callDifyAPI(content, "extract_keywords");
|
||||||
|
|
||||||
// 解析响应获取结构化数据(兼容 Workflow 与 Chat 返回结构)
|
// 解析响应获取结构化数据
|
||||||
JsonNode rootNode = objectMapper.readTree(response);
|
JsonNode rootNode = objectMapper.readTree(response);
|
||||||
KeywordExtractionResult result = new KeywordExtractionResult();
|
JsonNode answerNode = rootNode.path("answer");
|
||||||
|
|
||||||
boolean mapped = false;
|
String answer;
|
||||||
|
if (answerNode.isTextual()) {
|
||||||
// 优先:Workflow 返回,一般为 { data: { outputs: { ... } } } 或 { data: { ... } }
|
answer = answerNode.asText();
|
||||||
if (rootNode.has("data")) {
|
} else {
|
||||||
JsonNode dataNode = rootNode.get("data");
|
answer = answerNode.toString();
|
||||||
JsonNode outputsNode = dataNode.has("outputs") ? dataNode.get("outputs") : dataNode;
|
|
||||||
|
|
||||||
// 直接包含目标字段
|
|
||||||
if (outputsNode.has("drugNameChinese") || outputsNode.has("drugNameEnglish") || outputsNode.has("requestItem")) {
|
|
||||||
result.setDrugNameChinese(outputsNode.path("drugNameChinese").asText(null));
|
|
||||||
result.setDrugNameEnglish(outputsNode.path("drugNameEnglish").asText(null));
|
|
||||||
result.setRequestItem(outputsNode.path("requestItem").asText(null));
|
|
||||||
mapped = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 某些工作流可能将结构化结果放在 result 字段
|
// 尝试解析answer字段中的JSON
|
||||||
if (!mapped && outputsNode.has("result")) {
|
KeywordExtractionResult result;
|
||||||
JsonNode inner = outputsNode.get("result");
|
|
||||||
if (inner.isTextual()) {
|
|
||||||
try {
|
|
||||||
JsonNode parsed = objectMapper.readTree(inner.asText());
|
|
||||||
result.setDrugNameChinese(parsed.path("drugNameChinese").asText(null));
|
|
||||||
result.setDrugNameEnglish(parsed.path("drugNameEnglish").asText(null));
|
|
||||||
result.setRequestItem(parsed.path("requestItem").asText(null));
|
|
||||||
mapped = true;
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
// 非JSON文本则尝试从文本解析
|
|
||||||
result = parseKeywordsFromText(inner.asText());
|
|
||||||
mapped = true;
|
|
||||||
}
|
|
||||||
} else if (inner.isObject()) {
|
|
||||||
result.setDrugNameChinese(inner.path("drugNameChinese").asText(null));
|
|
||||||
result.setDrugNameEnglish(inner.path("drugNameEnglish").asText(null));
|
|
||||||
result.setRequestItem(inner.path("requestItem").asText(null));
|
|
||||||
mapped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容 Chat 返回:通常 { answer: "...可能是JSON或文本...", metadata: {...} }
|
|
||||||
if (!mapped && rootNode.has("answer")) {
|
|
||||||
JsonNode answerNode = rootNode.get("answer");
|
|
||||||
String answer = answerNode.isTextual() ? answerNode.asText() : answerNode.toString();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 如果answer是JSON格式
|
||||||
JsonNode resultNode = objectMapper.readTree(answer);
|
JsonNode resultNode = objectMapper.readTree(answer);
|
||||||
|
result = new KeywordExtractionResult();
|
||||||
result.setDrugNameChinese(resultNode.path("drugNameChinese").asText(null));
|
result.setDrugNameChinese(resultNode.path("drugNameChinese").asText(null));
|
||||||
result.setDrugNameEnglish(resultNode.path("drugNameEnglish").asText(null));
|
result.setDrugNameEnglish(resultNode.path("drugNameEnglish").asText(null));
|
||||||
result.setRequestItem(resultNode.path("requestItem").asText(null));
|
result.setRequestItem(resultNode.path("requestItem").asText(null));
|
||||||
mapped = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Answer is not JSON format, trying text parsing");
|
|
||||||
result = parseKeywordsFromText(answer);
|
|
||||||
mapped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取置信度(如有)
|
// 提取置信度
|
||||||
if (rootNode.has("metadata")) {
|
if (rootNode.has("metadata")) {
|
||||||
JsonNode metadata = rootNode.get("metadata");
|
JsonNode metadata = rootNode.get("metadata");
|
||||||
if (metadata.has("confidence")) {
|
if (metadata.has("confidence")) {
|
||||||
result.setConfidence(metadata.get("confidence").asDouble());
|
result.setConfidence(metadata.get("confidence").asDouble());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果answer不是JSON格式,尝试文本解析
|
||||||
|
log.warn("Answer is not JSON format, trying text parsing");
|
||||||
|
result = parseKeywordsFromText(answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若仍未映射,兜底为空对象
|
|
||||||
log.info("Extracted keywords: Chinese={}, English={}, Item={}",
|
log.info("Extracted keywords: Chinese={}, English={}, Item={}",
|
||||||
result.getDrugNameChinese(), result.getDrugNameEnglish(), result.getRequestItem());
|
result.getDrugNameChinese(), result.getDrugNameEnglish(), result.getRequestItem());
|
||||||
|
|
||||||
|
|
@ -174,28 +143,50 @@ public class DifyServiceImpl implements DifyService {
|
||||||
@Override
|
@Override
|
||||||
public ResponseGenerationDTO generateStructuredResponse(String inquiryContent, String selectedMaterials) {
|
public ResponseGenerationDTO generateStructuredResponse(String inquiryContent, String selectedMaterials) {
|
||||||
log.info("Generating structured response");
|
log.info("Generating structured response");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String combined = String.format(
|
String prompt = String.format(
|
||||||
"查询内容:%s\n\n选中资料:%s",
|
"请基于以下查询问题和检索到的资料生成标准化回复。\n\n" +
|
||||||
|
"查询问题:%s\n\n" +
|
||||||
|
"检索到的资料:%s\n\n" +
|
||||||
|
"请按照以下JSON格式返回:\n" +
|
||||||
|
"{\n" +
|
||||||
|
" \"question\": \"原始查询问题\",\n" +
|
||||||
|
" \"queriedMaterials\": \"查询到的资料简要说明\",\n" +
|
||||||
|
" \"summary\": \"基于资料的核心观点总结\",\n" +
|
||||||
|
" \"materialList\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"title\": \"标题\",\n" +
|
||||||
|
" \"authors\": \"作者\",\n" +
|
||||||
|
" \"source\": \"来源\",\n" +
|
||||||
|
" \"publicationDate\": \"发表日期\",\n" +
|
||||||
|
" \"url\": \"链接\",\n" +
|
||||||
|
" \"summary\": \"摘要\",\n" +
|
||||||
|
" \"relevance\": \"相关性说明\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}",
|
||||||
inquiryContent, selectedMaterials
|
inquiryContent, selectedMaterials
|
||||||
);
|
);
|
||||||
String response = callDifyAPI(combined, "generate_structured_response");
|
|
||||||
|
|
||||||
|
String response = callDifyAPI(prompt, "generate_structured_response");
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
JsonNode rootNode = objectMapper.readTree(response);
|
JsonNode rootNode = objectMapper.readTree(response);
|
||||||
JsonNode answerNode = rootNode.path("answer");
|
JsonNode answerNode = rootNode.path("answer");
|
||||||
String answerText = answerNode.isTextual() ? answerNode.asText() : answerNode.toString();
|
|
||||||
|
|
||||||
// 尝试直接将 answer 反序列化为结构化对象
|
String answer;
|
||||||
try {
|
if (answerNode.isTextual()) {
|
||||||
return objectMapper.readValue(answerText, ResponseGenerationDTO.class);
|
answer = answerNode.asText();
|
||||||
} catch (Exception ignore) {
|
} else {
|
||||||
// 如果不是严格JSON,尝试构造最小可用结构
|
answer = answerNode.toString();
|
||||||
ResponseGenerationDTO dto = new ResponseGenerationDTO();
|
|
||||||
dto.setQuestion(inquiryContent);
|
|
||||||
dto.setSummary(answerText);
|
|
||||||
dto.setQueriedMaterials("已选资料共计,详见前端展示");
|
|
||||||
return dto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析结构化回复
|
||||||
|
ResponseGenerationDTO result = objectMapper.readValue(answer, ResponseGenerationDTO.class);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error generating structured response: ", e);
|
log.error("Error generating structured response: ", e);
|
||||||
throw new RuntimeException("Failed to generate structured response: " + e.getMessage(), e);
|
throw new RuntimeException("Failed to generate structured response: " + e.getMessage(), e);
|
||||||
|
|
@ -207,48 +198,40 @@ public class DifyServiceImpl implements DifyService {
|
||||||
*/
|
*/
|
||||||
private String callDifyAPI(String content, String taskType) {
|
private String callDifyAPI(String content, String taskType) {
|
||||||
try {
|
try {
|
||||||
log.info("Calling Dify API: baseUrl={}, appType={}, taskType={}", difyApiUrl, difyAppType, taskType);
|
log.info("Calling Dify API: url={}, taskType={}", difyApiUrl, taskType);
|
||||||
|
log.info("API Key: {}", difyApiKey != null ? difyApiKey.substring(0, Math.min(10, difyApiKey.length())) + "..." : "null");
|
||||||
|
|
||||||
// 构建 inputs
|
// 构建请求
|
||||||
Map<String, Object> inputs = new HashMap<>();
|
|
||||||
inputs.put("task_type", taskType);
|
|
||||||
// Dify 工作流要求的输入参数名,值来自查询详情的 inquiry_content
|
|
||||||
inputs.put("Inquery_request", content);
|
|
||||||
|
|
||||||
String endpoint;
|
|
||||||
Object body;
|
|
||||||
|
|
||||||
if ("workflow".equalsIgnoreCase(difyAppType)) {
|
|
||||||
// Workflow 应用
|
|
||||||
Map<String, Object> workflowBody = new HashMap<>();
|
|
||||||
workflowBody.put("inputs", inputs);
|
|
||||||
workflowBody.put("response_mode", "blocking");
|
|
||||||
workflowBody.put("user", "medical-info-system");
|
|
||||||
endpoint = "/workflows/run";
|
|
||||||
body = workflowBody;
|
|
||||||
} else {
|
|
||||||
// Chat 应用
|
|
||||||
DifyRequest request = new DifyRequest();
|
DifyRequest request = new DifyRequest();
|
||||||
request.setInputs(inputs);
|
|
||||||
request.setQuery(content);
|
request.setQuery(content);
|
||||||
request.setResponseMode("blocking");
|
request.setResponseMode("blocking");
|
||||||
request.setUser("medical-info-system");
|
request.setUser("medical-info-system");
|
||||||
endpoint = "/chat-messages";
|
|
||||||
body = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 添加任务类型到inputs
|
||||||
|
Map<String, Object> inputs = new HashMap<>();
|
||||||
|
inputs.put("task_type", taskType);
|
||||||
|
inputs.put("content", content);
|
||||||
|
request.setInputs(inputs);
|
||||||
|
|
||||||
|
log.debug("Dify API request: {}", objectMapper.writeValueAsString(request));
|
||||||
|
|
||||||
|
// 调用API
|
||||||
String response = webClient.post()
|
String response = webClient.post()
|
||||||
.uri(difyApiUrl + endpoint)
|
.uri(difyApiUrl + "/chat-messages")
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + difyApiKey)
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + difyApiKey)
|
||||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||||
.bodyValue(body)
|
.bodyValue(request)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
||||||
clientResponse -> clientResponse.bodyToMono(String.class).map(errBody -> {
|
clientResponse -> {
|
||||||
String msg = String.format("Dify API error %s: %s", clientResponse.statusCode(), errBody);
|
log.error("Dify API error: status={}, headers={}",
|
||||||
log.error(msg);
|
clientResponse.statusCode(), clientResponse.headers().asHttpHeaders());
|
||||||
return new RuntimeException(msg);
|
return clientResponse.bodyToMono(String.class)
|
||||||
}))
|
.flatMap(body -> {
|
||||||
|
log.error("Dify API error body: {}", body);
|
||||||
|
return Mono.error(new RuntimeException("Dify API error: " + clientResponse.statusCode() + " - " + body));
|
||||||
|
});
|
||||||
|
})
|
||||||
.bodyToMono(String.class)
|
.bodyToMono(String.class)
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ import com.ipsen.medical.dto.KeywordConfirmationDTO;
|
||||||
import com.ipsen.medical.dto.ResponseGenerationDTO;
|
import com.ipsen.medical.dto.ResponseGenerationDTO;
|
||||||
import com.ipsen.medical.dto.SearchResultItemDTO;
|
import com.ipsen.medical.dto.SearchResultItemDTO;
|
||||||
import com.ipsen.medical.entity.InquiryRequest;
|
import com.ipsen.medical.entity.InquiryRequest;
|
||||||
import com.ipsen.medical.entity.AuditLog;
|
|
||||||
import com.ipsen.medical.repository.InquiryRequestRepository;
|
import com.ipsen.medical.repository.InquiryRequestRepository;
|
||||||
import com.ipsen.medical.repository.AuditLogRepository;
|
|
||||||
import com.ipsen.medical.service.InquiryService;
|
import com.ipsen.medical.service.InquiryService;
|
||||||
import com.ipsen.medical.service.ExcelParserService;
|
import com.ipsen.medical.service.ExcelParserService;
|
||||||
import com.ipsen.medical.service.DifyService;
|
import com.ipsen.medical.service.DifyService;
|
||||||
|
|
@ -46,24 +44,6 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuditLogRepository auditLogRepository;
|
|
||||||
|
|
||||||
private void assertStatus(InquiryRequest inquiryRequest, InquiryRequest.RequestStatus... allowed) {
|
|
||||||
for (InquiryRequest.RequestStatus s : allowed) {
|
|
||||||
if (inquiryRequest.getStatus() == s) return;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("当前状态不允许此操作,当前状态: " + inquiryRequest.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logAudit(InquiryRequest inquiryRequest, AuditLog.AuditAction action, String comments) {
|
|
||||||
AuditLog log = new AuditLog();
|
|
||||||
log.setInquiryRequest(inquiryRequest);
|
|
||||||
log.setAction(action);
|
|
||||||
log.setComments(comments);
|
|
||||||
auditLogRepository.save(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InquiryRequestDTO uploadInquiry(MultipartFile file) {
|
public InquiryRequestDTO uploadInquiry(MultipartFile file) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -85,7 +65,7 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
inquiryRequest.setCustomerEmail(inquiryRequestDTO.getCustomerEmail());
|
inquiryRequest.setCustomerEmail(inquiryRequestDTO.getCustomerEmail());
|
||||||
inquiryRequest.setCustomerTitle(inquiryRequestDTO.getCustomerTitle());
|
inquiryRequest.setCustomerTitle(inquiryRequestDTO.getCustomerTitle());
|
||||||
inquiryRequest.setInquiryContent(inquiryRequestDTO.getInquiryContent());
|
inquiryRequest.setInquiryContent(inquiryRequestDTO.getInquiryContent());
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.CREATED);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.PENDING);
|
||||||
inquiryRequest.setCreatedAt(LocalDateTime.now());
|
inquiryRequest.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
|
|
@ -122,19 +102,15 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO extractKeywords(Long id) {
|
public InquiryRequestDTO extractKeywords(Long id) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 已创建或已检索后允许重新提取关键词,进入检索中
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.CREATED, InquiryRequest.RequestStatus.SEARCHED);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用Dify服务提取关键词
|
// 使用Dify服务提取关键词
|
||||||
String keywords = difyService.extractKeywords(inquiryRequest.getInquiryContent());
|
String keywords = difyService.extractKeywords(inquiryRequest.getInquiryContent());
|
||||||
inquiryRequest.setKeywords(keywords);
|
inquiryRequest.setKeywords(keywords);
|
||||||
inquiryRequest.setKeywordsConfirmed(false);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.KEYWORD_EXTRACTED);
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
logAudit(savedRequest, AuditLog.AuditAction.SUBMITTED, "关键词已提取");
|
|
||||||
return convertToDTO(savedRequest);
|
return convertToDTO(savedRequest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("关键词提取失败: " + e.getMessage(), e);
|
throw new RuntimeException("关键词提取失败: " + e.getMessage(), e);
|
||||||
|
|
@ -145,20 +121,14 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO performSearch(Long id) {
|
public InquiryRequestDTO performSearch(Long id) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 仅在检索中触发检索
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行多源检索
|
// 执行多源检索
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
|
||||||
inquiryRequestRepository.save(inquiryRequest);
|
|
||||||
multiSourceSearchService.performMultiSourceSearch(id);
|
multiSourceSearchService.performMultiSourceSearch(id);
|
||||||
|
|
||||||
// 重新加载更新后的请求
|
// 重新加载更新后的请求
|
||||||
inquiryRequest = inquiryRequestRepository.findById(id)
|
inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
logAudit(inquiryRequest, AuditLog.AuditAction.SUBMITTED, "检索完成");
|
|
||||||
|
|
||||||
return convertToDTO(inquiryRequest);
|
return convertToDTO(inquiryRequest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -170,8 +140,6 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO generateResponse(Long id) {
|
public InquiryRequestDTO generateResponse(Long id) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 仅在已检索状态生成回复
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.SEARCHED);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取用户选中的检索结果(includeInResponse = true)
|
// 获取用户选中的检索结果(includeInResponse = true)
|
||||||
|
|
@ -196,11 +164,10 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
String responseContent = objectMapper.writeValueAsString(structuredResponse);
|
String responseContent = objectMapper.writeValueAsString(structuredResponse);
|
||||||
|
|
||||||
inquiryRequest.setResponseContent(responseContent);
|
inquiryRequest.setResponseContent(responseContent);
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.RESPONDING);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.UNDER_REVIEW);
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
logAudit(savedRequest, AuditLog.AuditAction.SUBMITTED, "已生成结构化回复");
|
|
||||||
return convertToDTO(savedRequest);
|
return convertToDTO(savedRequest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("回复生成失败: " + e.getMessage(), e);
|
throw new RuntimeException("回复生成失败: " + e.getMessage(), e);
|
||||||
|
|
@ -211,14 +178,12 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO reviewResponse(Long id, Boolean approved, String comments) {
|
public InquiryRequestDTO reviewResponse(Long id, Boolean approved, String comments) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 审核仅在回复中允许
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.RESPONDING);
|
|
||||||
|
|
||||||
// 审核结果仅记录,不改变整体生命周期(完成由人工标记)
|
|
||||||
if (approved) {
|
if (approved) {
|
||||||
logAudit(inquiryRequest, AuditLog.AuditAction.APPROVED, comments);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.COMPLETED);
|
||||||
|
inquiryRequest.setCompletedAt(LocalDateTime.now());
|
||||||
} else {
|
} else {
|
||||||
logAudit(inquiryRequest, AuditLog.AuditAction.REJECTED, comments);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.REJECTED);
|
||||||
}
|
}
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
|
@ -230,14 +195,12 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO completeInquiry(Long id) {
|
public InquiryRequestDTO completeInquiry(Long id) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 仅在回复中可标记完成
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.RESPONDING);
|
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.COMPLETED);
|
inquiryRequest.setStatus(InquiryRequest.RequestStatus.COMPLETED);
|
||||||
inquiryRequest.setCompletedAt(LocalDateTime.now());
|
inquiryRequest.setCompletedAt(LocalDateTime.now());
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
logAudit(savedRequest, AuditLog.AuditAction.COMPLETED, "处理完成");
|
|
||||||
return convertToDTO(savedRequest);
|
return convertToDTO(savedRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,20 +208,15 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO confirmKeywords(Long id, KeywordConfirmationDTO keywordDTO) {
|
public InquiryRequestDTO confirmKeywords(Long id, KeywordConfirmationDTO keywordDTO) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 仅在检索中允许确认关键词(如果保留该步骤)
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 将用户确认的关键词保存为JSON
|
// 将用户确认的关键词保存为JSON
|
||||||
String keywordsJson = objectMapper.writeValueAsString(keywordDTO);
|
String keywordsJson = objectMapper.writeValueAsString(keywordDTO);
|
||||||
inquiryRequest.setKeywords(keywordsJson);
|
inquiryRequest.setKeywords(keywordsJson);
|
||||||
inquiryRequest.setKeywordsConfirmed(true);
|
inquiryRequest.setKeywordsConfirmed(true);
|
||||||
// 确认后仍保持检索中
|
|
||||||
inquiryRequest.setStatus(InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
logAudit(savedRequest, AuditLog.AuditAction.SUBMITTED, "关键词已确认");
|
|
||||||
return convertToDTO(savedRequest);
|
return convertToDTO(savedRequest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("关键词确认失败: " + e.getMessage(), e);
|
throw new RuntimeException("关键词确认失败: " + e.getMessage(), e);
|
||||||
|
|
@ -269,8 +227,6 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
public InquiryRequestDTO selectDataSources(Long id, DataSourceSelectionDTO dataSourceDTO) {
|
public InquiryRequestDTO selectDataSources(Long id, DataSourceSelectionDTO dataSourceDTO) {
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
||||||
// 选择数据源需在检索中
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.SEARCHING);
|
|
||||||
|
|
||||||
// 保存用户选择的数据源
|
// 保存用户选择的数据源
|
||||||
inquiryRequest.setSearchInternalData(dataSourceDTO.getSearchInternalData());
|
inquiryRequest.setSearchInternalData(dataSourceDTO.getSearchInternalData());
|
||||||
|
|
@ -280,7 +236,6 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
InquiryRequest savedRequest = inquiryRequestRepository.save(inquiryRequest);
|
||||||
logAudit(savedRequest, AuditLog.AuditAction.SUBMITTED, "已选择数据源");
|
|
||||||
return convertToDTO(savedRequest);
|
return convertToDTO(savedRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,30 +272,4 @@ public class InquiryServiceImpl implements InquiryService {
|
||||||
dto.setCompletedAt(inquiryRequest.getCompletedAt());
|
dto.setCompletedAt(inquiryRequest.getCompletedAt());
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public InquiryRequestDTO generateDownloadTasks(Long id, List<Long> searchResultItemIds) {
|
|
||||||
InquiryRequest inquiryRequest = inquiryRequestRepository.findById(id)
|
|
||||||
.orElseThrow(() -> new RuntimeException("查询请求不存在: " + id));
|
|
||||||
assertStatus(inquiryRequest, InquiryRequest.RequestStatus.SEARCHED);
|
|
||||||
|
|
||||||
if (searchResultItemIds == null || searchResultItemIds.isEmpty()) {
|
|
||||||
throw new RuntimeException("未选中任何需要下载的条目");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记选中的条目为需要下载
|
|
||||||
for (Long itemId : searchResultItemIds) {
|
|
||||||
com.ipsen.medical.dto.SearchResultItemDTO dto = new com.ipsen.medical.dto.SearchResultItemDTO();
|
|
||||||
dto.setId(itemId);
|
|
||||||
dto.setNeedDownload(true);
|
|
||||||
searchResultService.updateSearchResult(itemId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成下载任务不改变主流程状态
|
|
||||||
inquiryRequest.setUpdatedAt(LocalDateTime.now());
|
|
||||||
InquiryRequest saved = inquiryRequestRepository.save(inquiryRequest);
|
|
||||||
|
|
||||||
logAudit(saved, AuditLog.AuditAction.SUBMITTED, "已生成下载任务");
|
|
||||||
return convertToDTO(saved);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,8 @@ public class MultiSourceSearchServiceImpl implements MultiSourceSearchService {
|
||||||
allResults.addAll(ctResults);
|
allResults.addAll(ctResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新查询请求状态为已检索
|
// 更新查询请求状态
|
||||||
inquiry.setStatus(InquiryRequest.RequestStatus.SEARCHED);
|
inquiry.setStatus(InquiryRequest.RequestStatus.SEARCH_COMPLETED);
|
||||||
inquiryRequestRepository.save(inquiry);
|
inquiryRequestRepository.save(inquiry);
|
||||||
|
|
||||||
log.info("Multi-source search completed, found {} results", allResults.size());
|
log.info("Multi-source search completed, found {} results", allResults.size());
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,3 @@ public class SearchResultServiceImpl implements SearchResultService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,8 @@ app:
|
||||||
|
|
||||||
# Dify配置
|
# Dify配置
|
||||||
dify:
|
dify:
|
||||||
api-url: https://api.dify.ai/v1
|
api-url: ${DIFY_API_URL:https://api.dify.ai/v1}
|
||||||
api-key: app-croZF0SSV5fiyXbK3neGrOT6
|
api-key: ${DIFY_API_KEY:your-dify-api-key-here}
|
||||||
# 可选: workflow 或 chat
|
|
||||||
app-type: workflow
|
|
||||||
|
|
||||||
# 大模型配置
|
# 大模型配置
|
||||||
llm:
|
llm:
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,3 @@ SELECT '药物模块初始化完成!' AS message,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-plugin-vue": "^9.19.2",
|
"eslint-plugin-vue": "^9.19.2",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"vite": "^6.4.1"
|
"vite": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
|
|
@ -89,9 +89,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==",
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -102,13 +102,13 @@
|
||||||
"aix"
|
"aix"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==",
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -119,13 +119,13 @@
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==",
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -136,13 +136,13 @@
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==",
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -153,13 +153,13 @@
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==",
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -170,13 +170,13 @@
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==",
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -187,13 +187,13 @@
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==",
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -204,13 +204,13 @@
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==",
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -221,13 +221,13 @@
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==",
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -238,13 +238,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==",
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -255,13 +255,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==",
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -272,13 +272,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||||
"integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==",
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -289,13 +289,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||||
"integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==",
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
|
|
@ -306,13 +306,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==",
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -323,13 +323,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||||
"integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==",
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -340,13 +340,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||||
"integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==",
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -357,13 +357,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==",
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -374,30 +374,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
|
||||||
"version": "0.25.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz",
|
|
||||||
"integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"netbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==",
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -408,30 +391,13 @@
|
||||||
"netbsd"
|
"netbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
|
||||||
"version": "0.25.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz",
|
|
||||||
"integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==",
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -442,30 +408,13 @@
|
||||||
"openbsd"
|
"openbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
|
||||||
"version": "0.25.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz",
|
|
||||||
"integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openharmony"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==",
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -476,13 +425,13 @@
|
||||||
"sunos"
|
"sunos"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==",
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -493,13 +442,13 @@
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==",
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -510,13 +459,13 @@
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==",
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -527,7 +476,7 @@
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
|
|
@ -1948,9 +1897,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.11",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
"integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -1958,35 +1907,32 @@
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.25.11",
|
"@esbuild/aix-ppc64": "0.21.5",
|
||||||
"@esbuild/android-arm": "0.25.11",
|
"@esbuild/android-arm": "0.21.5",
|
||||||
"@esbuild/android-arm64": "0.25.11",
|
"@esbuild/android-arm64": "0.21.5",
|
||||||
"@esbuild/android-x64": "0.25.11",
|
"@esbuild/android-x64": "0.21.5",
|
||||||
"@esbuild/darwin-arm64": "0.25.11",
|
"@esbuild/darwin-arm64": "0.21.5",
|
||||||
"@esbuild/darwin-x64": "0.25.11",
|
"@esbuild/darwin-x64": "0.21.5",
|
||||||
"@esbuild/freebsd-arm64": "0.25.11",
|
"@esbuild/freebsd-arm64": "0.21.5",
|
||||||
"@esbuild/freebsd-x64": "0.25.11",
|
"@esbuild/freebsd-x64": "0.21.5",
|
||||||
"@esbuild/linux-arm": "0.25.11",
|
"@esbuild/linux-arm": "0.21.5",
|
||||||
"@esbuild/linux-arm64": "0.25.11",
|
"@esbuild/linux-arm64": "0.21.5",
|
||||||
"@esbuild/linux-ia32": "0.25.11",
|
"@esbuild/linux-ia32": "0.21.5",
|
||||||
"@esbuild/linux-loong64": "0.25.11",
|
"@esbuild/linux-loong64": "0.21.5",
|
||||||
"@esbuild/linux-mips64el": "0.25.11",
|
"@esbuild/linux-mips64el": "0.21.5",
|
||||||
"@esbuild/linux-ppc64": "0.25.11",
|
"@esbuild/linux-ppc64": "0.21.5",
|
||||||
"@esbuild/linux-riscv64": "0.25.11",
|
"@esbuild/linux-riscv64": "0.21.5",
|
||||||
"@esbuild/linux-s390x": "0.25.11",
|
"@esbuild/linux-s390x": "0.21.5",
|
||||||
"@esbuild/linux-x64": "0.25.11",
|
"@esbuild/linux-x64": "0.21.5",
|
||||||
"@esbuild/netbsd-arm64": "0.25.11",
|
"@esbuild/netbsd-x64": "0.21.5",
|
||||||
"@esbuild/netbsd-x64": "0.25.11",
|
"@esbuild/openbsd-x64": "0.21.5",
|
||||||
"@esbuild/openbsd-arm64": "0.25.11",
|
"@esbuild/sunos-x64": "0.21.5",
|
||||||
"@esbuild/openbsd-x64": "0.25.11",
|
"@esbuild/win32-arm64": "0.21.5",
|
||||||
"@esbuild/openharmony-arm64": "0.25.11",
|
"@esbuild/win32-ia32": "0.21.5",
|
||||||
"@esbuild/sunos-x64": "0.25.11",
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
"@esbuild/win32-arm64": "0.25.11",
|
|
||||||
"@esbuild/win32-ia32": "0.25.11",
|
|
||||||
"@esbuild/win32-x64": "0.25.11"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
|
|
@ -3316,54 +3262,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
|
||||||
"version": "0.2.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
|
||||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fdir": "^6.5.0",
|
|
||||||
"picomatch": "^4.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tinyglobby/node_modules/fdir": {
|
|
||||||
"version": "6.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
|
||||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"picomatch": "^3 || ^4"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"picomatch": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
|
@ -3422,24 +3320,21 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.1",
|
"version": "5.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.21.3",
|
||||||
"fdir": "^6.4.4",
|
"postcss": "^8.4.43",
|
||||||
"picomatch": "^4.0.2",
|
"rollup": "^4.20.0"
|
||||||
"postcss": "^8.5.3",
|
|
||||||
"rollup": "^4.34.9",
|
|
||||||
"tinyglobby": "^0.2.13"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||||
|
|
@ -3448,25 +3343,19 @@
|
||||||
"fsevents": "~2.3.3"
|
"fsevents": "~2.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
"jiti": ">=1.21.0",
|
|
||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
"sass-embedded": "*",
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.16.0",
|
"terser": "^5.4.0"
|
||||||
"tsx": "^4.8.1",
|
|
||||||
"yaml": "^2.4.2"
|
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"jiti": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"less": {
|
"less": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
|
@ -3487,46 +3376,9 @@
|
||||||
},
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
|
||||||
"tsx": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"yaml": {
|
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/fdir": {
|
|
||||||
"version": "6.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
|
||||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"picomatch": "^3 || ^4"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"picomatch": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vite/node_modules/picomatch": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.22",
|
"version": "3.5.22",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,23 @@
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
|
||||||
"axios": "^1.6.2",
|
|
||||||
"dayjs": "^1.11.10",
|
|
||||||
"element-plus": "^2.5.0",
|
|
||||||
"pinia": "^2.1.7",
|
|
||||||
"vue": "^3.4.0",
|
"vue": "^3.4.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"element-plus": "^2.5.0",
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"dayjs": "^1.11.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.0",
|
"@vitejs/plugin-vue": "^5.0.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-plugin-vue": "^9.19.2",
|
"eslint-plugin-vue": "^9.19.2",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5"
|
||||||
"vite": "^6.4.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
export function login(data) {
|
|
||||||
return request({
|
|
||||||
url: '/auth/login',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -59,13 +59,12 @@ export function extractKeywords(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行检索(可携带查询表达式与检索范围)
|
* 执行检索
|
||||||
*/
|
*/
|
||||||
export function performSearch(id, data) {
|
export function performSearch(id) {
|
||||||
return request({
|
return request({
|
||||||
url: `/inquiries/${id}/search`,
|
url: `/inquiries/${id}/search`,
|
||||||
method: 'post',
|
method: 'post'
|
||||||
data
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,17 +182,6 @@ export function selectDataSources(id, data) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成下载任务(根据选中的检索结果ID)
|
|
||||||
*/
|
|
||||||
export function generateDownloadTasks(id, ids) {
|
|
||||||
return request({
|
|
||||||
url: `/inquiries/${id}/downloads`,
|
|
||||||
method: 'post',
|
|
||||||
data: ids
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,3 @@ export function markDownloadFailed(id, reason) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,9 +144,19 @@ const router = createRouter({
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
// 放开路由守卫(后端已不需要登录)
|
// 路由守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
|
||||||
|
if (to.path === '/login') {
|
||||||
next()
|
next()
|
||||||
|
} else {
|
||||||
|
if (!token) {
|
||||||
|
next('/login')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ const service = axios.create({
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
// 取消附加 Authorization 头,后端已不需要鉴权
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { login as apiLogin } from '@/api/auth'
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const loginFormRef = ref(null)
|
const loginFormRef = ref(null)
|
||||||
|
|
@ -69,19 +68,23 @@ const rules = {
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!loginFormRef.value) return
|
if (!loginFormRef.value) return
|
||||||
|
|
||||||
await loginFormRef.value.validate(async (valid) => {
|
await loginFormRef.value.validate((valid) => {
|
||||||
if (!valid) return
|
if (valid) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
|
||||||
const resp = await apiLogin({ username: loginForm.username, password: loginForm.password })
|
// TODO: 实际项目中应该调用登录API
|
||||||
localStorage.setItem('token', resp.token)
|
// 这里使用模拟登录
|
||||||
localStorage.setItem('username', resp.username)
|
setTimeout(() => {
|
||||||
|
if (loginForm.username === 'admin' && loginForm.password === 'admin123') {
|
||||||
|
localStorage.setItem('token', 'mock-token-' + Date.now())
|
||||||
|
localStorage.setItem('username', loginForm.username)
|
||||||
ElMessage.success('登录成功')
|
ElMessage.success('登录成功')
|
||||||
router.push('/dashboard')
|
router.push('/dashboard')
|
||||||
} catch (e) {
|
} else {
|
||||||
// 错误提示由拦截器处理
|
ElMessage.error('用户名或密码错误')
|
||||||
} finally {
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -435,7 +435,3 @@ const goBack = () => {
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -482,7 +482,3 @@ const goBack = () => {
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,15 @@
|
||||||
style="max-width: 600px;"
|
style="max-width: 600px;"
|
||||||
>
|
>
|
||||||
<el-form-item label="客户姓名" prop="customerName">
|
<el-form-item label="客户姓名" prop="customerName">
|
||||||
<el-input v-model="form.customerName" placeholder="请输入客户姓名(可选)" />
|
<el-input v-model="form.customerName" placeholder="请输入客户姓名" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="客户邮箱" prop="customerEmail">
|
<el-form-item label="客户邮箱" prop="customerEmail">
|
||||||
<el-input v-model="form.customerEmail" placeholder="请输入客户邮箱(可选)" />
|
<el-input v-model="form.customerEmail" placeholder="请输入客户邮箱" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="客户职称" prop="customerTitle">
|
<el-form-item label="客户职称" prop="customerTitle">
|
||||||
<el-input v-model="form.customerTitle" placeholder="请输入客户职称(可选)" />
|
<el-input v-model="form.customerTitle" placeholder="请输入客户职称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="查询内容" prop="inquiryContent">
|
<el-form-item label="查询内容" prop="inquiryContent">
|
||||||
|
|
@ -59,12 +59,12 @@
|
||||||
v-model="form.inquiryContent"
|
v-model="form.inquiryContent"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="8"
|
:rows="8"
|
||||||
placeholder="请输入查询内容(必填)"
|
placeholder="请输入查询内容"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="指派给" prop="assignedTo">
|
<el-form-item label="指派给" prop="assignedTo">
|
||||||
<el-input v-model="form.assignedTo" placeholder="请输入指派人员(可选)" />
|
<el-input v-model="form.assignedTo" placeholder="请输入指派人员" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
|
|
@ -103,8 +103,14 @@ const form = ref({
|
||||||
assignedTo: ''
|
assignedTo: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 仅保留“查询内容”为必填,其余为可选
|
|
||||||
const rules = {
|
const rules = {
|
||||||
|
customerName: [
|
||||||
|
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
customerEmail: [
|
||||||
|
{ required: true, message: '请输入客户邮箱', trigger: 'blur' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||||
|
],
|
||||||
inquiryContent: [
|
inquiryContent: [
|
||||||
{ required: true, message: '请输入查询内容', trigger: 'blur' }
|
{ required: true, message: '请输入查询内容', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,14 @@
|
||||||
<el-descriptions-item label="请求编号">
|
<el-descriptions-item label="请求编号">
|
||||||
{{ inquiry.requestNumber }}
|
{{ inquiry.requestNumber }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="客户姓名">
|
<el-descriptions-item label="客户姓名"> <el-steps :active="currentStep" align-center>
|
||||||
|
<el-step title="提取关键词" description="使用AI提取查询关键词" />
|
||||||
|
<el-step title="信息检索" description="检索相关文献和数据" />
|
||||||
|
<el-step title="生成回复" description="整理信息生成回复" />
|
||||||
|
<el-step title="审核回复" description="人工审核回复内容" />
|
||||||
|
<el-step title="下载文献" description="下载相关文献" />
|
||||||
|
<el-step title="完成" description="处理完成" />
|
||||||
|
</el-steps>
|
||||||
{{ inquiry.customerName }}
|
{{ inquiry.customerName }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="客户邮箱">
|
<el-descriptions-item label="客户邮箱">
|
||||||
|
|
@ -36,34 +43,139 @@
|
||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
|
|
||||||
<!-- 操作按钮:与“提取关键词”并排展示 -->
|
|
||||||
|
|
||||||
<div class="action-section">
|
<div class="action-section">
|
||||||
|
<!-- 快捷导航按钮 -->
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="inquiry.status === 'PENDING'"
|
||||||
|
type="primary"
|
||||||
|
@click="goToKeywordConfirmation"
|
||||||
|
>
|
||||||
|
开始处理(关键词提取)
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'KEYWORD_EXTRACTED' && !inquiry.keywordsConfirmed"
|
||||||
|
type="primary"
|
||||||
|
@click="goToKeywordConfirmation"
|
||||||
|
>
|
||||||
|
确认关键词
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.keywordsConfirmed && inquiry.status === 'KEYWORD_EXTRACTED'"
|
||||||
|
type="success"
|
||||||
|
@click="goToDataSourceSelection"
|
||||||
|
>
|
||||||
|
选择数据源并检索
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'SEARCH_COMPLETED'"
|
||||||
|
type="warning"
|
||||||
|
@click="goToSearchResults"
|
||||||
|
>
|
||||||
|
查看检索结果
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.responseContent"
|
||||||
|
type="info"
|
||||||
|
@click="goToResponseView"
|
||||||
|
>
|
||||||
|
查看生成的回复
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<!-- AI智能流程按钮 -->
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'PENDING' || inquiry.status === 'KEYWORD_EXTRACTED'"
|
||||||
|
type="primary"
|
||||||
|
icon="MagicStick"
|
||||||
|
@click="handleFullWorkflow"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
一键执行AI流程
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'PENDING'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleExtractKeywords"
|
@click="handleExtractKeywords"
|
||||||
:loading="extractLoading"
|
:loading="processing"
|
||||||
:disabled="extractLoading || searchLoading || !['CREATED','SEARCHED'].includes(inquiry.status)"
|
>
|
||||||
>提取关键词</el-button>
|
提取关键词
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="inquiry.status === 'KEYWORD_EXTRACTED'"
|
||||||
|
type="success"
|
||||||
|
icon="Search"
|
||||||
|
@click="handleAutoSearch"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
智能自动检索
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'KEYWORD_EXTRACTED'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleSearchWithParams"
|
@click="handleSearch"
|
||||||
:loading="searchLoading"
|
:loading="processing"
|
||||||
:disabled="extractLoading || searchLoading || !standardQuery?.trim()"
|
>
|
||||||
>一键检索</el-button>
|
执行检索
|
||||||
<el-button @click="showResults" :disabled="!hasAnyResults">查看检索结果</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
v-if="inquiry.status === 'SEARCH_COMPLETED'"
|
||||||
|
type="primary"
|
||||||
@click="handleGenerateResponse"
|
@click="handleGenerateResponse"
|
||||||
:loading="processing"
|
:loading="processing"
|
||||||
:disabled="processing || inquiry.status !== 'SEARCHED'"
|
>
|
||||||
>生成回复</el-button>
|
生成回复
|
||||||
<el-button @click="goToResponseView" :disabled="!inquiry.responseContent">查看回复</el-button>
|
</el-button>
|
||||||
|
|
||||||
|
<template v-if="inquiry.status === 'UNDER_REVIEW'">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
@click="handleApprove"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
批准回复
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
@click="handleReject"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
要求修改
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'DOWNLOADING'"
|
||||||
|
type="primary"
|
||||||
|
@click="handleDownloadLiteratures"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
下载文献
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="inquiry.status === 'DOWNLOADING'"
|
||||||
|
type="success"
|
||||||
|
@click="handleComplete"
|
||||||
|
:loading="processing"
|
||||||
|
>
|
||||||
|
完成处理
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- AI识别结果 + 标准检索词 -->
|
<el-divider />
|
||||||
<div class="result-section">
|
|
||||||
|
<div v-if="inquiry.keywords" class="result-section">
|
||||||
<h3>AI识别结果</h3>
|
<h3>AI识别结果</h3>
|
||||||
<div v-if="inquiry.keywords && isStructuredKeywords(inquiry.keywords)" class="keywords-structured">
|
<div v-if="isStructuredKeywords(inquiry.keywords)" class="keywords-structured">
|
||||||
<el-descriptions :column="3" border>
|
<el-descriptions :column="3" border>
|
||||||
<el-descriptions-item label="药物中文名">
|
<el-descriptions-item label="药物中文名">
|
||||||
<el-tag type="success">{{ getKeywordField(inquiry.keywords, 'drugNameChinese') }}</el-tag>
|
<el-tag type="success">{{ getKeywordField(inquiry.keywords, 'drugNameChinese') }}</el-tag>
|
||||||
|
|
@ -83,101 +195,66 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 新:检索范围与标准检索词表格(第一列包含选择框) -->
|
<div v-if="inquiry.searchResults" class="result-section">
|
||||||
<div class="result-section">
|
<h3>检索结果</h3>
|
||||||
<h3>检索范围与标准检索词</h3>
|
<pre>{{ formatJSON(inquiry.searchResults) }}</pre>
|
||||||
<el-table :data="tableRows" border style="width: 100%;">
|
|
||||||
<el-table-column label="检索范围" width="260">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-checkbox
|
|
||||||
:label="row.scope"
|
|
||||||
:model-value="isScopeSelected(row.scope)"
|
|
||||||
@change="toggleScope(row.scope)"
|
|
||||||
>{{ row.label }}</el-checkbox>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="检索式(标准检索词)">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div class="query-editor">
|
|
||||||
<div
|
|
||||||
class="query-chip"
|
|
||||||
v-for="(item, idx) in scopeQueries[row.scope]"
|
|
||||||
:key="idx"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="scopeQueries[row.scope][idx].text"
|
|
||||||
:disabled="!isScopeSelected(row.scope) || !!item.summary"
|
|
||||||
placeholder="请输入检索式"
|
|
||||||
size="small"
|
|
||||||
style="width: 520px;"
|
|
||||||
/>
|
|
||||||
<el-button
|
|
||||||
:disabled="!isScopeSelected(row.scope)"
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="removeQuery(row.scope, idx)"
|
|
||||||
>删除</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
|
||||||
:disabled="!isScopeSelected(row.scope)"
|
<div v-if="inquiry.responseContent" class="result-section">
|
||||||
type="primary"
|
<h3>回复内容</h3>
|
||||||
size="small"
|
<div style="white-space: pre-wrap; line-height: 1.8;">
|
||||||
plain
|
{{ inquiry.responseContent }}
|
||||||
style="width: 40px;"
|
|
||||||
@click="addQuery(row.scope)"
|
|
||||||
>+ 添加检索式</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="结果概述" width="900">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-for="(item, idx) in scopeQueries[row.scope]"
|
|
||||||
:key="`s-${row.scope}-${idx}`"
|
|
||||||
style="margin-bottom: 6px;"
|
|
||||||
>
|
|
||||||
<span v-if="item.summary">{{ item.summary }}</span>
|
|
||||||
<span v-else style="color:#909399;">—</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<div style="margin-top: 8px; color:#909399; font-size: 12px;">
|
|
||||||
未选中的检索范围禁用编辑;当该条有“结果概述”表示已完成,不可修改。
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
|
|
||||||
<!-- 结果多 Tab 展示 -->
|
<!-- 临床试验部分 -->
|
||||||
<el-tabs v-model="activeResultsTab">
|
|
||||||
<!-- 动态:临床试验分组 Tab(按多条检索式分别展示) -->
|
|
||||||
<el-tab-pane
|
|
||||||
v-for="(group, gIdx) in clinicalTrialsGroups"
|
|
||||||
:key="`ct-${gIdx}`"
|
|
||||||
:label="group.label"
|
|
||||||
:name="group.name"
|
|
||||||
>
|
|
||||||
<!-- 临床试验部分(沿用原表格) -->
|
|
||||||
<div class="result-section">
|
<div class="result-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3>临床试验信息 (ClinicalTrials.gov)</h3>
|
<h3>临床试验信息 (ClinicalTrials.gov)</h3>
|
||||||
<div class="section-actions">
|
<div class="section-actions">
|
||||||
<el-button v-if="group.data.length > 0" type="success" @click="handleExportClinicalTrials" icon="Download">
|
<el-input
|
||||||
|
v-model="clinicalTrialKeyword"
|
||||||
|
placeholder="输入药品名称(英文)"
|
||||||
|
style="width: 300px; margin-right: 10px;"
|
||||||
|
@keyup.enter="handleSearchClinicalTrials"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSearchClinicalTrials"
|
||||||
|
:loading="searchingClinicalTrials"
|
||||||
|
:disabled="!clinicalTrialKeyword"
|
||||||
|
>
|
||||||
|
搜索临床试验
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="clinicalTrials.length > 0"
|
||||||
|
type="success"
|
||||||
|
@click="handleExportClinicalTrials"
|
||||||
|
icon="Download"
|
||||||
|
>
|
||||||
导出CSV
|
导出CSV
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="group.data.length > 0" @click="loadClinicalTrials" icon="Refresh">
|
<el-button
|
||||||
|
v-if="clinicalTrials.length > 0"
|
||||||
|
@click="loadClinicalTrials"
|
||||||
|
icon="Refresh"
|
||||||
|
>
|
||||||
刷新
|
刷新
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="group.data.length > 0" style="margin-top: 20px;">
|
<div v-if="clinicalTrials.length > 0" style="margin-top: 20px;">
|
||||||
<el-alert :title="`共找到 ${group.data.length} 个临床试验`" type="info" :closable="false" style="margin-bottom: 15px;" />
|
<el-alert
|
||||||
<el-table :data="group.data" border stripe style="width: 100%">
|
:title="`共找到 ${clinicalTrials.length} 个临床试验`"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 15px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table :data="clinicalTrials" border stripe style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div style="padding: 20px;">
|
<div style="padding: 20px;">
|
||||||
|
|
@ -211,12 +288,21 @@
|
||||||
{{ row.sponsor }}
|
{{ row.sponsor }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="适应症" :span="2">
|
<el-descriptions-item label="适应症" :span="2">
|
||||||
<el-tag v-for="(condition, index) in row.conditions" :key="index" style="margin-right: 5px; margin-bottom: 5px;">
|
<el-tag
|
||||||
|
v-for="(condition, index) in row.conditions"
|
||||||
|
:key="index"
|
||||||
|
style="margin-right: 5px; margin-bottom: 5px;"
|
||||||
|
>
|
||||||
{{ condition }}
|
{{ condition }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="干预措施" :span="2">
|
<el-descriptions-item label="干预措施" :span="2">
|
||||||
<el-tag v-for="(intervention, index) in row.interventions" :key="index" type="success" style="margin-right: 5px; margin-bottom: 5px;">
|
<el-tag
|
||||||
|
v-for="(intervention, index) in row.interventions"
|
||||||
|
:key="index"
|
||||||
|
type="success"
|
||||||
|
style="margin-right: 5px; margin-bottom: 5px;"
|
||||||
|
>
|
||||||
{{ intervention }}
|
{{ intervention }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
@ -227,7 +313,11 @@
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="研究地点" :span="2">
|
<el-descriptions-item label="研究地点" :span="2">
|
||||||
<div v-if="row.locations && row.locations.length > 0">
|
<div v-if="row.locations && row.locations.length > 0">
|
||||||
<div v-for="(location, index) in row.locations.slice(0, 5)" :key="index" style="margin-bottom: 5px;">
|
<div
|
||||||
|
v-for="(location, index) in row.locations.slice(0, 5)"
|
||||||
|
:key="index"
|
||||||
|
style="margin-bottom: 5px;"
|
||||||
|
>
|
||||||
{{ location.facility }} - {{ location.city }}, {{ location.country }}
|
{{ location.facility }} - {{ location.city }}, {{ location.country }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="row.locations.length > 5" style="color: #909399;">
|
<div v-if="row.locations.length > 5" style="color: #909399;">
|
||||||
|
|
@ -261,20 +351,11 @@
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-empty v-else-if="!searchingClinicalTrials" description="暂无临床试验数据,请输入药品名称进行搜索" />
|
<el-empty
|
||||||
|
v-else-if="!searchingClinicalTrials"
|
||||||
|
description="暂无临床试验数据,请输入药品名称进行搜索"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="内部数据" name="INTERNAL">
|
|
||||||
<el-empty description="暂无数据或未实现" />
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="知识库" name="KNOWLEDGE_BASE">
|
|
||||||
<el-empty description="暂无数据或未实现" />
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="知网" name="CNKI">
|
|
||||||
<el-empty description="暂无数据或未实现" />
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<div class="result-section">
|
<div class="result-section">
|
||||||
<el-button @click="goBack">返回列表</el-button>
|
<el-button @click="goBack">返回列表</el-button>
|
||||||
|
|
@ -284,7 +365,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, reactive } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
getInquiryDetail,
|
getInquiryDetail,
|
||||||
|
|
@ -306,72 +387,38 @@ const route = useRoute()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const processing = ref(false)
|
const processing = ref(false)
|
||||||
const extractLoading = ref(false)
|
|
||||||
const searchLoading = ref(false)
|
|
||||||
const inquiry = ref({})
|
const inquiry = ref({})
|
||||||
const clinicalTrials = ref([])
|
const clinicalTrials = ref([])
|
||||||
// 多检索式的临床试验结果分组:[{ name, label, data }]
|
const clinicalTrialKeyword = ref('')
|
||||||
const clinicalTrialsGroups = ref([])
|
|
||||||
const searchingClinicalTrials = ref(false)
|
const searchingClinicalTrials = ref(false)
|
||||||
|
|
||||||
// 新增:标准检索词与检索范围、多Tab
|
const currentStep = computed(() => {
|
||||||
const standardQuery = ref('')
|
const stepMap = {
|
||||||
const selectedSources = ref(['CLINICAL_TRIALS']) // 仍用于结果Tab默认行为
|
'PENDING': 0,
|
||||||
const activeResultsTab = ref('CLINICAL_TRIALS')
|
'KEYWORD_EXTRACTED': 1,
|
||||||
|
'SEARCHING': 1,
|
||||||
// 新:详情页内的范围选择控制(独立于 selectedSources 的展示用途)
|
'SEARCH_COMPLETED': 2,
|
||||||
const selectedScopes = ref([])
|
'UNDER_REVIEW': 3,
|
||||||
|
'DOWNLOADING': 4,
|
||||||
// 新:每个范围多条检索式,条目为 { text, summary }
|
'COMPLETED': 5
|
||||||
const scopeQueries = reactive({
|
|
||||||
INTERNAL: [],
|
|
||||||
KNOWLEDGE_BASE: [],
|
|
||||||
CNKI: [],
|
|
||||||
CLINICAL_TRIALS: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const scopeMeta = {
|
|
||||||
INTERNAL: { label: '内部数据' },
|
|
||||||
KNOWLEDGE_BASE: { label: '知识库' },
|
|
||||||
CNKI: { label: '知网 (CNKI)' },
|
|
||||||
CLINICAL_TRIALS: { label: 'ClinicalTrials.gov' }
|
|
||||||
}
|
}
|
||||||
|
return stepMap[inquiry.value.status] || 0
|
||||||
const tableRows = computed(() => [
|
|
||||||
{ scope: 'INTERNAL', label: scopeMeta.INTERNAL.label },
|
|
||||||
{ scope: 'KNOWLEDGE_BASE', label: scopeMeta.KNOWLEDGE_BASE.label },
|
|
||||||
{ scope: 'CNKI', label: scopeMeta.CNKI.label },
|
|
||||||
{ scope: 'CLINICAL_TRIALS', label: scopeMeta.CLINICAL_TRIALS.label }
|
|
||||||
])
|
|
||||||
|
|
||||||
const isScopeSelected = (scope) => selectedScopes.value.includes(scope)
|
|
||||||
const toggleScope = (scope) => {
|
|
||||||
const idx = selectedScopes.value.indexOf(scope)
|
|
||||||
if (idx >= 0) {
|
|
||||||
selectedScopes.value.splice(idx, 1)
|
|
||||||
} else {
|
|
||||||
selectedScopes.value.push(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addQuery = (scope) => {
|
|
||||||
scopeQueries[scope].push({ text: '', summary: '' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeQuery = (scope, index) => {
|
|
||||||
scopeQueries[scope].splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasAnyResults = computed(() => {
|
|
||||||
if (clinicalTrialsGroups.value && clinicalTrialsGroups.value.length > 0) {
|
|
||||||
return clinicalTrialsGroups.value.some(g => (g.data || []).length > 0)
|
|
||||||
}
|
|
||||||
return clinicalTrials.value && clinicalTrials.value.length > 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadDetail()
|
loadDetail()
|
||||||
loadClinicalTrials()
|
loadClinicalTrials()
|
||||||
|
// 自动填充关键词作为搜索词
|
||||||
|
if (inquiry.value.keywords) {
|
||||||
|
try {
|
||||||
|
const keywords = JSON.parse(inquiry.value.keywords)
|
||||||
|
if (Array.isArray(keywords) && keywords.length > 0) {
|
||||||
|
clinicalTrialKeyword.value = keywords[0]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略解析错误
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadDetail = async () => {
|
const loadDetail = async () => {
|
||||||
|
|
@ -379,20 +426,6 @@ const loadDetail = async () => {
|
||||||
try {
|
try {
|
||||||
const id = route.params.id
|
const id = route.params.id
|
||||||
inquiry.value = await getInquiryDetail(id)
|
inquiry.value = await getInquiryDetail(id)
|
||||||
|
|
||||||
// 同步范围到 selectedScopes(初始来自后端选择)
|
|
||||||
const scopes = []
|
|
||||||
if (inquiry.value.searchInternalData) scopes.push('INTERNAL')
|
|
||||||
if (inquiry.value.searchKnowledgeBase) scopes.push('KNOWLEDGE_BASE')
|
|
||||||
if (inquiry.value.searchCnki) scopes.push('CNKI')
|
|
||||||
if (inquiry.value.searchClinicalTrials) scopes.push('CLINICAL_TRIALS')
|
|
||||||
selectedScopes.value = scopes.length ? scopes : ['CLINICAL_TRIALS']
|
|
||||||
|
|
||||||
if (inquiry.value?.keywords) {
|
|
||||||
buildStandardQueryFromKeywords()
|
|
||||||
// 由AI关键词构建每范围默认检索式(中文+事项、英文+事项)
|
|
||||||
buildDefaultQueriesFromKeywords()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('加载详情失败')
|
ElMessage.error('加载详情失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -400,97 +433,41 @@ const loadDetail = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从AI关键词构建默认检索式(作为每范围多条的默认项)
|
|
||||||
const buildDefaultQueriesFromKeywords = () => {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(inquiry.value.keywords || '{}')
|
|
||||||
const cn = parsed.drugNameChinese || ''
|
|
||||||
const en = parsed.drugNameEnglish || ''
|
|
||||||
const item = parsed.requestItem || ''
|
|
||||||
|
|
||||||
const cnQuery = [cn, item].filter(Boolean).join(' + ')
|
|
||||||
const enQuery = [en, item].filter(Boolean).join(' + ')
|
|
||||||
|
|
||||||
const defaults = []
|
|
||||||
if (cnQuery) defaults.push({ text: cnQuery, summary: '' })
|
|
||||||
if (enQuery && enQuery !== cnQuery) defaults.push({ text: enQuery, summary: '' })
|
|
||||||
|
|
||||||
const applyIfEmpty = (scope) => {
|
|
||||||
if (!scopeQueries[scope] || scopeQueries[scope].length === 0) {
|
|
||||||
// 深拷贝每个默认检索项,避免跨范围引用同一对象导致联动
|
|
||||||
scopeQueries[scope] = defaults.map(item => ({ text: item.text, summary: item.summary }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyIfEmpty('CLINICAL_TRIALS')
|
|
||||||
applyIfEmpty('KNOWLEDGE_BASE')
|
|
||||||
applyIfEmpty('CNKI')
|
|
||||||
applyIfEmpty('INTERNAL')
|
|
||||||
|
|
||||||
// 维持旧按钮的行为:取 CT 第一条作为标准检索词回退
|
|
||||||
standardQuery.value = scopeQueries.CLINICAL_TRIALS[0]?.text || standardQuery.value
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadClinicalTrials = async () => {
|
const loadClinicalTrials = async () => {
|
||||||
try {
|
try {
|
||||||
const id = route.params.id
|
const id = route.params.id
|
||||||
clinicalTrials.value = await getClinicalTrials(id)
|
clinicalTrials.value = await getClinicalTrials(id)
|
||||||
// 将后端保存的单次结果映射为一个默认分组,便于统一渲染
|
|
||||||
clinicalTrialsGroups.value = [
|
|
||||||
{
|
|
||||||
name: 'CLINICAL_TRIALS',
|
|
||||||
label: 'ClinicalTrials',
|
|
||||||
data: clinicalTrials.value || []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载临床试验数据失败', error)
|
console.error('加载临床试验数据失败', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExtractKeywords = async () => {
|
const handleExtractKeywords = async () => {
|
||||||
extractLoading.value = true
|
processing.value = true
|
||||||
try {
|
try {
|
||||||
await extractKeywords(inquiry.value.id)
|
await extractKeywords(inquiry.value.id)
|
||||||
ElMessage.success('关键词提取成功')
|
ElMessage.success('关键词提取成功')
|
||||||
await loadDetail()
|
loadDetail()
|
||||||
buildStandardQueryFromKeywords()
|
|
||||||
buildDefaultQueriesFromKeywords()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('关键词提取失败')
|
ElMessage.error('关键词提取失败')
|
||||||
} finally {
|
} finally {
|
||||||
extractLoading.value = false
|
processing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchWithParams = async () => {
|
const handleSearch = async () => {
|
||||||
const keyword = (standardQuery.value || '').trim()
|
processing.value = true
|
||||||
if (!keyword) {
|
|
||||||
ElMessage.warning('请先填写标准检索词')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
searchLoading.value = true
|
|
||||||
try {
|
try {
|
||||||
// 仅当选择了 ClinicalTrials 时,执行临床试验检索
|
await performSearch(inquiry.value.id)
|
||||||
if (selectedSources.value.includes('CLINICAL_TRIALS')) {
|
|
||||||
await handleSearchClinicalTrials()
|
|
||||||
}
|
|
||||||
ElMessage.success('检索完成')
|
ElMessage.success('检索完成')
|
||||||
activeResultsTab.value = 'CLINICAL_TRIALS'
|
loadDetail()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('检索失败')
|
ElMessage.error('检索失败')
|
||||||
} finally {
|
} finally {
|
||||||
searchLoading.value = false
|
processing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showResults = () => {
|
|
||||||
activeResultsTab.value = selectedSources.value[0] || 'CLINICAL_TRIALS'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleGenerateResponse = async () => {
|
const handleGenerateResponse = async () => {
|
||||||
processing.value = true
|
processing.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -558,22 +535,28 @@ const goBack = () => {
|
||||||
|
|
||||||
const getStatusType = (status) => {
|
const getStatusType = (status) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'CREATED': 'info',
|
'PENDING': 'info',
|
||||||
|
'KEYWORD_EXTRACTED': 'warning',
|
||||||
'SEARCHING': 'warning',
|
'SEARCHING': 'warning',
|
||||||
'SEARCHED': '',
|
'SEARCH_COMPLETED': '',
|
||||||
'RESPONDING': 'warning',
|
'UNDER_REVIEW': 'warning',
|
||||||
'COMPLETED': 'success'
|
'DOWNLOADING': 'warning',
|
||||||
|
'COMPLETED': 'success',
|
||||||
|
'REJECTED': 'danger'
|
||||||
}
|
}
|
||||||
return typeMap[status] || 'info'
|
return typeMap[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const textMap = {
|
const textMap = {
|
||||||
'CREATED': '已创建',
|
'PENDING': '待处理',
|
||||||
|
'KEYWORD_EXTRACTED': '已提取关键词',
|
||||||
'SEARCHING': '检索中',
|
'SEARCHING': '检索中',
|
||||||
'SEARCHED': '已检索',
|
'SEARCH_COMPLETED': '检索完成',
|
||||||
'RESPONDING': '回复中',
|
'UNDER_REVIEW': '审核中',
|
||||||
'COMPLETED': '已完成'
|
'DOWNLOADING': '下载中',
|
||||||
|
'COMPLETED': '已完成',
|
||||||
|
'REJECTED': '已拒绝'
|
||||||
}
|
}
|
||||||
return textMap[status] || status
|
return textMap[status] || status
|
||||||
}
|
}
|
||||||
|
|
@ -583,10 +566,44 @@ const parseKeywords = (keywords) => {
|
||||||
const parsed = JSON.parse(keywords)
|
const parsed = JSON.parse(keywords)
|
||||||
return Array.isArray(parsed) ? parsed : [keywords]
|
return Array.isArray(parsed) ? parsed : [keywords]
|
||||||
} catch {
|
} catch {
|
||||||
return keywords ? [keywords] : []
|
return [keywords]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatJSON = (jsonStr) => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(jsonStr), null, 2)
|
||||||
|
} catch {
|
||||||
|
return jsonStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearchClinicalTrials = async () => {
|
||||||
|
if (!clinicalTrialKeyword.value.trim()) {
|
||||||
|
ElMessage.warning('请输入药品名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
searchingClinicalTrials.value = true
|
||||||
|
try {
|
||||||
|
await searchClinicalTrials(inquiry.value.id, clinicalTrialKeyword.value.trim())
|
||||||
|
ElMessage.success('临床试验搜索完成')
|
||||||
|
await loadClinicalTrials()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('搜索失败: ' + (error.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
searchingClinicalTrials.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExportClinicalTrials = () => {
|
||||||
|
const exportUrl = exportClinicalTrials(inquiry.value.id)
|
||||||
|
// 使用后端API的完整URL
|
||||||
|
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
||||||
|
window.open(baseURL + exportUrl, '_blank')
|
||||||
|
ElMessage.success('开始下载CSV文件')
|
||||||
|
}
|
||||||
|
|
||||||
const getStatusTagType = (status) => {
|
const getStatusTagType = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'COMPLETED': 'success',
|
'COMPLETED': 'success',
|
||||||
|
|
@ -601,92 +618,32 @@ const getStatusTagType = (status) => {
|
||||||
return statusMap[status?.toUpperCase().replace(/ /g, '_')] || 'info'
|
return statusMap[status?.toUpperCase().replace(/ /g, '_')] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeClinicalTabLabel = (q, idx) => {
|
const handleFullWorkflow = async () => {
|
||||||
const t = (q || '').trim()
|
processing.value = true
|
||||||
const snippet = t.length > 20 ? t.slice(0, 20) + '…' : t
|
|
||||||
return `Clinical Trials ${idx + 1}${snippet ? ' - ' + snippet : ''}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 针对 ClinicalTrials.gov 进行检索式清洗:
|
|
||||||
// 1) 去掉加号,压缩空白;2) 若包含中文,优先回退到英文药名
|
|
||||||
const sanitizeForClinicalTrials = (q) => {
|
|
||||||
const original = (q || '').trim()
|
|
||||||
// 去掉加号、压缩空白
|
|
||||||
let cleaned = original.replace(/\+/g, ' ').replace(/\s+/g, ' ').trim()
|
|
||||||
// 若包含中文,尝试用英文药名
|
|
||||||
const hasCJK = /[\u4e00-\u9fa5]/.test(cleaned)
|
|
||||||
if (hasCJK) {
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(inquiry.value.keywords || '{}')
|
await executeFullWorkflow(inquiry.value.id)
|
||||||
const en = (parsed.drugNameEnglish || '').trim()
|
ElMessage.success('AI智能流程执行完成!正在加载结果...')
|
||||||
if (en) {
|
await loadDetail()
|
||||||
cleaned = en
|
await loadClinicalTrials()
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cleaned
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearchClinicalTrials = async () => {
|
|
||||||
// 取被选中的 ClinicalTrials 范围内的所有检索式(最多两条按需求展示)
|
|
||||||
const queries = (scopeQueries.CLINICAL_TRIALS || [])
|
|
||||||
.map(q => (q.text || '').trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
// 若未设置多条,则回退到标准检索词
|
|
||||||
if (queries.length === 0) {
|
|
||||||
const fallback = (standardQuery.value || '').trim()
|
|
||||||
if (fallback) queries.push(fallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queries.length === 0) {
|
|
||||||
ElMessage.warning('请先填写标准检索词')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
searchingClinicalTrials.value = true
|
|
||||||
try {
|
|
||||||
clinicalTrialsGroups.value = []
|
|
||||||
|
|
||||||
// 逐条检索并收集结果,不依赖后端保存的数据进行展示
|
|
||||||
for (let i = 0; i < queries.length; i++) {
|
|
||||||
const q = queries[i]
|
|
||||||
const searchTerm = sanitizeForClinicalTrials(q)
|
|
||||||
if (!searchTerm) {
|
|
||||||
// 若清洗后为空,则跳过该条但仍保留一个空分组用于可见性
|
|
||||||
clinicalTrialsGroups.value.push({
|
|
||||||
name: `CLINICAL_TRIALS_${i + 1}`,
|
|
||||||
label: makeClinicalTabLabel(q, i),
|
|
||||||
data: []
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 调用现有接口(该接口会清空并保存到后端),但我们直接使用返回值渲染分组,避免相互覆盖的问题
|
|
||||||
const result = await searchClinicalTrials(inquiry.value.id, searchTerm)
|
|
||||||
clinicalTrialsGroups.value.push({
|
|
||||||
name: `CLINICAL_TRIALS_${i + 1}`,
|
|
||||||
label: makeClinicalTabLabel(q, i),
|
|
||||||
data: result || []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认激活第一个分组
|
|
||||||
activeResultsTab.value = clinicalTrialsGroups.value[0]?.name || 'CLINICAL_TRIALS'
|
|
||||||
ElMessage.success('临床试验搜索完成')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('搜索失败: ' + (error.message || '未知错误'))
|
ElMessage.error('执行失败: ' + (error.message || '未知错误'))
|
||||||
} finally {
|
} finally {
|
||||||
searchingClinicalTrials.value = false
|
processing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExportClinicalTrials = () => {
|
const handleAutoSearch = async () => {
|
||||||
const exportUrl = exportClinicalTrials(inquiry.value.id)
|
processing.value = true
|
||||||
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
|
try {
|
||||||
window.open(baseURL + exportUrl, '_blank')
|
await performAutoSearch(inquiry.value.id)
|
||||||
ElMessage.success('开始下载CSV文件')
|
ElMessage.success('智能自动检索完成!')
|
||||||
|
await loadDetail()
|
||||||
|
await loadClinicalTrials()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('检索失败: ' + (error.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
processing.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStructuredKeywords = (keywords) => {
|
const isStructuredKeywords = (keywords) => {
|
||||||
|
|
@ -698,16 +655,6 @@ const isStructuredKeywords = (keywords) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildStandardQueryFromKeywords = () => {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(inquiry.value.keywords || '{}')
|
|
||||||
const parts = [parsed.drugNameChinese, parsed.drugNameEnglish, parsed.requestItem].filter(Boolean)
|
|
||||||
standardQuery.value = parts.join(' ')
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getKeywordField = (keywords, field) => {
|
const getKeywordField = (keywords, field) => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(keywords)
|
const parsed = JSON.parse(keywords)
|
||||||
|
|
@ -717,6 +664,18 @@ const getKeywordField = (keywords, field) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goToKeywordConfirmation = () => {
|
||||||
|
router.push(`/inquiry/${inquiry.value.id}/keyword-confirmation`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToDataSourceSelection = () => {
|
||||||
|
router.push(`/inquiry/${inquiry.value.id}/data-source-selection`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToSearchResults = () => {
|
||||||
|
router.push(`/inquiry/${inquiry.value.id}/search-results`)
|
||||||
|
}
|
||||||
|
|
||||||
const goToResponseView = () => {
|
const goToResponseView = () => {
|
||||||
router.push(`/inquiry/${inquiry.value.id}/response-view`)
|
router.push(`/inquiry/${inquiry.value.id}/response-view`)
|
||||||
}
|
}
|
||||||
|
|
@ -773,16 +732,6 @@ const goToResponseView = () => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.query-editor {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.query-chip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@
|
||||||
<el-form-item label="状态">
|
<el-form-item label="状态">
|
||||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||||
<el-option label="全部" value="" />
|
<el-option label="全部" value="" />
|
||||||
<el-option label="已创建" value="CREATED" />
|
<el-option label="待处理" value="PENDING" />
|
||||||
|
<el-option label="已提取关键词" value="KEYWORD_EXTRACTED" />
|
||||||
<el-option label="检索中" value="SEARCHING" />
|
<el-option label="检索中" value="SEARCHING" />
|
||||||
<el-option label="已检索" value="SEARCHED" />
|
<el-option label="检索完成" value="SEARCH_COMPLETED" />
|
||||||
<el-option label="回复中" value="RESPONDING" />
|
<el-option label="审核中" value="UNDER_REVIEW" />
|
||||||
|
<el-option label="下载中" value="DOWNLOADING" />
|
||||||
<el-option label="已完成" value="COMPLETED" />
|
<el-option label="已完成" value="COMPLETED" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -142,22 +144,28 @@ const processInquiry = (id) => {
|
||||||
|
|
||||||
const getStatusType = (status) => {
|
const getStatusType = (status) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'CREATED': 'info',
|
'PENDING': 'info',
|
||||||
|
'KEYWORD_EXTRACTED': 'warning',
|
||||||
'SEARCHING': 'warning',
|
'SEARCHING': 'warning',
|
||||||
'SEARCHED': '',
|
'SEARCH_COMPLETED': '',
|
||||||
'RESPONDING': 'warning',
|
'UNDER_REVIEW': 'warning',
|
||||||
'COMPLETED': 'success'
|
'DOWNLOADING': 'warning',
|
||||||
|
'COMPLETED': 'success',
|
||||||
|
'REJECTED': 'danger'
|
||||||
}
|
}
|
||||||
return typeMap[status] || 'info'
|
return typeMap[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const textMap = {
|
const textMap = {
|
||||||
'CREATED': '已创建',
|
'PENDING': '待处理',
|
||||||
|
'KEYWORD_EXTRACTED': '已提取关键词',
|
||||||
'SEARCHING': '检索中',
|
'SEARCHING': '检索中',
|
||||||
'SEARCHED': '已检索',
|
'SEARCH_COMPLETED': '检索完成',
|
||||||
'RESPONDING': '回复中',
|
'UNDER_REVIEW': '审核中',
|
||||||
'COMPLETED': '已完成'
|
'DOWNLOADING': '下载中',
|
||||||
|
'COMPLETED': '已完成',
|
||||||
|
'REJECTED': '已拒绝'
|
||||||
}
|
}
|
||||||
return textMap[status] || status
|
return textMap[status] || status
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,7 +336,3 @@ const goBack = () => {
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,3 @@ const goBack = () => {
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,6 @@
|
||||||
>
|
>
|
||||||
批量标记下载
|
批量标记下载
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleGenerateDownloadTasks"
|
|
||||||
:disabled="downloadSelectedIds.length === 0"
|
|
||||||
>
|
|
||||||
生成下载任务 ({{ downloadSelectedIds.length }})
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleBatchDelete"
|
@click="handleBatchDelete"
|
||||||
|
|
@ -222,7 +215,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { getInquiryDetail, generateResponse, generateDownloadTasks } from '@/api/inquiry'
|
import { getInquiryDetail, generateResponse } from '@/api/inquiry'
|
||||||
import {
|
import {
|
||||||
getSearchResults,
|
getSearchResults,
|
||||||
updateSearchResult,
|
updateSearchResult,
|
||||||
|
|
@ -239,7 +232,6 @@ const generating = ref(false)
|
||||||
const inquiry = ref({})
|
const inquiry = ref({})
|
||||||
const results = ref([])
|
const results = ref([])
|
||||||
const selectedResults = ref([])
|
const selectedResults = ref([])
|
||||||
const downloadSelectedIds = computed(() => selectedResults.value.filter(r => r.needDownload && !r.isDeleted).map(r => r.id))
|
|
||||||
const filterType = ref('active')
|
const filterType = ref('active')
|
||||||
const searchText = ref('')
|
const searchText = ref('')
|
||||||
const tableRef = ref(null)
|
const tableRef = ref(null)
|
||||||
|
|
@ -403,19 +395,6 @@ const handleBatchDownload = async () => {
|
||||||
ElMessage.error('批量操作失败')
|
ElMessage.error('批量操作失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const handleGenerateDownloadTasks = async () => {
|
|
||||||
if (downloadSelectedIds.value.length === 0) {
|
|
||||||
ElMessage.warning('请先选择并标记需要下载的条目')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await generateDownloadTasks(inquiry.value.id, downloadSelectedIds.value)
|
|
||||||
ElMessage.success('下载任务已生成,状态已更新为“下载中”')
|
|
||||||
await loadDetail()
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('生成下载任务失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBatchDelete = async () => {
|
const handleBatchDelete = async () => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Ipsen",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue