WXL (wul)
昨天 a2c10da81668de1f5b7d38f5962d46d795e3cc7e
测试完成
已修改7个文件
已添加2个文件
1185 ■■■■ 文件已修改
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
sltd.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/getters.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/configurationmyd/index.vue 523 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue 634 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/propaganda/particty.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip
Binary files differ
sltd.zip
Binary files differ
src/api/system/user.js
@@ -196,3 +196,19 @@
    data: data,
  });
}
// 省立同德满意度统计页题目明细
export function statistics(data) {
  return request({
    url: "/smartor/satisfaction/statistics",
    method: "post",
    data: data,
  });
}
// 省立同德满意度统计页题目明细
export function satisfactionGraph(data) {
  return request({
    url: "/smartor/satisfaction/satisfactionGraph",
    method: "post",
    data: data,
  });
}
src/store/getters.js
@@ -15,6 +15,7 @@
  permissions: (state) => state.user.permissions,
  belongWards: (state) => state.user.belongWards,
  belongDepts: (state) => state.user.belongDepts,
  satisfactionCategories: (state) => state.user.satisfactionCategories,
  hisUserId: (state) => state.user.hisUserId,
  permission_routes: (state) => state.permission.routes,
  topbarRouters: (state) => state.permission.topbarRouters,
src/store/modules/user.js
@@ -13,6 +13,7 @@
    belongDepts: [],
    roles: [],
    permissions: [],
    satisfactionCategories:{},
    // 服务类型
    Serviceauthority: [
      {
@@ -111,6 +112,9 @@
    },
    SET_hisUserId: (state, hisUserId) => {
      state.hisUserId = hisUserId;
    },
        SET_satisfactionCategories: (state, satisfactionCategories) => {
      state.satisfactionCategories = satisfactionCategories;
    },
    SET_leaveldeptcodes: (state, belongDepts) => {
      state.belongDepts = belongDepts;
@@ -262,6 +266,7 @@
            commit("SET_nickNAME", user.nickName);
            commit("SET_Id", user.userId);
            commit("SET_hisUserId", user.hisUserId);
            commit("SET_satisfactionCategories", user.satisfactionCategories);
            // if (user.userName == "admin") {
            //   commit("SET_leaveldeptcodes", []);
src/views/Satisfaction/configurationmyd/index.vue
@@ -272,6 +272,45 @@
                </div>
              </div>
              <div class="header-right">
                <!-- 异常选项状态 -->
                <div
                  class="option-status"
                  v-if="
                    templateForm.templateType != 3 &&
                    templateForm.templateType != 4
                  "
                >
                  <el-tooltip
                    :content="
                      checkHasAbnormalOptions(question)
                        ? '已有异常选项'
                        : '暂无异常选项'
                    "
                    placement="top"
                  >
                    <el-tag
                      :type="
                        checkHasAbnormalOptions(question) ? 'success' : 'danger'
                      "
                      size="small"
                      class="status-tag"
                    >
                      <i
                        :class="
                          checkHasAbnormalOptions(question)
                            ? 'el-icon-success'
                            : 'el-icon-warning'
                        "
                      ></i>
                      {{
                        checkHasAbnormalOptions(question)
                          ? "异常选项已配置"
                          : "无异常选项"
                      }}
                    </el-tag>
                  </el-tooltip>
                </div>
                <el-button
                  type="text"
                  icon="el-icon-view"
@@ -279,6 +318,20 @@
                  size="small"
                >
                  预览
                </el-button>
                <!-- 添加配置选项按钮 -->
                <el-button
                  v-if="
                    templateForm.templateType != 3 &&
                    templateForm.templateType != 4
                  "
                  type="text"
                  icon="el-icon-setting"
                  @click="openOptionDialog(question)"
                  size="small"
                >
                  配置选项
                </el-button>
              </div>
            </div>
@@ -428,7 +481,108 @@
        </div>
      </div>
    </div>
<!-- 选项配置对话框 -->
<el-dialog
  title="选项异常状态配置"
  :visible.sync="optionDialogVisible"
  width="700px"
  center
  :close-on-click-modal="false"
>
  <div v-if="editingQuestion" class="option-config-wrapper">
    <div class="dialog-header">
      <h4>{{ editingQuestion.scriptTopic || '无主题' }}</h4>
      <p class="dialog-subtitle">{{ editingQuestion.scriptContent }}</p>
    </div>
    <div class="option-list">
      <el-alert
        v-if="!currentOptions.some(opt => opt.isabnormal === 1)"
        title="请至少设置一个异常选项(标记为异常)"
        type="warning"
        :closable="false"
        show-icon
        style="margin-bottom: 20px;"
      />
      <div v-for="(option, index) in currentOptions" :key="index" class="option-item">
        <el-form
          :model="option"
          :rules="optionRules"
          ref="optionForm"
          size="small"
          class="option-form"
        >
          <el-row :gutter="12" align="middle">
            <el-col :span="2">
              <div class="option-index">#{{ index + 1 }}</div>
            </el-col>
            <el-col :span="12">
              <el-form-item prop="targetvalue">
                <el-input
                  v-model="option.targetvalue"
                  placeholder="请输入选项内容"
                  clearable
                  maxlength="200"
                  show-word-limit
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item prop="isabnormal">
                <el-select
                  v-model="option.isabnormal"
                  placeholder="选择状态"
                  style="width: 100%"
                >
                  <el-option
                    v-for="status in abnormalOptions"
                    :key="status.value"
                    :label="status.label"
                    :value="status.value"
                  >
                    <el-tag :type="status.type" size="small">{{ status.label }}</el-tag>
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-button
                type="danger"
                icon="el-icon-delete"
                @click="removeOption(index)"
                size="small"
                circle
                plain
              />
            </el-col>
          </el-row>
        </el-form>
      </div>
      <!-- <el-button
        type="primary"
        icon="el-icon-plus"
        @click="addNewOption"
        size="small"
        plain
        style="width: 100%; margin-top: 10px;"
      >
        添加选项
      </el-button> -->
    </div>
  </div>
  <span slot="footer" class="dialog-footer">
    <el-button @click="optionDialogVisible = false">取消</el-button>
    <el-button type="primary" @click="saveOptions" :loading="savingOptions">
      保存配置
    </el-button>
  </span>
</el-dialog>
    <!-- 题目预览对话框 -->
    <el-dialog
      title="题目预览"
@@ -573,7 +727,25 @@
          { required: true, message: "请选择模板类型", trigger: "change" },
        ],
      },
      // 选项管理相关
      optionDialogVisible: false,
      currentOptions: [],
      editingQuestion: null,
      optionRules: {
        targetvalue: [
          { required: true, message: "请输入选项内容", trigger: "blur" },
        ],
        isabnormal: [
          { required: true, message: "请选择异常状态", trigger: "change" },
        ],
      },
      // 异常状态选项
      abnormalOptions: [
        { label: "正常", value: 0, type: "success" },
        { label: "异常", value: 1, type: "danger" },
        { label: "警告", value: 2, type: "warning" },
      ],
      // 模板选项
      questionnaireTemplates: [], // 问卷模板列表
      followupTemplates: [], // 语音模板列表
