diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
new file mode 100644
index 00000000..5ea69f33
--- /dev/null
+++ b/.github/workflows/codecov.yml
@@ -0,0 +1,39 @@
+# Unit Test Coverage Report
+name: Test Coverage
+
+on:
+  pull_request:
+    branches:
+      - feature/workflow
+      - develop
+      - main
+      - releases/**
+      - feature/**
+    paths:
+      - server/**
+  workflow_dispatch:
+
+jobs:
+  build:
+    name: Coverage
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup node
+        uses: actions/setup-node@v4
+        with:
+          node-version: 20
+
+      - name: Install dependencies
+        run: cd server && npm install
+
+      - name: Run tests and collect coverage
+        run: cd server && npm run test:cov
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v4
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/server-lint.yml b/.github/workflows/server-lint.yml
new file mode 100644
index 00000000..dec0c544
--- /dev/null
+++ b/.github/workflows/server-lint.yml
@@ -0,0 +1,34 @@
+# Lint
+name: Server Lint
+
+on:
+  pull_request:
+    branches:
+      - feature/workflow
+      - develop
+      - main
+      - releases/**
+      - feature/**
+    paths:
+      - server/**
+  workflow_dispatch:
+
+jobs:
+  build:
+    name: Lint
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup node
+        uses: actions/setup-node@v4
+        with:
+          node-version: 20
+
+      - name: Install dependencies
+        run: cd server && npm install
+
+      - name: Lint
+        run: cd server && npm run lint
diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml
new file mode 100644
index 00000000..f0963789
--- /dev/null
+++ b/.github/workflows/web-lint.yml
@@ -0,0 +1,34 @@
+# Lint
+name: Web Lint
+
+on:
+  pull_request:
+    branches:
+      - feature/workflow
+      - develop
+      - main
+      - releases/**
+      - feature/**
+    paths:
+      - web/**
+  workflow_dispatch:
+
+jobs:
+  build:
+    name: Lint
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup node
+        uses: actions/setup-node@v4
+        with:
+          node-version: 20
+
+      - name: Install dependencies
+        run: cd web && npm install
+
+      - name: Lint
+        run: cd web && npm run lint
diff --git a/web/package.json b/web/package.json
index 063fb000..1b879af2 100644
--- a/web/package.json
+++ b/web/package.json
@@ -4,8 +4,8 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "serve": "vite",
-    "dev": "vite",
+    "serve": "npm run dev",
+    "dev": "vite --open",
     "build": "run-p type-check \"build-only {@}\" --",
     "preview": "vite preview",
     "build-only": "vite build",
@@ -45,6 +45,7 @@
     "@vue/tsconfig": "^0.5.1",
     "eslint": "^8.49.0",
     "eslint-plugin-vue": "^9.17.0",
+    "husky": "^9.0.11",
     "npm-run-all2": "^6.1.1",
     "prettier": "^3.0.3",
     "sass": "^1.72.0",
@@ -56,8 +57,19 @@
     "vite-plugin-virtual-mpa": "^1.11.0",
     "vue-tsc": "^1.8.27"
   },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "src/**/*.{.vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts}": [
+      "prettier --write",
+      "eslint --fix"
+    ]
+  },
   "engines": {
-    "node": ">=14.21.0",
-    "npm": ">=6.14.17"
+    "node": ">=18.0.0",
+    "npm": ">=8.6.0"
   }
 }
diff --git a/web/src/management/config/dnd.ts b/web/src/management/config/dnd.ts
new file mode 100644
index 00000000..11039a41
--- /dev/null
+++ b/web/src/management/config/dnd.ts
@@ -0,0 +1 @@
+export const DND_GROUP = 'question'
\ No newline at end of file
diff --git a/web/src/management/pages/edit/components/MaterialGroup.vue b/web/src/management/pages/edit/components/MaterialGroup.vue
index e581ca5c..c3edfd9e 100644
--- a/web/src/management/pages/edit/components/MaterialGroup.vue
+++ b/web/src/management/pages/edit/components/MaterialGroup.vue
@@ -1,8 +1,9 @@
 <template>
   <draggable
