瀏覽代碼

添加创建问卷的默认列表

lm 1 月之前
父節點
當前提交
bfba9a1c1d

+ 638 - 0
src/baseComponents/Form.vue

@@ -0,0 +1,638 @@
+<template>
+    <el-form 
+        :inline="true"
+        :model="formData"  
+        ref="formRef" 
+        label-width="auto"
+    >
+      <el-form-item 
+        v-for="item in finalFormItemList" 
+        :key="item.key"
+        :label="item.label ?  (item.label + ':') : ''"
+        :prop="item.key"
+        :rules="item.rules"
+        :class="[item.className,{'top_label':(item.type=='textarea'||item.type=='checkbox'||item.type=='file' || item.custom === true || item.type=='noDragFile' || item.type=='img')}]"
+      >
+        <template v-if="item.custom">
+          <slot :name="item.key" :formRef="formRef" :formData="formData"></slot>
+        </template>
+
+        <template v-else>
+          <!-- autocomplete="off" -->
+          <template v-if="!props.formDisabled">
+            <el-input v-if="item.type=='input'" v-model="formData[item.key]"  :disabled="item.disabled" :placeholder="item.placeholder" :style="item.style"/>
+            <el-input v-else-if="item.type=='password'" type="password" v-model="formData[item.key]"  :disabled="item.disabled" :style="item.style"/>
+            <el-input v-else-if="item.type=='textarea'" :autosize="{ minRows: item.rows, maxRows: item.rows }" v-model="formData[item.key]" type="textarea" :rows="item.rows"  :disabled="item.disabled" :placeholder="item.placeholder" :style="item.style"/>
+            <el-input v-else-if="item.type=='number'" type="number"  v-model="formData[item.key]" :rows="item.rows"  :disabled="item.disabled" :placeholder="item.placeholder" :style="item.style"/>
+            
+            <el-radio-group 
+              v-else-if="item.type=='radio'"  
+              v-model="formData[item.key]" 
+              :disabled="item.disabled" 
+              :placeholder="item.placeholder"
+              @change="(val)=>handleSelectChange(item.key,val,item.type)"
+            >
+              <el-radio v-for="radioItem in item.selectList" :key="radioItem.value" :value="radioItem.value">{{ radioItem.label }}</el-radio>
+            </el-radio-group>
+            
+            <el-select 
+              v-else-if="item.type=='select'"  
+              @change="(val)=>handleSelectChange(item.key,val)"
+              v-model="formData[item.key]"  :style="item.style" :disabled="item.disabled" :placeholder="item.placeholder"
+            >
+              <el-option
+                v-for="selectItem in item.selectList"
+                :key="selectItem.value"
+                :label="selectItem.label"
+                :value="selectItem.value"
+              />
+            </el-select>
+
+            <el-select
+              class="multipleSelect"
+              v-else-if="item.type=='multipleSelect'"
+              v-model="formData[item.key]"
+              multiple
+              @change="(val)=>handleSelectChange(item.key,val)"
+              :disabled="item.disabled" :placeholder="item.placeholder"
+            >
+              <el-option
+                v-for="selectItem in item.selectList"
+                :key="selectItem.value"
+                :label="selectItem.label"
+                :value="selectItem.value"
+                />
+            </el-select>
+
+            <el-select
+              class="multipleSelect"
+              v-else-if="item.type=='multipleSelectSearch'"
+              v-model="formData[item.key]"
+              multiple
+              filterable
+              @change="(val)=>handleSelectChange(item.key,val)"
+              :disabled="item.disabled" :placeholder="item.placeholder"
+            >
+              <el-option
+                v-for="selectItem in item.selectList"
+                :key="selectItem.value"
+                :label="selectItem.label"
+                :value="selectItem.value"
+                />
+            </el-select>
+
+
+
+            <template v-else-if="item.type == 'unionSelect'">
+              <span style="display: flex;width: 100%;gap:20px">
+                <el-select v-for="unionSubItem in item.unionSelectList"
+                  :key="unionSubItem.key"
+                  @change="(val)=>handleSelectChange(item.key,val,item.type,null,unionSubItem.key)"
+                  :model-value="formData[item.key]?.[unionSubItem.key]"
+                  :style="unionSubItem.style" :disabled="unionSubItem.disabled" :placeholder="unionSubItem.placeholder"
+                >
+                  <el-option
+                    v-for="selectItem in unionSubItem.selectList"
+                    :key="selectItem.value"
+                    :label="selectItem.label"
+                    :value="selectItem.value"
+                  />
+                </el-select>
+              </span>
+            </template>
+
+            <template v-else-if="item.type=='checkbox'" >
+              <el-checkbox
+                v-if="item?.checkAll"
+                :disabled="item.disabled"
+                :model-value="formData[item.key]?.length === item.selectList?.length"
+                :indeterminate="formData[item.key]?.length > 0 && formData[item.key]?.length < item.selectList?.length"
+                @change="(val)=>handleCheckAllOrNot(val,item.key,item.selectList)"
+              >
+                全部
+              </el-checkbox>
+
+              <el-checkbox-group  
+                class="check_group_list"
+                :disabled="item.disabled"
+                v-model="formData[item.key] "
+                @change="(val)=>handleSelectChange(item.key,val,item.type,item.selectList)"
+              >
+                <el-checkbox 
+                  v-for="checkItem in item.selectList" 
+                  :key="checkItem.value"
+                  :label="checkItem.label"
+                  :value="checkItem.value"
+                />
+              </el-checkbox-group>
+
+            </template>
+
+            <template v-else-if="item.type == 'switch'">
+              <div style="display: flex;align-items: center;gap:20px;">
+                <el-switch
+                  v-model="formData[item.key] "
+                  class="ml-2"
+                  style="--el-switch-on-color: #2e64fa; --el-switch-off-color: #E2E2E3"
+                />
+                <span style="color:#999999;font-size: 12px;">{{ item.tips }}</span>
+              </div>
+            </template>
+          
+
+            <el-date-picker
+              v-else-if="item.type=='datePicker'"
+              v-model="formData[item.key] "
+              type="daterange"
+              :range-separator="item.placeholder.rangeSeparator"
+              :start-placeholder="item.placeholder.startPlaceholder"
+              :end-placeholder="item.placeholder.endPlaceholder"
+              size="default"
+              :disabled="item.disabled"
+              @change="(val)=>handleSelectChange(item.key,val,item.type)"
+            />
+            
+            <el-date-picker
+              v-else-if="item.type=='dateTimePicker'"
+              v-model="formData[item.key]"
+              :start-placeholder="item.placeholder.startPlaceholder"
+              :end-placeholder="item.placeholder.endPlaceholder"
+              type="datetimerange"
+              format="YYYY-MM-DD HH:mm:ss"
+              date-format="YYYY/MM/DD ddd"
+              time-format="A hh:mm:ss"
+              :range-separator="item.placeholder.rangeSeparator"
+              size="default"
+              @change="(val)=>handleSelectChange(item.key,val,item.type)"
+              :disabled="item.disabled"
+            />
+
+
+            <el-date-picker
+              v-else-if="item.type=='dateTimePickerNoRange'"
+              v-model="formData[item.key]"
+              type="datetime"
+              :placeholder="item.placeholder"
+              :disabled="item.disabled"
+              style="width: 100%;"
+              @change="(val)=>handleSelectChange(item.key,val,item.type)"
+            />
+
+
+            <el-upload
+              v-else-if="item.type=='file'"
+              class="upload-demo"
+              action="#"
+              drag
+              :auto-upload="false"
+              @change="(file)=>handleFileChange(file,item.key)"
+              :disabled="item.disabled"
+              >
+                <span v-if="props.fileLoading" style="display: flex;flex-direction: row;align-items: center;gap: 3px;line-height: 1.5;color:#2e64fa">
+                  上传中<el-icon class="is-loading">
+                    <Loading />
+                  </el-icon>
+                </span>
+                
+                <span v-else-if="formData[item.key]?.name" class="file_list">
+                  <span class="el-upload__tip">
+                    <span  @click.stop.prevent="HandlePreviewFile(formData[item.key])">
+                      {{ formData[item.key]?.name  }}   
+                    </span> 
+                    <img class="delete_item"  @click="HandleRemove(item.key)" :src="deleteIcon" alt="">
+                  </span>
+                  <i  class="iconfont hjx-dui"></i>
+                </span>
+
+                <img class="no_data" v-else :src="noDataBg" alt="">
+                <el-button type="primary"><i class="iconfont hjx-daochu"></i> 上传文件</el-button>
+
+            </el-upload>
+
+
+            <el-upload
+              v-else-if="item.type == 'noDragFile'"
+              class="upload-demo"
+              action="#"
+              :auto-upload="false"
+              @change="(file)=>handleFileChange(file,item.key)"
+              :disabled="item.disabled"
+              >
+                <el-button type="primary"><i class="iconfont hjx-daochu"></i> 上传文件</el-button>
+                
+                <template #tip>
+                  <div class="no_drag_content el-upload__tip ">
+                    <span v-if="props.fileLoading" style="display: flex;flex-direction: row;align-items: center;gap: 3px;line-height: 1.5;color:#2e64fa">
+                      上传中<el-icon class="is-loading">
+                        <Loading />
+                      </el-icon>
+                    </span>
+                    
+                    <span v-else-if="formData[item.key]?.name" class="file_list">
+                      <span class="el-upload__tip">
+                        <span  @click.stop.prevent="HandlePreviewFile(formData[item.key])">
+                          {{ formData[item.key]?.name  }}   
+                        </span> 
+                        <img class="delete_item"  @click="HandleRemove(item.key)" :src="deleteIcon" alt="">
+                      </span>
+                      <i  class="iconfont hjx-dui"></i>
+                    </span>
+                  </div>
+
+                </template>
+            </el-upload>
+
+
+            <template  v-else-if="item.type=='imgFile'" >
+              <div class="img_file_area">
+                 <span v-if="props.fileLoading" class="cover"  style="gap: 3px;line-height: 1.5;color:#2e64fa">
+                  上传中
+                  <el-icon class="is-loading">
+                    <Loading />
+                  </el-icon>
+                </span>
+                <span v-else-if="formData[item.key]?.url">
+                  <img class="cover"   :src="formData[item.key]?.url" alt="">
+                  <img   class="close" @click="HandleRemove(item.key)" :src="closeFillImg">
+                </span>
+                <el-upload 
+                  v-model:file-list="fileList"
+                  action="#" 
+                  :auto-upload="false"
+                  @change="(file)=>handleFileChange(file,item.key)"
+                  :on-preview="handlePictureCardPreview"
+                  :on-remove="handleRemove"
+                >
+                  <div class="operation_area">
+                    <i class="iconfont hjx-a-Folder-uploadwenjianjia-shangchuan"></i>
+                  </div>
+                </el-upload>
+              </div>
+
+
+            </template>
+
+            <img  width="100%" v-else-if="item.type=='img'" :src="formData[item.key]?.url">
+            
+
+            <a v-else-if="item.type=='url'"  @click="HandlePreviewFile(formData[item.key])" >{{ formData[item.key]?.name }}</a>
+          </template>
+
+          <template v-else>
+            
+            <el-date-picker
+              v-if="item.type=='dateTimePicker'"
+              v-model="formData[item.key]"
+              :start-placeholder="item.placeholder.startPlaceholder"
+              :end-placeholder="item.placeholder.endPlaceholder"
+              type="datetimerange"
+              format="YYYY-MM-DD HH:mm:ss"
+              date-format="YYYY/MM/DD ddd"
+              time-format="A hh:mm:ss"
+              :range-separator="item.placeholder.rangeSeparator"
+              size="default"
+              :disabled="true"
+            />
+            <img v-else-if="item.type=='img'" :src="formData[item.key]?.url">
+          </template>
+
+        </template>
+      </el-form-item>
+
+      <div style="width: 100%;">
+        <slot name="submit" :formRef="formRef" :formData="formData"></slot>
+      </div>
+
+    </el-form>
+
+</template>
+
+
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+import noDataBg from '@/assets/bg/table_no_data.png'
+import deleteIcon from '@/assets/icon/delete.svg'
+import {Loading} from '@element-plus/icons-vue'
+
+import closeFillImg from '@/assets/icon/close_fill.svg'
+
+
+const previewVisible = ref(false)
+const fileList = ref([
+   {
+    name: 'food.jpeg',
+    url: 'https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260114_50007/14091841_0834e935-5393-4981-ba53-3c37fda73a1f.jpeg',
+  },
+])
+
+const handlePictureCardPreview = ()=>{
+  console.log('预览')
+}
+
+
+const handleRemove = ()=>{
+  console.log('移除')
+}
+type  ItemType = 
+  |'input'|'password'|'textarea'|'number'|'radio'
+  |'select'|'multipleSelect'| 'multipleSelectSearch' |'unionSelect' |'checkbox'|'datePicker' | 'dateTimePickerNoRange'
+  |'dateTimePicker'|'url'|'file' | 'switch' | 'imgFile' | 'noDragFile' | 'img'
+
+
+interface formItemProperty {
+  key: String,
+  label: String,
+  type:ItemType,
+  checkAll?:Boolean
+  placeholder?:|String 
+              | {
+                rangeSeparator:String,
+                startPlaceholder:String
+                endPlaceholder:String
+              },
+  rules: any[],
+  hidden?: Boolean,
+  disabled?: Boolean,
+  custom?:Boolean,
+  style?: Record<string, any>,
+  className?:String,
+  unionSelectList?:{
+    key:String,
+    type:String,
+    placeholder:String,
+    disabled:Boolean,
+    style:Record<string, any>,
+    selectList: { label: string, value: any }[] | [],
+  }[]
+  selectList?: { label: string, value: any }[] | [],
+  rows?:number,
+  tips?:String,
+}
+
+const props = defineProps({
+    width:{
+      required:false,
+    },
+   
+    formItemList:{
+        type:Array<formItemProperty>,
+        required:true,
+        // default:()=>[]
+    },
+    
+    defaultFormData:{
+      type:Object,
+      required:true,
+    },
+    formDisabled:{
+      type:Boolean,
+      default:false
+    },
+    fileLoading:{
+      type:Boolean,
+      default:false
+    }
+})
+const emit  = defineEmits(['close','confirm','formItemChange','fileUpload'])
+
+const fileMessage = ref({})
+const formData = ref({})
+const formRef = ref()
+
+const finalFormItemList = computed(()=>{
+    return props.formItemList.filter(item=>!item.hidden)
+})
+
+// 是否全选
+
+watch(()=>props.defaultFormData,(newVal)=>{
+  formData.value = newVal
+},{immediate:true})
+
+const formSubmitCheck = async (formEl) => {
+  if(props.formDisabled){
+    closeDialog()
+    return
+  }
+  if (!formEl) return
+  await formEl.validate(async(valid) => {
+    if (valid) {
+      emit('confirm', {formData:formData.value,formRef:formEl})
+    }
+  }).catch(err=>{
+    console.log('报错信息',err)
+  })
+}
+
+const HandlePreviewFile = (file)=>{
+  console.log('附件信息',file)
+  previewVisible.value = true
+  fileMessage.value  = {url:file.url,name:file.name}
+}
+
+const closeDialog = () => {
+  formRef.value.resetFields()
+  emit('close')
+}
+
+const HandClosePreview = ()=>{
+  fileMessage.value = {}
+  previewVisible.value = false
+}
+
+const handleSelectChange = (key,val,type,selectList,subKey) =>{
+  if(type == 'unionSelect'){
+    if(formData.value[key]){
+      formData.value[key][subKey] = val
+    }else{
+      formData.value[key] = {}
+      formData.value[key][subKey] = val
+    }
+  }
+  emit('formItemChange',{key,val,formData:formData.value,subKey})
+}
+
+const handleCheckAllOrNot = (val,key,allData)=>{
+  let allValue = allData.map(item=>item.value)
+  let finalVal = val ? allValue : [] 
+  formData.value = {...formData.value,[key]:finalVal}
+  // 选项----
+  emit('formItemChange',{key,val:finalVal,formData:formData.value})
+}
+
+const handleFileChange = (file,formKey)=>{
+  emit('fileUpload',{key:formKey,file})
+}
+
+const HandleRemove = (formKey)=>{
+  formData.value[formKey] = null
+}
+
+</script>  
+<style lang="scss" scoped>
+.img_file_area{
+  width: 100%;
+  display: flex;
+  gap:10px;
+  align-items: center;
+
+  >span{
+    position: relative;
+    .close{
+      width: 14px;
+      height: 14px;
+      color: #F56C6C;
+      position:absolute;
+      top:-5px;
+      right: -2px;
+    }
+  }
+
+  .cover,
+  .operation_area{
+    width: 90px;
+    height: 60px;
+    border-radius: 6px;
+    background-color: #f0f4f8;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .operation_area{
+    background-color: #F0F4F8;
+    font-size: 24px;
+  }
+}
+
+.multipleSelect{
+  :deep(.el-select__wrapper){
+     height: auto;
+  }
+}
+
+.upload-demo{
+  width: 100%;
+  .file_list{
+    // 拖拽
+    width: 80%;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    gap:5px;
+
+    .el-upload__tip{
+      flex:1 1;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      margin-top: 0;
+      // 拖拽
+      background-color: white;
+      padding:0 8px;
+      > a{
+        color: #666;
+      }
+      > img{
+        width: 15px;
+      }
+    }
+
+    .hjx-dui {
+      color: #2dab85;
+      font-size: 16px;
+    }
+  }
+  .el-button{
+    background-color: #2e64fa;
+  }
+
+ 
+
+  .no_drag_content{
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    align-items: flex-start;
+    .file_list{
+      width: 100%;
+      background-color: #F5F7FC;
+      .el-upload__tip{
+        background-color: transparent;
+      }
+    }
+  }
+
+  :deep(.el-upload-dragger){
+    background-color: #f0f4f7;
+    border:dashed #9a9c9e 1px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    gap: 10px;
+    .no_data{
+      width: 60px;
+    }
+  }
+}
+
+.el-form{
+  .el-form-item{
+    margin-bottom: 20px;
+  }
+}
+
+
+.check_group_list{
+  width: 100%;
+}
+
+.delete_item{
+  width: 96px;
+  height: 36px !important;
+  font-weight: 400;
+  font-size: 14px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.iconfont {
+  margin-right: 5px;
+
+  &.hjx-a-Folder-uploadwenjianjia-shangchuan{
+    color: #999999;
+    font-size: 24px;
+  }
+}
+
+.page_dialog{
+  :deep(.el-dialog__body){
+    min-height: 156px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  a{
+    &:hover{
+      cursor: pointer;
+    }
+  }
+}
+
+</style>
+
+<style lang="scss">
+//部分标签顶部对齐
+.top_label{
+  .el-form-item__label-wrap{
+    // border:solid red 1px;
+    align-self: flex-start;
+  }
+}
+
+
+</style>

+ 70 - 0
src/components/TestQuestionConfig.vue

@@ -0,0 +1,70 @@
+<template>
+    <Form 
+        :formItemList="formItemList" 
+        :defaultFormData="defaultFormData"
+        @formItemChange="HandleFormItmChange"
+    >
+        <template #submit>
+            <div>
+                <el-button class="button_background">确定</el-button>
+                <el-button class="button_refresh">取消</el-button>
+            </div>
+        </template>
+        
+    </Form>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+import Form from '@/baseComponents/Form.vue';
+
+const props  = defineProps({
+    formItemList:{
+        type:Array,
+        required:true
+    }
+})
+
+const defaultFormData = ref({})
+
+
+const HandleFormItmChange = (params)=>{
+    const {key,val,formData} = params
+    console.log('触发了',formData)
+}
+</script>
+
+
+
+<style lang="scss" scoped>
+
+.el-form{
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    gap:16px 0;
+
+    :deep(.el-form-item){
+        margin: 0px;
+
+        &.percent_70{
+            width: 60%;
+        }
+
+        &.percent_30{
+            width: 35%;
+        }
+        &.percent_40{
+            width:40%;
+        }
+
+        &.percent_auto{
+            flex-grow: 1;
+            margin-left: 30px;
+        }
+    }
+}
+
+</style>
+

+ 2 - 2
src/styles/common.scss

@@ -185,7 +185,7 @@ body {
   border: 1px solid  #2E64FA;
   color: #2E64FA;
   &:hover {
-    background: none;
+    background: #fff;
   }
 }
 .el-button.button_border_gray {
@@ -204,7 +204,7 @@ body {
   border: 1px solid #DCDFE6;
   color: #606266;
   &:hover {
-    background: none;
+    background: #fff;
   }
   &.cancel  {
     color: #606266;

+ 521 - 4
src/views/surveyManagement/createQuestionnaire/CreateQuestionnaire.vue

@@ -9,9 +9,139 @@
         </TopBackNav>
 
         <div class="page_jg_20"></div>
-        <div  class="page_item end_item">
-            问卷
-           
+
+        <div class="questionnaire_config" >
+            <div class="left_panel">
+                <div class="panel_section page_item">
+                    <h3 class="section_title">统计题</h3>
+                    <!-- 题型循环 -->
+                    <div class="type_list">
+                        <template v-for="typeItm in statisticQuestionType" :key="typeItm.value">
+                            <el-button class="button_refresh" 
+                                        @click="HandleQuestionTypeSelect(typeItm.value)" >
+                                <img :src="typeItm.icon" alt="">
+                                 {{ typeItm.label }}
+                            </el-button>
+                        </template>
+                    </div>
+                </div>
+
+                <div class="panel_section page_item">
+                    <h3 class="section_title">评分题</h3>
+                     <!-- 题型循环 -->
+                    <div class="type_list">
+                        <template v-for="typeItm in scoredQuestionType" :key="typeItm.value">
+                            <el-button class="button_refresh"
+                                     @click="HandleQuestionTypeSelect(typeItm.value)">
+                                <img :src="typeItm.icon" alt="">
+                                 {{ typeItm.label }}
+                            </el-button>
+                        </template>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 中间面板:编辑区域 -->
+            <div class="center_panel">
+                <div class="page_item questionnaire_base">
+                    <div class="input_group">
+                        <label class="input_label">问卷标题:</label>
+                        <input type="text" class="custom_input" placeholder="请输入问卷标题(限40字)" />
+                    </div>
+
+                    <div class="input_group">
+                        <label class="input_label">问卷说明:</label>
+                        <input type="text" class="custom_input" placeholder="请输入问卷说明(限100字)" />
+                    </div>
+                </div>
+
+                <!-- 题目添加区域 -->
+                 <div class="page_item end_item  add_question_area">
+
+                  
+                    <div v-if="!selectedQuestionType" class="add_question">
+                        <div class="add_action">+ 点击左侧添加题型</div>
+                    </div>
+
+                      <div   v-else class="question_itm_edit">
+                        <TestQuestionConfig :formItemList="formItemList"></TestQuestionConfig>
+                    </div>
+
+                    
+                    <div  v-if="0"  class="question_itm_edit">
+                        <!-- 1. 题目内容行 -->
+                        <div class="edit_row edit_row_title">
+                            <span class="row_label">1:</span>
+                            <input type="text" class="form_input" placeholder="请输入题目" />
+                        </div>
+
+                        <!-- 2. 设置选项行 (类型、必答、横排) -->
+                        <div class="edit_row edit_row_settings">
+                            <span class="row_label">类型:</span>
+                            <div class="setting_controls">
+                                <!-- 下拉选择框 -->
+                                <div class="select_wrapper">
+                                    <select class="form_select">
+                                        <option value="single">单选题</option>
+                                        <option value="multi">多选题</option>
+                                    </select>
+                                    <span class="select_arrow"></span>
+                                </div>
+
+                                <!-- 复选框组 -->
+                                <label class="checkbox_label">
+                                    <input type="checkbox" checked class="custom_checkbox" />
+                                    <span class="check_text">必答题</span>
+                                </label>
+
+                                <label class="checkbox_label">
+                                    <input type="checkbox" class="custom_checkbox" />
+                                    <span class="check_text">选项横排</span>
+                                </label>
+                            </div>
+                        </div>
+
+                        <!-- 3. 选项列表区域 -->
+                        <div class="options_list_container">
+                            <!-- 默认显示的选项 -->
+                            <div class="option_item">
+                                <input type="text" class="form_input option_input" placeholder="请输入选项内容" />
+                            </div>
+                            <div class="option_item">
+                                <input type="text" class="form_input option_input" placeholder="请输入选项内容" />
+                            </div>
+
+                            <!-- 可删除的选项 (带 X 号) -->
+                            <div class="option_item has_delete">
+                                <input type="text" class="form_input option_input" placeholder="请输入选项内容" />
+                                <span class="delete_icon">×</span>
+                            </div>
+                            <div class="option_item has_delete">
+                                <input type="text" class="form_input option_input" placeholder="请输入选项内容" />
+                                <span class="delete_icon">×</span>
+                            </div>
+                        </div>
+
+                        <!-- 4. 添加选项按钮 -->
+                        <div class="add_option_area">
+                            <button class="btn_add_option">⊕ 添加选项</button>
+                        </div>
+
+                        <!-- 5. 底部操作栏 -->
+                        <div class="action_bar">
+                            <button class="btn_primary">确定</button>
+                            <button class="btn_default">取消</button>
+                        </div>
+                    </div>
+
+                 </div>
+            </div>
+
+            <!-- 右侧面板:目录 -->
+            <div class="right_panel page_item">
+                <h3 class="section_title right_title">问卷目录 (拖动排序)</h3>
+                <div class="empty_state">暂无问卷题目</div>
+            </div>
         </div>
     </div>
 </template>
@@ -20,9 +150,26 @@
 import {onMounted, ref} from 'vue'
 
 import TopBackNav from '@/components/TopBackNav.vue';
+import {statisticQuestionType,scoredQuestionType} from './questionTypeList'
+import TestQuestionConfig from '@/components/TestQuestionConfig.vue';
+
+import { formItemList } from './form'
 
-onMounted(async ()=>{
+
+// 试卷基本信息
+const questionnaireInfo = ref({
+    title:'',
+    description:''
 })
+// 已经添加的题型
+const addedQuestionTypes = ref([])
+// 当前选中的题型
+const selectedQuestionType = ref('')
+
+// 选中题型时的处理
+const HandleQuestionTypeSelect = (type:any) => {
+    selectedQuestionType.value = type
+}
 </script>
 
 <style lang="scss" scoped>
@@ -47,7 +194,377 @@ onMounted(async ()=>{
     }
 }
 
+ /* 定义变量以匹配图片色调 */
+$border-color: #dcdfe6;
+$primary-blue: #409eff;
+
+// 定义变量以匹配图片颜色
+$text_main: #333333;
+$text_secondary: #999999;
+$bg_gray: #f5f7fa;
+$primary_color: #3b76f6; // 蓝色主色调
+
+.questionnaire_config {
+    flex-grow: 1;
+    display: flex;
+    gap: 20px;
+    
+    /* --- 左侧面板 --- */
+    .left_panel {
+        width: 200px;
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+        .panel_section {
+            padding: 16px 20px;
+
+            .type_list {
+                display: flex;
+                flex-direction: column;
+                gap: 16px;
+                                
+                .el-button {
+                    display: flex;
+                    justify-content: flex-start;
+                    align-items: center;
+                    padding:12px;
+                    margin: 0;
+                    font-size: 14px;
+                    color:#666666;
+                    img{
+                        vertical-align: middle;
+                        width: 14px;
+                        height: 14px;
+                        border-radius: 50%;
+                        margin-right: 8px;
+                        position: relative;
+                    }
+                    &:hover{
+                        color:#2E64FA;
+                        border:solid #2E64FA 1px;
+                    }
+                    
+                }
+            }
+        }
+    }
+
+    /* --- 中间面板 --- */
+    .center_panel {
+        flex: 1; /* 占据剩余空间 */
+        display: flex;
+        flex-direction: column;
+
+        .questionnaire_base{
+            display: flex;
+            flex-direction: column;
+            gap:20px;
+            border-radius:10px 10px 0 0;
+            .input_group {
+                display: flex;
+                align-items: center;
+                .input_label {
+                    text-align: right;
+                    margin-right: 15px;
+                    font-weight: 400;
+                    font-size: 14px;
+                    color: #666666;
+                }
+
+                .custom_input {
+                    flex: 1;
+                    height: 36px;
+                    padding: 0 15px;
+                    border: 1px solid $border-color;
+                    border-radius: 4px;
+                    outline: none;
+                    font-size: 14px;
+
+                    &:focus {
+                    border-color: $primary-blue;
+                    }
+
+                    &::placeholder {
+                    color: #c0c4cc;
+                    }
+                }
+            }
+
+        }
+
+        .add_question_area{
+            border-radius:0 0 10px 10px;
+
+            .add_question {
+                flex-grow: 0;
+                border: 1px dashed #C0C4CC;
+                border-radius: 4px;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                height: 240px;
+                .add_action {
+                    color: #c0c4cc;
+                    font-size: 16px;
+                    cursor: pointer;
+                }
+            }
+
+            .question_itm_edit{
+                flex-grow: 0;
+            }
+
+
+        }
+    }
+
+    /* --- 右侧面板 --- */
+    .right_panel {
+        width: 240px;
+        display: flex;
+        flex-direction: column;
+        .empty_state {
+            flex: 1;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-size: 16px;
+            color: #C0C4CC;
+        }
+    }
+
+
+    .section_title {
+        font-weight: 600;
+        font-size: 16px;
+        margin-bottom: 10px;
+    }
+}
+
+
+
+.question_itm_edit {
+    // border:solid red 1px;
+    padding: 20px;
+    background-color: #F8FAFE;
+
+    // --- 通用行布局 ---
+    .edit_row {
+        display: flex;
+        align-items: center;
+        margin-bottom: 20px;
+
+        .row_label {
+            width: 60px;
+            text-align: right;
+            margin-right: 15px;
+            color: $text_secondary;
+            font-size: 14px;
+            flex-shrink: 0;
+        }
+    }
 
+    // --- 输入框通用样式 ---
+    .form_input {
+        flex: 1;
+        height: 36px;
+        border: 1px solid $border_color;
+        border-radius: 4px;
+        padding: 0 12px;
+        font-size: 14px;
+        outline: none;
+        transition: border-color 0.2s;
 
+        &:focus {
+        border-color: $primary_color;
+        }
+
+        &::placeholder {
+        color: #c0c4cc;
+        }
+    }
+
+    // --- 设置行特定样式 ---
+    .edit_row_settings {
+        .setting_controls {
+            display: flex;
+            align-items: center;
+            gap: 20px;
+            flex: 1;
+        }
+
+        // 模拟 Select 下拉框
+        .select_wrapper {
+        position: relative;
+        width: 140px;
+
+        .form_select {
+            appearance: none;
+            width: 100%;
+            height: 36px;
+            border: 1px solid $border_color;
+            border-radius: 4px;
+            padding: 0 30px 0 12px;
+            font-size: 14px;
+            color: $text_main;
+            cursor: pointer;
+            outline: none;
+            background-color: #fff;
+
+            &:hover {
+            border-color: #c0c4cc;
+            }
+        }
+
+        // 下拉箭头
+        .select_arrow {
+            position: absolute;
+            right: 12px;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 0;
+            height: 0;
+            border-left: 5px solid transparent;
+            border-right: 5px solid transparent;
+            border-top: 6px solid #c0c4cc;
+            pointer-events: none;
+        }
+        }
+
+        // 自定义 Checkbox 样式
+        .checkbox_label {
+        display: flex;
+        align-items: center;
+        cursor: pointer;
+        user-select: none;
+
+        .custom_checkbox {
+            appearance: none;
+            width: 16px;
+            height: 16px;
+            border: 1px solid $border_color;
+            border-radius: 2px;
+            margin-right: 6px;
+            position: relative;
+            cursor: pointer;
+
+            &:checked {
+            background-color: $primary_color;
+            border-color: $primary_color;
+
+            &::after {
+                content: "";
+                position: absolute;
+                left: 5px;
+                top: 1px;
+                width: 4px;
+                height: 8px;
+                border: solid white;
+                border-width: 0 2px 2px 0;
+                transform: rotate(45deg);
+            }
+            }
+        }
+
+        .check_text {
+            font-size: 14px;
+            color: $text_main;
+        }
+        }
+    }
+
+    // --- 选项列表样式 ---
+    .options_list_container {
+        margin-left: 75px; // 对齐输入框起始位置 (Label宽度 + Margin)
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
+        margin-bottom: 20px;
+
+        .option_item {
+        position: relative;
+        display: flex;
+        align-items: center;
+
+        .option_input {
+            width: 100%;
+        }
+
+        // 删除按钮 (X)
+        .delete_icon {
+            position: absolute;
+            right: -30px;
+            font-size: 20px;
+            color: #c0c4cc;
+            cursor: pointer;
+            line-height: 1;
+
+            &:hover {
+            color: #f56c6c; // 悬停变红
+            }
+        }
+        }
+    }
+
+    // --- 添加选项按钮 ---
+    .add_option_area {
+        margin-left: 75px;
+        margin-bottom: 30px;
+
+        .btn_add_option {
+        background: none;
+        border: none;
+        color: $primary_color;
+        font-size: 14px;
+        cursor: pointer;
+        padding: 0;
+        display: flex;
+        align-items: center;
+        gap: 4px;
+
+        &:hover {
+            opacity: 0.8;
+        }
+        }
+    }
+
+    // --- 底部操作栏 ---
+    .action_bar {
+        margin-left: 75px;
+        display: flex;
+        gap: 12px;
+
+        button {
+        min-width: 80px;
+        height: 36px;
+        border-radius: 4px;
+        font-size: 14px;
+        cursor: pointer;
+        border: 1px solid transparent;
+        transition: all 0.3s;
+        }
+
+        .btn_primary {
+        background-color: $primary_color;
+        color: #ffffff;
+
+        &:hover {
+            background-color: darken($primary_color, 10%);
+        }
+        }
+
+        .btn_default {
+        background-color: #ffffff;
+        border-color: $border_color;
+        color: #606266;
+
+        &:hover {
+            color: $primary_color;
+            border-color: #c6e2ff;
+            background-color: #ecf5ff;
+        }
+        }
+    }
+}
 
 </style>

+ 84 - 0
src/views/surveyManagement/createQuestionnaire/form.ts

@@ -0,0 +1,84 @@
+import { ref } from "vue"
+
+export const formItemList = ref([
+    {
+        key: 'title',
+        label: '题目',
+        type:'input',
+        placeholder:'请输入题目',
+        rules: [],
+        hidden: false,
+        disabled: false,
+        custom:false,
+        style: {},
+        className:'percent_70'
+    },
+
+    {
+        key: 'titleScore',
+        label: '题目分值',
+        type:'input',
+        placeholder:'请输入分值',
+        rules: [],
+        style: {},
+        className:'percent_30'
+    },
+    {
+        key: 'questionType',
+        label: '类型',
+        type:'select',
+        selectList:[],
+        placeholder:'',
+        rules: [],
+        style: {},
+        className:'percent_40'
+    },
+    {
+        key: 'isRequired',
+        label: '是否必答',
+        type:'checkbox',
+        selectList:[],
+        rules: [],
+        style: {},
+        className:'percent_auto'
+    },
+   
+// 上面是固定配置
+
+    {
+        key: 'option1',
+        label: '选项1',
+        type:'input',
+        placeholder:'请输入选项内容',
+        rules: [],
+        style: {},
+        className:'percent_70'
+    },
+     {
+        key: 'titleScore1',
+        label: '题目分值',
+        type:'input',
+        placeholder:'请输入分值',
+        rules: [],
+        style: {},
+        className:'percent_30'
+    },
+     {
+        key: 'option2',
+        label: '选项2',
+        type:'input',
+        placeholder:'请输入选项内容',
+        rules: [],
+        style: {},
+        className:'percent_70'
+    },
+    {
+        key: 'titleScore2',
+        label: '题目分值',
+        type:'input',
+        placeholder:'请输入分值',
+        rules: [],
+        style: {},
+        className:'percent_30'
+    },
+])

+ 50 - 0
src/views/surveyManagement/createQuestionnaire/questionTypeList.ts

@@ -0,0 +1,50 @@
+import { ref } from "vue"
+import confirmIcon from '@/assets/icon/confirm.svg'
+
+interface QuestionTypeItm {
+    label:String,
+    value:String,
+    icon:String,
+    activeIcon:String
+}
+
+export const statisticQuestionType = ref<QuestionTypeItm[]>([
+    {
+        label:'单选题',
+        value:'0',
+        icon:confirmIcon,
+        activeIcon:confirmIcon
+    },
+    {
+        label:'多选题',
+        value:'1',
+        icon:confirmIcon,
+        activeIcon:confirmIcon
+    },
+    {
+        label:'问答题',
+        value:'2',
+        icon:confirmIcon,
+        activeIcon:confirmIcon
+    },
+])
+
+export const scoredQuestionType = ref<QuestionTypeItm[]>([
+    {
+        label:'评分单选题',
+        value:'3',
+        icon:confirmIcon,
+        activeIcon:confirmIcon
+    },
+    {
+        label:'评价题',
+        value:'4',
+        icon:confirmIcon,
+        activeIcon:confirmIcon
+    },
+])
+
+export const allQuestionType = ref([
+    ...statisticQuestionType.value,
+    ...scoredQuestionType.value,
+])