@@ -613,7 +785,8 @@
      // 满意度分类ID
      satisfactionCategoryIds: ["404", "405", "406", "10039", "10041", "10042"],
      questionnaireCategorys: [],
      voiceCategories: [],
      // 表单验证规则
      configRules: {
        responsibilityDept: [
@@ -668,27 +841,46 @@
    satisfactionQuestionsCount() {
      if (this.templateForm.templateType === 1) {
        return this.questionList.filter((q) =>
          this.satisfactionCategoryIds.includes(q.categoryid?.toString())
          this.questionnaireCategorys.includes(q.categoryid)
        ).length;
      } else if (this.templateForm.templateType === 2) {
        return this.questionList.filter((q) =>
          this.satisfactionCategoryIds.includes(q.scriptAssortid?.toString())
          this.voiceCategories.includes(q.scriptAssortid)
        ).length;
      }
    },
    // 检查题目是否有异常选项
    hasAbnormalOption(question) {
      return (question) => {
        if (!question) return false;
        // 问卷模板
        if (this.templateForm.templateType === 1) {
          const options = question.svyLibTemplateTargetoptions || [];
          return options.some((opt) => opt.isabnormal === 1);
        }
        // 语音模板
        else if (this.templateForm.templateType === 2) {
          const options = question.ivrLibaScriptTargetoptionList || [];
          return options.some((opt) => opt.isabnormal === 1);
        }
        return false;
      };
    },
    // 筛选后的题目列表
    filteredQuestionList() {
      let filtered = this.questionList;
      console.log(this.questionnaireCategorys);
      // 筛选满意度题目
      if (this.templateForm.templateType === 1) {
        filtered = filtered.filter((q) =>
          this.satisfactionCategoryIds.includes(q.categoryid?.toString())
          this.questionnaireCategorys.includes(q.categoryid)
        );
      } else if (this.templateForm.templateType === 2) {
        filtered = filtered.filter((q) =>
          this.satisfactionCategoryIds.includes(q.scriptAssortid?.toString())
          this.voiceCategories.includes(q.scriptAssortid)
        );
      }
@@ -712,6 +904,16 @@
    },
  },
  created() {
    if (store.getters.satisfactionCategories) {
      this.questionnaireCategorys =
        store.getters.satisfactionCategories.questionnaireCategorys.map(
          (item) => item.categoryid
        );
      this.voiceCategories =
        store.getters.satisfactionCategories.voiceCategories.map(
          (item) => item.categoryid
        );
    }
    this.getDeptOptions();
    this.loadAllTemplates();
  },
@@ -1010,7 +1212,9 @@
    /** 配置变更处理 */
    handleConfigChange(question) {
      this.$nextTick(() => {
        const index = this.filteredQuestionList.findIndex((q) => q.id === question.id);
        const index = this.filteredQuestionList.findIndex(
          (q) => q.id === question.id
        );
        if (index !== -1) {
          const formRef = this.$refs.configForm && this.$refs.configForm[index];
          if (formRef) {
@@ -1063,8 +1267,10 @@
    async saveSingleConfig(question) {
      if (!question.hasChanges) return;
      const index = this.filteredQuestionList.findIndex((q) => q.id === question.id);
      console.log(index,'filteredQuestionList');
      const index = this.filteredQuestionList.findIndex(
        (q) => q.id === question.id
      );
      console.log(index, "filteredQuestionList");
      if (index === -1) return;
@@ -1207,7 +1413,6 @@
    },
    /** 重置单个题目配置 */
    /** 重置单个题目配置 */
    resetSingleConfig(question) {
      this.$confirm("确定要重置当前题目的配置吗?", "提示", {
        confirmButtonText: "确定",
@@ -1300,7 +1505,247 @@
      this.previewAnswer = "";
      this.previewVisible = true;
    },
    /** 检查题目是否有异常选项 */
    checkHasAbnormalOptions(question) {
      if (this.templateForm.templateType === 1) {
        return (question.svyLibTemplateTargetoptions || []).some(
          (opt) => opt.isabnormal === 1
        );
      } else if (this.templateForm.templateType === 2) {
        return (question.ivrLibaScriptTargetoptionList || []).some(
          (opt) => opt.isabnormal === 1
        );
      }
      return false;
    },
    /** 打开选项管理对话框 */
    openOptionDialog(question) {
      this.editingQuestion = question;
      // 复制选项数据
      if (this.templateForm.templateType === 1) {
        this.currentOptions = JSON.parse(
          JSON.stringify(question.svyLibTemplateTargetoptions || [])
        ).map((opt) => ({
          ...opt,
          id: opt.id,
          targetvalue: opt.optioncontent || "",
          isabnormal: opt.isabnormal || 0,
        }));
      } else if (this.templateForm.templateType === 2) {
        this.currentOptions = JSON.parse(
          JSON.stringify(question.ivrLibaScriptTargetoptionList || [])
        ).map((opt) => ({
          ...opt,
          targetvalue: opt.targetvalue || "",
          isabnormal: opt.isabnormal || 0,
        }));
      }
      this.optionDialogVisible = true;
    },
    /** 添加新选项 */
    addNewOption() {
      this.currentOptions.push({
        id: Date.now(), // 临时ID
        targetvalue: "",
        isabnormal: 0,
        isNew: true,
      });
    },
    /** 删除选项 */
    removeOption(index) {
      this.currentOptions.splice(index, 1);
    },
    /** 保存选项配置 */
    async saveOptions() {
      try {
        // 验证必填项
        for (const option of this.currentOptions) {
          if (!option.targetvalue || option.targetvalue.trim() === "") {
            this.$message.warning("请填写所有选项内容");
            return;
          }
        }
        // 检查是否有异常选项
        const hasAbnormal = this.currentOptions.some(
          (opt) => opt.isabnormal === 1
        );
        if (!hasAbnormal) {
          this.$message.warning("请至少设置一个异常选项(isabnormal=1)");
          return;
        }
        // 保存逻辑 - 更新题目对象的选项数据
        if (this.templateForm.templateType === 1) {
          this.editingQuestion.svyLibTemplateTargetoptions =
            this.currentOptions.map((opt) => ({
              ...opt,
              optioncontent: opt.targetvalue,
              isabnormal: opt.isabnormal,
            }));
        } else if (this.templateForm.templateType === 2) {
          this.editingQuestion.ivrLibaScriptTargetoptionList =
            this.currentOptions;
        }
        // 触发配置变更检查
        this.handleConfigChange(this.editingQuestion);
        this.$message.success("选项配置保存成功");
        this.optionDialogVisible = false;
      } catch (error) {
        console.error("保存选项失败:", error);
        this.$message.error("保存选项失败");
      }
    },
    /** 修改保存单个题目配置方法,添加异常选项检查 */
    async saveSingleConfig(question) {
      // 检查是否有异常选项
      if (!this.checkHasAbnormalOptions(question)) {
        this.$confirm("该题目没有设置异常选项,是否先配置选项?", "提示", {
          confirmButtonText: "去配置",
          cancelButtonText: "取消",
          type: "warning",
        })
          .then(() => {
            this.openOptionDialog(question);
          })
          .catch(() => {});
        return;
      }
      // 原有的保存逻辑...
      if (!question.hasChanges) return;
      const index = this.filteredQuestionList.findIndex(
        (q) => q.id === question.id
      );
      if (index === -1) return;
      const formRef = this.$refs.configForm && this.$refs.configForm[index];
      if (!formRef) return;
      const valid = await formRef.validate();
      if (!valid) {
        this.$message.warning("请先完成必填项");
        return;
      }
      // 继续原有的保存逻辑...
      question.saving = true;
      question.saveStatus = null;
      try {
        // ... 原有的保存逻辑不变
      } catch (error) {
        // ... 错误处理不变
      } finally {
        question.saving = false;
      }
    },
    /** 批量保存时也要检查 */
    async handleBatchSave() {
      if (!this.hasChanges || this.batchSaving) return;
      // 检查所有有变更的题目是否都有异常选项
      const changedQuestions = this.questionList.filter((q) => q.hasChanges);
      const questionsWithoutAbnormal = changedQuestions.filter(
        (q) => !this.checkHasAbnormalOptions(q)
      );
      if (questionsWithoutAbnormal.length > 0) {
        this.$confirm(
          `有 ${questionsWithoutAbnormal.length} 个题目没有设置异常选项,请先配置选项。是否继续?`,
          "提示",
          {
            confirmButtonText: "继续",
            cancelButtonText: "去配置",
            type: "warning",
          }
        )
          .then(() => {
            // 继续执行批量保存
            this.executeBatchSave(changedQuestions);
          })
          .catch(() => {
            // 可以在这里跳转到第一个没有异常选项的题目
            if (questionsWithoutAbnormal.length > 0) {
              this.openOptionDialog(questionsWithoutAbnormal[0]);
            }
          });
      } else {
        this.executeBatchSave(changedQuestions);
      }
    },
    /** 执行批量保存 */
    async executeBatchSave(changedQuestions) {
      this.$confirm("确定要保存所有修改过的配置吗?", "批量保存", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(async () => {
          this.batchSaving = true;
          const results = [];
          for (const question of changedQuestions) {
            try {
              // 这里调用修改后的saveSingleConfig方法
              await this.saveSingleConfig(question);
              results.push({
                id: question.id,
                success:
                  !question.hasChanges &&
                  question.saveStatus?.type === "success",
              });
            } catch (error) {
              results.push({
                id: question.id,
                success: false,
              });
            }
          }
          this.batchSaving = false;
          // ... 后续处理不变
        })
        .catch(() => {
          this.batchSaving = false;
        });
    },
    /** 获取异常选项统计 */
    getAbnormalStats(question) {
      if (this.templateForm.templateType === 1) {
        const options = question.svyLibTemplateTargetoptions || [];
        return {
          total: options.length,
          abnormal: options.filter((opt) => opt.isabnormal === 1).length,
          warning: options.filter((opt) => opt.isabnormal === 2).length,
          normal: options.filter((opt) => opt.isabnormal === 0).length,
        };
      } else if (this.templateForm.templateType === 2) {
        const options = question.ivrLibaScriptTargetoptionList || [];
        return {
          total: options.length,
          abnormal: options.filter((opt) => opt.isabnormal === 1).length,
          warning: options.filter((opt) => opt.isabnormal === 2).length,
          normal: options.filter((opt) => opt.isabnormal === 0).length,
        };
      }
      return { total: 0, abnormal: 0, warning: 0, normal: 0 };
    },
    /** 搜索 */
    handleQuery() {
      // 仅筛选显示,不需要重新加载
@@ -1595,7 +2040,20 @@
          }
          .header-right {
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            align-items: flex-end;
            gap: 8px;
            .option-status {
              .status-tag {
                cursor: default;
                i {
                  margin-right: 4px;
                }
              }
            }
          }
        }
@@ -1789,7 +2247,52 @@
    }
  }
}
.option-config-wrapper {
  .dialog-header {
    margin-bottom: 20px;
    padding-bottom: 15px;
    border-bottom: 1px solid #ebeef5;
    h4 {
      margin: 0 0 8px 0;
      color: #303133;
      font-size: 16px;
      font-weight: 600;
    }
    .dialog-subtitle {
      margin: 0;
      color: #606266;
      font-size: 13px;
      line-height: 1.4;
    }
  }
  .option-list {
    .option-item {
      margin-bottom: 12px;
      padding: 12px;
      background: #f8f9fa;
      border-radius: 4px;
      border: 1px solid #ebeef5;
      &:hover {
        border-color: #dcdfe6;
      }
      .option-form {
        .option-index {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 100%;
          color: #909399;
          font-weight: 500;
        }
      }
    }
  }
}
@media (max-width: 768px) {
  .satisfaction-exception-config {
    padding: 12px;
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
@@ -11,19 +11,16 @@
          label-width="100px"
          class="query-form"
        >
          <el-form-item label="患者来源" prop="patientSource">
          <el-form-item label="统计类型" prop="patientSource">
            <el-select
              v-model="queryParams.patientSource"
              placeholder="请选择患者来源"
              v-model="queryParams.type"
              placeholder="请选择统计类型"
              clearable
              style="width: 200px"
              style="width: 100%"
            >
              <el-option
                v-for="source in patientSourceList"
                :key="source.value"
                :label="source.label"
                :value="source.value"
              />
              <el-option label="问卷类型" :value="2" />
              <el-option label="语音类型" :value="1" />
              <el-option label="全部" :value="null" />
            </el-select>
          </el-form-item>
@@ -34,6 +31,7 @@
              clearable
              filterable
              style="width: 200px"
              @change="handleDeptChange"
            >
              <el-option
                v-for="dept in deptList"
@@ -51,6 +49,7 @@
              clearable
              filterable
              style="width: 200px"
              @change="handleWardChange"
            >
              <el-option
                v-for="ward in wardList"
@@ -190,7 +189,7 @@
                  min-width="300"
                >
                  <template slot-scope="{ row }">
                    <span>{{ row.scriptContent }}?</span>
                    <span>{{ row.scriptContent }}</span>
                    <el-tag
                      :type="row.scriptType === 1 ? 'primary' : 'success'"
                      size="mini"
@@ -254,7 +253,7 @@
                  width="100"
                >
                  <template slot-scope="{ row }">
                    {{ row.totalCount - row.answerCount }}
                    {{ row.noAnswerPerson }}
                  </template>
                </el-table-column>
@@ -265,7 +264,7 @@
                  width="100"
                >
                  <template slot-scope="{ row }">
                    {{ formatPercent(row.answerCount / row.totalCount) }}
                    {{ formatPercent(row.answerRate) }}
                  </template>
                </el-table-column>
              </el-table>
@@ -273,10 +272,6 @@
              <!-- 综合得分行 -->
              <div class="summary-row">
                <div class="summary-content">
                  <!-- <div class="summary-item">
                    <span class="label">综合得分:</span>
                    <span class="value">{{ totalScore.toFixed(1) }}</span>
                  </div> -->
                  <div class="summary-item">
                    <span class="label">总答题人数:</span>
                    <span class="value">{{ totalAnswerCount }}</span>
@@ -403,32 +398,6 @@
                </el-table-column>
                <el-table-column
                  label="最高分"
                  prop="maxScore"
                  align="center"
                  width="120"
                >
                  <template slot-scope="{ row }">
                    <span class="score-text">{{
                      row.maxScore.toFixed(1)
                    }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="最低分"
                  prop="minScore"
                  align="center"
                  width="120"
                >
                  <template slot-scope="{ row }">
                    <span class="score-text">{{
                      row.minScore.toFixed(1)
                    }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="满意度等级"
                  prop="satisfactionLevel"
                  align="center"
@@ -539,6 +508,8 @@
<script>
import * as echarts from "echarts";
import { statistics, satisfactionGraph } from "@/api/system/user";
import store from "@/store";
export default {
  name: "SatisfactionStatistics",
@@ -546,6 +517,7 @@
    return {
      // 查询参数
      queryParams: {
        type: 2,
        patientSource: "",
        deptCode: "",
        wardCode: "",
@@ -597,9 +569,12 @@
      // 各类型统计明细数据
      typeDetailData: [],
      // 柱状图数据
      chartData: [],
      // 统计信息
      totalSendCount: 12560,
      totalReceiveCount: 10240,
      totalSendCount: 0,
      totalReceiveCount: 0,
      overallRecoveryRate: 0,
      // 类型统计汇总
@@ -650,7 +625,54 @@
        { id: 403, name: "门诊满意度", color: "#409EFF" },
        { id: 404, name: "常用满意度", color: "#FF9D4D" },
      ],
      // 新增:默认服务类型数组
      defaultServiceTypes: ["6", "14", "15", "16"],
      // 新增:基础模板问题ID集合
      scriptIds: [],
      // 新增:模板ID
      templateId: null,
    };
  },
  computed: {
    // 计算查询开始时间
    startTime() {
      if (this.queryParams.dateRange && this.queryParams.dateRange[0]) {
        return this.queryParams.dateRange[0];
      }
      // 默认最近7天
      const date = new Date();
      date.setDate(date.getDate() - 7);
      return this.formatDate(date);
    },
    // 计算查询结束时间
    endTime() {
      if (this.queryParams.dateRange && this.queryParams.dateRange[1]) {
        return this.queryParams.dateRange[1];
      }
      // 默认今天
      return this.formatDate(new Date());
    },
    // 计算科室编码数组
    deptCodes() {
      if (this.queryParams.deptCode) {
        return [this.queryParams.deptCode];
      }
      return this.deptList.map((dept) => dept.value);
    },
    // 计算病区编码数组
    hospitalDistrictCodes() {
      if (this.queryParams.wardCode) {
        return [this.queryParams.wardCode];
      }
      return this.wardList.map((ward) => ward.value);
    },
  },
  mounted() {
@@ -665,6 +687,14 @@
  },
  methods: {
    // 格式化日期
    formatDate(date) {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, "0");
      const day = String(date.getDate()).padStart(2, "0");
      return `${year}-${month}-${day}`;
    },
    // 初始化数据
    async initData() {
      await this.getDeptList();
@@ -676,34 +706,26 @@
    // 获取科室列表
    getDeptList() {
      return new Promise((resolve) => {
        setTimeout(() => {
          this.deptList = [
            { value: "dept001", label: "心血管内科" },
            { value: "dept002", label: "神经内科" },
            { value: "dept003", label: "普外科" },
            { value: "dept004", label: "骨科" },
            { value: "dept005", label: "妇产科" },
            { value: "dept006", label: "儿科" },
          ];
          resolve();
        }, 100);
        this.deptList = (this.$store.getters.belongDepts || []).map((dept) => {
          return {
            label: dept.deptName,
            value: dept.deptCode,
          };
        });
        resolve();
      });
    },
    // 获取病区列表
    getWardList() {
      return new Promise((resolve) => {
        setTimeout(() => {
          this.wardList = [
            { value: "ward001", label: "内科一病区" },
            { value: "ward002", label: "内科二病区" },
            { value: "ward003", label: "外科一病区" },
            { value: "ward004", label: "外科二病区" },
            { value: "ward005", label: "妇产科病区" },
            { value: "ward006", label: "儿科病区" },
          ];
          resolve();
        }, 100);
        this.wardList = (this.$store.getters.belongWards || []).map((ward) => {
          return {
            label: ward.districtName,
            value: ward.districtCode,
          };
        });
        resolve();
      });
    },
@@ -720,21 +742,104 @@
    async loadChartData() {
      this.loading = true;
      try {
        // 模拟API调用
        const chartData = await this.generateChartData();
        this.renderChart(chartData);
        const params = {
          type: this.queryParams.type,
          startTime: this.startTime,
          endTime: this.endTime,
          deptcodes: this.deptCodes,
          hospitaldistrictcodes: this.hospitalDistrictCodes,
          templateid: this.templateId,
        };
        // 计算总体回收率
        this.overallRecoveryRate = this.totalReceiveCount / this.totalSendCount;
        const response = await satisfactionGraph(params);
        if (response.code === 200) {
          this.processChartData(response);
        } else {
          this.$message.error(response.msg || "获取图表数据失败");
          // 使用mock数据
          await this.generateMockChartData();
        }
      } catch (error) {
        console.error("获取图表数据出错:", error);
        this.$message.error("获取图表数据失败");
        // 错误时使用mock数据
        await this.generateMockChartData();
      } finally {
        this.loading = false;
      }
    },
    // 处理图表数据
    processChartData(apiData) {
      if (!apiData || !apiData.rows || Object.keys(apiData.rows).length === 0) {
        this.chartData = [];
        this.totalSendCount = 0;
        this.totalReceiveCount = 0;
        this.overallRecoveryRate = 0;
        this.renderChart([]);
        return;
      }
      const chartData = [];
      let totalSend = 0;
      let totalReceive = 0;
      let index = 0;
      // 处理接口返回的满意度类型统计
      Object.entries(apiData.rows).forEach(([typeName, typeStat]) => {
        const sendCount = typeStat.subidAll || 0;
        const receiveCount = typeStat.fillCountAll || 0;
        const recoveryRate = typeStat.receiveRate || 0;
        chartData.push({
          name: typeName,
          value: recoveryRate * 100, // 转换为百分比
          sendCount: sendCount,
          receiveCount: receiveCount,
          averageScore: typeStat.averageScore || 0,
          itemStyle: { color: this.getChartColor(index) },
        });
        totalSend += sendCount;
        totalReceive += receiveCount;
        index++;
      });
      this.totalSendCount = totalSend;
      this.totalReceiveCount = totalReceive;
      this.overallRecoveryRate = totalSend > 0 ? totalReceive / totalSend : 0;
      this.chartData = chartData;
      this.renderChart(chartData);
    },
    // 加载题目明细数据
    async loadQuestionDetailData() {
      this.detailLoading = true;
      try {
        const params = {
          type: this.queryParams.type,
          startTime: this.startTime,
          endTime: this.endTime,
          scriptids: this.scriptIds,
          templateid: this.templateId,
        };
        const response = await statistics(params);
        if (response.code === 200) {
          this.processQuestionDetailData(response.rows);
        } else {
          this.$message.error(response.msg || "获取题目明细数据失败");
          const mockData = await this.generateMockQuestionDetail();
          this.questionDetailData = mockData.list;
          this.detailTotal = mockData.total;
          this.calculateSummary(mockData);
        }
      } catch (error) {
        console.error("获取题目明细数据出错:", error);
        this.$message.error("获取题目明细数据失败");
        const mockData = await this.generateMockQuestionDetail();
        this.questionDetailData = mockData.list;
        this.detailTotal = mockData.total;
@@ -744,18 +849,137 @@
      }
    },
    // 处理接口返回的题目明细数据
    processQuestionDetailData(apiData) {
      if (!apiData || !apiData.patSatisfactionDetailEntities) {
        this.questionDetailData = [];
        this.detailTotal = 0;
        this.totalAnswerCount = 0;
        this.totalAnswerRate = 0;
        return;
      }
      const detailData = apiData.patSatisfactionDetailEntities.map((item) => {
        const options = [];
        if (item.matchedtextStats) {
          Object.keys(item.matchedtextStats).forEach((key) => {
            const stat = item.matchedtextStats[key];
            options.push({
              optionText: key,
              chosenQuantity: stat.count || 0,
              chosenPercentage: (stat.ratio || 0) / 100,
            });
          });
        }
        return {
          scriptContent: item.scriptContent || "",
          scriptType: 1,
          answerPerson: item.answerPerson || 0,
          noAnswerPerson: item.noAnswerPerson || 0,
          answerCount: item.answerPerson || 0,
          averageScore: item.averageScore || 0,
          maxScore: item.maxScore || 0,
          minScore: item.minScore || 0,
          answerRate: item.answerRate || 0,
          totalCount: (item.answerPerson || 0) + (item.noAnswerPerson || 0),
          options: options,
        };
      });
      const startIndex =
        (this.detailQueryParams.pageNum - 1) * this.detailQueryParams.pageSize;
      const endIndex = startIndex + this.detailQueryParams.pageSize;
      const paginatedData = detailData.slice(startIndex, endIndex);
      this.questionDetailData = paginatedData;
      this.detailTotal = detailData.length;
      this.totalAnswerCount = apiData.totalPerson || 0;
      this.totalAnswerRate = apiData.totalAnswerRate || 0;
    },
    // 加载类型明细数据
    async loadTypeDetailData() {
      this.typeDetailLoading = true;
      try {
        const params = {
          type: this.queryParams.type,
          startTime: this.startTime,
          endTime: this.endTime,
          deptcodes: this.deptCodes,
          hospitaldistrictcodes: this.hospitalDistrictCodes,
          templateid: this.templateId,
        };
        const response = await satisfactionGraph(params);
        if (response.code === 200) {
          this.processTypeDetailData(response.data);
        } else {
          this.$message.error(response.msg || "获取类型明细数据失败");
          const mockData = await this.generateMockTypeDetail();
          this.typeDetailData = mockData;
          this.calculateTypeSummary(mockData);
        }
      } catch (error) {
        console.error("获取类型明细数据出错:", error);
        this.$message.error("获取类型明细数据失败");
        const mockData = await this.generateMockTypeDetail();
        this.typeDetailData = mockData;
        // 计算类型统计汇总
        this.calculateTypeSummary(mockData);
      } finally {
        this.typeDetailLoading = false;
      }
    },
    // 处理类型明细数据
    processTypeDetailData(apiData) {
      if (!apiData || !apiData.rows || Object.keys(apiData.rows).length === 0) {
        this.typeDetailData = [];
        this.calculateTypeSummary([]);
        return;
      }
      const typeDetail = [];
      Object.entries(apiData.rows).forEach(([typeName, typeStat], index) => {
        const sendCount = typeStat.subidAll || 0;
        const receiveCount = typeStat.fillCountAll || 0;
        const recoveryRate = typeStat.receiveRate || 0;
        const averageScore = typeStat.averageScore || 0;
        typeDetail.push({
          id: index + 1,
          typeName: typeName,
          isSpecial: false, // 根据实际情况判断
          sendCount: sendCount,
          receiveCount: receiveCount,
          recoveryRate: recoveryRate,
          averageScore: averageScore,
          maxScore: 5, // 默认值
          minScore: 0, // 默认值
          satisfactionLevel: this.getSatisfactionLevel(averageScore),
          trend: "stable", // 默认稳定
        });
      });
      this.typeDetailData = typeDetail;
      this.calculateTypeSummary(typeDetail);
    },
    // 根据平均分获取满意度等级
    getSatisfactionLevel(score) {
      if (score >= 4.5) return "优秀";
      if (score >= 4.0) return "良好";
      if (score >= 3.0) return "一般";
      if (score >= 2.0) return "较差";
      return "差";
    },
    // 获取趋势
    getTrend(trend) {
      if (trend > 0.1) return "up";
      if (trend < -0.1) return "down";
      return "stable";
    },
    // 计算综合得分
@@ -814,50 +1038,27 @@
      window.addEventListener("resize", this.handleChartResize);
    },
    // 生成图表数据
    generateChartData() {
      return new Promise((resolve) => {
        setTimeout(() => {
          const data = this.satisfactionTypes.map((type) => ({
            name: type.name,
            recoveryRate: Math.random() * 0.3 + 0.6, // 60%-90%的回收率
            sendCount: Math.floor(Math.random() * 3000) + 1500, // 1500-4500
            receiveCount: 0,
            color: type.color,
          }));
          // 计算回收数量
          data.forEach((item) => {
            item.receiveCount = Math.floor(item.sendCount * item.recoveryRate);
          });
          // 更新总量
          this.totalSendCount = data.reduce(
            (sum, item) => sum + item.sendCount,
            0
          );
          this.totalReceiveCount = data.reduce(
            (sum, item) => sum + item.receiveCount,
            0
          );
          resolve({
            data: data.map((item) => ({
              name: item.name,
              value: item.recoveryRate * 100, // 转换为百分比
              sendCount: item.sendCount,
              receiveCount: item.receiveCount,
              itemStyle: { color: item.color },
            })),
          });
        }, 300);
      });
    },
    // 渲染图表
    renderChart(chartData) {
      if (!this.barChart) return;
 if (!chartData || chartData.length === 0) {
    const emptyOption = {
      title: {
        text: "暂无数据",
        left: "center",
        top: "center",
        textStyle: {
          color: "#999",
          fontSize: 16,
          fontWeight: "normal"
        }
      },
      xAxis: { show: false },
      yAxis: { show: false }
    };
    this.barChart.setOption(emptyOption);
    return;
  }
      const option = {
        title: {
          text: "",
@@ -900,7 +1101,7 @@
        },
        xAxis: {
          type: "category",
          data: chartData.data.map((item) => item.name),
          data: chartData.map((item) => item.name),
          axisLabel: {
            interval: 0,
            rotate: 0,
@@ -942,7 +1143,7 @@
            name: "填报比例",
            type: "bar",
            barWidth: 40,
            data: chartData.data,
            data: chartData,
            itemStyle: {
              color: (params) => {
                return params.data.itemStyle.color;
@@ -962,19 +1163,100 @@
      this.barChart.setOption(option);
    },
    // 生成Mock图表数据
    generateMockChartData() {
      return new Promise((resolve) => {
        setTimeout(() => {
          const data = this.satisfactionTypes.map((type, index) => ({
            name: type.name,
            recoveryRate: Math.random() * 0.3 + 0.6,
            sendCount: Math.floor(Math.random() * 3000) + 1500,
            receiveCount: 0,
            color: type.color,
          }));
          data.forEach((item) => {
            item.receiveCount = Math.floor(item.sendCount * item.recoveryRate);
          });
          this.totalSendCount = data.reduce(
            (sum, item) => sum + item.sendCount,
            0
          );
          this.totalReceiveCount = data.reduce(
            (sum, item) => sum + item.receiveCount,
            0
          );
          this.overallRecoveryRate =
            this.totalSendCount > 0
              ? this.totalReceiveCount / this.totalSendCount
              : 0;
          const chartData = data.map((item) => ({
            name: item.name,
            value: item.recoveryRate * 100,
            sendCount: item.sendCount,
            receiveCount: item.receiveCount,
            itemStyle: { color: item.color },
          }));
          this.chartData = chartData;
          this.renderChart(chartData);
          resolve();
        }, 300);
      });
    },
    // 生成Mock题目详情数据
    generateMockQuestionDetail() {
      return new Promise((resolve) => {
        setTimeout(() => {
          const questions = [
            {
              scriptContent: "您对医护人员的服务态度是否满意",
              scriptContent: "您对本次就医的整体满意程度?",
              scriptType: 1,
              totalCount: 156,
              answerPerson: 120,
              noAnswerPerson: 30,
              answerCount: 120,
              totalCount: 150,
              averageScore: 4.2,
              maxScore: 5.0,
              minScore: 1.0,
              answerRate: 0.8,
              options: [
                {
                  optionText: "非常满意",
                  chosenQuantity: 60,
                  chosenPercentage: 0.5,
                },
                {
                  optionText: "满意",
                  chosenQuantity: 36,
                  chosenPercentage: 0.3,
                },
                {
                  optionText: "一般",
                  chosenQuantity: 18,
                  chosenPercentage: 0.15,
                },
                {
                  optionText: "不满意",
                  chosenQuantity: 6,
                  chosenPercentage: 0.05,
                },
              ],
            },
            {
              scriptContent: "您对医护人员的服务态度是否满意?",
              scriptType: 1,
              answerPerson: 145,
              noAnswerPerson: 11,
              answerCount: 145,
              totalCount: 156,
              averageScore: 4.5,
              maxScore: 5,
              minScore: 3,
              answerRate: 0.93,
              options: [
                {
                  optionText: "非常满意",
@@ -1003,78 +1285,6 @@
                },
              ],
            },
            {
              scriptContent: "您对医生的诊疗水平和技术能力评价如何",
              scriptType: 1,
              totalCount: 156,
              answerCount: 142,
              averageScore: 4.7,
              maxScore: 5,
              minScore: 3,
              options: [
                {
                  optionText: "非常专业",
                  chosenQuantity: 95,
                  chosenPercentage: 0.67,
                },
                {
                  optionText: "比较专业",
                  chosenQuantity: 40,
                  chosenPercentage: 0.28,
                },
                {
                  optionText: "一般",
                  chosenQuantity: 5,
                  chosenPercentage: 0.04,
                },
                {
                  optionText: "不够专业",
                  chosenQuantity: 2,
                  chosenPercentage: 0.01,
                },
                {
                  optionText: "非常不专业",
                  chosenQuantity: 0,
                  chosenPercentage: 0,
                },
              ],
            },
            {
              scriptContent: "您对医院的环境和卫生状况是否满意",
              scriptType: 1,
              totalCount: 156,
              answerCount: 138,
              averageScore: 4.3,
              maxScore: 5,
              minScore: 2,
              options: [
                {
                  optionText: "非常满意",
                  chosenQuantity: 75,
                  chosenPercentage: 0.54,
                },
                {
                  optionText: "满意",
                  chosenQuantity: 50,
                  chosenPercentage: 0.36,
                },
                {
                  optionText: "一般",
                  chosenQuantity: 10,
                  chosenPercentage: 0.07,
                },
                {
                  optionText: "不满意",
                  chosenQuantity: 3,
                  chosenPercentage: 0.02,
                },
                {
                  optionText: "非常不满意",
                  chosenQuantity: 0,
                  chosenPercentage: 0,
                },
              ],
            },
          ];
          const startIndex =
@@ -1095,7 +1305,6 @@
    generateMockTypeDetail() {
      return new Promise((resolve) => {
        setTimeout(() => {
          // 在 generateMockTypeDetail 方法中替换为:
          const types = [
            {
              id: 401,
@@ -1156,6 +1365,19 @@
      });
    },
    // 获取图表颜色
    getChartColor(index) {
      const colors = [
        "#36B37E",
        "#4CAF50",
        "#409EFF",
        "#FF9D4D",
        "#9B8DFF",
        "#FF6B6B",
      ];
      return colors[index % colors.length];
    },
    // 处理图表响应式
    handleChartResize() {
      if (this.barChart) {
@@ -1167,6 +1389,12 @@
    handleSearch() {
      this.detailQueryParams.pageNum = 1;
      this.loadData();
      // 强制重新渲染图表
      setTimeout(() => {
        if (this.chartData.length === 0) {
          this.renderChart([]);
        }
      }, 100);
    },
    // 处理重置
@@ -1200,21 +1428,21 @@
    // 处理类型详情
    handleTypeDetail(row) {
      this.$message.info(`查看类型详情:${row.typeName}`);
      // 这里可以跳转到详情页面或打开详情对话框
    },
    // 处理导出数据
    handleExportData(row) {
      this.$message.success(`正在导出 ${row.typeName} 数据...`);
      // 这里可以实现导出逻辑
    },
    // 格式化百分比
    formatPercent(value) {
      if (value === null || value === undefined) return "-";
      const num = parseFloat(value);
      if (isNaN(num)) return "-";
      return `${(num * 100).toFixed(2)}%`;
   if (value === null || value === undefined) return "-";
  const num = parseFloat(value);
  if (isNaN(num)) return "-";
  // 如果值小于1,认为是小数比例,需要乘以100
  const percentValue = num < 1 ? num * 100 : num;
  return `${percentValue.toFixed(2)}%`;
    },
    // 获取回收率样式类
@@ -1235,6 +1463,16 @@
      };
      return levelMap[level] || "info";
    },
    // 处理科室选择变化
    handleDeptChange() {
      this.loadData();
    },
    // 处理病区选择变化
    handleWardChange() {
      this.loadData();
    },
  },
};
</script>
src/views/patient/propaganda/particty.vue
@@ -2326,6 +2326,8 @@
              this.objyl.suitway = this.objyl.suitway.join(",");
            }
            this.objyl.templateid = this.objyl.id;
            this.form.libtemplateid = this.objyl.id;
            this.objyl.isoperation = 1;
            this.objyl.ivrLibaTemplateScriptVOList.forEach((item) => {
              item.ivrTaskScriptTargetoptionList =
vue.config.js
@@ -37,9 +37,9 @@
      [process.env.VUE_APP_BASE_API]: {
        // target: `https://www.health-y.cn/lssf`,
        // target: `http://192.168.100.10:8096`,
        target: `http://192.168.100.10:8094`,//省立同德
        // target: `http://192.168.100.10:8094`,//省立同德
        // target: `http://192.168.100.10:8095`,//新华
        // target:`http://localhost:8095`,
        target:`http://localhost:8095`,
        // target:`http://35z1t16164.qicp.vip`,
        // target: `http://192.168.100.172:8095`,
        // target: `http://192.168.101.166:8093`,