Explorar el Código

feat(问卷管理): 新增基于avue-crud的问卷管理页面

yz hace 2 meses
padre
commit
79ddcb8de5

+ 374 - 0
src/mixins/survey/surveyCrudMixin.js

@@ -0,0 +1,374 @@
+/**
+ * Survey模块avue-crud配置mixin
+ * @fileoverview 提供基于avue-crud的调查问卷管理功能
+ * @author 开发者
+ * @date 2024-XX-XX
+ */
+
+import {
+  getList,
+  add,
+  update,
+  getDetail
+} from '@/api/survey/survey'
+import {
+  SURVEY_STATUS_OPTIONS,
+  SURVEY_TEMPLATE_OPTIONS,
+  getSurveyStatusLabel,
+  getSurveyStatusType,
+  getSurveyTemplateLabel,
+  getSurveyTemplateType
+} from '@/constants/survey'
+
+export default {
+  data() {
+    return {
+      // avue-crud标准配置
+      option: {
+        height: 'auto',
+        calcHeight: 80,
+        tip: false,
+        searchShow: true,
+        searchMenuSpan: 12,
+        searchIndex: 3,
+        border: true,
+        index: true,
+        viewBtn: true,
+        editBtn: true,
+        delBtn: false, // 暂时禁用删除
+        addBtn: true,
+        selection: false,
+        dialogClickModal: false,
+        menu: true,
+        menuWidth: 200,
+        column: [
+          // 问卷编码列
+          {
+            label: '问卷编码',
+            prop: 'surveyCode',
+            width: 150,
+            search: true,
+            rules: [{
+              required: true,
+              message: '请输入问卷编码',
+              trigger: 'blur'
+            }, {
+              min: 3,
+              max: 50,
+              message: '长度在 3 到 50 个字符',
+              trigger: 'blur'
+            }, {
+              pattern: /^[A-Za-z0-9_-]+$/,
+              message: '只能包含字母、数字、下划线和横线',
+              trigger: 'blur'
+            }]
+          },
+          // 问卷标题列
+          {
+            label: '问卷标题',
+            prop: 'title',
+            minWidth: 200,
+            search: true,
+            overHidden: true,
+            rules: [{
+              required: true,
+              message: '请输入问卷标题',
+              trigger: 'blur'
+            }, {
+              min: 2,
+              max: 100,
+              message: '长度在 2 到 100 个字符',
+              trigger: 'blur'
+            }]
+          },
+          // 问卷描述列
+          {
+            label: '问卷描述',
+            prop: 'description',
+            minWidth: 250,
+            search: false,
+            overHidden: true,
+            type: 'textarea',
+            span: 24,
+            minRows: 3,
+            maxRows: 6,
+            rules: [{
+              required: true,
+              message: '请输入问卷描述',
+              trigger: 'blur'
+            }, {
+              min: 5,
+              max: 500,
+              message: '长度在 5 到 500 个字符',
+              trigger: 'blur'
+            }]
+          },
+          // 状态列
+          {
+            label: '状态',
+            prop: 'status',
+            width: 100,
+            search: true,
+            type: 'select',
+            dicData: SURVEY_STATUS_OPTIONS,
+            slot: true,
+            rules: [{
+              required: true,
+              message: '请选择状态',
+              trigger: 'change'
+            }]
+          },
+          // 是否模板列
+          {
+            label: '是否模板',
+            prop: 'isTemplate',
+            width: 100,
+            search: true,
+            type: 'select',
+            dicData: SURVEY_TEMPLATE_OPTIONS,
+            slot: true
+          },
+          // 开始时间列
+          {
+            label: '开始时间',
+            prop: 'startTime',
+            width: 160,
+            type: 'datetime',
+            format: 'yyyy-MM-dd HH:mm:ss',
+            valueFormat: 'yyyy-MM-dd HH:mm:ss',
+            searchRange: true,
+            search: true,
+            rules: [{
+              required: true,
+              message: '请选择开始时间',
+              trigger: 'change'
+            }]
+          },
+          // 结束时间列
+          {
+            label: '结束时间',
+            prop: 'endTime',
+            width: 160,
+            type: 'datetime',
+            format: 'yyyy-MM-dd HH:mm:ss',
+            valueFormat: 'yyyy-MM-dd HH:mm:ss',
+            searchRange: true,
+            search: true,
+            rules: [{
+              required: true,
+              message: '请选择结束时间',
+              trigger: 'change'
+            }]
+          },
+          // 创建时间列
+          {
+            label: '创建时间',
+            prop: 'createTime',
+            width: 160,
+            type: 'datetime',
+            format: 'yyyy-MM-dd HH:mm:ss',
+            valueFormat: 'yyyy-MM-dd HH:mm:ss',
+            addDisplay: false,
+            editDisplay: false,
+            search: false
+          }
+        ]
+      },
+
+      // 数据和分页
+      data: [],
+      page: {
+        currentPage: 1,
+        pageSize: 20,
+        total: 0
+      },
+
+      // 查询和表单
+      query: {},
+      form: {},
+      loading: true,
+
+      // 题目编辑相关
+      questionEditorVisible: false,
+      currentSurveyId: null,
+
+      // 权限配置
+      permissionList: {
+        addBtn: true,
+        viewBtn: true,
+        editBtn: true,
+        delBtn: false
+      }
+    }
+  },
+
+
+  mounted() {
+    this.onLoad(this.page)
+  },
+
+  methods: {
+    // 导入工具函数
+    getSurveyStatusLabel,
+    getSurveyStatusType,
+    getSurveyTemplateLabel,
+    getSurveyTemplateType,
+
+    // avue-crud标准方法
+    async onLoad(page, params = {}) {
+      this.loading = true
+      try {
+        // 处理时间范围查询
+        const queryParams = { ...params }
+        if (queryParams.startTime && Array.isArray(queryParams.startTime)) {
+          queryParams.startTimeStart = queryParams.startTime[0]
+          queryParams.startTimeEnd = queryParams.startTime[1]
+          delete queryParams.startTime
+        }
+        if (queryParams.endTime && Array.isArray(queryParams.endTime)) {
+          queryParams.endTimeStart = queryParams.endTime[0]
+          queryParams.endTimeEnd = queryParams.endTime[1]
+          delete queryParams.endTime
+        }
+
+        const response = await getList(
+          page.currentPage,
+          page.pageSize,
+          queryParams
+        )
+
+        if (response.data && response.data.success) {
+          const { records, total } = response.data.data
+          this.data = records || []
+          this.page.total = total || 0
+        } else {
+          this.$message.error(response.data?.msg || '获取数据失败')
+        }
+      } catch (error) {
+        console.error('加载数据失败:', error)
+        this.$message.error('加载数据失败,请稍后重试')
+      } finally {
+        this.loading = false
+      }
+    },
+
+    searchChange(params, done) {
+      this.query = params
+      this.onLoad(this.page, params)
+      done()
+    },
+
+    searchReset(done) {
+      this.query = {}
+      this.onLoad(this.page)
+      done()
+    },
+
+
+    currentChange(currentPage) {
+      this.page.currentPage = currentPage
+    },
+
+    sizeChange(pageSize) {
+      this.page.pageSize = pageSize
+    },
+
+    refreshChange() {
+      this.onLoad(this.page, this.query)
+    },
+
+    // CRUD操作
+    async beforeOpen(done, type) {
+      if (['edit', 'view'].includes(type)) {
+        try {
+          const response = await getDetail(this.form.id)
+          if (response.data && response.data.success) {
+            this.form = response.data.data
+          } else {
+            this.$message.error('获取详情失败')
+            return
+          }
+        } catch (error) {
+          console.error('获取详情失败:', error)
+          this.$message.error('获取详情失败')
+          return
+        }
+      }
+      done()
+    },
+
+    async rowSave(row, done, loading) {
+      try {
+        // 时间验证
+        if (new Date(row.startTime) >= new Date(row.endTime)) {
+          this.$message.error('开始时间必须小于结束时间')
+          loading()
+          return
+        }
+
+        const response = await add(row)
+        if (response.data && response.data.success) {
+          this.$message.success('新增问卷成功')
+          this.onLoad(this.page)
+          done()
+        } else {
+          this.$message.error(response.data?.msg || '新增失败')
+          loading()
+        }
+      } catch (error) {
+        console.error('新增失败:', error)
+        this.$message.error('新增失败,请稍后重试')
+        loading()
+      }
+    },
+
+    async rowUpdate(row, index, done, loading) {
+      try {
+        // 时间验证
+        if (new Date(row.startTime) >= new Date(row.endTime)) {
+          this.$message.error('开始时间必须小于结束时间')
+          loading()
+          return
+        }
+
+        const response = await update(row)
+        if (response.data && response.data.success) {
+          this.$message.success('更新问卷成功')
+          this.onLoad(this.page)
+          done()
+        } else {
+          this.$message.error(response.data?.msg || '更新失败')
+          loading()
+        }
+      } catch (error) {
+        console.error('更新失败:', error)
+        this.$message.error('更新失败,请稍后重试')
+        loading()
+      }
+    },
+
+    // 题目编辑功能
+    handleEditQuestions(row) {
+      this.currentSurveyId = row.id
+      this.questionEditorVisible = true
+
+      this.$nextTick(() => {
+        if (this.$refs.questionEditor) {
+          this.$refs.questionEditor.loadQuestionList()
+        }
+      })
+    },
+
+    handleCloseQuestionEditor() {
+      this.questionEditorVisible = false
+      this.currentSurveyId = null
+
+      this.$nextTick(() => {
+        if (this.$refs.questionEditor) {
+          this.$refs.questionEditor = null
+        }
+      })
+    }
+
+  }
+}

