WXL (wul)
2025-08-27 6d1c826ab6bb3f2cc7cd598a8317b40e403d898f
测试完成
已添加2个文件
已修改6个文件
996 ■■■■■ 文件已修改
src/api/AiCentre/index.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/AiCentre/satisfaction.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/tasklist/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/knowledge/questionbank/particulars/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/knowledge/questionnaire/compilequer/index.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/satisfaction.vue 875 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/AiCentre/index.js
@@ -10,3 +10,5 @@
export * from './patientexternal'
export * from './EChartsdata'
export * from './satisfactionse'
export * from './satisfaction'
src/api/AiCentre/satisfaction.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
import request from "@/utils/request";
// å¤–链获取随访
export function getScriptByCondition(data) {
  return request({
    url: "/smartor/servicetask/getScriptByCondition",
    method: "post",
    data: data
  });
}
// ç¼“存问卷
export function saveMYDQuestionAnswer(data) {
  return request({
    url: "/smartor/subtaskAnswer/saveMYDQuestionAnswer",
    method: "post",
    data: data
  });
}
src/permission.js
@@ -1,55 +1,67 @@
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "@/utils/auth";
import { isRelogin } from "@/utils/request";
NProgress.configure({ showSpinner: false })
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/auth-redirect', '/bind', '/register','/wt','/xj','/sf', '/outsideChain','/outsideChainwt','/outsideChainxj']
const whiteList = [
  "/login",
  "/auth-redirect",
  "/bind",
  "/register",
  "/wt",
  "/xj",
  "/sf",
  "/satisfaction",
];
router.beforeEach((to, from, next) => {
  NProgress.start()
  NProgress.start();
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    to.meta.title && store.dispatch("settings/setTitle", to.meta.title);
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    if (to.path === "/login") {
      next({ path: "/" });
      NProgress.done();
    } else {
      if (store.getters.roles.length === 0) {
        isRelogin.show = true
        isRelogin.show = true;
        // åˆ¤æ–­å½“前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          isRelogin.show = false
          store.dispatch('GenerateRoutes').then(accessRoutes => {
        store
          .dispatch("GetInfo")
          .then(() => {
            isRelogin.show = false;
            store.dispatch("GenerateRoutes").then((accessRoutes) => {
            // æ ¹æ®roles权限生成可访问的路由表
            router.addRoutes(accessRoutes) // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
            next({ ...to, replace: true }) // hack方法 ç¡®ä¿addRoutes已完成
              router.addRoutes(accessRoutes); // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
              next({ ...to, replace: true }); // hack方法 ç¡®ä¿addRoutes已完成
            });
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
          .catch((err) => {
            store.dispatch("LogOut").then(() => {
              Message.error(err);
              next({ path: "/" });
            });
          });
      } else {
        next()
        next();
      }
    }
  } else {
    // æ²¡æœ‰token
    if (whiteList.indexOf(to.path) !== -1) {
      // åœ¨å…ç™»å½•白名单,直接进入
      next()
      next();
    } else {
      next(`/login?redirect=${to.fullPath}`) // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页
      NProgress.done()
      next(`/login?redirect=${to.fullPath}`); // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页
      NProgress.done();
    }
  }
})
});
router.afterEach(() => {
  NProgress.done()
})
  NProgress.done();
});
src/router/index.js
@@ -62,6 +62,11 @@
    hidden: true
  },
  {
    path: '/satisfaction',
    component: () => import('@/views/satisfaction'),
    hidden: true
  },
  {
    path: '/outsideChain',
    component: () => import('@/views/outsideChain'),
    hidden: true
src/views/followvisit/tasklist/index.vue
@@ -475,7 +475,7 @@
        },
      ],
      taskoptions: store.getters.tasktypes,
      tasktopic: "2", //新增类型
      tasktopic: 2, //新增类型
      activname: "",
      value: [],
      list: [],