-    :list="renderData"
+    v-model="renderData"
     handle=".question-wrapper.isSelected"
     filter=".question-wrapper.isSelected .question.isSelected"
+    :group="DND_GROUP"
     :onEnd="checkEnd"
     :move="checkMove"
     itemKey="field"
@@ -34,10 +35,12 @@
 
 <script>
 import { computed, defineComponent, ref, getCurrentInstance } from 'vue'
+import { useStore } from 'vuex'
 import QuestionContainerB from '@/materials/questions/QuestionContainerB'
 import QuestionWrapper from '@/management/pages/edit/components/QuestionWrapper.vue'
 import draggable from 'vuedraggable'
 import { filterQuestionPreviewData } from '@/management/utils/index'
+import { DND_GROUP } from '@/management/config/dnd'
 
 export default defineComponent({
   components: {
@@ -58,8 +61,15 @@ export default defineComponent({
     }
   },
   setup(props, { emit }) {
-    const renderData = computed(() => {
-      return filterQuestionPreviewData(props.questionDataList)
+    const store = useStore()
+    
+    const renderData = computed({
+      get () {
+        return filterQuestionPreviewData(props.questionDataList)
+      },
+      set (questionDataList) {
+        store.commit('edit/setQuestionDataList', questionDataList)
+      }
     })
     const handleSelect = (index) => {
       emit('select', index)
@@ -89,6 +99,7 @@ export default defineComponent({
     }
 
     return {
+      DND_GROUP,
       renderData,
       handleSelect,
       handleChangeSeq,
diff --git a/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue b/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue
index 652feb7f..e74100dd 100644
--- a/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue
+++ b/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue
@@ -6,58 +6,91 @@
       :name="index"
       :key="index"
     >
-      <div class="questiontype-list">
-        <el-popover
-          v-for="(item, index) in item.questionList"
-          :key="item.type"
-          placement="right"
-          trigger="hover"
-          :popper-class="'qtype-popper-' + (index % 3)"
-          :popper-style="{ width: '369px' }"
+        <draggable
+          class="questiontype-list"
+          :list="item.questionList"
+          :group="{ name: DND_GROUP, pull: 'clone', put: false }"
+          :clone="getNewQuestion"
+          item-key="path"
         >
-          <img :src="item.snapshot" width="345px" />
-          <template #reference>
-            <div :key="item.type" class="qtopic-item" @click="onQuestionType({ type: item.type })">
-              <i class="iconfont" :class="['icon-' + item.icon]"></i>
-              <p class="text">{{ item.title }}</p>
-            </div>
+          <template #item="{ element }">
+              <div
+                :key="element.type"
+                class="qtopic-item"
+                :id="'qtopic' + element.type"
+                @click="onQuestionType({ type: element.type })"
+                @mouseenter="showPreview(element, 'qtopic' + element.type)"
+                @mouseleave="isShowPreviewImage = false"
+                @mousedown="isShowPreviewImage = false"
+              >
+                <i class="iconfont" :class="['icon-' + element.icon]"></i>
+                <p class="text">{{ element.title }}</p>
+              </div>
           </template>
-        </el-popover>
-      </div>
+          
+        </draggable>
     </el-collapse-item>
+    <Teleport to="body">
+      <div class="preview-popover" v-show="isShowPreviewImage" :style="{ top: previewTop + 'px'}">
+          <img :src="previewImg" class="preview-image"/>
+          <span class="preview-arrow"></span>
+      </div>
+    </Teleport>
   </el-collapse>
 </template>
 
 <script setup>
 import questionLoader from '@/materials/questions/questionLoader'
+import draggable from 'vuedraggable'
+import { DND_GROUP } from '@/management/config/dnd'
 
 import questionMenuConfig, { questionTypeList } from '@/management/config/questionMenuConfig'
 import { getQuestionByType } from '@/management/utils/index'
 import { useStore } from 'vuex'
-import { get as _get } from 'lodash-es'
+import { get as _get, isNumber as _isNumber } from 'lodash-es'
 import { computed, ref } from 'vue'
 
-const activeNames = ref([0, 1])
-
 const store = useStore()
+
+const activeNames = ref([0, 1])
+const previewImg = ref('')
+const isShowPreviewImage = ref(false)
+const previewTop = ref(0)
 const questionDataList = computed(() => _get(store, 'state.edit.schema.questionDataList'))
+const newQuestionIndex = computed(() => {
+  const currentEditOne = _get(store, 'state.edit.currentEditOne')
+  const index = _isNumber(currentEditOne) ? currentEditOne + 1 : questionDataList.value.length
+  return index
+})
 
 questionLoader.init({
   typeList: questionTypeList.map((item) => item.type)
 })
 
-const onQuestionType = ({ type }) => {
+const getNewQuestion = ({ type }) => {
   const fields = questionDataList.value.map((item) => item.field)
-  const currentEditOne = _get(store, 'state.edit.currentEditOne')
-  const index =
-    typeof currentEditOne === 'number' ? currentEditOne + 1 : questionDataList.value.length
   const newQuestion = getQuestionByType(type, fields)
-  newQuestion.title = newQuestion.title = `标题${index + 1}`
+  newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}`
   if (type === 'vote') {
     newQuestion.innerType = 'radio'
   }
-  store.dispatch('edit/addQuestion', { question: newQuestion, index })
-  store.commit('edit/setCurrentEditOne', index)
+  return newQuestion
+}
+
+const onQuestionType = ({ type }) => {
+  const newQuestion = getNewQuestion({ type })  
+  store.dispatch('edit/addQuestion', { question: newQuestion, index: newQuestionIndex.value })
+  store.commit('edit/setCurrentEditOne', newQuestionIndex.value)
+}
+
+const showPreview = ({ snapshot }, id) => {
+  previewImg.value = snapshot
+  
+  const dragEl = document.getElementById(id)
+  const { top, height } = dragEl.getBoundingClientRect()
+  previewTop.value = top + height / 2
+
+  isShowPreviewImage.value = true
 }
 </script>
 
@@ -103,9 +136,10 @@ const onQuestionType = ({ type }) => {
       background-color: $primary-color-light;
       border: 1px solid $primary-color;
     }
-
+    
     .text {
       font-size: 12px;
+      user-select: none;
     }
   }
 
@@ -128,4 +162,64 @@ const onQuestionType = ({ type }) => {
 .qtype-popper-2 {
   transform: translateX(30px);
 }
+// 设置拖拽到编辑区的样式
+.box .qtopic-item {
+  height: 2px;
+  width: 100%;
+  background-color: var(--primary-color);
+  * {
+    display: none;
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+.preview-popover {
+  position: fixed;
+  left: 390px;
+  z-index: 9;
+  width: 371px;
+  padding: 12px;
+  background: white;
+  border: 1px solid var(--el-border-color-light);
+  box-shadow: var(--el-box-shadow-light);
+  transform: translateY(-50%);
+  animation: fadeIn 100ms linear forwards;
+
+  .preview-image {
+    width: 100%;
+    object-fit: contain;
+  }
+
+  .preview-arrow {
+    position: absolute;
+    top: 50%;
+    left: -6px;
+    height: 10px;
+    width: 10px;
+    transform: translateX(-50%);
+    background: var(--el-border-color-light);
+    z-index: -1;
+    transform: rotate(-45deg);
+
+    &::before {
+      position: absolute;
+      content: "";
+      height: 10px;
+      width: 10px;
+      border: 1px solid var(--el-border-color-light);
+      background: #ffffff;
+      border-bottom-color: transparent;
+      border-right-color: transparent;
+    }
+
+  }
+}
 </style>
diff --git a/web/src/management/store/edit/mutations.js b/web/src/management/store/edit/mutations.js
index fab89077..412b87a9 100644
--- a/web/src/management/store/edit/mutations.js
+++ b/web/src/management/store/edit/mutations.js
@@ -61,5 +61,8 @@ export default {
     Object.keys(presets).forEach((key) => {
       _set(state.schema, key, presets[key])
     })
+  },
+  setQuestionDataList(state, data) {
+    state.schema.questionDataList = data
   }
 }
diff --git a/web/src/render/App.vue b/web/src/render/App.vue
index 6fba4f81..185c00c8 100644
--- a/web/src/render/App.vue
+++ b/web/src/render/App.vue
@@ -1,11 +1,21 @@
 <template>
   <div id="app">
-    <Component v-if="$store.state.router" :is="$store.state.router"></Component>
-    <LogoIcon v-if="!['successPage', 'indexPage'].includes($store.state.router)" />
+    <Component
+      v-if="store.state.router"
+      :is="
+        components[
+          upperFirst(store.state.router) as 'IndexPage' | 'EmptyPage' | 'ErrorPage' | 'SuccessPage'
+        ]
+      "
+    >
+    </Component>
+    <LogoIcon v-if="!['successPage', 'indexPage'].includes(store.state.router)" />
   </div>
 </template>
+<script setup lang="ts">
+import { computed, watch, onMounted } from 'vue'
+import { useStore } from 'vuex'
 
-<script>
 import { getPublishedSurveyInfo } from './api/survey'
 import useCommandComponent from './hooks/useCommandComponent'
 
@@ -16,89 +26,82 @@ import SuccessPage from './pages/SuccessPage.vue'
 import AlertDialog from './components/AlertDialog.vue'
 
 import LogoIcon from './components/LogoIcon.vue'
-import { get as _get } from 'lodash-es'
-import { initRuleEngine } from '@/render/hooks/useRuleEngine.js'
-
-export default {
-  name: 'App',
-  components: {
-    EmptyPage,
-    IndexPage,
-    ErrorPage,
-    SuccessPage,
-    LogoIcon
-  },
-  data() {
-    return {}
-  },
-  computed: {
-    skinConf() {
-      return _get(this.$store, 'state.skinConf', {})
-    }
-  },
-  watch: {
-    skinConf(value) {
-      this.setSkin(value)
-    }
-  },
-  async created() {
-    this.init()
-    this.alert = useCommandComponent(AlertDialog)
-  },
-  beforeCreate() {},
-  methods: {
-    async init() {
-      const surveyPath = location.pathname.split('/').pop()
-      if (!surveyPath) {
-        this.$store.commit('setRouter', 'EmptyPage')
-      } else {
-        try {
-          const res = await getPublishedSurveyInfo({ surveyPath })
-          if (res.code === 200) {
-            const data = res.data
-            const { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf, logicConf } = data.code
-            document.title = data.title
-            const questionData = {
-              bannerConf,
-              baseConf,
-              bottomConf,
-              dataConf,
-              skinConf,
-              submitConf
-            }
-            this.setSkin(skinConf)
-            this.$store.commit('setSurveyPath', surveyPath)
-            this.$store.dispatch('init', questionData)
-            initRuleEngine(logicConf?.showLogicConf)
-            this.$store.dispatch('getEncryptInfo')
-          } else {
-            throw new Error(res.errmsg)
-          }
-        } catch (error) {
-          console.log(error)
-          this.alert({
-            title: error.message || '获取问卷失败'
-          })
-        }
-      }
-    },
-    setSkin(skinConf) {
-      const { themeConf, backgroundConf, contentConf } = skinConf
-      const root = document.documentElement
-      if (themeConf?.color) {
-        root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
-      }
-      if (backgroundConf?.color) {
-        root.style.setProperty('--primary-background-color', backgroundConf?.color) // 设置背景颜色
-      }
-      if (contentConf?.opacity.toString()) {
-        root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
+import { get as _get, upperFirst } from 'lodash-es'
+
+const store = useStore()
+const skinConf = computed(() => _get(store, 'state.skinConf', {}))
+const components = {
+  EmptyPage,
+  IndexPage,
+  ErrorPage,
+  SuccessPage
+}
+
+const updateSkinConfig = (value: any) => {
+  const root = document.documentElement
+  const { themeConf, backgroundConf, contentConf } = value
+
+  if (themeConf?.color) {
+    // 设置主题颜色
+    root.style.setProperty('--primary-color', themeConf?.color)
+  }
+
+  if (backgroundConf?.color) {
+    // 设置背景颜色
+    root.style.setProperty('--primary-background-color', backgroundConf?.color)
+  }
+
+  if (contentConf?.opacity.toString()) {
+    // 设置全局透明度
+    root.style.setProperty('--opacity', `${parseInt(contentConf.opacity) / 100}`)
+  }
+}
+
+watch(skinConf, (value) => {
+  updateSkinConfig(value)
+})
+
+onMounted(async () => {
+  const surveyPath = location.pathname.split('/').pop()
+
+  if (!surveyPath) {
+    store.commit('setRouter', 'EmptyPage')
+    return
+  }
+
+  const alert = useCommandComponent(AlertDialog)
+
+  try {
+    const res: any = await getPublishedSurveyInfo({ surveyPath })
+
+    if (res.code === 200) {
+      const data = res.data
+      const { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf } = data.code
+      const questionData = {
+        bannerConf,
+        baseConf,
+        bottomConf,
+        dataConf,
+        skinConf,
+        submitConf
       }
+
+      document.title = data.title
+
+      updateSkinConfig(skinConf)
+
+      store.commit('setSurveyPath', surveyPath)
+      store.dispatch('init', questionData)
+      store.dispatch('getEncryptInfo')
+    } else {
+      throw new Error(res.errmsg)
     }
+  } catch (error: any) {
+    console.log(error)
+    alert({ title: error.message || '获取问卷失败' })
   }
-}
+})
 </script>
-
 <style lang="scss">
 @import url('./styles/icon.scss');
 @import url('../materials/questions/common/css/icon.scss');
diff --git a/web/src/render/main.js b/web/src/render/main.js
index cd5b7278..ecc3e906 100644
--- a/web/src/render/main.js
+++ b/web/src/render/main.js
@@ -3,7 +3,6 @@ import App from './App.vue'
 import EventBus from './utils/eventbus'
 
 import store from './store'
-import './styles/reset.scss'
 
 const app = createApp(App)
 
diff --git a/web/src/render/pages/ErrorPage.vue b/web/src/render/pages/ErrorPage.vue
index c7fb81f5..30c5a30d 100755
--- a/web/src/render/pages/ErrorPage.vue
+++ b/web/src/render/pages/ErrorPage.vue
@@ -8,29 +8,23 @@
     </div>
   </div>
 </template>
-<script>
-export default {
-  name: 'errorPage',
-  data() {
-    return {
-      imageMap: {
-        overTime: '/imgs/icons/overtime.webp'
-      }
-    }
-  },
-  computed: {
-    errorImageUrl() {
-      return this.imageMap[this.errorType] || this.imageMap.default
-    },
-    errorType() {
-      return this.$store.state?.errorInfo?.errorType
-    },
-    errorMsg() {
-      return this.$store.state?.errorInfo?.errorMsg
-    }
-  },
-  mounted() {}
-}
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useStore } from 'vuex'
+
+const store = useStore()
+
+const errorImageUrl = computed(() => {
+  const errorType = store.state?.errorInfo?.errorType
+  const imageMap = {
+    overTime: '/imgs/icons/overtime.webp',
+    default: '/imgs/icons/error.webp'
+  }
+
+  return imageMap[errorType as 'overTime'] || imageMap.default
+})
+
+const errorMsg = computed(() => store.state?.errorInfo?.errorMsg)
 </script>
 <style lang="scss" scoped>
 .result-page-wrap {
diff --git a/web/src/render/pages/IndexPage.vue b/web/src/render/pages/IndexPage.vue
index 02450124..6878dbca 100644
--- a/web/src/render/pages/IndexPage.vue
+++ b/web/src/render/pages/IndexPage.vue
@@ -1,19 +1,21 @@
 <template>
   <div class="index">
-    <ProgressBar />
-    <div class="wrapper" ref="box">
+    <progressBar />
+    <div class="wrapper" ref="boxRef">
       <HeaderSetter></HeaderSetter>
       <div class="content">
         <MainTitle></MainTitle>
-        <MainRenderer ref="main"></MainRenderer>
-        <Submit :validate="validate" :renderData="renderData" @submit="onSubmit"></Submit>
+        <MainRenderer ref="mainRef"></MainRenderer>
+        <submit :validate="validate" :renderData="renderData" @submit="handleSubmit"></submit>
         <LogoIcon />
       </div>
     </div>
   </div>
 </template>
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useStore } from 'vuex'
 
-<script>
 import HeaderSetter from '../components/HeaderSetter.vue'
 import MainTitle from '../components/MainTitle.vue'
 import Submit from '../components/SubmitSetter.vue'
@@ -27,117 +29,102 @@ import { submitForm } from '../api/survey'
 import encrypt from '../utils/encrypt'
 
 import useCommandComponent from '../hooks/useCommandComponent'
-import { cloneDeep } from 'lodash-es'
-
-export default {
-  name: 'indexPage',
-  props: {
-    questionInfo: {
-      type: Object,
-      default: () => ({})
-    },
-    isMobile: {
-      type: Boolean,
-      default: false
+
+interface Props {
+  questionInfo?: any
+  isMobile?: boolean
+}
+
+withDefaults(defineProps<Props>(), {
+  questionInfo: {},
+  isMobile: false
+})
+
+const mainRef = ref<any>()
+const boxRef = ref<HTMLElement>()
+
+const alert = useCommandComponent(AlertDialog)
+const confirm = useCommandComponent(ConfirmDialog)
+
+const store = useStore()
+
+const renderData = computed(() => store.getters.renderData)
+
+const validate = (cbk: (v: boolean) => void) => {
+  const index = 0
+  mainRef.value.$refs.formGroup[index].validate(cbk)
+}
+
+const normalizationRequestBody = () => {
+  const enterTime = store.state.enterTime
+  const encryptInfo = store.state.encryptInfo
+  const formModel = store.getters.formModel
+  const surveyPath = store.state.surveyPath
+
+  const result: any = {
+    surveyPath,
+    data: JSON.stringify(formModel),
+    difTime: Date.now() - enterTime,
+    clientTime: Date.now()
+  }
+
+  if (encryptInfo?.encryptType) {
+    result.encryptType = encryptInfo?.encryptType
+    result.data = encrypt[result.encryptType as 'rsa']({
+      data: result.data,
+      secretKey: encryptInfo?.data?.secretKey
+    })
+    if (encryptInfo?.data?.sessionId) {
+      result.sessionId = encryptInfo.data.sessionId
     }
-  },
-  components: {
-    HeaderSetter,
-    MainTitle,
-    Submit,
-    MainRenderer,
-    ProgressBar,
-    LogoIcon
-  },
-  computed: {
-    confirmAgain() {
-      return this.$store.state.submitConf.confirmAgain
-    },
-    surveyPath() {
-      return this.$store.state.surveyPath
-    },
-    renderData() {
-      return this.$store.getters.renderData
-    },
-    encryptInfo() {
-      return this.$store.state.encryptInfo
+  } else {
+    result.data = JSON.stringify(result.data)
+  }
+
+  return result
+}
+
+const submitSurver = async () => {
+  try {
+    const params = normalizationRequestBody()
+    console.log(params)
+    const res: any = await submitForm(params)
+    if (res.code === 200) {
+      store.commit('setRouter', 'successPage')
+    } else {
+      alert({
+        title: res.errmsg || '提交失败'
+      })
     }
-  },
-  created() {
-    this.alert = useCommandComponent(AlertDialog)
-    this.confirm = useCommandComponent(ConfirmDialog)
-  },
-  methods: {
-    validate(cbk) {
-      const index = 0
-      this.$refs.main.$refs.formGroup[index].validate(cbk)
-    },
-    onSubmit() {
-      const { again_text, is_again } = this.confirmAgain
-      if (is_again) {
-        this.confirm({
-          title: again_text,
-          onConfirm: async () => {
-            try {
-              await this.submitForm()
-            } catch (error) {
-              console.error(error)
-            } finally {
-              this.confirm.close()
-            }
-          }
-        })
-      } else {
-        this.submitForm()
-      }
-    },
-    getSubmitData() {
-      const formValues = cloneDeep(this.$store.state.formValues)
-
-      const result = {
-        surveyPath: this.surveyPath,
-        data: JSON.stringify(formValues),
-        difTime: Date.now() - this.$store.state.enterTime,
-        clientTime: Date.now()
-      }
-      if (this.encryptInfo?.encryptType) {
-        result.encryptType = this.encryptInfo?.encryptType
-        result.data = encrypt[result.encryptType]({
-          data: result.data,
-          secretKey: this.encryptInfo?.data?.secretKey
-        })
-        if (this.encryptInfo?.data?.sessionId) {
-          result.sessionId = this.encryptInfo.data.sessionId
-        }
-      } else {
-        result.data = JSON.stringify(result.data)
-      }
+  } catch (error) {
+    console.log(error)
+  }
+}
 
-      return result
-    },
-    async submitForm() {
-      try {
-        const submitData = this.getSubmitData()
-        
-        const res = await submitForm(submitData)
-        if (res.code === 200) {
-          this.$store.commit('setRouter', 'successPage')
-        } else {
-          this.alert({
-            title: res.errmsg || '提交失败'
-          })
+const handleSubmit = () => {
+  const confirmAgain = store.state.submitConf.confirmAgain
+  const { again_text, is_again } = confirmAgain
+
+  if (is_again) {
+    confirm({
+      title: again_text,
+      onConfirm: async () => {
+        try {
+          submitSurver()
+        } catch (error) {
+          console.log(error)
+        } finally {
+          confirm.close()
         }
-      } catch (error) {
-        console.log(error)
       }
-    }
+    })
+  } else {
+    submitSurver()
   }
 }
 </script>
-
 <style scoped lang="scss">
 .index {
-  // padding-bottom: 0.8rem;
   min-height: 100%;
   .wrapper {
     min-height: 100%;
diff --git a/web/src/render/pages/SuccessPage.vue b/web/src/render/pages/SuccessPage.vue
index 171cdbfb..587eb3e7 100644
--- a/web/src/render/pages/SuccessPage.vue
+++ b/web/src/render/pages/SuccessPage.vue
@@ -9,20 +9,17 @@
     </div>
   </div>
 </template>
-<script>
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useStore } from 'vuex'
 import LogoIcon from '../components/LogoIcon.vue'
-export default {
-  name: 'resultPage',
-  components: { LogoIcon },
-  computed: {
-    submitConf() {
-      return this.$store?.state?.submitConf || {}
-    },
-    successMsg() {
-      return this.submitConf?.msgContent?.msg_200 || '提交成功'
-    }
-  }
-}
+
+const store = useStore()
+
+const successMsg = computed(() => {
+  const msgContent = store.state?.submitConf?.msgContent || {}
+  return msgContent?.msg_200 || '提交成功'
+})
 </script>
 <style lang="scss" scoped>
 @import '@/render/styles/variable.scss';