Browse Source

提交代码

zlwu 1 năm trước cách đây
mục cha
commit
9709df603d

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     "screenfull": "6.0.2",
     "vue": "3.5.13",
     "vue-cropper": "1.1.1",
+    "vue-draggable-plus": "^0.6.0",
     "vue-i18n": "11.1.3",
     "vue-json-pretty": "2.4.0",
     "vue-router": "4.5.0",

+ 155 - 0
src/api/hq/answerSurvey/index.ts

@@ -0,0 +1,155 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatSurveyVO, SatSurveyForm, SatSurveyQuery } from '@/api/hq/answerSurvey/types';
+
+/**
+ * 查询问卷列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatSurvey = (query?: SatSurveyQuery): AxiosPromise<SatSurveyVO[]> => {
+  return request({
+    url: '/hq/satSurvey/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询问卷详细
+ * @param id
+ */
+export const getSatSurvey = (id: string | number): AxiosPromise<SatSurveyVO> => {
+  return request({
+    url: '/hq/satSurvey/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增问卷
+ * @param data
+ */
+export const addSatSurvey = (data: SatSurveyForm) => {
+  return request({
+    url: '/hq/satSurvey',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改问卷
+ * @param data
+ */
+export const updateSatSurvey = (data: SatSurveyForm) => {
+  return request({
+    url: '/hq/satSurvey',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除问卷
+ * @param id
+ */
+export const delSatSurvey = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satSurvey/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取问卷关联的题目列表
+ * @param surveyId 问卷ID
+ */
+export const getSurveyQuestions = (surveyId: string | number): AxiosPromise<any[]> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/questions',
+    method: 'get'
+  });
+};
+
+/**
+ * 批量更新问卷题目关联
+ * @param surveyId 问卷ID
+ * @param questionIds 题目ID列表
+ */
+export const updateSurveyQuestions = (surveyId: string | number, data: any[]) => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/questions',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 发布问卷
+ * @param id 问卷ID
+ */
+export const publishSurvey = (id: string | number) => {
+  return request({
+    url: '/hq/satSurvey/' + id + '/publish',
+    method: 'put'
+  });
+};
+
+/**
+ * 撤销发布问卷
+ * @param id 问卷ID
+ */
+export const unpublishSurvey = (id: string | number) => {
+  return request({
+    url: '/hq/satSurvey/' + id + '/unpublish',
+    method: 'put'
+  });
+};
+
+/**
+ * 检查用户是否已答过问卷
+ * @param surveyId 问卷ID
+ */
+export const checkUserAnswered = (surveyId: string | number): AxiosPromise<boolean> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/answered',
+    method: 'get'
+  });
+};
+
+/**
+ * 获取用户问卷答题记录
+ * @param surveyId 问卷ID
+ */
+export const getUserSurveyAnswer = (surveyId: string | number): AxiosPromise<any> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/answer',
+    method: 'get'
+  });
+};
+
+/**
+ * 提交问卷答案
+ * @param data 答案数据
+ */
+export const submitSurveyAnswer = (data: any) => {
+  return request({
+    url: '/hq/satSurvey/answer/submit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 保存问卷答案草稿
+ * @param data 答案数据
+ */
+export const saveSurveyDraft = (data: any) => {
+  return request({
+    url: '/hq/satSurvey/answer/draft',
+    method: 'post',
+    data: data
+  });
+};

+ 216 - 0
src/api/hq/answerSurvey/types.ts

@@ -0,0 +1,216 @@
+export interface SatSurveyVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup: string;
+
+  /**
+   * 开始时间
+   */
+  startTime: string;
+
+  /**
+   * 结束时间
+   */
+  endTime: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished: number;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatSurveyForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode?: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle?: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc?: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction?: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes?: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous?: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatSurveyQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode?: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle?: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc?: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction?: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes?: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous?: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/hq/satCategory/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatCategoryVO, SatCategoryForm, SatCategoryQuery } from '@/api/hq/satCategory/types';
+
+/**
+ * 查询题目分类列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatCategory = (query?: SatCategoryQuery): AxiosPromise<SatCategoryVO[]> => {
+  return request({
+    url: '/hq/satCategory/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询题目分类详细
+ * @param id
+ */
+export const getSatCategory = (id: string | number): AxiosPromise<SatCategoryVO> => {
+  return request({
+    url: '/hq/satCategory/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增题目分类
+ * @param data
+ */
+export const addSatCategory = (data: SatCategoryForm) => {
+  return request({
+    url: '/hq/satCategory',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改题目分类
+ * @param data
+ */
+export const updateSatCategory = (data: SatCategoryForm) => {
+  return request({
+    url: '/hq/satCategory',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除题目分类
+ * @param id
+ */
+export const delSatCategory = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satCategory/' + id,
+    method: 'delete'
+  });
+};

+ 126 - 0
src/api/hq/satCategory/types.ts

@@ -0,0 +1,126 @@
+export interface SatCategoryVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 分类编码
+   */
+  categoryCode: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName: string;
+
+  /**
+   * 分类描述
+   */
+  categoryDesc: string;
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatCategoryForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 分类编码
+   */
+  categoryCode?: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  /**
+   * 分类描述
+   */
+  categoryDesc?: string;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatCategoryQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 分类编码
+   */
+  categoryCode?: string;
+
+  /**
+   * 分类名称
+   */
+  categoryName?: string;
+
+  /**
+   * 分类描述
+   */
+  categoryDesc?: string;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/hq/satDetail/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatDetailVO, SatDetailForm, SatDetailQuery } from '@/api/hq/satDetail/types';
+
+/**
+ * 查询答题详情列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatDetail = (query?: SatDetailQuery): AxiosPromise<SatDetailVO[]> => {
+  return request({
+    url: '/hq/satDetail/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询答题详情详细
+ * @param id
+ */
+export const getSatDetail = (id: string | number): AxiosPromise<SatDetailVO> => {
+  return request({
+    url: '/hq/satDetail/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增答题详情
+ * @param data
+ */
+export const addSatDetail = (data: SatDetailForm) => {
+  return request({
+    url: '/hq/satDetail',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改答题详情
+ * @param data
+ */
+export const updateSatDetail = (data: SatDetailForm) => {
+  return request({
+    url: '/hq/satDetail',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除答题详情
+ * @param id
+ */
+export const delSatDetail = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satDetail/' + id,
+    method: 'delete'
+  });
+};

+ 96 - 0
src/api/hq/satDetail/types.ts

@@ -0,0 +1,96 @@
+export interface SatDetailVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 答题记录ID
+   */
+  recordId: string | number;
+
+  /**
+   * 题目ID
+   */
+  questionId: string | number;
+
+  /**
+   * 答案分数
+   */
+  answerScore: number;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatDetailForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 答题记录ID
+   */
+  recordId?: string | number;
+
+  /**
+   * 题目ID
+   */
+  questionId?: string | number;
+
+  /**
+   * 答案分数
+   */
+  answerScore?: number;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatDetailQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 答题记录ID
+   */
+  recordId?: string | number;
+
+  /**
+   * 题目ID
+   */
+  questionId?: string | number;
+
+  /**
+   * 答案分数
+   */
+  answerScore?: number;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/hq/satOption/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatOptionVO, SatOptionForm, SatOptionQuery } from '@/api/hq/satOption/types';
+
+/**
+ * 查询评分选项列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatOption = (query?: SatOptionQuery): AxiosPromise<SatOptionVO[]> => {
+  return request({
+    url: '/hq/satOption/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询评分选项详细
+ * @param id
+ */
+export const getSatOption = (id: string | number): AxiosPromise<SatOptionVO> => {
+  return request({
+    url: '/hq/satOption/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增评分选项
+ * @param data
+ */
+export const addSatOption = (data: SatOptionForm) => {
+  return request({
+    url: '/hq/satOption',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改评分选项
+ * @param data
+ */
+export const updateSatOption = (data: SatOptionForm) => {
+  return request({
+    url: '/hq/satOption',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除评分选项
+ * @param id
+ */
+export const delSatOption = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satOption/' + id,
+    method: 'delete'
+  });
+};

+ 111 - 0
src/api/hq/satOption/types.ts

@@ -0,0 +1,111 @@
+export interface SatOptionVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 选项文本
+   */
+  optionText: string;
+
+  /**
+   * 选项分值
+   */
+  optionValue: number;
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatOptionForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 选项文本
+   */
+  optionText?: string;
+
+  /**
+   * 选项分值
+   */
+  optionValue?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatOptionQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 选项文本
+   */
+  optionText?: string;
+
+  /**
+   * 选项分值
+   */
+  optionValue?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 74 - 0
src/api/hq/satQuestion/index.ts

@@ -0,0 +1,74 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatQuestionVO, SatQuestionForm, SatQuestionQuery } from '@/api/hq/satQuestion/types';
+
+/**
+ * 查询题库列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatQuestion = (query?: SatQuestionQuery): AxiosPromise<SatQuestionVO[]> => {
+  return request({
+    url: '/hq/satQuestion/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询题库详细
+ * @param id
+ */
+export const getSatQuestion = (id: string | number): AxiosPromise<SatQuestionVO> => {
+  return request({
+    url: '/hq/satQuestion/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增题库
+ * @param data
+ */
+export const addSatQuestion = (data: SatQuestionForm) => {
+  return request({
+    url: '/hq/satQuestion',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改题库
+ * @param data
+ */
+export const updateSatQuestion = (data: SatQuestionForm) => {
+  return request({
+    url: '/hq/satQuestion',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除题库
+ * @param id
+ */
+export const delSatQuestion = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satQuestion/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取题目的选项列表
+ * @param questionId 题目ID
+ */
+export const getQuestionOptions = (questionId: string | number): AxiosPromise<any[]> => {
+  return request({
+    url: '/hq/satQuestion/' + questionId + '/options',
+    method: 'get'
+  });
+};

+ 156 - 0
src/api/hq/satQuestion/types.ts

@@ -0,0 +1,156 @@
+export interface SatQuestionVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 题目标题
+   */
+  questionTitle: string;
+
+  /**
+   * 题目内容
+   */
+  questionContent: string;
+
+  /**
+   * 所属分类ID
+   */
+  categoryId: string | number;
+
+  /**
+   * 题目类型
+   */
+  questionType: string;
+
+  /**
+   * 是否必填
+   */
+  isRequired: number;
+
+  /**
+   * 排序
+   */
+  sortOrder: number;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatQuestionForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 题目标题
+   */
+  questionTitle?: string;
+
+  /**
+   * 题目内容
+   */
+  questionContent?: string;
+
+  /**
+   * 所属分类ID
+   */
+  categoryId?: string | number;
+
+  /**
+   * 题目类型
+   */
+  questionType?: string;
+
+  /**
+   * 是否必填
+   */
+  isRequired?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatQuestionQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 题目标题
+   */
+  questionTitle?: string;
+
+  /**
+   * 题目内容
+   */
+  questionContent?: string;
+
+  /**
+   * 所属分类ID
+   */
+  categoryId?: string | number;
+
+  /**
+   * 题目类型
+   */
+  questionType?: string;
+
+  /**
+   * 是否必填
+   */
+  isRequired?: number;
+
+  /**
+   * 排序
+   */
+  sortOrder?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 155 - 0
src/api/hq/satSurvey/index.ts

@@ -0,0 +1,155 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SatSurveyVO, SatSurveyForm, SatSurveyQuery } from '@/api/hq/satSurvey/types';
+
+/**
+ * 查询问卷列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSatSurvey = (query?: SatSurveyQuery): AxiosPromise<SatSurveyVO[]> => {
+  return request({
+    url: '/hq/satSurvey/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询问卷详细
+ * @param id
+ */
+export const getSatSurvey = (id: string | number): AxiosPromise<SatSurveyVO> => {
+  return request({
+    url: '/hq/satSurvey/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增问卷
+ * @param data
+ */
+export const addSatSurvey = (data: SatSurveyForm) => {
+  return request({
+    url: '/hq/satSurvey',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改问卷
+ * @param data
+ */
+export const updateSatSurvey = (data: SatSurveyForm) => {
+  return request({
+    url: '/hq/satSurvey',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除问卷
+ * @param id
+ */
+export const delSatSurvey = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/satSurvey/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取问卷关联的题目列表
+ * @param surveyId 问卷ID
+ */
+export const getSurveyQuestions = (surveyId: string | number): AxiosPromise<any[]> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/questions',
+    method: 'get'
+  });
+};
+
+/**
+ * 批量更新问卷题目关联
+ * @param surveyId 问卷ID
+ * @param questionIds 题目ID列表
+ */
+export const updateSurveyQuestions = (surveyId: string | number, data: any[]) => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/questions',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 发布问卷
+ * @param id 问卷ID
+ */
+export const publishSurvey = (id: string | number) => {
+  return request({
+    url: '/hq/satSurvey/' + id + '/publish',
+    method: 'put'
+  });
+};
+
+/**
+ * 撤销发布问卷
+ * @param id 问卷ID
+ */
+export const unpublishSurvey = (id: string | number) => {
+  return request({
+    url: '/hq/satSurvey/' + id + '/unpublish',
+    method: 'put'
+  });
+};
+
+/**
+ * 提交问卷答案
+ * @param data 答案数据
+ */
+export const submitSurveyAnswer = (data: any) => {
+  return request({
+    url: '/hq/satSurvey/answer/submit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 保存问卷答案草稿
+ * @param data 答案数据
+ */
+export const saveSurveyDraft = (data: any) => {
+  return request({
+    url: '/hq/satSurvey/answer/draft',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 获取用户问卷答题记录
+ * @param surveyId 问卷ID
+ */
+export const getUserSurveyAnswer = (surveyId: string | number): AxiosPromise<any> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/answer',
+    method: 'get'
+  });
+};
+
+/**
+ * 检查用户是否已答过问卷
+ * @param surveyId 问卷ID
+ */
+export const checkUserAnswered = (surveyId: string | number): AxiosPromise<boolean> => {
+  return request({
+    url: '/hq/satSurvey/' + surveyId + '/answered',
+    method: 'get'
+  });
+};

+ 216 - 0
src/api/hq/satSurvey/types.ts

@@ -0,0 +1,216 @@
+export interface SatSurveyVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup: string;
+
+  /**
+   * 开始时间
+   */
+  startTime: string;
+
+  /**
+   * 结束时间
+   */
+  endTime: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished: number;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface SatSurveyForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode?: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle?: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc?: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction?: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes?: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous?: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SatSurveyQuery extends PageQuery {
+
+  /**
+   * 删除标志
+   */
+  deleted?: number;
+
+  /**
+   * 问卷编号
+   */
+  surveyCode?: string;
+
+  /**
+   * 问卷标题
+   */
+  surveyTitle?: string;
+
+  /**
+   * 问卷描述
+   */
+  surveyDesc?: string;
+
+  /**
+   * 填写说明
+   */
+  surveyInstruction?: string;
+
+  /**
+   * 目标群体
+   */
+  targetGroup?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 最大提交次数
+   */
+  maxSubmitTimes?: number;
+
+  /**
+   * 是否匿名
+   */
+  isAnonymous?: number;
+
+  /**
+   * 是否发布
+   */
+  isPublished?: number;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 4 - 3
src/assets/styles/sidebar.scss

@@ -37,7 +37,7 @@
     }
 
     .scrollbar-wrapper {
-      overflow-x: hidden !important;
+      overflow-x: auto !important;
     }
 
     .el-scrollbar__bar.is-vertical {
@@ -72,6 +72,7 @@
       border: none;
       height: 100%;
       width: 100% !important;
+      min-width: max-content;
 
       // 强制设置菜单文本颜色为纯黑色
       color: #000000 !important;
@@ -92,8 +93,8 @@
 
     .el-menu-item,
     .menu-title {
-      overflow: hidden !important;
-      text-overflow: ellipsis !important;
+      overflow: visible !important;
+      text-overflow: initial !important;
       white-space: nowrap !important;
     }
 

+ 2 - 2
src/assets/styles/variables.module.scss

@@ -112,7 +112,7 @@ $--color-warning: #e6a23c;
 $--color-danger: #f56c6c;
 $--color-info: #909399;
 
-$base-sidebar-width: 200px;
+$base-sidebar-width: 250px;
 
 // the :export directive is the magic sauce for webpack
 // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
@@ -132,4 +132,4 @@ $base-sidebar-width: 200px;
   dangerColor: $--color-danger;
   infoColor: $--color-info;
   warningColor: $--color-warning;
-}
+}

+ 1 - 0
src/views/hq/answerSurvey/README.md

@@ -0,0 +1 @@
+ 

+ 0 - 0
src/views/hq/answerSurvey/index.vue


+ 449 - 0
src/views/hq/satCategory/index.vue

@@ -0,0 +1,449 @@
+<template>
+  <div class="p-2 page-container">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div ref="searchRef" v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <!-- <el-form-item label="删除标志" prop="deleted">
+              <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable>
+                <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="分类编码" prop="categoryCode">
+              <el-input v-model="queryParams.categoryCode" placeholder="请输入分类编码" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="分类名称" prop="categoryName">
+              <el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <!-- <el-form-item label="分类描述" prop="categoryDesc">
+              <el-input v-model="queryParams.categoryDesc" placeholder="请输入分类描述" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <!-- <el-form-item label="排序" prop="sortOrder">
+              <el-input v-model="queryParams.sortOrder" placeholder="请输入排序" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satCategory:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satCategory:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satCategory:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satCategory:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <div class="table-container">
+        <el-table v-loading="loading" border :data="satCategoryList" :height="tableHeight" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" align="center" />
+          <!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
+          <!-- <el-table-column label="删除标志" align="center" prop="deleted">
+            <template #default="scope">
+              <dict-tag :options="sys_item_delete" :value="scope.row.deleted"/>
+            </template>
+          </el-table-column> -->
+          <el-table-column label="分类编码" align="center" prop="categoryCode" />
+          <el-table-column label="分类名称" align="center" prop="categoryName" />
+          <el-table-column label="分类描述" align="center" prop="categoryDesc" />
+          <!-- <el-table-column label="排序" align="center" prop="sortOrder" /> -->
+          <el-table-column label="状态" align="center" prop="status">
+            <template #default="scope">
+              <dict-tag :options="sys_item_enable" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center" prop="remark" />
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="修改" placement="top">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satCategory:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satCategory:remove']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div class="pagination-container">
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </div>
+    </el-card>
+    <!-- 添加或修改题目分类对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="satCategoryFormRef" :model="form" :rules="rules" label-width="80px">
+        <!-- <el-form-item label="删除标志" prop="deleted">
+          <el-select v-model="form.deleted" placeholder="请选择删除标志">
+            <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item> -->
+        <el-form-item label="分类编码" prop="categoryCode">
+          <el-input v-model="form.categoryCode" placeholder="请输入分类编码" />
+        </el-form-item>
+        <el-form-item label="分类名称" prop="categoryName">
+          <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
+        </el-form-item>
+        <el-form-item label="分类描述" prop="categoryDesc">
+          <el-input v-model="form.categoryDesc" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
+        </el-form-item>
+        <!-- <el-form-item label="排序" prop="sortOrder">
+          <el-input v-model="form.sortOrder" placeholder="请输入排序" />
+        </el-form-item> -->
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" placeholder="请选择状态">
+            <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SatCategory" lang="ts">
+import { listSatCategory, getSatCategory, delSatCategory, addSatCategory, updateSatCategory } from '@/api/hq/satCategory';
+import { SatCategoryVO, SatCategoryQuery, SatCategoryForm } from '@/api/hq/satCategory/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_item_enable, sys_item_delete } = toRefs<any>(proxy?.useDict('sys_item_enable', 'sys_item_delete'));
+
+const satCategoryList = ref<SatCategoryVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const satCategoryFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: SatCategoryForm = {
+  id: undefined,
+  deleted: undefined,
+  categoryCode: undefined,
+  categoryName: undefined,
+  categoryDesc: undefined,
+  sortOrder: undefined,
+  status: '0',
+  remark: undefined
+};
+const data = reactive<PageData<SatCategoryForm, SatCategoryQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    deleted: undefined,
+    categoryCode: undefined,
+    categoryName: undefined,
+    categoryDesc: undefined,
+    sortOrder: undefined,
+    status: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
+    categoryCode: [{ required: true, message: '分类编码不能为空', trigger: 'blur' }],
+    categoryName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询题目分类列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSatCategory(queryParams.value);
+  satCategoryList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  satCategoryFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SatCategoryVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加题目分类';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SatCategoryVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getSatCategory(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改题目分类';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  satCategoryFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSatCategory(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addSatCategory(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SatCategoryVO) => {
+  const _ids = row?.id || ids.value;
+  let confirmMessage = '';
+
+  if (row) {
+    // 单个删除
+    confirmMessage = `是否确认删除题目分类"${row.categoryName}(${row.categoryCode})"的数据项?`;
+  } else {
+    // 批量删除
+    const selectedRows = satCategoryList.value.filter((item) => ids.value.includes(item.id));
+    if (selectedRows.length === 1) {
+      confirmMessage = `是否确认删除题目分类"${selectedRows[0].categoryName}(${selectedRows[0].categoryCode})"的数据项?`;
+    } else {
+      const categoryNames = selectedRows.map((item) => `${item.categoryName}(${item.categoryCode})`).join('、');
+      confirmMessage = `是否确认删除题目分类"${categoryNames}"的数据项?`;
+    }
+  }
+
+  await proxy?.$modal.confirm(confirmMessage).finally(() => (loading.value = false));
+  await delSatCategory(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/satCategory/export',
+    {
+      ...queryParams.value
+    },
+    `satCategory_${new Date().getTime()}.xlsx`
+  );
+};
+
+const toolbarRef = ref<HTMLElement>();
+const searchRef = ref<HTMLElement>();
+const actualToolbarHeight = ref(70); // 默认工具栏高度
+const actualSearchHeight = ref(160); // 默认搜索栏高度
+const windowHeight = ref(window.innerHeight);
+
+/** 计算搜索栏实际高度 */
+const calculateSearchHeight = () => {
+  nextTick(() => {
+    if (searchRef.value && showSearch.value) {
+      // 获取搜索栏元素的完整高度,包括边距和内边距
+      const rect = searchRef.value.getBoundingClientRect();
+      const computedStyle = window.getComputedStyle(searchRef.value);
+      const marginTop = parseFloat(computedStyle.marginTop);
+      const marginBottom = parseFloat(computedStyle.marginBottom);
+      actualSearchHeight.value = rect.height + marginTop + marginBottom;
+    } else {
+      // 如果搜索栏隐藏,高度为0
+      actualSearchHeight.value = 0;
+    }
+  });
+};
+
+/** 计算工具栏实际高度 */
+const calculateToolbarHeight = () => {
+  nextTick(() => {
+    if (toolbarRef.value) {
+      // 获取工具栏元素的完整高度,包括边距和内边距
+      const toolbarElement = toolbarRef.value.closest('.el-card__header');
+      if (toolbarElement) {
+        const rect = toolbarElement.getBoundingClientRect();
+        const computedStyle = window.getComputedStyle(toolbarElement);
+        const marginTop = parseFloat(computedStyle.marginTop);
+        const marginBottom = parseFloat(computedStyle.marginBottom);
+        actualToolbarHeight.value = rect.height + marginTop + marginBottom;
+      } else {
+        // 如果找不到父元素,直接使用工具栏元素高度
+        const rect = toolbarRef.value.getBoundingClientRect();
+        actualToolbarHeight.value = rect.height + 20; // 添加一些边距
+      }
+    }
+  });
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+  // 窗口大小变化时重新计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+};
+
+/** 计算表格高度 */
+const tableHeight = computed(() => {
+  // 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
+  const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
+  const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
+  const pageTopPadding = 8; // 页面顶部边距
+  const pageBottomMargin = 10; // 分页栏距离底部的边距
+  const paginationOwnHeight = 32; // 分页组件自身高度
+  const paginationPadding = 20; // 分页栏上下内边距
+  const cardPadding = 40; // 卡片内边距(上下各20px)
+
+  // 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
+  const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
+
+  const calculatedHeight = windowHeight.value - fixedHeight - 35;
+
+  // 确保至少有300px的表格高度,避免过小
+  return Math.max(calculatedHeight, 300);
+});
+
+onMounted(() => {
+  getList();
+  // 初始计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+  // 移除监听器
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+
+<style scoped>
+/* 页面容器样式 */
+.page-container {
+  height: calc(100vh - 85px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
+}
+
+/* 隐藏滚动条但保持滚动功能 */
+.page-container::-webkit-scrollbar {
+  display: none;
+}
+
+.page-container {
+  -ms-overflow-style: none; /* IE and Edge */
+  scrollbar-width: none; /* Firefox */
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 0;
+}
+
+.table-card :deep(.el-card__body) {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  padding: 0px 20px 0 20px !important;
+  min-height: 0;
+  margin-bottom: 0 !important;
+}
+
+/* 表格主体 */
+.table-main {
+  flex: 1;
+  overflow: auto;
+}
+
+/* 分页容器固定在底部 */
+.pagination-container {
+  flex-shrink: 0;
+  padding-top: 10px 0 0 0 !important;
+  margin: 0 !important;
+  /* padding-bottom: 10px !important; 距离底部10px */
+  /* margin: 0 !important; */
+  /* margin-bottom: 10px !important; 确保距离视窗底部10px */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+</style>

+ 263 - 0
src/views/hq/satDetail/index.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="删除标志" prop="deleted">
+              <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
+                <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="答题记录ID" prop="recordId">
+              <el-input v-model="queryParams.recordId" placeholder="请输入答题记录ID" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="题目ID" prop="questionId">
+              <el-input v-model="queryParams.questionId" placeholder="请输入题目ID" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="答案分数" prop="answerScore">
+              <el-input v-model="queryParams.answerScore" placeholder="请输入答案分数" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satDetail:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satDetail:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satDetail:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="satDetailList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="主键ID" align="center" prop="id" v-if="true" />
+        <el-table-column label="删除标志" align="center" prop="deleted">
+          <template #default="scope">
+            <dict-tag :options="sys_item_delete" :value="scope.row.deleted"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="答题记录ID" align="center" prop="recordId" />
+        <el-table-column label="题目ID" align="center" prop="questionId" />
+        <el-table-column label="答案分数" align="center" prop="answerScore" />
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satDetail:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satDetail:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改答题详情对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="satDetailFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="删除标志" prop="deleted">
+          <el-select v-model="form.deleted" placeholder="请选择删除标志">
+            <el-option
+                v-for="dict in sys_item_delete"
+                :key="dict.value"
+                :label="dict.label"
+                :value="parseInt(dict.value)"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="答题记录ID" prop="recordId">
+          <el-input v-model="form.recordId" placeholder="请输入答题记录ID" />
+        </el-form-item>
+        <el-form-item label="题目ID" prop="questionId">
+          <el-input v-model="form.questionId" placeholder="请输入题目ID" />
+        </el-form-item>
+        <el-form-item label="答案分数" prop="answerScore">
+          <el-input v-model="form.answerScore" placeholder="请输入答案分数" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SatDetail" lang="ts">
+import { listSatDetail, getSatDetail, delSatDetail, addSatDetail, updateSatDetail } from '@/api/hq/satDetail';
+import { SatDetailVO, SatDetailQuery, SatDetailForm } from '@/api/hq/satDetail/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_item_delete } = toRefs<any>(proxy?.useDict('sys_item_delete'));
+
+const satDetailList = ref<SatDetailVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const satDetailFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: SatDetailForm = {
+  id: undefined,
+  deleted: undefined,
+  recordId: undefined,
+  questionId: undefined,
+  answerScore: undefined,
+  remark: undefined
+}
+const data = reactive<PageData<SatDetailForm, SatDetailQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    deleted: undefined,
+    recordId: undefined,
+    questionId: undefined,
+    answerScore: undefined,
+    params: {
+    }
+  },
+  rules: {
+    id: [
+      { required: true, message: "主键ID不能为空", trigger: "blur" }
+    ],
+    recordId: [
+      { required: true, message: "答题记录ID不能为空", trigger: "blur" }
+    ],
+    questionId: [
+      { required: true, message: "题目ID不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询答题详情列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSatDetail(queryParams.value);
+  satDetailList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  satDetailFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SatDetailVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加答题详情";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SatDetailVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getSatDetail(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改答题详情";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  satDetailFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSatDetail(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addSatDetail(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SatDetailVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除答题详情编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delSatDetail(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('hq/satDetail/export', {
+    ...queryParams.value
+  }, `satDetail_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 438 - 0
src/views/hq/satOption/index.vue

@@ -0,0 +1,438 @@
+<template>
+  <div class="p-2 page-container">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div ref="searchRef" v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <!-- <el-form-item label="删除标志" prop="deleted">
+              <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
+                <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="选项文本" prop="optionText">
+              <el-input v-model="queryParams.optionText" placeholder="请输入选项文本" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="选项分值" prop="optionValue">
+              <el-input v-model="queryParams.optionValue" placeholder="请输入选项分值" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <!-- <el-form-item label="排序" prop="sortOrder">
+              <el-input v-model="queryParams.sortOrder" placeholder="请输入排序" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satOption:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satOption:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satOption:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satOption:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+      <div class="table-container">
+        <el-table :height="tableHeight" v-loading="loading" border :data="satOptionList" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" align="center" />
+          <!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" />
+        <el-table-column label="删除标志" align="center" prop="deleted">
+          <template #default="scope">
+            <dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
+          </template>
+        </el-table-column> -->
+          <el-table-column label="选项文本" align="center" prop="optionText" />
+          <el-table-column label="选项分值" align="center" prop="optionValue" />
+          <el-table-column label="排序" align="center" prop="sortOrder" />
+          <el-table-column label="状态" align="center" prop="status">
+            <template #default="scope">
+              <dict-tag :options="sys_item_enable" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center" prop="remark" />
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="修改" placement="top">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satOption:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satOption:remove']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div class="pagination-container">
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </div>
+    </el-card>
+    <!-- 添加或修改评分选项对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="satOptionFormRef" :model="form" :rules="rules" label-width="80px">
+        <!-- <el-form-item label="删除标志" prop="deleted">
+          <el-select v-model="form.deleted" placeholder="请选择删除标志">
+            <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item> -->
+        <el-form-item label="选项文本" prop="optionText">
+          <el-input v-model="form.optionText" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入选项文本" />
+        </el-form-item>
+        <el-form-item label="选项分值" prop="optionValue">
+          <el-input v-model="form.optionValue" placeholder="请输入选项分值" />
+        </el-form-item>
+        <el-form-item label="排序" prop="sortOrder">
+          <el-input v-model="form.sortOrder" placeholder="请输入排序" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" placeholder="请选择状态">
+            <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SatOption" lang="ts">
+import { listSatOption, getSatOption, delSatOption, addSatOption, updateSatOption } from '@/api/hq/satOption';
+import { SatOptionVO, SatOptionQuery, SatOptionForm } from '@/api/hq/satOption/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_item_enable, sys_item_delete } = toRefs<any>(proxy?.useDict('sys_item_enable', 'sys_item_delete'));
+
+const satOptionList = ref<SatOptionVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const satOptionFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: SatOptionForm = {
+  id: undefined,
+  deleted: undefined,
+  optionText: undefined,
+  optionValue: undefined,
+  sortOrder: undefined,
+  status: '0',
+  remark: undefined
+};
+const data = reactive<PageData<SatOptionForm, SatOptionQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    deleted: undefined,
+    optionText: undefined,
+    optionValue: undefined,
+    sortOrder: undefined,
+    status: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
+    optionText: [{ required: true, message: '选项文本不能为空', trigger: 'blur' }],
+    optionValue: [{ required: true, message: '选项分值不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询评分选项列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSatOption(queryParams.value);
+  satOptionList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  satOptionFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SatOptionVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加评分选项';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SatOptionVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getSatOption(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改评分选项';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  satOptionFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSatOption(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addSatOption(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SatOptionVO) => {
+  const _ids = row?.id || ids.value;
+
+  // 构建删除提示信息
+  let confirmMessage = '';
+  if (row) {
+    // 单项删除
+    confirmMessage = `是否确认删除评分选项"${row.optionText}(${row.optionValue}分)"?`;
+  } else {
+    // 批量删除
+    const selectedItems = satOptionList.value.filter((item) => ids.value.includes(item.id));
+    if (selectedItems.length === 1) {
+      const item = selectedItems[0];
+      confirmMessage = `是否确认删除评分选项"${item.optionText}(${item.optionValue}分)"?`;
+    } else {
+      const itemTexts = selectedItems.map((item) => `${item.optionText}(${item.optionValue}分)`).join('、');
+      confirmMessage = `是否确认删除以下评分选项:${itemTexts}?`;
+    }
+  }
+
+  await proxy?.$modal.confirm(confirmMessage).finally(() => (loading.value = false));
+  await delSatOption(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/satOption/export',
+    {
+      ...queryParams.value
+    },
+    `satOption_${new Date().getTime()}.xlsx`
+  );
+};
+
+const toolbarRef = ref<HTMLElement>();
+const searchRef = ref<HTMLElement>();
+const actualToolbarHeight = ref(70); // 默认工具栏高度
+const actualSearchHeight = ref(160); // 默认搜索栏高度
+const windowHeight = ref(window.innerHeight);
+
+/** 计算搜索栏实际高度 */
+const calculateSearchHeight = () => {
+  nextTick(() => {
+    if (searchRef.value && showSearch.value) {
+      // 获取搜索栏元素的完整高度,包括边距和内边距
+      const rect = searchRef.value.getBoundingClientRect();
+      const computedStyle = window.getComputedStyle(searchRef.value);
+      const marginTop = parseFloat(computedStyle.marginTop);
+      const marginBottom = parseFloat(computedStyle.marginBottom);
+      actualSearchHeight.value = rect.height + marginTop + marginBottom;
+    } else {
+      // 如果搜索栏隐藏,高度为0
+      actualSearchHeight.value = 0;
+    }
+  });
+};
+
+/** 计算工具栏实际高度 */
+const calculateToolbarHeight = () => {
+  nextTick(() => {
+    if (toolbarRef.value) {
+      // 获取工具栏元素的完整高度,包括边距和内边距
+      const toolbarElement = toolbarRef.value.closest('.el-card__header');
+      if (toolbarElement) {
+        const rect = toolbarElement.getBoundingClientRect();
+        const computedStyle = window.getComputedStyle(toolbarElement);
+        const marginTop = parseFloat(computedStyle.marginTop);
+        const marginBottom = parseFloat(computedStyle.marginBottom);
+        actualToolbarHeight.value = rect.height + marginTop + marginBottom;
+      } else {
+        // 如果找不到父元素,直接使用工具栏元素高度
+        const rect = toolbarRef.value.getBoundingClientRect();
+        actualToolbarHeight.value = rect.height + 20; // 添加一些边距
+      }
+    }
+  });
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+  // 窗口大小变化时重新计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+};
+
+/** 计算表格高度 */
+const tableHeight = computed(() => {
+  // 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
+  const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
+  const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
+  const pageTopPadding = 8; // 页面顶部边距
+  const pageBottomMargin = 10; // 分页栏距离底部的边距
+  const paginationOwnHeight = 32; // 分页组件自身高度
+  const paginationPadding = 20; // 分页栏上下内边距
+  const cardPadding = 40; // 卡片内边距(上下各20px)
+
+  // 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
+  const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
+
+  const calculatedHeight = windowHeight.value - fixedHeight - 35;
+
+  // 确保至少有300px的表格高度,避免过小
+  return Math.max(calculatedHeight, 300);
+});
+
+onMounted(() => {
+  getList();
+  // 初始计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+  // 移除监听器
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+<style scoped>
+/* 页面容器样式 */
+.page-container {
+  height: calc(100vh - 85px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
+}
+
+/* 隐藏滚动条但保持滚动功能 */
+.page-container::-webkit-scrollbar {
+  display: none;
+}
+
+.page-container {
+  -ms-overflow-style: none; /* IE and Edge */
+  scrollbar-width: none; /* Firefox */
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 0;
+}
+
+.table-card :deep(.el-card__body) {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  padding: 0px 20px 0 20px !important;
+  min-height: 0;
+  margin-bottom: 0 !important;
+}
+
+/* 表格主体 */
+.table-main {
+  flex: 1;
+  overflow: auto;
+}
+
+/* 分页容器固定在底部 */
+.pagination-container {
+  flex-shrink: 0;
+  padding-top: 10px 0 0 0 !important;
+  margin: 0 !important;
+  /* padding-bottom: 10px !important; 距离底部10px */
+  /* margin: 0 !important; */
+  /* margin-bottom: 10px !important; 确保距离视窗底部10px */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+</style>

+ 535 - 0
src/views/hq/satQuestion/index.vue

@@ -0,0 +1,535 @@
+<template>
+  <div class="p-2 page-container">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div ref="searchRef" v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <!-- <el-form-item label="删除标志" prop="deleted">
+              <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
+                <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="题目标题" prop="questionTitle">
+              <el-input v-model="queryParams.questionTitle" placeholder="请输入题目标题" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="所属分类" prop="categoryId">
+              <el-select v-model="queryParams.categoryId" placeholder="请选择所属分类" clearable @change="handleQuery">
+                <el-option v-for="category in satCategoryList" :key="category.id" :label="category.categoryName" :value="category.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="题目类型" prop="questionType">
+              <el-select v-model="queryParams.questionType" placeholder="请选择题目类型" clearable>
+                <el-option v-for="dict in sys_question_category" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <!-- <el-form-item label="是否必填" prop="isRequired">
+              <el-select v-model="queryParams.isRequired" placeholder="请选择是否必填" clearable>
+                <el-option v-for="dict in sys_item_required" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item> -->
+            <!-- <el-form-item label="排序" prop="sortOrder">
+              <el-input v-model="queryParams.sortOrder" placeholder="请输入排序" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satQuestion:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satQuestion:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satQuestion:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satQuestion:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+      <div class="table-container">
+        <el-table v-loading="loading" border :data="satQuestionList" @selection-change="handleSelectionChange" :height="tableHeight">
+          <el-table-column type="selection" width="55" align="center" />
+          <!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
+          <!-- <el-table-column label="删除标志" align="center" prop="deleted">
+            <template #default="scope">
+              <dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
+            </template>
+          </el-table-column> -->
+          <el-table-column label="题目标题" align="center" prop="questionTitle" width="200">
+            <template #default="scope">
+              <el-tooltip
+                effect="dark"
+                :content="scope.row.questionTitle"
+                placement="top"
+                :show-after="100"
+                :hide-after="100"
+                popper-style="max-width: 300px; max-height: 200px;"
+              >
+                <div class="text-ellipsis">
+                  {{ scope.row.questionTitle }}
+                </div>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="题目内容" align="center" prop="questionContent" /> -->
+          <el-table-column label="所属分类" align="center" prop="categoryId">
+            <template #default="scope">
+              {{ satCategoryList.find((cat) => cat.id == scope.row.categoryId)?.categoryName || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="题目类型" align="center" prop="questionType">
+            <template #default="scope">
+              <dict-tag :options="sys_question_category" :value="scope.row.questionType" />
+            </template>
+          </el-table-column>
+          <el-table-column label="是否必填" align="center" prop="isRequired">
+            <template #default="scope">
+              <dict-tag :options="sys_item_required" :value="scope.row.isRequired" />
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="排序" align="center" prop="sortOrder" /> -->
+          <el-table-column label="状态" align="center" prop="status">
+            <template #default="scope">
+              <dict-tag :options="sys_item_enable" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="备注" align="center" prop="remark" /> -->
+          <el-table-column label="备注" align="center" prop="remark" width="200">
+            <template #default="scope">
+              <el-tooltip
+                effect="dark"
+                :content="scope.row.remark"
+                placement="top"
+                :show-after="100"
+                :hide-after="100"
+                popper-style="max-width: 300px; max-height: 200px;"
+              >
+                <div class="text-ellipsis">
+                  {{ scope.row.remark }}
+                </div>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="修改" placement="top">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satQuestion:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satQuestion:remove']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="pagination-container">
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </div>
+    </el-card>
+    <!-- 添加或修改题库对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="satQuestionFormRef" :model="form" :rules="rules" label-width="80px">
+        <!-- <el-form-item label="删除标志" prop="deleted">
+          <el-select v-model="form.deleted" placeholder="请选择删除标志">
+            <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item> -->
+        <el-form-item label="题目标题" prop="questionTitle">
+          <el-input v-model="form.questionTitle" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入题目标题" />
+        </el-form-item>
+        <!-- <el-form-item label="题目内容">
+          <editor v-model="form.questionContent" :min-height="192" />
+        </el-form-item> -->
+        <el-form-item label="所属分类" prop="categoryId">
+          <el-select v-model="form.categoryId" placeholder="请选择所属分类">
+            <el-option v-for="category in satCategoryList" :key="category.id" :label="category.categoryName" :value="category.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="题目类型" prop="questionType">
+          <el-select v-model="form.questionType" placeholder="请选择题目类型">
+            <el-option v-for="dict in sys_question_category" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="是否必填" prop="isRequired">
+          <el-select v-model="form.isRequired" placeholder="请选择是否必填">
+            <el-option v-for="dict in sys_item_required" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item>
+        <!-- <el-form-item label="排序" prop="sortOrder">
+          <el-input v-model="form.sortOrder" placeholder="请输入排序" />
+        </el-form-item> -->
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" placeholder="请选择状态">
+            <el-option v-for="dict in sys_item_enable" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" resize="none" :autosize="false" :rows="4" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SatQuestion" lang="ts">
+import { listSatQuestion, getSatQuestion, delSatQuestion, addSatQuestion, updateSatQuestion } from '@/api/hq/satQuestion';
+import { SatQuestionVO, SatQuestionQuery, SatQuestionForm } from '@/api/hq/satQuestion/types';
+import { listSatCategory } from '@/api/hq/satCategory';
+import { SatCategoryVO } from '@/api/hq/satCategory/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_question_category, sys_item_enable, sys_item_required, sys_item_delete } = toRefs<any>(
+  proxy?.useDict('sys_question_category', 'sys_item_enable', 'sys_item_required', 'sys_item_delete')
+);
+
+const satQuestionList = ref<SatQuestionVO[]>([]);
+const satCategoryList = ref<SatCategoryVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const satQuestionFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: SatQuestionForm = {
+  id: undefined,
+  deleted: undefined,
+  questionTitle: undefined,
+  questionContent: undefined,
+  categoryId: undefined,
+  questionType: '1', // 默认单选
+  isRequired: 1,
+  sortOrder: undefined,
+  status: '0',
+  remark: undefined
+};
+const data = reactive<PageData<SatQuestionForm, SatQuestionQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    deleted: undefined,
+    questionTitle: undefined,
+    questionContent: undefined,
+    categoryId: undefined,
+    questionType: undefined,
+    isRequired: undefined,
+    sortOrder: undefined,
+    status: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
+    questionTitle: [{ required: true, message: '题目标题不能为空', trigger: 'blur' }],
+    categoryId: [{ required: true, message: '所属分类不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询题库列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSatQuestion(queryParams.value);
+  satQuestionList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 获取分类列表 */
+const getCategoryList = async () => {
+  const res = await listSatCategory({
+    pageNum: 1,
+    pageSize: 1000,
+    status: '0'
+  }); // 只获取启用状态的分类
+  satCategoryList.value = res.rows.sort((a, b) => a.sortOrder - b.sortOrder); // 按sort_order排序
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  satQuestionFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SatQuestionVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加题库';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SatQuestionVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getSatQuestion(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改题库';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  satQuestionFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSatQuestion(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addSatQuestion(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SatQuestionVO) => {
+  const _ids = row?.id || ids.value;
+
+  let confirmMessage = '';
+  if (row) {
+    // 单个删除,显示题目标题
+    confirmMessage = `是否确认删除题目"${row.questionTitle}"?`;
+  } else {
+    // 批量删除,显示数量
+    confirmMessage = `是否确认删除选中的${ids.value.length}个题目?`;
+  }
+
+  await proxy?.$modal.confirm(confirmMessage).finally(() => (loading.value = false));
+  await delSatQuestion(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/satQuestion/export',
+    {
+      ...queryParams.value
+    },
+    `satQuestion_${new Date().getTime()}.xlsx`
+  );
+};
+
+const toolbarRef = ref<HTMLElement>();
+const searchRef = ref<HTMLElement>();
+const actualToolbarHeight = ref(70); // 默认工具栏高度
+const actualSearchHeight = ref(160); // 默认搜索栏高度
+const windowHeight = ref(window.innerHeight);
+
+/** 计算搜索栏实际高度 */
+const calculateSearchHeight = () => {
+  nextTick(() => {
+    if (searchRef.value && showSearch.value) {
+      // 获取搜索栏元素的完整高度,包括边距和内边距
+      const rect = searchRef.value.getBoundingClientRect();
+      const computedStyle = window.getComputedStyle(searchRef.value);
+      const marginTop = parseFloat(computedStyle.marginTop);
+      const marginBottom = parseFloat(computedStyle.marginBottom);
+      actualSearchHeight.value = rect.height + marginTop + marginBottom;
+    } else {
+      // 如果搜索栏隐藏,高度为0
+      actualSearchHeight.value = 0;
+    }
+  });
+};
+
+/** 计算工具栏实际高度 */
+const calculateToolbarHeight = () => {
+  nextTick(() => {
+    if (toolbarRef.value) {
+      // 获取工具栏元素的完整高度,包括边距和内边距
+      const toolbarElement = toolbarRef.value.closest('.el-card__header');
+      if (toolbarElement) {
+        const rect = toolbarElement.getBoundingClientRect();
+        const computedStyle = window.getComputedStyle(toolbarElement);
+        const marginTop = parseFloat(computedStyle.marginTop);
+        const marginBottom = parseFloat(computedStyle.marginBottom);
+        actualToolbarHeight.value = rect.height + marginTop + marginBottom;
+      } else {
+        // 如果找不到父元素,直接使用工具栏元素高度
+        const rect = toolbarRef.value.getBoundingClientRect();
+        actualToolbarHeight.value = rect.height + 20; // 添加一些边距
+      }
+    }
+  });
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+  // 窗口大小变化时重新计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+};
+
+/** 计算表格高度 */
+const tableHeight = computed(() => {
+  // 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
+  const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
+  const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
+  const pageTopPadding = 8; // 页面顶部边距
+  const pageBottomMargin = 10; // 分页栏距离底部的边距
+  const paginationOwnHeight = 32; // 分页组件自身高度
+  const paginationPadding = 20; // 分页栏上下内边距
+  const cardPadding = 40; // 卡片内边距(上下各20px)
+
+  // 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
+  const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
+
+  const calculatedHeight = windowHeight.value - fixedHeight - 35;
+
+  // 确保至少有300px的表格高度,避免过小
+  return Math.max(calculatedHeight, 300);
+});
+
+onMounted(() => {
+  getList();
+  getCategoryList();
+  // 初始计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+  // 移除监听器
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+<style scoped>
+/* 页面容器样式 */
+.page-container {
+  height: calc(100vh - 85px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
+}
+
+/* 隐藏滚动条但保持滚动功能 */
+.page-container::-webkit-scrollbar {
+  display: none;
+}
+
+.page-container {
+  -ms-overflow-style: none; /* IE and Edge */
+  scrollbar-width: none; /* Firefox */
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 0;
+}
+
+.table-card :deep(.el-card__body) {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  padding: 0px 20px 0 20px !important;
+  min-height: 0;
+  margin-bottom: 0 !important;
+}
+
+/* 表格主体 */
+.table-main {
+  flex: 1;
+  overflow: auto;
+}
+
+/* 分页容器固定在底部 */
+.pagination-container {
+  flex-shrink: 0;
+  padding-top: 10px 0 0 0 !important;
+  margin: 0 !important;
+  /* padding-bottom: 10px !important; 距离底部10px */
+  /* margin: 0 !important; */
+  /* margin-bottom: 10px !important; 确保距离视窗底部10px */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+
+/* 文本省略号样式 */
+.text-ellipsis {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  width: 100%;
+  /* cursor: help; */
+}
+</style>

+ 1581 - 0
src/views/hq/satSurvey/index.vue

@@ -0,0 +1,1581 @@
+<template>
+  <div class="p-2 page-container">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div ref="searchRef" v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <!-- <el-form-item label="删除标志" prop="deleted">
+              <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
+                <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="问卷编号" prop="surveyCode">
+              <el-input v-model="queryParams.surveyCode" placeholder="请输入问卷编号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="问卷标题" prop="surveyTitle">
+              <el-input v-model="queryParams.surveyTitle" placeholder="请输入问卷标题" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <!-- <el-form-item label="问卷描述" prop="surveyDesc">
+              <el-input v-model="queryParams.surveyDesc" placeholder="请输入问卷描述" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="填写说明" prop="surveyInstruction">
+              <el-input v-model="queryParams.surveyInstruction" placeholder="请输入填写说明" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <el-form-item label="目标群体" prop="targetGroup">
+              <el-select v-model="queryParams.targetGroup" placeholder="请选择目标群体" clearable>
+                <el-option v-for="dict in sys_survey_people" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="开始时间" prop="startTime">
+              <el-date-picker clearable v-model="queryParams.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始时间" />
+            </el-form-item>
+            <el-form-item label="结束时间" prop="endTime">
+              <el-date-picker clearable v-model="queryParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择结束时间" />
+            </el-form-item>
+            <!-- <el-form-item label="最大提交次数" prop="maxSubmitTimes">
+              <el-input v-model="queryParams.maxSubmitTimes" placeholder="请输入最大提交次数" clearable @keyup.enter="handleQuery" />
+            </el-form-item> -->
+            <el-form-item label="是否匿名" prop="isAnonymous">
+              <el-select v-model="queryParams.isAnonymous" placeholder="请选择是否匿名" clearable>
+                <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="是否发布" prop="isPublished">
+              <el-select v-model="queryParams.isPublished" placeholder="请选择是否发布" clearable>
+                <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option v-for="dict in sys_survey_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satSurvey:add']">新增</el-button>
+          </el-col>
+          <!-- <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satSurvey:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satSurvey:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satSurvey:export']">导出</el-button>
+          </el-col> -->
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <div class="table-container">
+        <el-table
+          :height="tableHeight"
+          v-loading="loading"
+          border
+          :data="satSurveyList"
+          @selection-change="handleSelectionChange"
+          style="width: 100%"
+          :scroll-x="true"
+          class="scroll-table"
+        >
+          <el-table-column type="selection" width="55" align="center" fixed="left" />
+          <!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
+          <!-- <el-table-column label="删除标志" align="center" prop="deleted">
+            <template #default="scope">
+              <dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
+            </template>
+          </el-table-column> -->
+          <el-table-column label="问卷编号" align="center" prop="surveyCode" width="140" show-overflow-tooltip />
+          <el-table-column label="问卷标题" align="center" prop="surveyTitle" min-width="200" show-overflow-tooltip />
+          <el-table-column label="问卷描述" align="center" prop="surveyDesc" min-width="250" show-overflow-tooltip />
+          <el-table-column label="填写说明" align="center" prop="surveyInstruction" min-width="250" show-overflow-tooltip />
+          <el-table-column label="目标群体" align="center" prop="targetGroup" width="120">
+            <template #default="scope">
+              <dict-tag :options="sys_survey_people" :value="scope.row.targetGroup" />
+            </template>
+          </el-table-column>
+          <el-table-column label="开始时间" align="center" prop="startTime" width="180">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="结束时间" align="center" prop="endTime" width="180">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="最大提交次数" align="center" prop="maxSubmitTimes" /> -->
+          <el-table-column label="是否匿名" align="center" prop="isAnonymous" width="100">
+            <template #default="scope">
+              <dict-tag :options="sys_yesno" :value="scope.row.isAnonymous" />
+            </template>
+          </el-table-column>
+          <el-table-column label="是否发布" align="center" prop="isPublished" width="100">
+            <template #default="scope">
+              <dict-tag :options="sys_yesno" :value="scope.row.isPublished" />
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" align="center" prop="status" width="100">
+            <template #default="scope">
+              <dict-tag :options="sys_survey_status" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center" prop="remark" min-width="200" show-overflow-tooltip />
+          <el-table-column label="操作" align="center" width="250" fixed="right" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="题目管理" placement="top">
+                <el-button
+                  link
+                  type="primary"
+                  icon="EditPen"
+                  @click="handleQuestionManage(scope.row)"
+                  v-hasPermi="['hq:satSurvey:question']"
+                ></el-button>
+              </el-tooltip>
+              <el-tooltip content="修改" placement="top">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satSurvey:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip v-if="scope.row.isPublished === 0 || scope.row.isPublished === '0'" content="发布" placement="top">
+                <el-button link type="success" icon="Upload" @click="handlePublish(scope.row)" v-hasPermi="['hq:satSurvey:publish']"></el-button>
+              </el-tooltip>
+              <el-tooltip v-else content="撤销发布" placement="top">
+                <el-button
+                  link
+                  type="warning"
+                  icon="Download"
+                  @click="handleUnpublish(scope.row)"
+                  v-hasPermi="['hq:satSurvey:unpublish']"
+                ></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satSurvey:remove']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="pagination-container">
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </div>
+    </el-card>
+    <!-- 添加或修改问卷对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="satSurveyFormRef" :model="form" :rules="rules" label-width="80px">
+        <!-- <el-form-item label="删除标志" prop="deleted">
+          <el-select v-model="form.deleted" placeholder="请选择删除标志">
+            <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item> -->
+        <el-form-item label="问卷编号" prop="surveyCode">
+          <el-input v-model="form.surveyCode" placeholder="请输入问卷编号" />
+        </el-form-item>
+        <el-form-item label="问卷标题" prop="surveyTitle">
+          <el-input v-model="form.surveyTitle" placeholder="请输入问卷标题" />
+        </el-form-item>
+        <el-form-item label="问卷描述" prop="surveyDesc">
+          <el-input v-model="form.surveyDesc" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="填写说明" prop="surveyInstruction">
+          <el-input v-model="form.surveyInstruction" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="目标群体" prop="targetGroup">
+          <el-select v-model="form.targetGroup" placeholder="请选择目标群体">
+            <el-option v-for="dict in sys_survey_people" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <el-date-picker clearable v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择结束时间">
+          </el-date-picker>
+        </el-form-item>
+        <!-- <el-form-item label="最大提交次数" prop="maxSubmitTimes">
+          <el-input v-model="form.maxSubmitTimes" placeholder="请输入最大提交次数" />
+        </el-form-item> -->
+        <el-form-item label="是否匿名" prop="isAnonymous">
+          <el-select v-model="form.isAnonymous" placeholder="请选择是否匿名">
+            <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="是否发布" prop="isPublished">
+          <el-select v-model="form.isPublished" placeholder="请选择是否发布">
+            <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" placeholder="请选择状态">
+            <el-option v-for="dict in sys_survey_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 题目管理对话框 -->
+    <el-dialog :title="questionDialog.title" v-model="questionDialog.visible" width="1200px" append-to-body>
+      <div class="new-question-manage-container" v-loading="questionLoading">
+        <!-- 题目管理工具栏 -->
+        <div v-if="!readonly && selectedQuestions.length > 0" class="question-toolbar">
+          <div class="toolbar-left">
+            <span class="question-count">共 {{ selectedQuestions.length }} 个题目</span>
+          </div>
+          <div class="toolbar-right">
+            <el-button type="danger" icon="Delete" @click="confirmDeleteAllQuestions" size="small"> 删除所有题目 </el-button>
+          </div>
+        </div>
+
+        <!-- 题目列表 -->
+        <div class="question-builder">
+          <!-- 题目显示区域 -->
+          <div class="question-list-container">
+            <div class="question-list">
+              <div v-for="(question, index) in selectedQuestions" :key="question.id || question.questionId" class="question-card">
+                <div class="question-header">
+                  <div class="question-number">第 {{ index + 1 }} 题</div>
+                  <div class="question-actions" v-if="!readonly">
+                    <el-button type="text" size="small" @click="removeQuestion(index)">
+                      <el-icon><Delete /></el-icon>
+                    </el-button>
+                  </div>
+                </div>
+
+                <div class="question-content-display">
+                  <div class="question-title-display">{{ question.questionTitle }}</div>
+                  <div class="question-meta-info">
+                    <el-tag size="small" type="primary">{{ getQuestionType(question.questionType) }}</el-tag>
+                    <el-tag v-if="question.isRequired === 1 || question.isRequired === '1'" size="small" type="danger">必填</el-tag>
+                  </div>
+
+                  <!-- 题目答案展示区域 -->
+                  <div class="answer-display-area">
+                    <!-- 单选题 -->
+                    <el-radio-group v-if="question.questionType === '1' || question.questionType === 1" disabled>
+                      <el-radio v-for="option in question.options || []" :key="option.id" :label="option.optionValue" class="answer-option">
+                        {{ option.optionText }}
+                      </el-radio>
+                    </el-radio-group>
+
+                    <!-- 多选题 -->
+                    <el-checkbox-group v-else-if="question.questionType === '2' || question.questionType === 2" disabled>
+                      <el-checkbox v-for="option in question.options || []" :key="option.id" :label="option.optionValue" class="answer-option">
+                        {{ option.optionText }}
+                      </el-checkbox>
+                    </el-checkbox-group>
+
+                    <!-- 问答题 -->
+                    <el-input
+                      v-else-if="question.questionType === '3' || question.questionType === 3"
+                      type="textarea"
+                      :rows="3"
+                      placeholder="请输入答案..."
+                      disabled
+                      class="answer-textarea"
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 空状态显示区域 -->
+          <div class="action-area">
+            <div v-if="selectedQuestions.length === 0" class="empty-state">
+              <el-icon class="empty-icon"><DocumentAdd /></el-icon>
+              <p class="empty-text">{{ readonly ? '该问卷还没有配置题目' : '问卷还没有题目,请添加第一个题目' }}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer question-manage-footer">
+          <el-button v-if="!readonly" type="primary" icon="Plus" @click="openQuestionSelector">
+            {{ selectedQuestions.length === 0 ? '添加第一个题目' : '继续添加题目' }}
+          </el-button>
+          <el-button v-if="!readonly && hasLoadedData" type="success" icon="Check" :loading="questionLoading" @click="saveQuestionChanges">
+            保存
+          </el-button>
+          <el-button @click="cancelQuestionManage">{{ readonly ? '关闭' : '取消' }}</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 题目选择对话框 -->
+    <el-dialog title="选择题目" v-model="showQuestionSelector" width="1400px" append-to-body>
+      <div class="question-selector">
+        <!-- 搜索区域 -->
+        <div class="selector-search">
+          <div class="search-row">
+            <div class="search-group">
+              <label class="search-label">题目搜索:</label>
+              <el-input v-model="questionSearchText" placeholder="输入关键词搜索题目标题..." clearable style="width: 350px" size="default" />
+            </div>
+            <div class="search-group">
+              <label class="search-label">题目类型:</label>
+              <el-select v-model="questionTypeFilter" placeholder="请选择题目类型" clearable style="width: 150px" size="default">
+                <el-option label="单选题" value="1" />
+                <el-option label="多选题" value="2" />
+                <el-option label="问答题" value="3" />
+              </el-select>
+            </div>
+            <div class="search-group">
+              <label class="search-label">题目分类:</label>
+              <el-select v-model="questionCategoryFilter" placeholder="请选择分类" clearable style="width: 180px" size="default">
+                <el-option v-for="category in categories" :key="category.id" :label="category.categoryName" :value="category.id" />
+              </el-select>
+            </div>
+          </div>
+          <div class="search-stats">
+            <span class="stats-text">共找到 {{ filteredAvailableQuestions.length }} 个题目</span>
+          </div>
+        </div>
+
+        <!-- 题目列表 -->
+        <div class="selector-question-list">
+          <div
+            v-for="question in filteredAvailableQuestions"
+            :key="question.id"
+            class="selector-question-item"
+            @click="toggleQuestionSelection(question.id)"
+          >
+            <div class="selector-question-checkbox">
+              <el-checkbox :model-value="selectedQuestionIds.includes(question.id)" @change="toggleQuestionSelection(question.id)" />
+            </div>
+            <div class="selector-question-content">
+              <div class="selector-question-header">
+                <div class="selector-question-title">{{ question.questionTitle }}</div>
+                <div class="selector-question-meta">
+                  <el-tag size="default" type="primary">{{ getQuestionType(question.questionType) }}</el-tag>
+                  <el-tag size="default" type="info">{{ getCategoryName(question.categoryId) }}</el-tag>
+                </div>
+              </div>
+              <div class="selector-question-description" v-if="question.questionContent">
+                {{ question.questionContent }}
+              </div>
+            </div>
+          </div>
+
+          <div v-if="filteredAvailableQuestions.length === 0" class="no-questions">
+            <el-icon class="no-questions-icon"><DocumentRemove /></el-icon>
+            <p class="no-questions-text">暂无符合条件的题目</p>
+            <p class="no-questions-hint">请尝试调整搜索条件或清空筛选器</p>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <div class="footer-left">
+            <el-checkbox :model-value="isAllQuestionsSelected" :indeterminate="isIndeterminate" @change="toggleSelectAll"> 全选 </el-checkbox>
+            <span class="selected-count">已选择 {{ selectedQuestionIds.length }} 个题目</span>
+          </div>
+          <div class="footer-right">
+            <el-button @click="confirmSelectQuestions" type="primary" :disabled="selectedQuestionIds.length === 0"> 确认添加 </el-button>
+            <el-button @click="cancelQuestionSelector">取消</el-button>
+          </div>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SatSurvey" lang="ts">
+import {
+  listSatSurvey,
+  getSatSurvey,
+  delSatSurvey,
+  addSatSurvey,
+  updateSatSurvey,
+  getSurveyQuestions,
+  updateSurveyQuestions,
+  publishSurvey,
+  unpublishSurvey
+} from '@/api/hq/satSurvey';
+import { SatSurveyVO, SatSurveyQuery, SatSurveyForm } from '@/api/hq/satSurvey/types';
+import { listSatQuestion, getQuestionOptions } from '@/api/hq/satQuestion';
+import { SatQuestionVO } from '@/api/hq/satQuestion/types';
+import { listSatCategory } from '@/api/hq/satCategory';
+import { SatCategoryVO } from '@/api/hq/satCategory/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_survey_people, sys_item_delete, sys_survey_status, sys_yesno } = toRefs<any>(
+  proxy?.useDict('sys_survey_people', 'sys_item_delete', 'sys_survey_status', 'sys_yesno')
+);
+
+// 注册draggable组件
+defineOptions({
+  name: 'SatSurvey'
+});
+
+const satSurveyList = ref<SatSurveyVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const satSurveyFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+// 题目管理相关状态
+const questionDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+const currentSurvey = ref<SatSurveyVO | null>(null);
+const selectedQuestions = ref<any[]>([]);
+const availableQuestions = ref<SatQuestionVO[]>([]);
+const categories = ref<SatCategoryVO[]>([]);
+const questionLoading = ref(false);
+const readonly = ref(false); // 是否只读模式
+const hasLoadedData = ref(false); // 是否已经加载过数据
+
+// 题目选择器相关状态
+const showQuestionSelector = ref(false);
+const questionSearchText = ref('');
+const questionTypeFilter = ref('');
+const questionCategoryFilter = ref('');
+const selectedQuestionIds = ref<Array<string | number>>([]); // 选择器中选中的题目ID列表
+
+const initFormData: SatSurveyForm = {
+  id: undefined,
+  deleted: undefined,
+  surveyCode: undefined,
+  surveyTitle: undefined,
+  surveyDesc: undefined,
+  surveyInstruction: undefined,
+  targetGroup: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  maxSubmitTimes: undefined,
+  isAnonymous: undefined,
+  isPublished: undefined,
+  status: undefined,
+  remark: undefined
+};
+const data = reactive<PageData<SatSurveyForm, SatSurveyQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    deleted: undefined,
+    surveyCode: undefined,
+    surveyTitle: undefined,
+    surveyDesc: undefined,
+    surveyInstruction: undefined,
+    targetGroup: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    maxSubmitTimes: undefined,
+    isAnonymous: undefined,
+    isPublished: undefined,
+    status: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
+    surveyCode: [{ required: true, message: '问卷编号不能为空', trigger: 'blur' }],
+    surveyTitle: [{ required: true, message: '问卷标题不能为空', trigger: 'blur' }],
+    targetGroup: [{ required: true, message: '目标群体不能为空', trigger: 'change' }],
+    startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
+    endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询问卷列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSatSurvey(queryParams.value);
+  satSurveyList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  satSurveyFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SatSurveyVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加问卷';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SatSurveyVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getSatSurvey(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改问卷';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  satSurveyFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSatSurvey(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addSatSurvey(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SatSurveyVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除问卷编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delSatSurvey(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 发布问卷操作 */
+const handlePublish = async (row: SatSurveyVO) => {
+  try {
+    await proxy?.$modal.confirm(`确定要发布问卷"${row.surveyTitle}"吗?发布后问卷将对目标群体可见。`);
+    await publishSurvey(row.id);
+    proxy?.$modal.msgSuccess('发布成功');
+    await getList();
+  } catch (error) {
+    // 用户取消或发布失败
+    if (error !== 'cancel') {
+      proxy?.$modal.msgError('发布失败,请重试');
+    }
+  }
+};
+
+/** 撤销发布问卷操作 */
+const handleUnpublish = async (row: SatSurveyVO) => {
+  try {
+    await proxy?.$modal.confirm(`确定要撤销发布问卷"${row.surveyTitle}"吗?撤销后问卷将不再对外开放。`);
+    await unpublishSurvey(row.id);
+    proxy?.$modal.msgSuccess('撤销发布成功');
+    await getList();
+  } catch (error) {
+    // 用户取消或撤销失败
+    if (error !== 'cancel') {
+      proxy?.$modal.msgError('撤销发布失败,请重试');
+    }
+  }
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/satSurvey/export',
+    {
+      ...queryParams.value
+    },
+    `satSurvey_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 题目管理按钮操作 */
+const handleQuestionManage = async (row: SatSurveyVO) => {
+  console.log('=== 题目管理按钮被点击 ===');
+  console.log('当前问卷数据:', row);
+  console.log('问卷ID:', row.id);
+  console.log('问卷状态:', row.status);
+
+  // 重置状态
+  selectedQuestions.value = [];
+  availableQuestions.value = [];
+  categories.value = [];
+  hasLoadedData.value = false;
+
+  currentSurvey.value = row;
+  // 根据问卷状态判断是否只读
+  readonly.value = row.status !== '0'; // 状态为草稿时可编辑,其他状态只读
+
+  console.log('是否只读模式:', readonly.value);
+
+  questionDialog.visible = true;
+  questionDialog.title = readonly.value ? '查看问卷题目' : '管理问卷题目';
+
+  console.log('对话框已打开,准备加载题目数据');
+  console.log('当前selectedQuestions长度:', selectedQuestions.value.length);
+
+  try {
+    await loadQuestionData();
+    console.log('题目数据加载完成,最终selectedQuestions长度:', selectedQuestions.value.length);
+  } catch (error) {
+    console.error('加载题目数据时发生错误:', error);
+    proxy?.$modal.msgError('加载题目数据失败,请重试');
+  }
+};
+
+/** 加载题目管理数据 */
+const loadQuestionData = async () => {
+  console.log('=== 开始执行 loadQuestionData 方法 ===');
+  console.log('当前问卷ID:', currentSurvey.value?.id);
+
+  questionLoading.value = true;
+  try {
+    console.log('开始并行加载三个API数据...');
+
+    // 并行加载数据
+    const [surveyQuestionsRes, availableQuestionsRes, categoriesRes] = await Promise.all([
+      getSurveyQuestions(currentSurvey.value!.id),
+      listSatQuestion({ pageNum: 1, pageSize: 999, status: '0' }),
+      listSatCategory({ pageNum: 1, pageSize: 999, status: '0' })
+    ]);
+
+    console.log('API请求完成,开始处理数据...');
+    console.log('问卷题目数据完整响应:', surveyQuestionsRes);
+    console.log('问卷题目数据data字段:', surveyQuestionsRes.data);
+    console.log('分类数据加载结果:', categoriesRes.rows);
+    console.log('题目数据加载结果:', availableQuestionsRes.rows);
+
+    // 处理已选题目数据 - 确保数据结构正确
+    let selectedQuestionsData = surveyQuestionsRes.data || [];
+    console.log('选中的题目数量:', selectedQuestionsData.length);
+    console.log('选中的题目详细数据:', selectedQuestionsData);
+
+    // 检查第一个题目的数据结构
+    if (selectedQuestionsData.length > 0) {
+      console.log('第一个题目的完整数据结构:', selectedQuestionsData[0]);
+      console.log('第一个题目的questionTitle字段:', selectedQuestionsData[0].questionTitle);
+      console.log('第一个题目的questionType字段:', selectedQuestionsData[0].questionType);
+    }
+
+    // 为每个题目加载选项(异步并行处理提高性能)
+    if (selectedQuestionsData.length > 0) {
+      console.log('开始并行加载所有题目的选项...');
+      const optionsPromises = selectedQuestionsData.map(async (question, index) => {
+        try {
+          // 确保 questionId 存在
+          const questionId = question.questionId || question.id;
+          if (!questionId) {
+            console.warn(`题目 ${index} 缺少 questionId:`, question);
+            question.options = [];
+            return question;
+          }
+
+          const optionsRes = await getQuestionOptions(questionId);
+          question.options = optionsRes.data || [];
+          console.log(`题目${questionId}的选项:`, question.options);
+          return question;
+        } catch (error) {
+          console.error(`获取题目${question.questionId || question.id}选项失败:`, error);
+          question.options = [];
+          return question;
+        }
+      });
+
+      // 等待所有选项加载完成
+      selectedQuestionsData = await Promise.all(optionsPromises);
+      console.log('所有题目选项加载完成');
+    }
+
+    // 确保数据响应性更新
+    selectedQuestions.value = [...selectedQuestionsData];
+    availableQuestions.value = availableQuestionsRes.rows || [];
+    categories.value = categoriesRes.rows || [];
+
+    console.log('最终selectedQuestions数据:', selectedQuestions.value);
+    console.log('最终分类数据:', categories.value);
+    console.log('最终题目数据:', availableQuestions.value);
+
+    // 强制触发视图更新
+    nextTick(() => {
+      console.log('视图已更新,当前selectedQuestions长度:', selectedQuestions.value.length);
+    });
+
+    // 标记已经加载过数据
+    hasLoadedData.value = true;
+    console.log('=== loadQuestionData 执行成功完成 ===');
+  } catch (error) {
+    console.error('=== loadQuestionData 执行失败 ===');
+    console.error('错误详情:', error);
+    proxy?.$modal.msgError('加载数据失败');
+  } finally {
+    questionLoading.value = false;
+    console.log('loadQuestionData 方法执行结束,loading状态已重置');
+  }
+};
+
+/** 保存问卷题目 */
+const saveQuestionChanges = async () => {
+  if (readonly.value) return;
+
+  try {
+    questionLoading.value = true;
+    await updateSurveyQuestions(currentSurvey.value!.id, selectedQuestions.value);
+    proxy?.$modal.msgSuccess('保存成功');
+    questionDialog.visible = false;
+  } catch (error) {
+    proxy?.$modal.msgError('保存失败');
+  } finally {
+    questionLoading.value = false;
+  }
+};
+
+/** 取消题目管理 */
+const cancelQuestionManage = () => {
+  questionDialog.visible = false;
+  selectedQuestions.value = [];
+  availableQuestions.value = [];
+  currentSurvey.value = null;
+  hasLoadedData.value = false;
+  // 重置题目选择器状态
+  showQuestionSelector.value = false;
+  questionSearchText.value = '';
+  questionTypeFilter.value = '';
+  questionCategoryFilter.value = '';
+};
+
+/** 获取题目类型名称 */
+const getQuestionType = (type: string) => {
+  const typeMap: Record<string, string> = {
+    '1': '单选题',
+    '2': '多选题',
+    '3': '问答题'
+  };
+  return typeMap[type] || '未知';
+};
+
+/** 获取分类名称 */
+const getCategoryName = (categoryId: string | number) => {
+  console.log('查找分类, categoryId:', categoryId, '类型:', typeof categoryId);
+  console.log('可用分类列表:', categories.value);
+
+  const category = categories.value.find((c) => {
+    const match = String(c.id) === String(categoryId);
+    console.log(`分类ID ${c.id} vs ${categoryId}, 匹配: ${match}`);
+    return match;
+  });
+
+  const result = category?.categoryName || '未分类';
+  console.log('找到的分类名称:', result);
+  return result;
+};
+
+/** 确认删除所有题目 */
+const confirmDeleteAllQuestions = async () => {
+  try {
+    await proxy?.$modal.confirm(`确定要删除所有 ${selectedQuestions.value.length} 个题目吗?此操作不可撤销。`);
+
+    // 清空所有题目
+    selectedQuestions.value = [];
+    proxy?.$modal.msgSuccess('已删除所有题目');
+  } catch (error) {
+    // 用户取消删除
+    console.log('用户取消删除操作');
+  }
+};
+
+/** 移除题目 */
+const removeQuestion = (index: number) => {
+  selectedQuestions.value.splice(index, 1);
+  // 重新排序
+  selectedQuestions.value.forEach((item, idx) => {
+    item.sortOrder = idx + 1;
+  });
+};
+
+/** 计算过滤后的可选题目 */
+const filteredAvailableQuestions = computed(() => {
+  let filtered = availableQuestions.value;
+
+  console.log('过滤前题目数量:', filtered.length);
+  console.log('搜索文本:', questionSearchText.value);
+  console.log('类型过滤:', questionTypeFilter.value);
+  console.log('分类过滤:', questionCategoryFilter.value);
+
+  // 过滤掉已经在问卷中的题目
+  const selectedQuestionIdSet = new Set(selectedQuestions.value.map((q) => q.questionId || q.id));
+  filtered = filtered.filter((q) => !selectedQuestionIdSet.has(q.id));
+  console.log('过滤已选题目后:', filtered.length);
+
+  // 根据搜索文本过滤
+  if (questionSearchText.value) {
+    filtered = filtered.filter((q) => q.questionTitle.toLowerCase().includes(questionSearchText.value.toLowerCase()));
+    console.log('搜索过滤后:', filtered.length);
+  }
+
+  // 根据题目类型过滤
+  if (questionTypeFilter.value) {
+    console.log('开始类型过滤,过滤值:', questionTypeFilter.value);
+    const beforeTypeFilter = filtered.length;
+    filtered = filtered.filter((q) => {
+      // 确保数据类型匹配,将两者都转换为字符串进行比较
+      const questionType = String(q.questionType);
+      const filterType = String(questionTypeFilter.value);
+      console.log(`题目类型: ${questionType}, 过滤类型: ${filterType}, 匹配: ${questionType === filterType}`);
+      return questionType === filterType;
+    });
+    console.log(`类型过滤: ${beforeTypeFilter} -> ${filtered.length}`);
+  }
+
+  // 根据分类过滤
+  if (questionCategoryFilter.value) {
+    filtered = filtered.filter((q) => {
+      // 确保数据类型匹配
+      const categoryId = String(q.categoryId);
+      const filterId = String(questionCategoryFilter.value);
+      return categoryId === filterId;
+    });
+    console.log('分类过滤后:', filtered.length);
+  }
+
+  console.log('最终过滤结果:', filtered.length);
+  return filtered;
+});
+
+/** 是否全选 */
+const isAllQuestionsSelected = computed(() => {
+  return filteredAvailableQuestions.value.length > 0 && selectedQuestionIds.value.length === filteredAvailableQuestions.value.length;
+});
+
+/** 是否半选状态 */
+const isIndeterminate = computed(() => {
+  return selectedQuestionIds.value.length > 0 && selectedQuestionIds.value.length < filteredAvailableQuestions.value.length;
+});
+
+/** 打开题目选择器 */
+const openQuestionSelector = () => {
+  // 重置过滤状态
+  questionSearchText.value = '';
+  questionTypeFilter.value = '';
+  questionCategoryFilter.value = '';
+  selectedQuestionIds.value = [];
+  showQuestionSelector.value = true;
+};
+
+/** 切换题目选择状态 */
+const toggleQuestionSelection = (questionId: string | number) => {
+  const index = selectedQuestionIds.value.indexOf(questionId);
+  if (index > -1) {
+    selectedQuestionIds.value.splice(index, 1);
+  } else {
+    selectedQuestionIds.value.push(questionId);
+  }
+};
+
+/** 全选/取消全选 */
+const toggleSelectAll = (checked: boolean) => {
+  if (checked) {
+    selectedQuestionIds.value = filteredAvailableQuestions.value.map((q) => q.id);
+  } else {
+    selectedQuestionIds.value = [];
+  }
+};
+
+/** 确认添加选中的题目 */
+const confirmSelectQuestions = async () => {
+  try {
+    // 批量获取选中题目的详细信息和选项
+    const selectedQuestionDetails = [];
+
+    for (const questionId of selectedQuestionIds.value) {
+      const question = availableQuestions.value.find((q) => q.id === questionId);
+      if (question) {
+        try {
+          // 获取题目选项
+          const optionsRes = await getQuestionOptions(questionId);
+
+          const newQuestion = {
+            questionId: question.id,
+            questionTitle: question.questionTitle,
+            questionType: question.questionType,
+            categoryId: question.categoryId,
+            sortOrder: selectedQuestions.value.length + selectedQuestionDetails.length + 1,
+            weight: 1.0,
+            isRequired: 1,
+            options: optionsRes.data || []
+          };
+
+          selectedQuestionDetails.push(newQuestion);
+        } catch (error) {
+          console.error(`获取题目${questionId}选项失败:`, error);
+          // 即使获取选项失败,也添加题目(选项为空)
+          const newQuestion = {
+            questionId: question.id,
+            questionTitle: question.questionTitle,
+            questionType: question.questionType,
+            categoryId: question.categoryId,
+            sortOrder: selectedQuestions.value.length + selectedQuestionDetails.length + 1,
+            weight: 1.0,
+            isRequired: 1,
+            options: []
+          };
+          selectedQuestionDetails.push(newQuestion);
+        }
+      }
+    }
+
+    // 添加到已选题目列表
+    selectedQuestions.value.push(...selectedQuestionDetails);
+
+    // 关闭选择器
+    showQuestionSelector.value = false;
+    selectedQuestionIds.value = [];
+
+    proxy?.$modal.msgSuccess(`成功添加 ${selectedQuestionDetails.length} 个题目`);
+  } catch (error) {
+    proxy?.$modal.msgError('添加题目失败');
+  }
+};
+
+/** 取消题目选择器 */
+const cancelQuestionSelector = () => {
+  showQuestionSelector.value = false;
+  selectedQuestionIds.value = [];
+};
+
+const toolbarRef = ref<any>();
+const searchRef = ref<HTMLElement>();
+const actualToolbarHeight = ref(70); // 默认工具栏高度
+const actualSearchHeight = ref(160); // 默认搜索栏高度
+const windowHeight = ref(window.innerHeight);
+
+/** 计算搜索栏实际高度 */
+const calculateSearchHeight = () => {
+  nextTick(() => {
+    if (searchRef.value && showSearch.value) {
+      // 获取搜索栏元素的完整高度,包括边距和内边距
+      const rect = searchRef.value.getBoundingClientRect();
+      const computedStyle = window.getComputedStyle(searchRef.value);
+      const marginTop = parseFloat(computedStyle.marginTop);
+      const marginBottom = parseFloat(computedStyle.marginBottom);
+      actualSearchHeight.value = rect.height + marginTop + marginBottom;
+    } else {
+      // 如果搜索栏隐藏,高度为0
+      actualSearchHeight.value = 0;
+    }
+  });
+};
+
+/** 计算工具栏实际高度 */
+const calculateToolbarHeight = () => {
+  nextTick(() => {
+    if (toolbarRef.value) {
+      // 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
+      const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
+
+      // 检查是否是有效的DOM元素
+      if (toolbarDom && typeof toolbarDom.closest === 'function') {
+        // 获取工具栏元素的完整高度,包括边距和内边距
+        const toolbarElement = toolbarDom.closest('.el-card__header');
+        if (toolbarElement) {
+          const rect = toolbarElement.getBoundingClientRect();
+          const computedStyle = window.getComputedStyle(toolbarElement);
+          const marginTop = parseFloat(computedStyle.marginTop);
+          const marginBottom = parseFloat(computedStyle.marginBottom);
+          actualToolbarHeight.value = rect.height + marginTop + marginBottom;
+        } else {
+          // 如果找不到父元素,直接使用工具栏元素高度
+          const rect = toolbarDom.getBoundingClientRect();
+          actualToolbarHeight.value = rect.height + 20; // 添加一些边距
+        }
+      } else {
+        // 如果无法获取DOM元素,使用默认高度
+        actualToolbarHeight.value = 70;
+      }
+    }
+  });
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+  // 窗口大小变化时重新计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+};
+
+/** 计算表格高度 */
+const tableHeight = computed(() => {
+  // 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
+  const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
+  const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
+  const pageTopPadding = 8; // 页面顶部边距
+  const pageBottomMargin = 10; // 分页栏距离底部的边距
+  const paginationOwnHeight = 32; // 分页组件自身高度
+  const paginationPadding = 20; // 分页栏上下内边距
+  const cardPadding = 40; // 卡片内边距(上下各20px)
+
+  // 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
+  const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
+
+  const calculatedHeight = windowHeight.value - fixedHeight - 45;
+
+  // 确保至少有300px的表格高度,避免过小
+  return Math.max(calculatedHeight, 300);
+});
+
+onMounted(() => {
+  getList();
+  // 初始计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+  // 移除监听器
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+
+<style scoped>
+/* 页面容器样式 */
+.page-container {
+  height: calc(100vh - 85px);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
+}
+
+/* 自定义页面滚动条样式 */
+.page-container::-webkit-scrollbar {
+  width: 8px;
+}
+
+.page-container::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 4px;
+}
+
+.page-container::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 4px;
+}
+
+.page-container::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 0;
+}
+
+.table-card :deep(.el-card__body) {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  padding: 0px 20px 0 20px !important;
+  min-height: 0;
+  margin-bottom: 0 !important;
+}
+
+/* 表格主体 */
+.table-main {
+  flex: 1;
+  overflow: auto;
+}
+
+/* 表格容器 - 支持横向滚动 */
+.table-container {
+  width: 100%;
+  /* overflow: hidden; */
+}
+
+/* 横向滚动表格样式 */
+.scroll-table {
+  width: 100%;
+}
+
+/* 表格横向滚动条样式优化 */
+.scroll-table :deep(.el-table__body-wrapper) {
+  overflow-x: auto !important; /* 自动显示横向滚动条 */
+  overflow-y: auto;
+}
+
+.scroll-table :deep(.el-table__header-wrapper) {
+  overflow-x: hidden;
+}
+
+/* 强制显示表格横向滚动条 - 适用于所有浏览器 */
+.scroll-table :deep(.el-table__body-wrapper) {
+  scrollbar-width: thin; /* Firefox */
+  scrollbar-color: #409eff #f0f0f0; /* Firefox - thumb track */
+}
+
+/* Webkit 浏览器的横向滚动条样式 */
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
+  height: 12px !important; /* 增加滚动条高度 */
+  width: 12px !important;
+}
+
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
+  background: #f0f0f0 !important;
+  border-radius: 6px !important;
+  border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
+}
+
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
+  background: #409eff !important; /* 使用主题色,更明显 */
+  border-radius: 6px !important;
+  border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
+  min-height: 20px !important; /* 确保最小高度 */
+}
+
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
+  background: #337ecc !important; /* 悬停时更深的蓝色 */
+}
+
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
+  background: #2b6cb0 !important; /* 点击时的颜色 */
+}
+
+/* 确保滚动条始终可见的额外设置 */
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
+  background: #f0f0f0;
+}
+
+/* 固定列样式优化 */
+.scroll-table :deep(.el-table__fixed-right) {
+  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
+}
+
+.scroll-table :deep(.el-table__fixed-left) {
+  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 确保固定列在滚动时有正确的背景色 */
+.scroll-table :deep(.el-table__fixed-right .el-table__cell),
+.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
+  background-color: #fff;
+}
+
+/* 表格行悬停效果在固定列上的同步 */
+.scroll-table :deep(.el-table__row:hover .el-table__cell) {
+  background-color: #f5f7fa !important;
+}
+
+/* 分页容器固定在底部 */
+.pagination-container {
+  flex-shrink: 0;
+  padding-top: 10px 0 0 0 !important;
+  margin: 0 !important;
+  /* padding-bottom: 10px !important; 距离底部10px */
+  /* margin: 0 !important; */
+  /* margin-bottom: 10px !important; 确保距离视窗底部10px */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+
+/* 新版题目管理对话框样式 */
+.new-question-manage-container {
+  height: 600px;
+  overflow: hidden;
+}
+
+.question-builder {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 题目管理工具栏样式 */
+.question-toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 16px;
+  margin-bottom: 16px;
+  background: #f8f9fa;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+}
+
+.toolbar-left {
+  display: flex;
+  align-items: center;
+}
+
+.toolbar-right {
+  display: flex;
+  align-items: center;
+}
+
+.question-count {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+}
+
+.question-list-container {
+  flex: 1;
+  overflow-y: auto;
+  padding-bottom: 20px;
+  min-height: 300px;
+}
+
+.question-list {
+  width: 100%;
+  min-height: 50px;
+}
+
+/* 题目卡片样式 */
+.question-card {
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  margin-bottom: 16px;
+  background: #fff;
+  transition: all 0.3s ease;
+}
+
+.question-card:hover {
+  border-color: #409eff;
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
+}
+
+.question-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 16px;
+  background: #f8f9fa;
+  border-bottom: 1px solid #e4e7ed;
+  border-radius: 8px 8px 0 0;
+}
+
+.question-number {
+  font-weight: 600;
+  color: #409eff;
+  font-size: 14px;
+}
+
+.question-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.question-content-display {
+  padding: 16px;
+}
+
+.question-title-display {
+  font-size: 16px;
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 12px;
+  line-height: 1.5;
+}
+
+.question-meta-info {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 16px;
+}
+
+.answer-display-area {
+  margin-top: 12px;
+}
+
+.answer-option {
+  display: block !important;
+  margin-bottom: 8px;
+  white-space: normal;
+  word-break: break-all;
+}
+
+.answer-textarea {
+  width: 100%;
+}
+
+/* 空状态样式 */
+.action-area {
+  flex-shrink: 0;
+  text-align: center;
+  padding: 20px;
+}
+
+.empty-state {
+  margin-bottom: 20px;
+}
+
+.empty-icon {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 16px;
+}
+
+.empty-text {
+  color: #909399;
+  font-size: 14px;
+  margin: 0;
+}
+
+/* Dialog footer样式 */
+.dialog-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 12px;
+  width: 100%;
+}
+
+/* 题目管理对话框footer - 按钮右对齐 */
+.question-manage-footer {
+  justify-content: flex-end !important;
+}
+
+.footer-left {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.footer-right {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.selected-count {
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 题目选择器样式 */
+.question-selector {
+  height: 700px;
+  display: flex;
+  flex-direction: column;
+}
+
+.selector-search {
+  margin-bottom: 20px;
+  padding: 20px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
+}
+
+.search-row {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-bottom: 12px;
+  flex-wrap: wrap;
+}
+
+.search-group {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.search-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #606266;
+  white-space: nowrap;
+  min-width: 80px;
+}
+
+.search-stats {
+  display: flex;
+  justify-content: flex-end;
+  padding-top: 8px;
+  border-top: 1px solid #e4e7ed;
+  margin-top: 8px;
+}
+
+.stats-text {
+  font-size: 13px;
+  color: #909399;
+  font-weight: 500;
+}
+
+.selector-question-list {
+  flex: 1;
+  overflow-y: auto;
+}
+
+.selector-question-item {
+  display: flex;
+  align-items: flex-start;
+  padding: 16px 20px;
+  margin-bottom: 12px;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  background: #fff;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.selector-question-item:hover {
+  border-color: #409eff;
+  background: #f0f9ff;
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
+}
+
+.selector-question-checkbox {
+  margin-right: 16px;
+  margin-top: 4px;
+  flex-shrink: 0;
+}
+
+.selector-question-content {
+  flex: 1;
+}
+
+.selector-question-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 8px;
+}
+
+.selector-question-title {
+  font-size: 15px;
+  font-weight: 500;
+  color: #303133;
+  line-height: 1.5;
+  margin-right: 12px;
+  flex: 1;
+}
+
+.selector-question-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-shrink: 0;
+}
+
+.selector-question-description {
+  font-size: 13px;
+  color: #909399;
+  line-height: 1.4;
+  margin-top: 8px;
+}
+
+.category-name {
+  color: #909399;
+}
+
+.no-questions {
+  text-align: center;
+  padding: 60px 20px;
+  background: #fafafa;
+  border-radius: 8px;
+  margin: 20px 0;
+}
+
+.no-questions-icon {
+  font-size: 64px;
+  color: #c0c4cc;
+  margin-bottom: 16px;
+}
+
+.no-questions-text {
+  font-size: 16px;
+  color: #606266;
+  margin: 0 0 8px 0;
+  font-weight: 500;
+}
+
+.no-questions-hint {
+  font-size: 14px;
+  color: #909399;
+  margin: 0;
+}
+
+/* 滚动条样式 */
+.question-list-container::-webkit-scrollbar,
+.selector-question-list::-webkit-scrollbar {
+  width: 6px;
+}
+
+.question-list-container::-webkit-scrollbar-track,
+.selector-question-list::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.question-list-container::-webkit-scrollbar-thumb,
+.selector-question-list::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.question-list-container::-webkit-scrollbar-thumb:hover,
+.selector-question-list::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+</style>