feat: 完成后端基础功能开发,包括用户注册、问卷调查等功能
This commit is contained in:
parent
20c57a13d2
commit
d9c64cb28f
13
.cursorrules
13
.cursorrules
@ -60,3 +60,16 @@
|
|||||||
- 使用Git进行版本控制
|
- 使用Git进行版本控制
|
||||||
- 遵循语义化版本规范
|
- 遵循语义化版本规范
|
||||||
- 重要配置文件加入版本控制
|
- 重要配置文件加入版本控制
|
||||||
|
|
||||||
|
## 11. 文件操作
|
||||||
|
- 当需要复制文件时,使用 `command cp` 命令
|
||||||
|
- 如果发现目录不存在,首先确认自己当前目录是否正确
|
||||||
|
- 如果需要创建目录,使用 `command mkdir -p` 命令
|
||||||
|
- 不要尝试重新安装开发依赖工具,比如jdk, node,python等
|
||||||
|
|
||||||
|
## 前端开发
|
||||||
|
- 前端使用vue3开发
|
||||||
|
- 代码必须严格遵守 eslint 规则
|
||||||
|
- 前端项目使用yarn打包,用最新的4.x版
|
||||||
|
- 前端项目根目录下需要有`.yarn.yml`配置文件
|
||||||
|
- 前端启动开发服务器,需要把切换到前端目录以及启动开发服务器两个命令合并执行
|
||||||
@ -83,6 +83,11 @@
|
|||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>${jackson.version}</version>
|
<version>${jackson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- PageHelper -->
|
<!-- PageHelper -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
package com.fasterxml.jackson.databind;
|
|
||||||
|
|
||||||
import java.sql.CallableStatement;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import org.apache.ibatis.type.BaseTypeHandler;
|
|
||||||
import org.apache.ibatis.type.JdbcType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义的 JSON 类型处理器
|
|
||||||
*/
|
|
||||||
public class JsonTypeHandler extends BaseTypeHandler<Object> {
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
|
|
||||||
throws SQLException {
|
|
||||||
try {
|
|
||||||
ps.setString(i, MAPPER.writeValueAsString(parameter));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SQLException("Error converting JSON to String", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
|
||||||
return parse(rs.getString(columnName));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
|
||||||
return parse(rs.getString(columnIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
|
||||||
return parse(cs.getString(columnIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object parse(String json) throws SQLException {
|
|
||||||
try {
|
|
||||||
if (json == null || json.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return MAPPER.readValue(json, Object.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SQLException("Error converting String to JSON", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package ltd.qubit.survey.common.mybatis;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Instant;
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instant类型处理器
|
||||||
|
*/
|
||||||
|
public class InstantTypeHandler extends BaseTypeHandler<Instant> {
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType)
|
||||||
|
throws SQLException {
|
||||||
|
ps.setTimestamp(i, Timestamp.from(parameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||||
|
Timestamp timestamp = rs.getTimestamp(columnName);
|
||||||
|
return timestamp != null ? timestamp.toInstant() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||||
|
Timestamp timestamp = rs.getTimestamp(columnIndex);
|
||||||
|
return timestamp != null ? timestamp.toInstant() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||||
|
Timestamp timestamp = cs.getTimestamp(columnIndex);
|
||||||
|
return timestamp != null ? timestamp.toInstant() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.model;
|
package ltd.qubit.survey.model;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,5 +36,5 @@ public class Option {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime createdAt;
|
private Instant createdAt;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.model;
|
package ltd.qubit.survey.model;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,5 +46,5 @@ public class Question {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime createdAt;
|
private Instant createdAt;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.model;
|
package ltd.qubit.survey.model;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -37,5 +37,5 @@ public class SurveyResponse {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime createdAt;
|
private Instant createdAt;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.model;
|
package ltd.qubit.survey.model;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,5 +36,5 @@ public class User {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime createdAt;
|
private Instant createdAt;
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.service.impl;
|
package ltd.qubit.survey.service.impl;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -19,7 +19,7 @@ public class OptionServiceImpl implements OptionService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Option create(Option option) {
|
public Option create(Option option) {
|
||||||
option.setCreatedAt(LocalDateTime.now());
|
option.setCreatedAt(Instant.now());
|
||||||
optionDao.insert(option);
|
optionDao.insert(option);
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ public class OptionServiceImpl implements OptionService {
|
|||||||
@Override
|
@Override
|
||||||
public List<Option> batchCreate(List<Option> options) {
|
public List<Option> batchCreate(List<Option> options) {
|
||||||
// 设置创建时间
|
// 设置创建时间
|
||||||
LocalDateTime now = LocalDateTime.now();
|
Instant now = Instant.now();
|
||||||
options.forEach(option -> option.setCreatedAt(now));
|
options.forEach(option -> option.setCreatedAt(now));
|
||||||
|
|
||||||
// 批量插入
|
// 批量插入
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package ltd.qubit.survey.service.impl;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -32,7 +32,7 @@ public class QuestionServiceImpl implements QuestionService {
|
|||||||
if (question.getQuestionNumber() == null) {
|
if (question.getQuestionNumber() == null) {
|
||||||
question.setQuestionNumber(questionDao.getNextQuestionNumber());
|
question.setQuestionNumber(questionDao.getNextQuestionNumber());
|
||||||
}
|
}
|
||||||
question.setCreatedAt(LocalDateTime.now());
|
question.setCreatedAt(Instant.now());
|
||||||
questionDao.insert(question);
|
questionDao.insert(question);
|
||||||
return question;
|
return question;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.service.impl;
|
package ltd.qubit.survey.service.impl;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -22,7 +22,7 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SurveyResponse create(SurveyResponse response) {
|
public SurveyResponse create(SurveyResponse response) {
|
||||||
response.setCreatedAt(LocalDateTime.now());
|
response.setCreatedAt(Instant.now());
|
||||||
surveyResponseDao.insert(response);
|
surveyResponseDao.insert(response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
|||||||
@Override
|
@Override
|
||||||
public List<SurveyResponse> batchSave(List<SurveyResponse> responses) {
|
public List<SurveyResponse> batchSave(List<SurveyResponse> responses) {
|
||||||
// 设置创建时间
|
// 设置创建时间
|
||||||
LocalDateTime now = LocalDateTime.now();
|
Instant now = Instant.now();
|
||||||
responses.forEach(response -> response.setCreatedAt(now));
|
responses.forEach(response -> response.setCreatedAt(now));
|
||||||
|
|
||||||
// 批量插入
|
// 批量插入
|
||||||
@ -98,7 +98,7 @@ public class SurveyResponseServiceImpl implements SurveyResponseService {
|
|||||||
deleteByUserId(userId);
|
deleteByUserId(userId);
|
||||||
|
|
||||||
// 设置用户ID和创建时间
|
// 设置用户ID和创建时间
|
||||||
LocalDateTime now = LocalDateTime.now();
|
Instant now = Instant.now();
|
||||||
responses.forEach(response -> {
|
responses.forEach(response -> {
|
||||||
response.setUserId(userId);
|
response.setUserId(userId);
|
||||||
response.setCreatedAt(now);
|
response.setCreatedAt(now);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package ltd.qubit.survey.service.impl;
|
package ltd.qubit.survey.service.impl;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -20,7 +20,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User create(User user) {
|
public User create(User user) {
|
||||||
user.setCreatedAt(LocalDateTime.now());
|
user.setCreatedAt(Instant.now());
|
||||||
userDao.insert(user);
|
userDao.insert(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package ltd.qubit.survey.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义的ObjectMapper,支持Java 8日期时间类型
|
||||||
|
*/
|
||||||
|
public class CustomObjectMapper extends ObjectMapper {
|
||||||
|
public CustomObjectMapper() {
|
||||||
|
super();
|
||||||
|
// 注册Java 8日期时间模块
|
||||||
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
registerModule(javaTimeModule);
|
||||||
|
|
||||||
|
// 配置序列化特性
|
||||||
|
setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
// 将日期序列化为时间戳(毫秒)
|
||||||
|
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
|
||||||
|
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
||||||
|
// 设置默认时区为UTC
|
||||||
|
setTimeZone(java.util.TimeZone.getTimeZone(ZoneOffset.UTC));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
<id property="id" column="id"/>
|
<id property="id" column="id"/>
|
||||||
<result property="userId" column="user_id"/>
|
<result property="userId" column="user_id"/>
|
||||||
<result property="questionId" column="question_id"/>
|
<result property="questionId" column="question_id"/>
|
||||||
<result property="selectedOptions" column="selected_options" typeHandler="com.fasterxml.jackson.databind.JsonTypeHandler"/>
|
<result property="selectedOptions" column="selected_options" typeHandler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"/>
|
||||||
<result property="textAnswer" column="text_answer"/>
|
<result property="textAnswer" column="text_answer"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<!-- 插入 -->
|
<!-- 插入 -->
|
||||||
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
|
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
|
||||||
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler}, #{textAnswer})
|
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler}, #{textAnswer})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<!-- 批量插入 -->
|
<!-- 批量插入 -->
|
||||||
@ -28,7 +28,7 @@
|
|||||||
VALUES
|
VALUES
|
||||||
<foreach collection="list" item="item" separator=",">
|
<foreach collection="list" item="item" separator=",">
|
||||||
(#{item.userId}, #{item.questionId},
|
(#{item.userId}, #{item.questionId},
|
||||||
#{item.selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler},
|
#{item.selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||||
#{item.textAnswer})
|
#{item.textAnswer})
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
UPDATE survey_responses
|
UPDATE survey_responses
|
||||||
SET user_id = #{userId},
|
SET user_id = #{userId},
|
||||||
question_id = #{questionId},
|
question_id = #{questionId},
|
||||||
selected_options = #{selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler},
|
selected_options = #{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||||
text_answer = #{textAnswer}
|
text_answer = #{textAnswer}
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|||||||
@ -29,6 +29,9 @@
|
|||||||
<!-- JSON类型处理器 -->
|
<!-- JSON类型处理器 -->
|
||||||
<typeHandler handler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"
|
<typeHandler handler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"
|
||||||
javaType="java.util.List"/>
|
javaType="java.util.List"/>
|
||||||
|
<!-- Instant类型处理器 -->
|
||||||
|
<typeHandler handler="ltd.qubit.survey.common.mybatis.InstantTypeHandler"
|
||||||
|
javaType="java.time.Instant"/>
|
||||||
</typeHandlers>
|
</typeHandlers>
|
||||||
|
|
||||||
<!-- 映射器配置 -->
|
<!-- 映射器配置 -->
|
||||||
|
|||||||
@ -16,18 +16,8 @@
|
|||||||
<!-- 加载属性文件 -->
|
<!-- 加载属性文件 -->
|
||||||
<context:property-placeholder location="classpath:application.properties"/>
|
<context:property-placeholder location="classpath:application.properties"/>
|
||||||
|
|
||||||
<!-- 配置ObjectMapper -->
|
<!-- 配置自定义ObjectMapper -->
|
||||||
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" primary="true">
|
<bean id="objectMapper" class="ltd.qubit.survey.utils.CustomObjectMapper" primary="true"/>
|
||||||
<property name="dateFormat">
|
|
||||||
<bean class="java.text.SimpleDateFormat">
|
|
||||||
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
<!-- 配置JSON序列化特性 -->
|
|
||||||
<property name="serializationInclusion">
|
|
||||||
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<!-- 开启注解扫描,排除Controller -->
|
<!-- 开启注解扫描,排除Controller -->
|
||||||
<context:component-scan base-package="ltd.qubit.survey">
|
<context:component-scan base-package="ltd.qubit.survey">
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -6,7 +6,7 @@
|
|||||||
<id property="id" column="id"/>
|
<id property="id" column="id"/>
|
||||||
<result property="userId" column="user_id"/>
|
<result property="userId" column="user_id"/>
|
||||||
<result property="questionId" column="question_id"/>
|
<result property="questionId" column="question_id"/>
|
||||||
<result property="selectedOptions" column="selected_options" typeHandler="com.fasterxml.jackson.databind.JsonTypeHandler"/>
|
<result property="selectedOptions" column="selected_options" typeHandler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"/>
|
||||||
<result property="textAnswer" column="text_answer"/>
|
<result property="textAnswer" column="text_answer"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<!-- 插入 -->
|
<!-- 插入 -->
|
||||||
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" parameterType="ltd.qubit.survey.model.SurveyResponse" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
|
INSERT INTO survey_responses (user_id, question_id, selected_options, text_answer)
|
||||||
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler}, #{textAnswer})
|
VALUES (#{userId}, #{questionId}, #{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler}, #{textAnswer})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<!-- 批量插入 -->
|
<!-- 批量插入 -->
|
||||||
@ -28,7 +28,7 @@
|
|||||||
VALUES
|
VALUES
|
||||||
<foreach collection="list" item="item" separator=",">
|
<foreach collection="list" item="item" separator=",">
|
||||||
(#{item.userId}, #{item.questionId},
|
(#{item.userId}, #{item.questionId},
|
||||||
#{item.selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler},
|
#{item.selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||||
#{item.textAnswer})
|
#{item.textAnswer})
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
UPDATE survey_responses
|
UPDATE survey_responses
|
||||||
SET user_id = #{userId},
|
SET user_id = #{userId},
|
||||||
question_id = #{questionId},
|
question_id = #{questionId},
|
||||||
selected_options = #{selectedOptions,typeHandler=com.fasterxml.jackson.databind.JsonTypeHandler},
|
selected_options = #{selectedOptions,typeHandler=ltd.qubit.survey.common.mybatis.JsonTypeHandler},
|
||||||
text_answer = #{textAnswer}
|
text_answer = #{textAnswer}
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|||||||
@ -29,6 +29,9 @@
|
|||||||
<!-- JSON类型处理器 -->
|
<!-- JSON类型处理器 -->
|
||||||
<typeHandler handler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"
|
<typeHandler handler="ltd.qubit.survey.common.mybatis.JsonTypeHandler"
|
||||||
javaType="java.util.List"/>
|
javaType="java.util.List"/>
|
||||||
|
<!-- Instant类型处理器 -->
|
||||||
|
<typeHandler handler="ltd.qubit.survey.common.mybatis.InstantTypeHandler"
|
||||||
|
javaType="java.time.Instant"/>
|
||||||
</typeHandlers>
|
</typeHandlers>
|
||||||
|
|
||||||
<!-- 映射器配置 -->
|
<!-- 映射器配置 -->
|
||||||
|
|||||||
@ -16,18 +16,8 @@
|
|||||||
<!-- 加载属性文件 -->
|
<!-- 加载属性文件 -->
|
||||||
<context:property-placeholder location="classpath:application.properties"/>
|
<context:property-placeholder location="classpath:application.properties"/>
|
||||||
|
|
||||||
<!-- 配置ObjectMapper -->
|
<!-- 配置自定义ObjectMapper -->
|
||||||
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" primary="true">
|
<bean id="objectMapper" class="ltd.qubit.survey.utils.CustomObjectMapper" primary="true"/>
|
||||||
<property name="dateFormat">
|
|
||||||
<bean class="java.text.SimpleDateFormat">
|
|
||||||
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
<!-- 配置JSON序列化特性 -->
|
|
||||||
<property name="serializationInclusion">
|
|
||||||
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<!-- 开启注解扫描,排除Controller -->
|
<!-- 开启注解扫描,排除Controller -->
|
||||||
<context:component-scan base-package="ltd.qubit.survey">
|
<context:component-scan base-package="ltd.qubit.survey">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
ltd/qubit/survey/common/mybatis/InstantTypeHandler.class
|
||||||
ltd/qubit/survey/controller/GlobalExceptionHandler.class
|
ltd/qubit/survey/controller/GlobalExceptionHandler.class
|
||||||
ltd/qubit/survey/dao/QuestionDao.class
|
ltd/qubit/survey/dao/QuestionDao.class
|
||||||
ltd/qubit/survey/model/PositionType.class
|
ltd/qubit/survey/model/PositionType.class
|
||||||
@ -10,6 +11,7 @@ ltd/qubit/survey/controller/QuestionController.class
|
|||||||
ltd/qubit/survey/service/impl/UserServiceImpl.class
|
ltd/qubit/survey/service/impl/UserServiceImpl.class
|
||||||
ltd/qubit/survey/service/impl/QuestionServiceImpl.class
|
ltd/qubit/survey/service/impl/QuestionServiceImpl.class
|
||||||
ltd/qubit/survey/model/SurveyResponse.class
|
ltd/qubit/survey/model/SurveyResponse.class
|
||||||
|
ltd/qubit/survey/utils/CustomObjectMapper.class
|
||||||
ltd/qubit/survey/model/User.class
|
ltd/qubit/survey/model/User.class
|
||||||
ltd/qubit/survey/service/QuestionService.class
|
ltd/qubit/survey/service/QuestionService.class
|
||||||
ltd/qubit/survey/common/mybatis/JsonTypeHandler.class
|
ltd/qubit/survey/common/mybatis/JsonTypeHandler.class
|
||||||
@ -26,4 +28,3 @@ ltd/qubit/survey/service/SurveyResponseService.class
|
|||||||
ltd/qubit/survey/model/Option.class
|
ltd/qubit/survey/model/Option.class
|
||||||
ltd/qubit/survey/model/QuestionType.class
|
ltd/qubit/survey/model/QuestionType.class
|
||||||
ltd/qubit/survey/service/BaseService.class
|
ltd/qubit/survey/service/BaseService.class
|
||||||
com/fasterxml/jackson/databind/JsonTypeHandler.class
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/User.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/User.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/OptionServiceImpl.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/OptionServiceImpl.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/QuestionServiceImpl.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/impl/QuestionServiceImpl.java
|
||||||
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/common/mybatis/InstantTypeHandler.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/SurveyResponseService.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/service/SurveyResponseService.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/com/fasterxml/jackson/databind/JsonTypeHandler.java
|
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/Option.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/Option.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/GlobalExceptionHandler.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/GlobalExceptionHandler.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/QuestionType.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/QuestionType.java
|
||||||
@ -22,6 +22,7 @@
|
|||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/SurveyResponseDao.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/SurveyResponseDao.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/QuestionController.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/QuestionController.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/WorkArea.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/WorkArea.java
|
||||||
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/utils/CustomObjectMapper.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/PositionType.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/model/PositionType.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/BaseDao.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/dao/BaseDao.java
|
||||||
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/SurveyController.java
|
/Volumes/working/qubit/project/llm-survey/backend/src/main/java/ltd/qubit/survey/controller/SurveyController.java
|
||||||
|
|||||||
18
frontend/.eslintrc.js
Normal file
18
frontend/.eslintrc.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-prettier',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'vue/no-v-model-argument': 'off',
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
7
frontend/.prettierrc
Normal file
7
frontend/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
BIN
frontend/.yarn/install-state.gz
Normal file
BIN
frontend/.yarn/install-state.gz
Normal file
Binary file not shown.
2
frontend/.yarnrc.yml
Normal file
2
frontend/.yarnrc.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
|
enableGlobalCache: true
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
|
||||||
|
<title>LLM 问卷调查</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
frontend/package.json
Normal file
38
frontend/package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "llm-survey",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vant": "^4.8.3",
|
||||||
|
"vue": "^3.4.15",
|
||||||
|
"vue-router": "^4.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.2",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-vue": "^9.21.1",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"postcss-px-to-viewport": "^1.1.1",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"rollup": "^4.34.8",
|
||||||
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"vite": "^5.0.12"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.6.0"
|
||||||
|
}
|
||||||
27
frontend/src/App.vue
Normal file
27
frontend/src/App.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<component :is="Component" />
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 根组件逻辑
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局样式 */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
||||||
|
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
frontend/src/api/survey.js
Normal file
47
frontend/src/api/survey.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// 获取用户的问题列表
|
||||||
|
export function getUserQuestions(userId) {
|
||||||
|
return request({
|
||||||
|
url: `/question/user/${userId}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取问题的选项列表
|
||||||
|
export function getQuestionOptions(questionId) {
|
||||||
|
return request({
|
||||||
|
url: `/question/${questionId}/option`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取下一个问题
|
||||||
|
export function getNextQuestion(userId, currentQuestionNumber, selectedOptions) {
|
||||||
|
return request({
|
||||||
|
url: '/question/next',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
userId,
|
||||||
|
currentQuestionNumber,
|
||||||
|
selectedOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交问卷答案
|
||||||
|
export function submitSurvey(userId, responses) {
|
||||||
|
return request({
|
||||||
|
url: `/survey/submit/${userId}`,
|
||||||
|
method: 'post',
|
||||||
|
data: responses,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户的问卷答案
|
||||||
|
export function getUserResponses(userId) {
|
||||||
|
return request({
|
||||||
|
url: `/survey/user/${userId}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
26
frontend/src/api/user.js
Normal file
26
frontend/src/api/user.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// 用户注册
|
||||||
|
export function register(data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/register',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查手机号是否已注册
|
||||||
|
export function checkPhone(phone) {
|
||||||
|
return request({
|
||||||
|
url: `/user/check/${phone}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取用户信息
|
||||||
|
export function getUserInfo(id) {
|
||||||
|
return request({
|
||||||
|
url: `/user/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
12
frontend/src/main.js
Normal file
12
frontend/src/main.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import App from './App.vue';
|
||||||
|
import router from './router';
|
||||||
|
import 'vant/lib/index.css';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mount('#app');
|
||||||
43
frontend/src/router/index.js
Normal file
43
frontend/src/router/index.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('@/views/HomeView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
|
component: () => import('@/views/RegisterView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/survey',
|
||||||
|
name: 'survey',
|
||||||
|
component: () => import('@/views/SurveyView.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
|
||||||
|
// 如果是从注册页面来的,允许直接进入需要认证的页面
|
||||||
|
if (from.name === 'register' && to.meta.requiresAuth) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况下检查认证
|
||||||
|
if (to.meta.requiresAuth && !userId) {
|
||||||
|
next({ name: 'register' });
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
105
frontend/src/stores/survey.js
Normal file
105
frontend/src/stores/survey.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import {
|
||||||
|
getUserQuestions,
|
||||||
|
getQuestionOptions,
|
||||||
|
getNextQuestion,
|
||||||
|
submitSurvey,
|
||||||
|
getUserResponses
|
||||||
|
} from '@/api/survey';
|
||||||
|
import { showToast } from 'vant';
|
||||||
|
|
||||||
|
export const useSurveyStore = defineStore('survey', () => {
|
||||||
|
const currentQuestion = ref(null);
|
||||||
|
const questionOptions = ref([]);
|
||||||
|
const userResponses = ref([]);
|
||||||
|
const currentQuestionNumber = ref(0);
|
||||||
|
const isCompleted = ref(false);
|
||||||
|
|
||||||
|
// 获取用户的问题列表
|
||||||
|
async function fetchUserQuestions(userId) {
|
||||||
|
try {
|
||||||
|
const response = await getUserQuestions(userId);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('获取问题列表失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取问题的选项列表
|
||||||
|
async function fetchQuestionOptions(questionId) {
|
||||||
|
try {
|
||||||
|
const response = await getQuestionOptions(questionId);
|
||||||
|
questionOptions.value = response.data;
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('获取问题选项失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取下一个问题
|
||||||
|
async function fetchNextQuestion(userId, selectedOptions) {
|
||||||
|
try {
|
||||||
|
const response = await getNextQuestion(userId, currentQuestionNumber.value, selectedOptions);
|
||||||
|
if (response.data) {
|
||||||
|
currentQuestion.value = response.data;
|
||||||
|
currentQuestionNumber.value++;
|
||||||
|
await fetchQuestionOptions(response.data.id);
|
||||||
|
} else {
|
||||||
|
isCompleted.value = true;
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('获取下一个问题失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交问卷答案
|
||||||
|
async function submitSurveyResponses(userId, responses) {
|
||||||
|
try {
|
||||||
|
const response = await submitSurvey(userId, responses);
|
||||||
|
userResponses.value = responses;
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('提交问卷失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户的问卷答案
|
||||||
|
async function fetchUserResponses(userId) {
|
||||||
|
try {
|
||||||
|
const response = await getUserResponses(userId);
|
||||||
|
userResponses.value = response.data;
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('获取问卷答案失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置问卷状态
|
||||||
|
function resetSurvey() {
|
||||||
|
currentQuestion.value = null;
|
||||||
|
questionOptions.value = [];
|
||||||
|
currentQuestionNumber.value = 0;
|
||||||
|
isCompleted.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentQuestion,
|
||||||
|
questionOptions,
|
||||||
|
userResponses,
|
||||||
|
currentQuestionNumber,
|
||||||
|
isCompleted,
|
||||||
|
fetchUserQuestions,
|
||||||
|
fetchQuestionOptions,
|
||||||
|
fetchNextQuestion,
|
||||||
|
submitSurveyResponses,
|
||||||
|
fetchUserResponses,
|
||||||
|
resetSurvey,
|
||||||
|
};
|
||||||
|
});
|
||||||
61
frontend/src/stores/user.js
Normal file
61
frontend/src/stores/user.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { register, checkPhone, getUserInfo } from '@/api/user';
|
||||||
|
import { showToast } from 'vant';
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', () => {
|
||||||
|
const userId = ref(localStorage.getItem('userId') || '');
|
||||||
|
const userInfo = ref(null);
|
||||||
|
|
||||||
|
// 注册用户
|
||||||
|
async function registerUser(data) {
|
||||||
|
try {
|
||||||
|
const response = await register(data);
|
||||||
|
userId.value = response.data.id;
|
||||||
|
localStorage.setItem('userId', userId.value);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('注册失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查手机号是否已注册
|
||||||
|
async function checkPhoneNumber(phone) {
|
||||||
|
try {
|
||||||
|
const response = await checkPhone(phone);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('检查手机号失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
async function fetchUserInfo(id) {
|
||||||
|
try {
|
||||||
|
const response = await getUserInfo(id);
|
||||||
|
userInfo.value = response.data;
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('获取用户信息失败:' + error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
function logout() {
|
||||||
|
userId.value = '';
|
||||||
|
userInfo.value = null;
|
||||||
|
localStorage.removeItem('userId');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userId,
|
||||||
|
userInfo,
|
||||||
|
registerUser,
|
||||||
|
checkPhoneNumber,
|
||||||
|
fetchUserInfo,
|
||||||
|
logout,
|
||||||
|
};
|
||||||
|
});
|
||||||
32
frontend/src/utils/request.js
Normal file
32
frontend/src/utils/request.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { showToast } from 'vant';
|
||||||
|
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: '/api',
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
request.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// 可以在这里添加 token 等认证信息
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
const message = error.response?.data?.message || '请求失败,请稍后重试';
|
||||||
|
showToast(message);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default request;
|
||||||
106
frontend/src/views/HomeView.vue
Normal file
106
frontend/src/views/HomeView.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home-view">
|
||||||
|
<van-nav-bar title="LLM 问卷调查" />
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<van-image
|
||||||
|
class="logo"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
src="/logo.png"
|
||||||
|
fit="contain"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h1 class="title">欢迎参与 LLM 问卷调查</h1>
|
||||||
|
<p class="description">
|
||||||
|
本问卷旨在了解您对大语言模型(LLM)的使用体验和看法。您的反馈对我们非常重要。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<van-button
|
||||||
|
v-if="!userStore.userId"
|
||||||
|
round
|
||||||
|
block
|
||||||
|
type="primary"
|
||||||
|
@click="router.push('/register')"
|
||||||
|
>
|
||||||
|
开始参与
|
||||||
|
</van-button>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<van-button
|
||||||
|
round
|
||||||
|
block
|
||||||
|
type="primary"
|
||||||
|
@click="router.push('/survey')"
|
||||||
|
>
|
||||||
|
继续答题
|
||||||
|
</van-button>
|
||||||
|
|
||||||
|
<van-button
|
||||||
|
round
|
||||||
|
block
|
||||||
|
type="default"
|
||||||
|
@click="onLogout"
|
||||||
|
class="logout-btn"
|
||||||
|
>
|
||||||
|
退出登录
|
||||||
|
</van-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { showToast } from 'vant';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
function onLogout() {
|
||||||
|
userStore.logout();
|
||||||
|
showToast('已退出登录');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-view {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #323233;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
181
frontend/src/views/RegisterView.vue
Normal file
181
frontend/src/views/RegisterView.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="register-view">
|
||||||
|
<van-nav-bar title="用户注册" />
|
||||||
|
|
||||||
|
<van-form @submit="onSubmit" class="register-form">
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-field
|
||||||
|
v-model="formData.phone"
|
||||||
|
name="phone"
|
||||||
|
label="手机号"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请填写手机号' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||||
|
]"
|
||||||
|
@blur="onPhoneBlur"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="formData.name"
|
||||||
|
name="name"
|
||||||
|
label="姓名"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
:rules="[{ required: true, message: '请填写姓名' }]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="displayWorkArea"
|
||||||
|
name="workArea"
|
||||||
|
label="工作内容"
|
||||||
|
readonly
|
||||||
|
clickable
|
||||||
|
placeholder="请选择工作内容"
|
||||||
|
:rules="[{ required: true, message: '请选择工作内容' }]"
|
||||||
|
@click="showWorkAreaPicker = true"
|
||||||
|
/>
|
||||||
|
<van-popup v-model:show="showWorkAreaPicker" position="bottom">
|
||||||
|
<van-picker
|
||||||
|
:columns="workAreaOptions"
|
||||||
|
@confirm="onWorkAreaConfirm"
|
||||||
|
@cancel="showWorkAreaPicker = false"
|
||||||
|
show-toolbar
|
||||||
|
title="选择工作内容"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="displayPositionType"
|
||||||
|
name="positionType"
|
||||||
|
label="岗位性质"
|
||||||
|
readonly
|
||||||
|
clickable
|
||||||
|
placeholder="请选择岗位性质"
|
||||||
|
:rules="[{ required: true, message: '请选择岗位性质' }]"
|
||||||
|
@click="showPositionTypePicker = true"
|
||||||
|
/>
|
||||||
|
<van-popup v-model:show="showPositionTypePicker" position="bottom">
|
||||||
|
<van-picker
|
||||||
|
:columns="positionTypeOptions"
|
||||||
|
@confirm="onPositionTypeConfirm"
|
||||||
|
@cancel="showPositionTypePicker = false"
|
||||||
|
show-toolbar
|
||||||
|
title="选择岗位性质"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="submit-btn">
|
||||||
|
<van-button round block type="primary" native-type="submit">
|
||||||
|
注册
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { showToast, showNotify } from 'vant';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
phone: '',
|
||||||
|
name: '',
|
||||||
|
workArea: '',
|
||||||
|
positionType: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const showWorkAreaPicker = ref(false);
|
||||||
|
const showPositionTypePicker = ref(false);
|
||||||
|
const displayWorkArea = ref('');
|
||||||
|
const displayPositionType = ref('');
|
||||||
|
|
||||||
|
const workAreaOptions = [
|
||||||
|
{ text: '研发', value: 'RD' },
|
||||||
|
{ text: '项目', value: 'PROJECT' },
|
||||||
|
{ text: '保险', value: 'INSURANCE' },
|
||||||
|
{ text: '财务', value: 'FINANCE' },
|
||||||
|
{ text: '运营', value: 'OPERATION' },
|
||||||
|
{ text: '客服', value: 'CUSTOMER_SERVICE' },
|
||||||
|
{ text: '综合管理', value: 'ADMIN' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const positionTypeOptions = [
|
||||||
|
{ text: '管理岗', value: 'MANAGEMENT' },
|
||||||
|
{ text: '技术岗', value: 'TECHNICAL' },
|
||||||
|
{ text: '业务岗', value: 'BUSINESS' },
|
||||||
|
{ text: '职能支持岗', value: 'SUPPORT' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 手机号失去焦点时检查是否已注册
|
||||||
|
async function onPhoneBlur() {
|
||||||
|
if (formData.phone && /^1[3-9]\d{9}$/.test(formData.phone)) {
|
||||||
|
try {
|
||||||
|
const isRegistered = await userStore.checkPhoneNumber(formData.phone);
|
||||||
|
if (isRegistered) {
|
||||||
|
showToast('该手机号已注册');
|
||||||
|
formData.phone = '';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查手机号失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工作内容选择确认
|
||||||
|
function onWorkAreaConfirm({ selectedOptions }) {
|
||||||
|
console.log(selectedOptions);
|
||||||
|
formData.workArea = selectedOptions[0].value;
|
||||||
|
displayWorkArea.value = selectedOptions[0].text;
|
||||||
|
showWorkAreaPicker.value = false;
|
||||||
|
console.log(formData.workArea);
|
||||||
|
console.log(displayWorkArea.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 岗位性质选择确认
|
||||||
|
function onPositionTypeConfirm({ selectedOptions }) {
|
||||||
|
formData.positionType = selectedOptions[0].value;
|
||||||
|
displayPositionType.value = selectedOptions[0].text;
|
||||||
|
showPositionTypePicker.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
async function onSubmit() {
|
||||||
|
try {
|
||||||
|
await userStore.registerUser(formData);
|
||||||
|
// 使用 showNotify 显示成功提示,不会干扰用户操作
|
||||||
|
showNotify({
|
||||||
|
type: 'success',
|
||||||
|
message: '注册成功',
|
||||||
|
duration: 2000,
|
||||||
|
position: 'top'
|
||||||
|
});
|
||||||
|
// 等待提示显示后再跳转
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace('/survey');
|
||||||
|
}, 500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.register-view {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-form {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
183
frontend/src/views/SurveyView.vue
Normal file
183
frontend/src/views/SurveyView.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<div class="survey-view">
|
||||||
|
<van-nav-bar
|
||||||
|
title="问卷调查"
|
||||||
|
left-text="返回"
|
||||||
|
left-arrow
|
||||||
|
@click-left="onBack"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="!surveyStore.isCompleted" class="survey-content">
|
||||||
|
<div v-if="surveyStore.currentQuestion" class="question-card">
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell>
|
||||||
|
<template #title>
|
||||||
|
<div class="question-title">
|
||||||
|
{{ surveyStore.currentQuestionNumber }}. {{ surveyStore.currentQuestion.content }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
|
||||||
|
<van-radio-group v-model="selectedOption">
|
||||||
|
<van-cell-group>
|
||||||
|
<van-cell
|
||||||
|
v-for="option in surveyStore.questionOptions"
|
||||||
|
:key="option.id"
|
||||||
|
clickable
|
||||||
|
@click="selectedOption = option.id"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<van-radio :name="option.id">{{ option.content }}</van-radio>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
</van-radio-group>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<van-button
|
||||||
|
round
|
||||||
|
block
|
||||||
|
type="primary"
|
||||||
|
:disabled="!selectedOption"
|
||||||
|
@click="onNextQuestion"
|
||||||
|
>
|
||||||
|
下一题
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-empty v-else description="加载中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="survey-completed">
|
||||||
|
<van-empty description="问卷已完成">
|
||||||
|
<template #image>
|
||||||
|
<van-icon name="success" size="64" color="#07c160" />
|
||||||
|
</template>
|
||||||
|
</van-empty>
|
||||||
|
<van-button
|
||||||
|
round
|
||||||
|
type="primary"
|
||||||
|
class="restart-button"
|
||||||
|
@click="onRestartSurvey"
|
||||||
|
>
|
||||||
|
重新开始
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { useSurveyStore } from '@/stores/survey';
|
||||||
|
import { showToast } from 'vant';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const surveyStore = useSurveyStore();
|
||||||
|
|
||||||
|
const selectedOption = ref('');
|
||||||
|
const responses = ref([]);
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
function onBack() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取下一个问题
|
||||||
|
async function onNextQuestion() {
|
||||||
|
if (!selectedOption.value) {
|
||||||
|
showToast('请选择一个选项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 保存当前答案
|
||||||
|
responses.value.push({
|
||||||
|
questionId: surveyStore.currentQuestion.id,
|
||||||
|
optionId: selectedOption.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取下一个问题
|
||||||
|
const nextQuestion = await surveyStore.fetchNextQuestion(
|
||||||
|
userStore.userId,
|
||||||
|
selectedOption.value
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果没有下一个问题,提交答案
|
||||||
|
if (!nextQuestion) {
|
||||||
|
await surveyStore.submitSurveyResponses(userStore.userId, responses.value);
|
||||||
|
showToast('问卷提交成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置选项
|
||||||
|
selectedOption.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取下一个问题失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新开始问卷
|
||||||
|
function onRestartSurvey() {
|
||||||
|
surveyStore.resetSurvey();
|
||||||
|
responses.value = [];
|
||||||
|
initSurvey();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化问卷
|
||||||
|
async function initSurvey() {
|
||||||
|
try {
|
||||||
|
await surveyStore.fetchNextQuestion(userStore.userId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化问卷失败:', error);
|
||||||
|
showToast('初始化问卷失败,请重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (!userStore.userId) {
|
||||||
|
router.replace('/register');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initSurvey();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.survey-view {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-completed {
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
frontend/vite.config.js
Normal file
48
frontend/vite.config.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { VantResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
import postcssPxToViewport from 'postcss-px-to-viewport';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
// 自动导入 Vant 组件
|
||||||
|
Components({
|
||||||
|
resolvers: [VantResolver()],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: [
|
||||||
|
// 移动端适配
|
||||||
|
postcssPxToViewport({
|
||||||
|
viewportWidth: 375, // 设计稿宽度
|
||||||
|
viewportUnit: 'vw',
|
||||||
|
fontViewportUnit: 'vw',
|
||||||
|
selectorBlackList: ['.ignore-'], // 不转换的类名
|
||||||
|
minPixelValue: 1, // 最小转换数值
|
||||||
|
mediaQuery: false, // 是否转换媒体查询中的像素值
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 3000,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:18080/llm-survey-api',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
3595
frontend/yarn.lock
Normal file
3595
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user