+ 1 - 1
src/router/page/index.js

@@ -74,7 +74,7 @@ export default [{
     }]
 
   },
-  {
+    {
     path: '*',
     redirect: '/404'
   }

+ 1 - 1
src/router/views/index.js

@@ -148,7 +148,7 @@ export default [
             {
                 path: "index",
                 name: "调查问卷管理",
-                component: () => import("@/views/survey/index"),
+                component: () => import("@/views/survey/index-avue"),
                 meta: {
                     keepAlive: true,
                     isAuth: true,

+ 330 - 0
src/views/survey/index-avue.scss

@@ -0,0 +1,330 @@
+// Survey模块avue-crud样式定制
+.survey-management-avue {
+  // 搜索区域样式
+  .avue-crud__search {
+    background: #fff;
+    padding: 20px;
+    border-radius: 4px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .avue-form {
+      .el-form-item {
+        margin-bottom: 18px;
+
+        .el-form-item__label {
+          font-weight: 500;
+          color: #303133;
+        }
+
+        .el-input,
+        .el-select,
+        .el-date-editor {
+          width: 100%;
+        }
+      }
+    }
+  }
+
+  // 表格区域样式
+  .avue-crud__menu {
+    margin-bottom: 16px;
+    padding: 16px 20px;
+    background: #fff;
+    border-radius: 4px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .el-button {
+      margin-right: 8px;
+
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+  }
+
+  // 表格样式
+  .avue-crud {
+    background: #fff;
+    border-radius: 4px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .el-table {
+      .el-table__header {
+        background-color: #f5f7fa;
+
+        th {
+          background-color: #f5f7fa;
+          color: #606266;
+          font-weight: 600;
+        }
+      }
+
+      .el-table__row {
+        &:hover {
+          background-color: #f5f7fa;
+        }
+      }
+    }
+
+    // 状态标签样式
+    .el-tag {
+      font-weight: 500;
+      border-radius: 4px;
+
+      &.el-tag--success {
+        background-color: #f0f9ff;
+        border-color: #67c23a;
+        color: #67c23a;
+      }
+
+      &.el-tag--warning {
+        background-color: #fdf6ec;
+        border-color: #e6a23c;
+        color: #e6a23c;
+      }
+
+      &.el-tag--info {
+        background-color: #f4f4f5;
+        border-color: #909399;
+        color: #909399;
+      }
+    }
+
+    // 操作按钮样式
+    .el-button--text {
+      padding: 2px 8px;
+      font-size: 12px;
+
+      &:hover {
+        background-color: #f0f9ff;
+        color: #409eff;
+      }
+    }
+  }
+
+  // 分页样式
+  .avue-crud__pagination {
+    padding: 20px 0;
+    text-align: right;
+    background: #fff;
+    border-radius: 4px;
+    margin-top: 16px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+    .el-pagination {
+      .el-pagination__total,
+      .el-pagination__sizes,
+      .el-pagination__jump {
+        color: #606266;
+      }
+    }
+  }
+}
+
+// 题目编辑弹窗样式
+.question-editor-dialog {
+  .el-dialog__header {
+    background-color: #f5f7fa;
+    padding: 16px 20px;
+    border-bottom: 1px solid #e4e7ed;
+
+    .el-dialog__title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+
+  .el-dialog__body {
+    padding: 10px 20px;
+    max-height: 70vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background-color: #c0c4cc;
+      border-radius: 3px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: #f5f7fa;
+      border-radius: 3px;
+    }
+  }
+
+  .el-dialog__footer {
+    padding: 16px 20px;
+    background-color: #f5f7fa;
+    border-top: 1px solid #e4e7ed;
+    text-align: center;
+
+    .el-button {
+      margin: 0 8px;
+      min-width: 80px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .survey-management-avue {
+    .avue-crud__search {
+      padding: 15px;
+    }
+
+    .avue-crud__menu {
+      padding: 12px 15px;
+
+      .el-button {
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .survey-management-avue {
+    .avue-crud__search {
+      padding: 10px;
+
+      .avue-form {
+        .el-form-item {
+          margin-bottom: 12px;
+        }
+      }
+    }
+
+    .avue-crud__menu {
+      padding: 10px;
+
+      .el-button {
+        width: 100%;
+        margin-bottom: 8px;
+        margin-right: 0;
+      }
+    }
+
+    .avue-crud {
+      .el-table {
+        font-size: 12px;
+
+        .el-table__header th {
+          padding: 8px 4px;
+        }
+
+        .el-table__row td {
+          padding: 8px 4px;
+        }
+      }
+
+      .el-button--text {
+        padding: 4px 6px;
+        font-size: 11px;
+      }
+    }
+
+    .avue-crud__pagination {
+      padding: 15px 0;
+      text-align: center;
+
+      .el-pagination {
+        justify-content: center;
+
+        .el-pagination__sizes,
+        .el-pagination__jump {
+          display: none;
+        }
+      }
+    }
+  }
+
+  .question-editor-dialog {
+    width: 95% !important;
+    margin-top: 5vh !important;
+
+    .el-dialog__body {
+      padding: 8px 15px;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .survey-management-avue {
+    .avue-crud__search {
+      .avue-form {
+        .el-form-item {
+          .el-form-item__content {
+            .el-input,
+            .el-select,
+            .el-date-editor {
+              font-size: 14px;
+            }
+          }
+        }
+      }
+    }
+
+    .avue-crud {
+      .el-table {
+        .el-table__header th,
+        .el-table__row td {
+          padding: 6px 2px;
+          font-size: 11px;
+        }
+
+        .cell {
+          padding: 0 2px;
+        }
+      }
+    }
+  }
+}
+
+// 动画效果
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+
+.slide-fade-enter-active {
+  transition: all 0.3s ease;
+}
+
+.slide-fade-leave-active {
+  transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
+}
+
+.slide-fade-enter,
+.slide-fade-leave-to {
+  transform: translateX(10px);
+  opacity: 0;
+}
+
+// 加载状态样式
+.avue-crud {
+  .el-loading-mask {
+    background-color: rgba(255, 255, 255, 0.8);
+
+    .el-loading-spinner {
+      .path {
+        stroke: #409eff;
+      }
+    }
+  }
+}
+
+// 工具提示样式
+.el-tooltip__popper {
+  max-width: 300px;
+  line-height: 1.5;
+}

+ 158 - 0
src/views/survey/index-avue.vue

@@ -0,0 +1,158 @@
+<template>
+  <basic-container>
+    <!-- avue-crud表格组件 -->
+    <avue-crud
+      :option="option"
+      :data="data"
+      ref="crud"
+      v-model="form"
+      :page.sync="page"
+      :permission="permissionList"
+      :before-open="beforeOpen"
+      @row-save="rowSave"
+      @row-update="rowUpdate"
+      @row-del="rowDel"
+      @search-change="searchChange"
+      @search-reset="searchReset"
+      @selection-change="selectionChange"
+      @current-change="currentChange"
+      @size-change="sizeChange"
+      @refresh-change="refreshChange"
+    >
+      <!-- 状态插槽 -->
+      <template slot="status" slot-scope="{row}">
+        <el-tag :type="getSurveyStatusType(row.status)">
+          {{ getSurveyStatusLabel(row.status) }}
+        </el-tag>
+      </template>
+
+      <!-- 是否模板插槽 -->
+      <template slot="isTemplate" slot-scope="{row}">
+        <el-tag :type="getSurveyTemplateType(row.isTemplate)">
+          {{ getSurveyTemplateLabel(row.isTemplate) }}
+        </el-tag>
+      </template>
+
+      <!-- 菜单插槽 -->
+      <template slot="menu" slot-scope="{row}">
+        <el-button
+          type="text"
+          size="small"
+          icon="el-icon-setting"
+          @click="handleEditQuestions(row)"
+        >
+          题目编辑
+        </el-button>
+      </template>
+
+          </avue-crud>
+
+    <!-- 题目编辑弹窗 -->
+    <el-dialog
+      title="题目编辑"
+      :visible.sync="questionEditorVisible"
+      width="90%"
+      append-to-body
+      :close-on-click-modal="false"
+      custom-class="question-editor-dialog"
+      @close="handleCloseQuestionEditor"
+    >
+      <survey-question-editor
+        v-if="questionEditorVisible"
+        :survey-id="currentSurveyId"
+        ref="questionEditor"
+      />
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="handleCloseQuestionEditor">关闭</el-button>
+      </div>
+    </el-dialog>
+  </basic-container>
+</template>
+
+<script>
+import surveyCrudMixin from '@/mixins/survey/surveyCrudMixin'
+import SurveyQuestionEditor from '@/components/survey-question-editor'
+
+export default {
+  name: 'SurveyManagementAvue',
+
+  components: {
+    SurveyQuestionEditor
+  },
+
+  mixins: [surveyCrudMixin]
+}
+</script>
+
+<style lang="scss" scoped>
+.survey-management-avue {
+  // 题目编辑弹窗样式
+  ::v-deep .question-editor-dialog {
+    .el-dialog__body {
+      padding: 10px 20px;
+      max-height: 70vh;
+      overflow-y: auto;
+    }
+
+    .el-dialog__footer {
+      padding: 10px 20px 20px;
+      text-align: center;
+    }
+  }
+
+  // avue-crud样式优化
+  ::v-deep .avue-crud {
+    .avue-crud__search {
+      .avue-form {
+        .el-form-item {
+          margin-bottom: 18px;
+        }
+      }
+    }
+
+    .avue-crud__menu {
+      .el-button {
+        margin-right: 8px;
+      }
+    }
+
+    .el-tag {
+      &--success {
+        background-color: #f0f9ff;
+        border-color: #67c23a;
+        color: #67c23a;
+      }
+
+      &--warning {
+        background-color: #fdf6ec;
+        border-color: #e6a23c;
+        color: #e6a23c;
+      }
+
+      &--info {
+        background-color: #f4f4f5;
+        border-color: #909399;
+        color: #909399;
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .survey-management-avue {
+    ::v-deep .avue-crud {
+      .avue-crud__search {
+        padding: 15px;
+      }
+
+      .avue-crud__menu {
+        .el-button {
+          margin-bottom: 8px;
+        }
+      }
+    }
+  }
+}
+</style>