src/views/knowledge/questionbank/particulars/index.vue
@@ -1092,10 +1092,10 @@
        )
        .then(() => {
          console.log(row);
          this.topicobj.scriptTopic = row.targetname;
          // this.topicobj.scriptTopic = row.targetname;
          this.topicobj.valueType = row.valueType;
          this.topicobj.scriptType = row.scriptType;
          this.topicobj.scriptContent = row.targetdesc;
          // this.topicobj.scriptContent = row.targetdesc;
          this.topicobj.targetid = row.id;
          this.topicobj.targetname = row.targetname;
          this.topicobj.svyLibScriptOptions = [];
src/views/knowledge/questionnaire/compilequer/index.vue
@@ -2388,7 +2388,10 @@
  display: flex;
  .presentation-left {
    width: 45%;
    // height: 500px;
    max-height: 80vh;
    padding: 0 20px;
    font-size: 18px;
    overflow: auto;
    .button-textxg {
      color: #024df0;
    }
@@ -2408,7 +2411,7 @@
  }
  .presentation-right {
    width: 55%;
    max-height: 688px;
    max-height: 80vh;
    padding: 0 20px;
    font-size: 18px;
    overflow: auto;
src/views/satisfaction.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,875 @@
<template>
  <div class="questionnaire" :class="'survey-type-' + surveyType">
    <div class="CONTENT" v-if="!accomplish">
      <div class="preview-left">
        <div class="toptitle">
          <div class="title">{{ surveyTitle }}</div>
          <div style="font-size: 22px; margin-bottom: 20px; line-height: 1.5">
            {{ surveyDescription }}
          </div>
        </div>
        <el-divider></el-divider>
        <!-- å•选题 -->
        <div
          class="topic-dev"
          v-for="(item, index) in questionList"
          :key="item.scriptId"
        >
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 1">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent }}
                <span style="color: #3ba2f7">[单选]</span></span
              >
            </div>
            <div class="dev-xx">
              <el-radio-group
                class="custom-radio"
                v-model="item.scriptResult"
                @change="handleOptionChange($event, index, item)"
              >
                <el-radio
                  border
                  v-for="(option, optIndex) in item.svyLibTemplateTargetoptions"
                  :class="
                    option.isabnormal &&
                    item.scriptResult == option.optioncontent
                      ? 'red-star'
                      : ''
                  "
                  :key="optIndex"
                  :label="option.optioncontent"
                  >{{ option.optioncontent }}</el-radio
                >
              </el-radio-group>
            </div>
            <div v-show="item.prompt">
              <el-alert :title="item.prompt" type="warning"> </el-alert>
            </div>
          </div>
          <!-- å¤šé€‰é¢˜ -->
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 2">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent }}
                <span style="color: #3ba2f7">[多选]</span></span
              >
            </div>
            <div class="dev-xx">
              <el-checkbox-group
                class="custom-radio"
                v-model="item.scriptResult"
              >
                <el-checkbox
                  border
                  @change="$forceUpdate()"
                  v-for="(option, optIndex) in item.svyLibTemplateTargetoptions"
                  :key="optIndex"
                  :label="option.optioncontent"
                >
                  {{ option.optioncontent }}
                </el-checkbox>
              </el-checkbox-group>
            </div>
            <div v-show="item.prompt && item.scriptResult[0]">
              <el-alert :title="item.prompt" type="warning"> </el-alert>
            </div>
          </div>
          <!-- å¡«ç©ºé¢˜ -->
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 4">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent
                }}<span style="color: #3ba2f7">[问答]</span></span
              >
            </div>
            <div class="dev-xx">
              <el-input
                type="textarea"
                :rows="3"
                placeholder="请输入您的意见或建议"
                v-model="item.scriptResult"
                clearable
              >
              </el-input>
            </div>
          </div>
        </div>
        <div class="bottom-fixed">
          <el-button
            type="primary"
            style="width: 80%; font-size: 20px"
            @click="submitSurvey"
            >提交问卷</el-button
          >
        </div>
      </div>
    </div>
    <div class="CONTENT" v-else>
      <div class="preview-lefts">
        <div class="completion-message">
          <div class="thank-you">{{ this.accomplish||'感谢您的配合!' }}</div>
          <div class="feedback-message">{{ completionMessage }}</div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import {
  getScriptByCondition,
  saveMYDQuestionAnswer,
} from "@/api/AiCentre/index";
export default {
  data() {
    return {
      surveyType: null, // 'outpatient', 'inpatient'
      surveyTitle: "",
      surveyDescription: "",
      questionList: [],
      completionMessage: "",
      accomplish: false,
      // åŠ å¯†åŽçš„å‚æ•°
      encryptedParams: {
        param1: "",
        param2: "",
        param3: "",
        param4: "",
      },
      // æµ‹è¯•数据
      testData: {
        1: {
          title: "门诊满意度调查",
          description:
            "亲爱的患者,感谢您选择我们的医疗服务。为了不断提升服务质量,请您花几分钟时间填写此问卷。",
          questions: [
            {
              scriptId: 1,
              scriptType: 1,
              scriptContent: "您对门诊医生的诊疗水平是否满意?",
              scriptResult: null,
              svyLibTemplateTargetoptions: [
                { optioncontent: "非常满意", value: "5", isabnormal: false },
                { optioncontent: "满意", value: "4", isabnormal: false },
                { optioncontent: "一般", value: "3", isabnormal: true },
                { optioncontent: "不满意", value: "2", isabnormal: true },
                { optioncontent: "非常不满意", value: "1", isabnormal: true },
              ],
            },
            {
              scriptId: 2,
              scriptType: 1,
              scriptContent: "您对门诊护士的服务态度是否满意?",
              scriptResult: null,
              svyLibTemplateTargetoptions: [
                { optioncontent: "非常满意", value: "5", isabnormal: false },
                { optioncontent: "满意", value: "4", isabnormal: false },
                { optioncontent: "一般", value: "3", isabnormal: true },
                { optioncontent: "不满意", value: "2", isabnormal: true },
                { optioncontent: "非常不满意", value: "1", isabnormal: true },
              ],
            },
            {
              scriptId: 3,
              scriptType: 2,
              scriptContent: "您认为门诊哪些方面需要改进?(可多选)",
              scriptResult: [],
              svyLibTemplateTargetoptions: [
                {
                  optioncontent: "排队等候时间",
                  value: "waiting_time",
                  isabnormal: false,
                },
                {
                  optioncontent: "医生沟通方式",
                  value: "communication",
                  isabnormal: false,
                },
                {
                  optioncontent: "就诊环境",
                  value: "environment",
                  isabnormal: false,
                },
                {
                  optioncontent: "医疗设备",
                  value: "equipment",
                  isabnormal: false,
                },
                { optioncontent: "其他", value: "other", isabnormal: false },
              ],
            },
            {
              scriptId: 4,
              scriptType: 4,
              scriptContent: "您对门诊服务还有什么其他建议?",
              scriptResult: null,
            },
          ],
          completionMessage:
            "感谢您宝贵的意见!我们将不断改进门诊服务质量,为您提供更好的医疗服务体验。",
        },
        2: {
          title: "住院满意度调查",
          description:
            "亲爱的患者及家属,感谢您选择在我院住院治疗。为了提升住院服务质量,请您填写此问卷。",
          questions: [
            {
              scriptId: 1,
              scriptType: 1,
              scriptContent: "您对住院期间医生的诊疗水平是否满意?",
              scriptResult: null,
              svyLibTemplateTargetoptions: [
                { optioncontent: "非常满意", value: "5", isabnormal: false },
                { optioncontent: "满意", value: "4", isabnormal: false },
                { optioncontent: "一般", value: "3", isabnormal: true },
                { optioncontent: "不满意", value: "2", isabnormal: true },
                { optioncontent: "非常不满意", value: "1", isabnormal: true },
              ],
            },
            {
              scriptId: 2,
              scriptType: 1,
              scriptContent: "您对住院期间护士的护理服务是否满意?",
              scriptResult: null,
              svyLibTemplateTargetoptions: [
                { optioncontent: "非常满意", value: "5", isabnormal: false },
                { optioncontent: "满意", value: "4", isabnormal: false },
                { optioncontent: "一般", value: "3", isabnormal: true },
                { optioncontent: "不满意", value: "2", isabnormal: true },
                { optioncontent: "非常不满意", value: "1", isabnormal: true },
              ],
            },
            {
              scriptId: 3,
              scriptType: 1,
              scriptContent: "您对住院病房的环境和卫生是否满意?",
              scriptResult: null,
              svyLibTemplateTargetoptions: [
                { optioncontent: "非常满意", value: "5", isabnormal: false },
                { optioncontent: "满意", value: "4", isabnormal: false },
                { optioncontent: "一般", value: "3", isabnormal: true },
                { optioncontent: "不满意", value: "2", isabnormal: true },
                { optioncontent: "非常不满意", value: "1", isabnormal: true },
              ],
            },
            {
              scriptId: 4,
              scriptType: 4,
              scriptContent: "您对住院服务还有什么其他建议?",
              scriptResult: null,
            },
          ],
          completionMessage:
            "感谢您对我们工作的支持!我们将根据您的反馈持续改进住院服务质量,祝您早日康复!",
        },
      },
    };
  },
  created() {
    this.initSurveyData();
  },
  methods: {
    // åˆå§‹åŒ–调查数据
    initSurveyData() {
      // ä»Žè·¯ç”±å‚数获取加密后的参数
      this.encryptedParams.param1 =
        this.$route.query.param1 ||
        "DBohZ1ARKfFmCdKrBKQ6JW3ddPTtDpgSaRZaKtxBMTJ4FngT06Vy-VskiwDYJJRwfvkHrPIZlkafgZybobGtKQ==";
      this.encryptedParams.param2 =
        this.$route.query.param2 ||
        "WQXniB7BIlizOwOQ4KZqITNrqWpLU3SD5vXdHLeYaviA-1T5Dtk70IJWAHbtcDUuYz-2ObYuMj4YKHfWhlCLzw==";
      this.encryptedParams.param3 = this.$route.query.param3 || null;
      this.encryptedParams.param4 = this.$route.query.param4 || "1"; // é»˜è®¤ä¸ºé—¨è¯Š
      this.surveyType = parseInt(this.encryptedParams.param4) || 1;
      // åŠ è½½é—®å·æ•°æ®
      this.loadSurveyData();
    },
    // åŠ è½½è°ƒæŸ¥æ•°æ®
    // åŠ è½½è°ƒæŸ¥æ•°æ®
    loadSurveyData() {
      // è°ƒç”¨æŽ¥å£èŽ·å–é—®å·æ•°æ®
      getScriptByCondition(this.encryptedParams)
        .then((res) => {
          if (res.code === 200) {
            if (res.data.result) {
              this.accomplish = res.data.result;
              return
            }
            // å¤„理接口返回的数据
            this.questionList = res.data.svyLibTemplateScriptVOS.map((item) => {
              return {
                ...item,
                scriptResult: item.scriptType === 2 ? [] : null,
              };
            });
            // æ ¹æ®surveyType设置标题和描述
            switch (this.surveyType) {
              case 1: // é—¨è¯Š
                this.surveyTitle = "门诊满意度调查";
                this.surveyDescription =
                  "亲爱的患者,感谢您选择我们的医疗服务。为了不断提升服务质量,请您花几分钟时间填写此问卷。";
                this.completionMessage =
                  "感谢您宝贵的意见!我们将不断改进门诊服务质量,为您提供更好的医疗服务体验。";
                break;
              case 2: // ä½é™¢
                this.surveyTitle = "住院满意度调查";
                this.surveyDescription =
                  "亲爱的患者及家属,感谢您选择在我院住院治疗。为了提升住院服务质量,请您填写此问卷。";
                this.completionMessage =
                  "感谢您对我们工作的支持!我们将根据您的反馈持续改进住院服务质量,祝您早日康复!";
                break;
              case 3: // æŠ•诉建议
                this.surveyTitle = "投诉建议反馈";
                this.surveyDescription =
                  "尊敬的客户,感谢您抽出宝贵时间提供反馈。您的意见对我们改进服务非常重要。";
                this.completionMessage =
                  "感谢您的反馈!我们已收到您的投诉/建议,将尽快处理并与您联系。";
                break;
              default:
                this.useTestData(1); // é»˜è®¤ä½¿ç”¨é—¨è¯Šæ•°æ®
            }
          } else {
            // æŽ¥å£æ— æ•°æ®æˆ–失败,使用测试数据
            this.useTestData(this.surveyType);
          }
        })
        .catch(() => {
          // æŽ¥å£è°ƒç”¨å¤±è´¥ï¼Œä½¿ç”¨æµ‹è¯•数据
          this.useTestData(this.surveyType);
        });
    },
    // ä½¿ç”¨æµ‹è¯•数据
    useTestData(surveyType) {
      const type = [1, 2, 3].includes(surveyType) ? surveyType : 1;
      const testData = this.testData[type];
      this.surveyTitle = testData.title;
      this.surveyDescription = testData.description;
      this.questionList = testData.questions;
      this.completionMessage = testData.completionMessage;
    },
    // æäº¤è°ƒæŸ¥é—®å·ï¼ˆç›´æŽ¥æäº¤ï¼Œä¸ç»è¿‡ç¼“存)
    async submitSurvey() {
      // éªŒè¯å¿…填项
      if (this.hasUnansweredRequiredQuestions()) {
        this.$message.error("请完成所有必填问题后再提交");
        return;
      }
      try {
        const submitData = this.prepareSubmitData();
        const res = await saveMYDQuestionAnswer(submitData);
        if (res.code === 200) {
          this.accomplish = false;
          this.$message.success("提交成功!感谢您的反馈。");
        } else {
          this.$message.error(res.msg || "提交失败,请稍后再试");
        }
      } catch (error) {
        this.$message.error("网络错误,提交失败");
        console.error("提交失败:", error);
      }
    },
    // æ£€æŸ¥æ˜¯å¦æœ‰æœªå›žç­”的必填问题
    hasUnansweredRequiredQuestions() {
      return this.questionList.some((question) => {
        return (
          question.required &&
          (question.scriptResult === null ||
            question.scriptResult === "" ||
            (Array.isArray(question.scriptResult) &&
              question.scriptResult.length === 0))
        );
      });
    },
    // å‡†å¤‡æäº¤æ•°æ®
    // å‡†å¤‡æäº¤æ•°æ®
    prepareSubmitData() {
      return {
        taskId: this.encryptedParams.param1,
        serialnum: this.encryptedParams.param2 || this.encryptedParams.param3,
        mzzy: this.surveyType, // 1=门诊, 2=住院, 3=投诉建议
        svyLibTemplateScriptVOS: this.questionList.map((item) => {
          return {
            scriptId: item.scriptId,
            scriptType: item.scriptType,
            scriptResult:
              item.scriptType === 2
                ? (item.scriptResult || []).join("&")
                : item.scriptResult || "",
            nextScriptno: item.nextScriptno,
            score: item.score,
            prompt: item.prompt,
            ...item,
          };
        }),
        excep: this.checkAbnormalOptions() ? 1 : 0,
      };
    },
    // æ£€æŸ¥å¼‚常选项
    checkAbnormalOptions() {
      return this.questionList.some((question) => {
        if (!question.scriptResult) return false;
        if (question.scriptType === 1) {
          // å•选题异常检查
          const selectedOption = question.svyLibTemplateTargetoptions.find(
            (opt) => opt.optioncontent === question.scriptResult
          );
          return selectedOption?.isabnormal;
        } else if (question.scriptType === 2) {
          // å¤šé€‰é¢˜å¼‚常检查
          return question.scriptResult.some((answer) => {
            const option = question.svyLibTemplateTargetoptions.find(
              (opt) => opt.optioncontent === answer
            );
            return option?.isabnormal;
          });
        }
        return false;
      });
    },
    // å¤„理单选选项变化
    handleOptionChange(selectedValue, index, question) {
      const selectedOption = question.svyLibTemplateTargetoptions.find(
        (option) => option.optioncontent === selectedValue
      );
      if (selectedOption) {
        this.questionList[index].nextScriptno = selectedOption.nextQuestion;
        this.questionList[index].score = selectedOption.score;
        this.questionList[index].prompt = selectedOption.prompt;
      }
    },
  },
};
</script>
<style lang="scss" scoped>
/* åŸºç¡€æ ·å¼å˜é‡ */
:root {
  --primary-color: #1a73e8; /* é»˜è®¤è“è‰² */
  --secondary-color: #34d399; /* é»˜è®¤ç»¿è‰² */
  --alert-color: #ed8936; /* é»˜è®¤æ©™è‰² */
}
/* é—¨è¯Šæ ·å¼å˜é‡ */
.survey-type-1 {
  --primary-color: #1a73e8; /* åŒ»ç–—蓝 */
  --secondary-color: #34d399; /* å¥åº·ç»¿ */
  --alert-color: #ed8936; /* è­¦ç¤ºæ©™ */
}
/* ä½é™¢æ ·å¼å˜é‡ */
.survey-type-2 {
  --primary-color: #5a67d8; /* æ·±è“ç´« */
  --secondary-color: #667eea; /* æµ…蓝紫 */
  --alert-color: #f56565; /* è­¦ç¤ºçº¢ */
}
/* æŠ•诉建议样式变量 */
.survey-type-3 {
  --primary-color: #e53e3e; /* ç´§æ€¥çº¢ */
  --secondary-color: #f6ad55; /* è­¦ç¤ºé»„ */
  --alert-color: #f56565; /* è­¦ç¤ºçº¢ */
}
.questionnaire {
  font-family: "PingFang SC", "Helvetica Neue", Arial, sans-serif;
  min-height: 100vh;
  margin: 0;
  padding: 0;
  color: #333;
  transition: all 0.3s ease;
  /* æ ¹æ®surveyType应用不同的主题 */
  &.survey-type-1 {
    background-color: #f5f9fc;
    --theme-gradient: linear-gradient(135deg, #1a73e8, #34d399);
  }
  &.survey-type-2 {
    background-color: #f8f9ff;
    --theme-gradient: linear-gradient(135deg, #5a67d8, #667eea);
  }
  &.survey-type-3 {
    background-color: #fff5f5;
    --theme-gradient: linear-gradient(135deg, #e53e3e, #f6ad55);
  }
}
.CONTENT {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
  .title {
    color: var(--primary-color);
    font-size: 28px;
    font-weight: 600;
    margin-bottom: 15px;
    text-align: center;
    letter-spacing: 0.5px;
    position: relative;
    &::after {
      content: "";
      position: absolute;
      bottom: -8px;
      left: 50%;
      transform: translateX(-50%);
      width: 60px;
      height: 3px;
      background: var(--theme-gradient);
      border-radius: 3px;
    }
  }
}
.preview-left {
  margin: 20px 0;
  margin-bottom: 100px;
  background-color: #fff;
  border-radius: 12px;
  padding: 30px;
  border: none;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  transition: all 0.3s ease;
  &:hover {
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
  }
  .toptitle {
    margin-bottom: 25px;
    div {
      color: #4a5568;
      font-size: 18px;
      line-height: 1.6;
      text-align: center;
    }
  }
  .el-divider {
    background-color: #e2e8f0;
    margin: 25px 0;
  }
  .topic-dev {
    margin-bottom: 30px;
    font-size: 17px;
    background-color: #f8fafc;
    border-radius: 10px;
    padding: 20px;
    transition: all 0.3s ease;
    position: relative;
    overflow: hidden;
    &:hover {
      background-color: #f1f5f9;
    }
    /* æ·»åŠ ç±»åž‹æ ‡è¯†å°æ ‡ç­¾ */
    &::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      width: 4px;
      height: 100%;
      background: var(--primary-color);
    }
    .dev-text {
      margin-bottom: 18px;
      font-weight: 500;
      color: #2d3748;
      font-size: 18px;
      line-height: 1.6;
      span[style*="color: #3ba2f7"] {
        font-size: 14px;
        margin-left: 8px;
        color: var(--primary-color) !important;
      }
    }
  }
}
.preview-lefts {
  margin: 20px 0;
  background-color: #fff;
  border-radius: 12px;
  padding: 40px;
  min-height: 400px;
  border: none;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  .completion-message {
    padding: 40px;
    max-width: 600px;
    .thank-you {
      font-size: 32px;
      color: var(--primary-color);
      font-weight: 600;
      margin-bottom: 25px;
      position: relative;
      display: inline-block;
      &::after {
        content: "";
        position: absolute;
        bottom: -10px;
        left: 50%;
        transform: translateX(-50%);
        width: 80px;
        height: 3px;
        background: var(--theme-gradient);
        border-radius: 3px;
      }
    }
    .feedback-message {
      font-size: 20px;
      line-height: 1.7;
      color: #4a5568;
      margin: 0 auto;
    }
  }
}
.red-star {
  ::v-deep .el-radio__label,
  ::v-deep .el-checkbox__label {
    position: relative;
    padding-right: 20px;
    &::after {
      content: "*";
      color: #ef4444;
      position: absolute;
      right: 0;
      top: 0;
      font-size: 16px;
    }
  }
}
::v-deep {
  .el-checkbox-group {
    display: flex;
    flex-direction: column;
    margin: 15px 0;
    gap: 12px;
  }
  .el-radio-group {
    display: flex;
    flex-direction: column;
    margin: 15px 0;
    gap: 12px;
  }
  .el-radio.is-bordered,
  .el-checkbox.is-bordered {
    width: 100%;
    margin-right: 0;
    margin-bottom: 10px;
    max-width: 400px;
    padding: 14px 20px 14px 15px;
    border-radius: 8px;
    height: auto;
    min-height: 50px;
    border: 1px solid #e2e8f0;
    transition: all 0.3s ease;
    margin-left: 0 !important;
    margin-top: 0 !important;
    .el-radio-group,
    .el-checkbox-group {
      align-items: center;
    }
    &:hover {
      border-color: var(--primary-color);
      box-shadow: 0 2px 8px rgba(var(--primary-color), 0.15);
    }
    &.is-checked {
      border-color: var(--primary-color);
      background-color: rgba(var(--primary-color), 0.05);
    }
  }
  .el-radio__label,
  .el-checkbox__label {
    font-size: 16px;
    color: #2d3748;
  }
  .el-alert--warning.is-light {
    background-color: #fff8f0;
    color: var(--alert-color);
    margin-top: 15px;
    border-radius: 8px;
    border-left: 4px solid var(--alert-color);
    .el-alert__title {
      font-size: 15px;
      line-height: 1.6;
      color: var(--alert-color);
    }
    .el-alert__closebtn {
      color: var(--alert-color);
    }
  }
  .el-textarea__inner {
    font-size: 16px;
    border-radius: 8px;
    border: 1px solid #e2e8f0;
    padding: 12px 15px;
    transition: all 0.3s ease;
    min-height: 100px;
    &:focus {
      border-color: var(--primary-color);
      box-shadow: 0 0 0 2px rgba(var(--primary-color), 0.2);
    }
    &::placeholder {
      color: #a0aec0;
    }
  }
  .el-radio__input.is-checked .el-radio__inner {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
  }
  .el-checkbox__input.is-checked .el-checkbox__inner {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
  }
  .el-radio__inner,
  .el-checkbox__inner {
    width: 18px;
    height: 18px;
  }
}
.bottom-fixed {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  text-align: center;
  padding: 10px 0;
  background: var(--theme-gradient);
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  transition: all 0.3s ease;
  .el-button {
    height: 56px;
    font-size: 18px;
    font-weight: 500;
    letter-spacing: 0.5px;
    border-radius: 8px;
    background-color: #fff;
    color: var(--primary-color);
    border: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transition: all 0.3s ease;
    width: 80%;
    max-width: 400px;
    position: relative;
    overflow: hidden;
    &::before {
      content: "";
      position: absolute;
      top: 0;
      left: -100%;
      width: 100%;
      height: 100%;
      background: linear-gradient(
        90deg,
        transparent,
        rgba(255, 255, 255, 0.4),
        transparent
      );
      transition: all 0.5s ease;
    }
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
      &::before {
        left: 100%;
      }
    }
    &:active {
      transform: translateY(0);
    }
  }
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .questionnaire {
    .CONTENT {
      padding: 15px;
    }
    .preview-left,
    .preview-lefts {
      padding: 20px;
      margin-bottom: 80px;
    }
    .title {
      font-size: 24px !important;
    }
    .dev-text {
      font-size: 16px !important;
    }
    .bottom-fixed .el-button {
      height: 50px;
      font-size: 16px;
      width: 90%;
    }
  }
}
</style>