| | |
| | | <div class="header-content"> |
| | | <h2 class="page-title">满意度题目异常处理配置</h2> |
| | | <p class="page-description"> |
| | | 为满意度题目配置责任科室和报备科室,优化异常反馈流程 |
| | | 基于模板配置满意度题目的责任科室和报备科室 |
| | | </p> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 搜索区域 --> |
| | | <div class="search-card"> |
| | | <!-- 模板选择区域 --> |
| | | <div class="template-section"> |
| | | <el-card shadow="never"> |
| | | <div class="template-header"> |
| | | <h3 class="template-title">模板选择</h3> |
| | | <p class="template-tip">请先选择模板类型和具体模板</p> |
| | | </div> |
| | | |
| | | <el-form |
| | | :model="templateForm" |
| | | :rules="templateRules" |
| | | ref="templateForm" |
| | | label-width="120px" |
| | | size="medium" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="模板类型" prop="templateType"> |
| | | <el-select |
| | | v-model="templateForm.templateType" |
| | | placeholder="请选择模板类型" |
| | | clearable |
| | | @change="handleTemplateTypeChange" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="问卷模板" :value="1" /> |
| | | <el-option label="语音模板" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-form-item |
| | | label="选择模板" |
| | | prop="templateId" |
| | | :rules=" |
| | | templateForm.templateType |
| | | ? [ |
| | | { |
| | | required: true, |
| | | message: '请选择模板', |
| | | trigger: 'change', |
| | | }, |
| | | ] |
| | | : [] |
| | | " |
| | | > |
| | | <el-select |
| | | v-model="templateForm.templateId" |
| | | placeholder="请选择模板" |
| | | clearable |
| | | filterable |
| | | @change="handleTemplateChange" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="template in filteredTemplateOptions" |
| | | :key="template.id" |
| | | :label="template.templateName" |
| | | :value="template.id" |
| | | /> |
| | | <div |
| | | v-if="templateOptionsLoading" |
| | | slot="empty" |
| | | class="select-loading" |
| | | > |
| | | <i class="el-icon-loading"></i> |
| | | <span>加载中...</span> |
| | | </div> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | icon="el-icon-search" |
| | | @click="handleLoadTemplate" |
| | | :loading="templateLoading" |
| | | :disabled="!templateForm.templateId" |
| | | > |
| | | 加载模板题目 |
| | | </el-button> |
| | | <el-button icon="el-icon-refresh" @click="handleResetTemplate"> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- 模板信息 --> |
| | | <div v-if="currentTemplateInfo" class="template-info-section"> |
| | | <el-card shadow="never"> |
| | | <div class="template-info"> |
| | | <div class="info-left"> |
| | | <h3 class="template-name"> |
| | | {{ currentTemplateInfo.templateName }} |
| | | </h3> |
| | | <div class="template-meta"> |
| | | <span class="meta-item"> |
| | | <i class="el-icon-s-order"></i> |
| | | 模板类型:{{ |
| | | templateForm.templateType === 1 ? "问卷模板" : "语音模板" |
| | | }} |
| | | </span> |
| | | <span class="meta-item"> |
| | | <i class="el-icon-s-management"></i> |
| | | 题目总数:{{ currentTemplateInfo.questionCount || 0 }} |
| | | </span> |
| | | <span class="meta-item"> |
| | | <i class="el-icon-star-on"></i> |
| | | 满意度题目:{{ satisfactionQuestionsCount }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div class="info-right"> |
| | | <el-tag |
| | | :type=" |
| | | currentTemplateInfo.templateStatus === 1 ? 'success' : 'info' |
| | | " |
| | | size="medium" |
| | | > |
| | | {{ currentTemplateInfo.templateStatus === 1 ? "启用" : "停用" }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- 搜索区域(题目筛选) --> |
| | | <div v-if="questionList.length > 0" class="search-section"> |
| | | <el-card shadow="never" class="search-container"> |
| | | <el-form :model="queryParams" :inline="true" size="medium"> |
| | | <el-form-item label="问题主题"> |
| | |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="是否可用"> |
| | | <el-select |
| | | v-model="queryParams.isavailable" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in qyoptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | icon="el-icon-search" |
| | | @click="handleQuery" |
| | | > |
| | | 搜索 |
| | | 筛选题目 |
| | | </el-button> |
| | | <el-button icon="el-icon-refresh" @click="resetQuery"> |
| | | 重置 |
| | | 重置筛选 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <span v-if="changedCount > 0" class="change-count"> |
| | | 有 {{ changedCount }} 项配置需要保存 |
| | | </span> |
| | | <div class="total-count">共 {{ total }} 条记录</div> |
| | | <div class="total-count"> |
| | | 共 {{ filteredQuestionList.length }} 条记录 |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="questionList.length === 0" class="empty-wrapper"> |
| | | <el-empty description="暂无满意度题目数据"> |
| | | <el-button type="primary" @click="getQuestionList" |
| | | >刷新数据</el-button |
| | | > |
| | | <div |
| | | v-else-if="questionList.length === 0 && templateForm.templateId" |
| | | class="empty-wrapper" |
| | | > |
| | | <el-empty description="该模板中暂无满意度题目"> |
| | | <p class="empty-tip"> |
| | | 请选择其他模板或检查模板中是否包含满意度类型题目(分类ID: |
| | | 404,405,406) |
| | | </p> |
| | | </el-empty> |
| | | </div> |
| | | |
| | | <!-- 一行一行的卡片列表 --> |
| | | <div v-else class="question-list"> |
| | | <div v-else-if="filteredQuestionList.length > 0" class="question-list"> |
| | | <div |
| | | v-for="(question, index) in questionList" |
| | | v-for="(question, index) in filteredQuestionList" |
| | | :key="question.id" |
| | | class="question-item" |
| | | > |
| | |
| | | {{ question.scriptTopic || "无主题" }} |
| | | </h3> |
| | | <div class="question-tags"> |
| | | <el-tag |
| | | :type="question.isavailable == 1 ? 'danger' : 'success'" |
| | | size="small" |
| | | > |
| | | {{ question.isavailable == 1 ? "不可用" : "可用" }} |
| | | </el-tag> |
| | | <dict-tag |
| | | :options="askvaluetype" |
| | | :value="question.scriptType" |
| | |
| | | </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" |
| | |
| | | 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> |
| | |
| | | class="config-form" |
| | | > |
| | | <div class="config-fields"> |
| | | <!-- 责任科室 --> |
| | | <!-- 责任科室(多选) --> |
| | | <div class="config-field"> |
| | | <el-form-item |
| | | label="责任科室" |
| | |
| | | placeholder="请选择责任科室" |
| | | filterable |
| | | clearable |
| | | multiple |
| | | style="width: 100%" |
| | | @change="handleConfigChange(question)" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptOptions" |
| | | :key="dept.id" |
| | | :label="dept.name" |
| | | :value="dept.id" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | <div class="config-tip">负责处理该题目反馈的科室</div> |
| | | <div class="config-tip"> |
| | | 负责处理该题目反馈的科室,可多选 |
| | | </div> |
| | | </el-form-item> |
| | | </div> |
| | | |
| | | <!-- 报备科室 --> |
| | | <!-- 报备科室(多选) --> |
| | | <div class="config-field"> |
| | | <el-form-item |
| | | label="报备科室" |
| | |
| | | filterable |
| | | clearable |
| | | multiple |
| | | collapse-tags |
| | | style="width: 100%" |
| | | @change="handleConfigChange(question)" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptOptions" |
| | | :key="dept.id" |
| | | :label="dept.name" |
| | | :value="dept.id" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | <div class="config-tip"> |
| | |
| | | </div> |
| | | </el-form-item> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 通知方式 --> |
| | | <div class="config-field"> |
| | | <el-form-item |
| | | label="通知方式" |
| | | prop="notifyTypes" |
| | | class="config-item" |
| | | > |
| | | <el-checkbox-group |
| | | v-model="question.exceptionConfig.notifyTypes" |
| | | @change="handleConfigChange(question)" |
| | | > |
| | | <el-checkbox label="system">系统消息</el-checkbox> |
| | | <el-checkbox label="sms">短信</el-checkbox> |
| | | <el-checkbox label="email">邮件</el-checkbox> |
| | | <el-checkbox label="wechat">企业微信</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <!-- 当前配置信息 --> |
| | | <div v-if="question.hasChanges" class="current-config"> |
| | | <div class="config-preview"> |
| | | <div class="preview-item"> |
| | | <span class="preview-label">责任科室:</span> |
| | | <span class="preview-value"> |
| | | {{ |
| | | getDeptNames( |
| | | question.exceptionConfig.responsibilityDept || [] |
| | | ).join(", ") |
| | | }} |
| | | </span> |
| | | </div> |
| | | <div class="preview-item"> |
| | | <span class="preview-label">报备科室:</span> |
| | | <span class="preview-value"> |
| | | {{ |
| | | getDeptNames( |
| | | question.exceptionConfig.reportDept || [] |
| | | ).join(", ") |
| | | }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 分页 --> |
| | | <div v-if="questionList.length > 0" class="pagination-wrapper"> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | :page.sync="queryParams.pageNum" |
| | | :limit.sync="queryParams.pageSize" |
| | | @pagination="getQuestionList" |
| | | /> |
| | | </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="题目预览" |
| | |
| | | :value="currentPreview.scriptType" |
| | | size="small" |
| | | /> |
| | | <el-tag |
| | | :type="currentPreview.isavailable === 1 ? 'success' : 'danger'" |
| | | size="small" |
| | | > |
| | | {{ currentPreview.isavailable === 1 ? "可用" : "不可用" }} |
| | | </el-tag> |
| | | <el-tag v-if="currentPreview.targetname" size="small" type="info"> |
| | | {{ currentPreview.targetname }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | <!-- 模板题目展示 --> |
| | | |
| | | <div class="preview-content"> |
| | | <div class="preview-content" v-if="templateForm.templateType == 1"> |
| | | <p class="preview-question">{{ currentPreview.scriptContent }}</p> |
| | | |
| | | <div |
| | |
| | | > |
| | | <el-radio-group v-model="previewAnswer"> |
| | | <el-radio |
| | | v-for="(option, idx) in currentPreview.svyLibScriptOptions || |
| | | []" |
| | | v-for="( |
| | | option, idx |
| | | ) in currentPreview.svyLibTemplateTargetoptions || []" |
| | | :key="idx" |
| | | :label="option.optioncontent" |
| | | class="option-item" |
| | | > |
| | | {{ option.optioncontent }} |
| | | </el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | | |
| | | <div v-else class="preview-textarea"> |
| | | <el-input |
| | | type="textarea" |
| | | placeholder="请输入回答" |
| | | v-model="previewAnswer" |
| | | :rows="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <!-- 语音题目展示 --> |
| | | <div class="preview-content" v-else> |
| | | <p class="preview-question">{{ currentPreview.scriptContent }}</p> |
| | | |
| | | <div |
| | | v-if=" |
| | | currentPreview.scriptType != 3 && currentPreview.scriptType != 4 |
| | | " |
| | | class="preview-options" |
| | | > |
| | | <el-radio-group v-model="previewAnswer"> |
| | | <el-radio |
| | | v-for="( |
| | | option, idx |
| | | ) in currentPreview.ivrLibaScriptTargetoptionList || []" |
| | | :key="idx" |
| | | :label="option.targetvalue" |
| | | class="option-item" |
| | | > |
| | | {{ option.targetvalue }} |
| | | </el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | |
| | | |
| | | <script> |
| | | import { |
| | | getissuelist, |
| | | compileissue, |
| | | getissueclassify, |
| | | compileQtemplate, |
| | | compileFollowup, |
| | | getQtemplatelist, |
| | | getFollowuplist, |
| | | getvFollowup, |
| | | getQtemplateobj, |
| | | selectInfoByConditiony, |
| | | } from "@/api/AiCentre/index"; |
| | | import { deptTreeSelect } from "@/api/system/user"; |
| | | import store from "@/store"; |
| | | import Pagination from "@/components/Pagination"; |
| | | |
| | |
| | | components: { Pagination }, |
| | | data() { |
| | | return { |
| | | // 模板表单 |
| | | templateForm: { |
| | | templateType: "", |
| | | templateId: "", |
| | | }, |
| | | templateRules: { |
| | | templateType: [ |
| | | { 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: [], // 语音模板列表 |
| | | templateOptionsLoading: false, |
| | | |
| | | // 当前模板信息 |
| | | currentTemplateInfo: null, |
| | | templateLoading: false, |
| | | |
| | | // 查询参数 |
| | | queryParams: { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | scriptTopic: "", |
| | | scriptContent: "", |
| | | targetname: "", |
| | | isavailable: "", |
| | | categoryids: "404,405,406", // 固定查询满意度类型 |
| | | }, |
| | | |
| | | // 数据列表 |
| | | questionList: [], |
| | | total: 0, |
| | | loading: false, |
| | | batchSaving: false, |
| | | |
| | | // 字典数据 |
| | | askvaluetype: store.getters.askvaluetype || [], |
| | | qyoptions: store.getters.usable || [], |
| | | mode: store.getters.mode || [], |
| | | languagelist: store.getters.languagelist || [], |
| | | |
| | | // 科室选项 |
| | | deptOptions: [], |
| | |
| | | hasChanges: false, |
| | | changedCount: 0, |
| | | |
| | | // 满意度分类ID |
| | | satisfactionCategoryIds: ["404", "405", "406", "10039", "10041", "10042"], |
| | | questionnaireCategorys: [], |
| | | voiceCategories: [], |
| | | // 表单验证规则 |
| | | configRules: { |
| | | responsibilityDept: [ |
| | | { required: true, message: "请选择责任科室", trigger: "change" }, |
| | | ], |
| | | reportDept: [ |
| | | { |
| | | required: true, |
| | | message: "请选择至少一个报备科室", |
| | | message: "请至少选择一个责任科室", |
| | | trigger: "change", |
| | | }, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (!value || value.length === 0) { |
| | | callback(new Error("请选择至少一个报备科室")); |
| | | callback(new Error("请至少选择一个责任科室")); |
| | | } else { |
| | | callback(); |
| | | } |
| | |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | notifyTypes: [ |
| | | reportDept: [ |
| | | { |
| | | required: true, |
| | | message: "请至少选择一个报备科室", |
| | | trigger: "change", |
| | | }, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (!value || value.length === 0) { |
| | | callback(new Error("请至少选择一种通知方式")); |
| | | callback(new Error("请至少选择一个报备科室")); |
| | | } else { |
| | | callback(); |
| | | } |
| | |
| | | }, |
| | | }; |
| | | }, |
| | | computed: { |
| | | // 根据模板类型过滤模板选项 |
| | | filteredTemplateOptions() { |
| | | if (this.templateForm.templateType === 1) { |
| | | return this.questionnaireTemplates; |
| | | } else if (this.templateForm.templateType === 2) { |
| | | return this.followupTemplates; |
| | | } |
| | | return []; |
| | | }, |
| | | |
| | | // 满意度题目数量 |
| | | satisfactionQuestionsCount() { |
| | | if (this.templateForm.templateType === 1) { |
| | | return this.questionList.filter((q) => |
| | | this.questionnaireCategorys.includes(q.categoryid) |
| | | ).length; |
| | | } else if (this.templateForm.templateType === 2) { |
| | | return this.questionList.filter((q) => |
| | | 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.questionnaireCategorys.includes(q.categoryid) |
| | | ); |
| | | } else if (this.templateForm.templateType === 2) { |
| | | filtered = filtered.filter((q) => |
| | | this.voiceCategories.includes(q.scriptAssortid) |
| | | ); |
| | | } |
| | | |
| | | // 应用搜索条件 |
| | | if (this.queryParams.scriptTopic) { |
| | | const keyword = this.queryParams.scriptTopic.toLowerCase(); |
| | | filtered = filtered.filter( |
| | | (q) => q.scriptTopic && q.scriptTopic.toLowerCase().includes(keyword) |
| | | ); |
| | | } |
| | | |
| | | if (this.queryParams.scriptContent) { |
| | | const keyword = this.queryParams.scriptContent.toLowerCase(); |
| | | filtered = filtered.filter( |
| | | (q) => |
| | | q.scriptContent && q.scriptContent.toLowerCase().includes(keyword) |
| | | ); |
| | | } |
| | | |
| | | return filtered; |
| | | }, |
| | | }, |
| | | 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.getQuestionList(); |
| | | this.loadAllTemplates(); |
| | | }, |
| | | methods: { |
| | | /** 加载所有模板列表 */ |
| | | loadAllTemplates() { |
| | | this.templateOptionsLoading = true; |
| | | |
| | | // 并行加载问卷模板和语音模板 |
| | | Promise.all([ |
| | | this.loadQuestionnaireTemplates(), |
| | | this.loadFollowupTemplates(), |
| | | ]).finally(() => { |
| | | this.templateOptionsLoading = false; |
| | | }); |
| | | }, |
| | | |
| | | /** 查询科室列表 */ |
| | | getDeptOptions() { |
| | | getissueclassify({}) |
| | | deptTreeSelect() |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | this.deptOptions = res.rows || []; |
| | | if (res.code == 200) { |
| | | this.deptOptions = this.flattenArray(res.data) || []; |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | |
| | | }); |
| | | }, |
| | | |
| | | /** 查询满意度题目列表 */ |
| | | getQuestionList() { |
| | | this.loading = true; |
| | | flattenArray(multiArray) { |
| | | let result = []; |
| | | |
| | | function flatten(element) { |
| | | if (element.children && element.children.length > 0) { |
| | | element.children.forEach((child) => flatten(child)); |
| | | } else { |
| | | let item = JSON.parse(JSON.stringify(element)); |
| | | result.push(item); |
| | | } |
| | | } |
| | | |
| | | multiArray.forEach((element) => flatten(element)); |
| | | return result; |
| | | }, |
| | | |
| | | /** 根据科室编码获取科室名称 */ |
| | | getDeptName(deptCode) { |
| | | if (!deptCode) return ""; |
| | | const dept = this.deptOptions.find((d) => d.deptCode === deptCode); |
| | | return dept ? dept.label : deptCode; |
| | | }, |
| | | |
| | | /** 根据科室编码数组获取科室名称数组 */ |
| | | getDeptNames(deptCodes) { |
| | | if (!Array.isArray(deptCodes) || deptCodes.length === 0) return []; |
| | | return deptCodes |
| | | .map((code) => this.getDeptName(code)) |
| | | .filter((name) => name && name.trim()); |
| | | }, |
| | | |
| | | /** 模板类型变更 */ |
| | | handleTemplateTypeChange() { |
| | | this.templateForm.templateId = ""; |
| | | this.currentTemplateInfo = null; |
| | | this.questionList = []; |
| | | }, |
| | | |
| | | getissuelist(this.queryParams) |
| | | /** 加载问卷模板列表 */ |
| | | loadQuestionnaireTemplates() { |
| | | return new Promise((resolve) => { |
| | | getQtemplatelist({ pageSize: 1000 }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | this.questionnaireTemplates = (res.rows || []).map((item) => ({ |
| | | id: item.svyid, |
| | | templateName: item.svyname, |
| | | isavailable: item.isavailable, |
| | | })); |
| | | } else { |
| | | this.$message.error(res.msg || "加载问卷模板失败"); |
| | | } |
| | | resolve(); |
| | | }) |
| | | .catch((error) => { |
| | | console.error("加载问卷模板失败:", error); |
| | | this.$message.error("加载问卷模板失败"); |
| | | resolve(); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | /** 加载语音模板列表 */ |
| | | loadFollowupTemplates() { |
| | | return new Promise((resolve) => { |
| | | getFollowuplist({ pageSize: 1000 }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | this.followupTemplates = (res.rows || []).map((item) => ({ |
| | | id: item.id, |
| | | templateName: item.templateName, |
| | | isavailable: item.isavailable, |
| | | })); |
| | | } else { |
| | | this.$message.error(res.msg || "加载语音模板失败"); |
| | | } |
| | | resolve(); |
| | | }) |
| | | .catch((error) => { |
| | | console.error("加载语音模板失败:", error); |
| | | this.$message.error("加载语音模板失败"); |
| | | resolve(); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | /** 模板选择变更 */ |
| | | handleTemplateChange(templateId) { |
| | | if (templateId) { |
| | | const selectedTemplate = this.filteredTemplateOptions.find( |
| | | (t) => t.id === templateId |
| | | ); |
| | | if (selectedTemplate) { |
| | | this.currentTemplateInfo = { |
| | | templateName: selectedTemplate.templateName, |
| | | templateStatus: selectedTemplate.isavailable, |
| | | questionCount: 0, |
| | | }; |
| | | } |
| | | // 加载模板详情数据 |
| | | this.templateLoading = true; |
| | | this.loading = true; |
| | | this.questionList = []; |
| | | |
| | | if (this.templateForm.templateType === 1) { |
| | | this.loadQuestionnaireTemplateDetail(); |
| | | } else if (this.templateForm.templateType === 2) { |
| | | this.loadFollowupTemplateDetail(); |
| | | } |
| | | } else { |
| | | this.currentTemplateInfo = null; |
| | | this.questionList = []; |
| | | } |
| | | }, |
| | | |
| | | /** 加载模板详情和题目 */ |
| | | handleLoadTemplate() { |
| | | this.$refs.templateForm.validate((valid) => { |
| | | if (!valid) { |
| | | this.$message.warning("请先选择模板"); |
| | | return; |
| | | } |
| | | |
| | | this.templateLoading = true; |
| | | this.loading = true; |
| | | this.questionList = []; |
| | | |
| | | if (this.templateForm.templateType === 1) { |
| | | this.loadQuestionnaireTemplateDetail(); |
| | | } else if (this.templateForm.templateType === 2) { |
| | | this.loadFollowupTemplateDetail(); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | /** 加载问卷模板详情 */ |
| | | loadQuestionnaireTemplateDetail() { |
| | | getQtemplateobj({ svyid: this.templateForm.templateId }) |
| | | .then((res) => { |
| | | this.templateLoading = false; |
| | | this.loading = false; |
| | | if (res.code === 200) { |
| | | this.questionList = (res.rows || []).map((item) => { |
| | | // 解析异常处理配置 |
| | | let exceptionConfig = { |
| | | responsibilityDept: "", |
| | | reportDept: [], |
| | | notifyTypes: ["system"], |
| | | }; |
| | | |
| | | try { |
| | | if (item.otherdata) { |
| | | const otherData = JSON.parse(item.otherdata); |
| | | if (otherData.exceptionConfig) { |
| | | exceptionConfig = { |
| | | ...exceptionConfig, |
| | | ...otherData.exceptionConfig, |
| | | }; |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.warn("解析异常配置失败:", error); |
| | | } |
| | | if (res.code === 200 && res.rows && res.rows.length > 0) { |
| | | const templateDetail = res.rows[0]; |
| | | |
| | | return { |
| | | ...item, |
| | | originalConfig: JSON.parse(JSON.stringify(exceptionConfig)), |
| | | exceptionConfig: exceptionConfig, |
| | | hasChanges: false, |
| | | saving: false, |
| | | saveStatus: null, |
| | | }; |
| | | }); |
| | | // 更新模板信息 |
| | | this.currentTemplateInfo = { |
| | | ...templateDetail, |
| | | templateName: templateDetail.svyname, |
| | | templateStatus: templateDetail.isavailable, |
| | | questionCount: templateDetail.svyTemplateLibScripts?.length || 0, |
| | | }; |
| | | |
| | | this.total = res.total || 0; |
| | | this.updateChangedStatus(); |
| | | // 提取题目列表 |
| | | const questions = templateDetail.svyTemplateLibScripts || []; |
| | | this.processQuestions(questions); |
| | | |
| | | this.$message.success(`成功加载 ${questions.length} 个题目`); |
| | | } else { |
| | | this.$message.error(res.msg || "获取数据失败"); |
| | | this.$message.error(res.msg || "加载模板详情失败"); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | this.templateLoading = false; |
| | | this.loading = false; |
| | | console.error("查询失败:", error); |
| | | this.$message.error("获取数据失败"); |
| | | console.error("加载问卷模板详情失败:", error); |
| | | this.$message.error("加载模板详情失败"); |
| | | }); |
| | | }, |
| | | |
| | | /** 加载语音模板详情 */ |
| | | loadFollowupTemplateDetail() { |
| | | getvFollowup({ id: this.templateForm.templateId }) |
| | | .then((res) => { |
| | | this.templateLoading = false; |
| | | this.loading = false; |
| | | |
| | | if (res.code === 200) { |
| | | const templateDetail = res.data; |
| | | |
| | | // 更新模板信息 |
| | | this.currentTemplateInfo = { |
| | | ...this.currentTemplateInfo, |
| | | templateName: templateDetail.templateName, |
| | | templateStatus: templateDetail.isavailable, |
| | | questionCount: |
| | | templateDetail.ivrLibaTemplateScriptVOList?.length || 0, |
| | | }; |
| | | |
| | | // 提取题目列表 |
| | | const questions = templateDetail.ivrLibaTemplateScriptVOList || []; |
| | | this.processQuestions(questions); |
| | | |
| | | this.$message.success(`成功加载 ${questions.length} 个题目`); |
| | | } else { |
| | | this.$message.error(res.msg || "加载模板详情失败"); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | this.templateLoading = false; |
| | | this.loading = false; |
| | | console.error("加载语音模板详情失败:", error); |
| | | this.$message.error("加载模板详情失败"); |
| | | }); |
| | | }, |
| | | |
| | | /** 处理题目数据 */ |
| | | processQuestions(questions) { |
| | | this.questionList = questions.map((question) => { |
| | | // 解析责任科室和报备科室 |
| | | let exceptionConfig = { |
| | | responsibilityDept: [], // 责任科室编码数组 |
| | | reportDept: [], // 报备科室编码数组 |
| | | }; |
| | | |
| | | // 从题目顶层字段读取数据 |
| | | if (question.dutyDeptCode) { |
| | | // 从逗号分隔的字符串转为数组 |
| | | exceptionConfig.responsibilityDept = question.dutyDeptCode |
| | | .split(",") |
| | | .map((code) => code.trim()) |
| | | .filter((code) => code); |
| | | } |
| | | |
| | | if (question.reportDeptCode) { |
| | | exceptionConfig.reportDept = question.reportDeptCode |
| | | .split(",") |
| | | .map((code) => code.trim()) |
| | | .filter((code) => code); |
| | | } |
| | | |
| | | return { |
| | | ...question, |
| | | // 统一字段名 |
| | | id: question.id || question.scriptId, |
| | | scriptTopic: question.scriptTopic || question.scriptTopic, |
| | | scriptContent: question.scriptContent || question.scriptContent, |
| | | scriptType: question.scriptType, |
| | | isavailable: question.isavailable, |
| | | targetname: question.targetname, |
| | | categoryid: question.categoryid || question.categoryid, |
| | | originalConfig: JSON.parse(JSON.stringify(exceptionConfig)), |
| | | exceptionConfig: exceptionConfig, |
| | | hasChanges: false, |
| | | saving: false, |
| | | saveStatus: null, |
| | | }; |
| | | }); |
| | | |
| | | this.updateChangedStatus(); |
| | | }, |
| | | |
| | | /** 重置模板选择 */ |
| | | handleResetTemplate() { |
| | | this.templateForm = { |
| | | templateType: "", |
| | | templateId: "", |
| | | }; |
| | | this.currentTemplateInfo = null; |
| | | this.questionList = []; |
| | | this.resetQuery(); |
| | | this.$refs.templateForm?.clearValidate(); |
| | | }, |
| | | |
| | | /** 配置变更处理 */ |
| | | handleConfigChange(question) { |
| | | this.$nextTick(() => { |
| | | const index = this.questionList.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) { |
| | |
| | | isConfigEqual(config1, config2) { |
| | | if (!config1 || !config2) return false; |
| | | |
| | | const report1 = [...(config1.reportDept || [])].sort().join(","); |
| | | const report2 = [...(config2.reportDept || [])].sort().join(","); |
| | | const notify1 = [...(config1.notifyTypes || [])].sort().join(","); |
| | | const notify2 = [...(config2.notifyTypes || [])].sort().join(","); |
| | | const responsibility1 = [...(config1.responsibilityDept || [])] |
| | | .sort() |
| | | .join(",") |
| | | .toLowerCase(); |
| | | const responsibility2 = [...(config2.responsibilityDept || [])] |
| | | .sort() |
| | | .join(",") |
| | | .toLowerCase(); |
| | | const report1 = [...(config1.reportDept || [])] |
| | | .sort() |
| | | .join(",") |
| | | .toLowerCase(); |
| | | const report2 = [...(config2.reportDept || [])] |
| | | .sort() |
| | | .join(",") |
| | | .toLowerCase(); |
| | | |
| | | return ( |
| | | config1.responsibilityDept === config2.responsibilityDept && |
| | | report1 === report2 && |
| | | notify1 === notify2 |
| | | ); |
| | | return responsibility1 === responsibility2 && report1 === report2; |
| | | }, |
| | | |
| | | /** 更新变更状态 */ |
| | |
| | | async saveSingleConfig(question) { |
| | | if (!question.hasChanges) return; |
| | | |
| | | const index = this.questionList.findIndex((q) => q.id === question.id); |
| | | const index = this.filteredQuestionList.findIndex( |
| | | (q) => q.id === question.id |
| | | ); |
| | | console.log(index, "filteredQuestionList"); |
| | | |
| | | if (index === -1) return; |
| | | |
| | | const formRef = this.$refs.configForm && this.$refs.configForm[index]; |
| | |
| | | question.saveStatus = null; |
| | | |
| | | try { |
| | | // 构建保存数据 |
| | | const saveData = { |
| | | id: question.id, |
| | | isoperation: 2, // 修改操作 |
| | | ...question, |
| | | // 获取当前模板详情 |
| | | let templateDetail; |
| | | if (this.templateForm.templateType === 1) { |
| | | // 问卷模板 |
| | | const res = await getQtemplateobj({ |
| | | svyid: this.templateForm.templateId, |
| | | }); |
| | | if (res.code !== 200 || !res.rows || res.rows.length === 0) { |
| | | throw new Error(res.msg || "获取模板详情失败"); |
| | | } |
| | | templateDetail = res.rows[0]; |
| | | } else if (this.templateForm.templateType === 2) { |
| | | // 语音模板 |
| | | const res = await getvFollowup({ id: this.templateForm.templateId }); |
| | | if (res.code !== 200) { |
| | | throw new Error(res.msg || "获取模板详情失败"); |
| | | } |
| | | templateDetail = res.data; |
| | | } |
| | | |
| | | // 更新题目配置 |
| | | let updatedTemplateDetail = { ...templateDetail }; |
| | | let questionsField = |
| | | this.templateForm.templateType === 1 |
| | | ? "svyTemplateLibScripts" |
| | | : "ivrLibaTemplateScriptVOList"; |
| | | |
| | | const questions = updatedTemplateDetail[questionsField] || []; |
| | | const questionIndex = questions.findIndex((q) => q.id === question.id); |
| | | |
| | | if (questionIndex === -1) { |
| | | throw new Error("未找到题目"); |
| | | } |
| | | |
| | | // 获取科室名称 |
| | | const responsibilityDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.responsibilityDept |
| | | ); |
| | | const reportDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.reportDept |
| | | ); |
| | | |
| | | // 直接更新题目顶层字段 |
| | | questions[questionIndex] = { |
| | | ...questions[questionIndex], |
| | | // 设置Excel要求的字段 |
| | | dutyDeptCode: question.exceptionConfig.responsibilityDept.join(","), |
| | | dutyDeptName: responsibilityDeptNames.join(","), |
| | | reportDeptCode: question.exceptionConfig.reportDept.join(","), |
| | | reportDeptName: reportDeptNames.join(","), |
| | | }; |
| | | |
| | | // 将异常配置保存到 otherdata |
| | | const otherData = JSON.parse(question.otherdata || "{}"); |
| | | otherData.exceptionConfig = question.exceptionConfig; |
| | | saveData.otherdata = JSON.stringify(otherData); |
| | | // 更新模板 |
| | | updatedTemplateDetail[questionsField] = questions; |
| | | |
| | | // 移除不需要的字段 |
| | | delete saveData.originalConfig; |
| | | delete saveData.hasChanges; |
| | | delete saveData.saving; |
| | | delete saveData.saveStatus; |
| | | delete saveData.exceptionConfig; |
| | | |
| | | const response = await compileissue(saveData); |
| | | // 保存模板 |
| | | let response; |
| | | if (this.templateForm.templateType === 1) { |
| | | response = await compileQtemplate({ |
| | | ...updatedTemplateDetail, |
| | | id: this.templateForm.templateId, |
| | | isoperation: 2, |
| | | }); |
| | | } else { |
| | | response = await compileFollowup({ |
| | | ...updatedTemplateDetail, |
| | | id: this.templateForm.templateId, |
| | | isoperation: 2, |
| | | }); |
| | | } |
| | | |
| | | if (response.code === 200) { |
| | | // 更新原始配置 |
| | | question.originalConfig = JSON.parse( |
| | | JSON.stringify(question.exceptionConfig) |
| | | ); |
| | | question.hasChanges = false; |
| | | question.saveStatus = { |
| | | type: "success", |
| | | message: "配置保存成功", |
| | | }; |
| | | |
| | | this.updateChangedStatus(); |
| | | this.$message.success("配置保存成功"); |
| | | |
| | | // 5秒后清除成功提示 |
| | | setTimeout(() => { |
| | | question.saveStatus = null; |
| | | }, 5000); |
| | | this.handleSaveSuccess(question); |
| | | } else { |
| | | question.saveStatus = { |
| | | type: "error", |
| | | message: response.msg || "保存失败", |
| | | }; |
| | | this.$message.error(response.msg || "保存失败"); |
| | | throw new Error(response.msg || "保存失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("保存失败:", error); |
| | | question.saveStatus = { |
| | | type: "error", |
| | | message: "保存失败,请稍后重试", |
| | | message: error.message || "保存失败,请稍后重试", |
| | | }; |
| | | this.$message.error("保存失败,请稍后重试"); |
| | | this.$message.error(error.message || "保存失败,请稍后重试"); |
| | | } finally { |
| | | question.saving = false; |
| | | } |
| | | }, |
| | | |
| | | /** 处理保存成功 */ |
| | | /** 处理保存成功 */ |
| | | handleSaveSuccess(question) { |
| | | // 同时更新题目顶层字段 |
| | | const responsibilityDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.responsibilityDept |
| | | ); |
| | | const reportDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.reportDept |
| | | ); |
| | | |
| | | // 更新题目本身的字段 |
| | | question.dutyDeptCode = |
| | | question.exceptionConfig.responsibilityDept.join(","); |
| | | question.dutyDeptName = responsibilityDeptNames.join(","); |
| | | question.reportDeptCode = question.exceptionConfig.reportDept.join(","); |
| | | question.reportDeptName = reportDeptNames.join(","); |
| | | |
| | | // 更新原始配置 |
| | | question.originalConfig = JSON.parse( |
| | | JSON.stringify(question.exceptionConfig) |
| | | ); |
| | | question.hasChanges = false; |
| | | question.saveStatus = { |
| | | type: "success", |
| | | message: "配置保存成功", |
| | | }; |
| | | |
| | | this.updateChangedStatus(); |
| | | this.$message.success("配置保存成功"); |
| | | |
| | | // 5秒后清除成功提示 |
| | | setTimeout(() => { |
| | | question.saveStatus = null; |
| | | }, 5000); |
| | | }, |
| | | |
| | | /** 重置单个题目配置 */ |
| | |
| | | question.exceptionConfig = JSON.parse( |
| | | JSON.stringify(question.originalConfig) |
| | | ); |
| | | // 同时重置题目顶层字段 |
| | | const responsibilityDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.responsibilityDept |
| | | ); |
| | | const reportDeptNames = this.getDeptNames( |
| | | question.exceptionConfig.reportDept |
| | | ); |
| | | |
| | | question.dutyDeptCode = |
| | | question.exceptionConfig.responsibilityDept.join(","); |
| | | question.dutyDeptName = responsibilityDeptNames.join(","); |
| | | question.reportDeptCode = |
| | | question.exceptionConfig.reportDept.join(","); |
| | | question.reportDeptName = reportDeptNames.join(","); |
| | | |
| | | question.hasChanges = false; |
| | | question.saveStatus = null; |
| | | this.updateChangedStatus(); |
| | |
| | | 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() { |
| | | this.queryParams.pageNum = 1; |
| | | this.getQuestionList(); |
| | | // 仅筛选显示,不需要重新加载 |
| | | }, |
| | | |
| | | /** 重置搜索 */ |
| | | resetQuery() { |
| | | this.queryParams = { |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | scriptTopic: "", |
| | | scriptContent: "", |
| | | targetname: "", |
| | | isavailable: "", |
| | | categoryids: "404,405,406", |
| | | }; |
| | | this.getQuestionList(); |
| | | }, |
| | | }, |
| | | }; |
| | |
| | | } |
| | | } |
| | | |
| | | .search-card { |
| | | .template-section { |
| | | margin-bottom: 20px; |
| | | |
| | | .template-header { |
| | | margin-bottom: 20px; |
| | | |
| | | .template-title { |
| | | margin: 0 0 8px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .template-tip { |
| | | margin: 0; |
| | | color: #909399; |
| | | font-size: 13px; |
| | | } |
| | | } |
| | | |
| | | .select-loading { |
| | | text-align: center; |
| | | padding: 10px; |
| | | color: #909399; |
| | | |
| | | i { |
| | | margin-right: 8px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .template-info-section { |
| | | margin-bottom: 20px; |
| | | |
| | | .template-info { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 5px 0; |
| | | |
| | | .info-left { |
| | | .template-name { |
| | | margin: 0 0 10px 0; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .template-meta { |
| | | display: flex; |
| | | gap: 20px; |
| | | flex-wrap: wrap; |
| | | |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | |
| | | i { |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .search-section { |
| | | margin-bottom: 20px; |
| | | |
| | | .search-container { |
| | |
| | | .empty-wrapper { |
| | | min-height: 400px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .empty-tip { |
| | | margin-top: 10px; |
| | | color: #909399; |
| | | font-size: 13px; |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | .question-list { |
| | |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | .current-config { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | background: #f0f9ff; |
| | | border-radius: 6px; |
| | | border: 1px solid #d0ebff; |
| | | |
| | | .config-preview { |
| | | .preview-item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | margin-bottom: 8px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .preview-label { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | font-weight: 500; |
| | | min-width: 80px; |
| | | } |
| | | |
| | | .preview-value { |
| | | font-size: 13px; |
| | | color: #303133; |
| | | line-height: 1.5; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .config-footer { |
| | | display: flex; |
| | | justify-content: space-between; |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination-wrapper { |
| | | margin-top: 20px; |
| | | padding: 20px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | } |
| | | .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; |
| | |
| | | .page-header { |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .template-info { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .search-card { |
| | |
| | | gap: 16px; |
| | | } |
| | | |
| | | .current-config { |
| | | padding: 12px; |
| | | } |
| | | |
| | | .config-footer { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | <style lang="scss"> |
| | | .config-form { |
| | | .el-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .el-checkbox { |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .option-item { |
| | | .el-radio__label { |
| | | display: block; |
| | | padding-left: 8px; |
| | | } |
| | | } |
| | | </style> |