| | |
| | | <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 |
| | | :disabled=" |
| | | !templateForm.templateType || templateOptionsLoading |
| | | " |
| | | @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" |
| | |
| | | class="config-form" |
| | | > |
| | | <div class="config-fields"> |
| | | <!-- 责任科室 --> |
| | | <!-- 责任科室(多选) --> |
| | | <div class="config-field"> |
| | | <el-form-item |
| | | label="责任科室" |
| | |
| | | placeholder="请选择责任科室" |
| | | 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> |
| | | <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> |
| | | |
| | | <!-- 题目预览对话框 --> |
| | |
| | | :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" }, |
| | | ], |
| | | }, |
| | | |
| | | // 模板选项 |
| | | 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"], |
| | | |
| | | // 表单验证规则 |
| | | 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() { |
| | | return this.questionList.filter((q) => |
| | | this.satisfactionCategoryIds.includes(q.categoryid?.toString()) |
| | | ).length; |
| | | }, |
| | | |
| | | // 筛选后的题目列表 |
| | | filteredQuestionList() { |
| | | let filtered = this.questionList; |
| | | |
| | | // 筛选满意度题目 |
| | | filtered = filtered.filter((q) => |
| | | this.satisfactionCategoryIds.includes(q.categoryid?.toString()) |
| | | ); |
| | | |
| | | // 应用搜索条件 |
| | | 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() { |
| | | 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() { |
| | | 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 = []; |
| | | }, |
| | | |
| | | /** 加载问卷模板列表 */ |
| | | 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, |
| | | }; |
| | | } |
| | | } 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 = []; |
| | | |
| | | getissuelist(this.queryParams) |
| | | 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"], |
| | | |
| | | if (res.code === 200 && res.rows && res.rows.length > 0) { |
| | | const templateDetail = res.rows[0]; |
| | | |
| | | // 更新模板信息 |
| | | this.currentTemplateInfo = { |
| | | ...templateDetail, |
| | | templateName: templateDetail.svyname, |
| | | templateStatus: templateDetail.isavailable, |
| | | questionCount: templateDetail.svyTemplateLibScripts?.length || 0, |
| | | }; |
| | | |
| | | try { |
| | | if (item.otherdata) { |
| | | const otherData = JSON.parse(item.otherdata); |
| | | if (otherData.exceptionConfig) { |
| | | exceptionConfig = { |
| | | ...exceptionConfig, |
| | | ...otherData.exceptionConfig, |
| | | // 提取题目列表 |
| | | const questions = templateDetail.svyTemplateLibScripts || []; |
| | | 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("加载模板详情失败"); |
| | | }); |
| | | }, |
| | | |
| | | /** 加载语音模板详情 */ |
| | | 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); |
| | | } |
| | | } catch (error) { |
| | | console.warn("解析异常配置失败:", error); |
| | | |
| | | if (question.reportDeptCode) { |
| | | exceptionConfig.reportDept = question.reportDeptCode |
| | | .split(",") |
| | | .map((code) => code.trim()) |
| | | .filter((code) => code); |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | ...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, |
| | |
| | | }; |
| | | }); |
| | | |
| | | this.total = res.total || 0; |
| | | this.updateChangedStatus(); |
| | | } else { |
| | | this.$message.error(res.msg || "获取数据失败"); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | this.loading = false; |
| | | console.error("查询失败:", error); |
| | | this.$message.error("获取数据失败"); |
| | | }); |
| | | }, |
| | | |
| | | /** 重置模板选择 */ |
| | | handleResetTemplate() { |
| | | this.templateForm = { |
| | | templateType: "", |
| | | templateId: "", |
| | | }; |
| | | this.currentTemplateInfo = null; |
| | | this.questionList = []; |
| | | this.resetQuery(); |
| | | this.$refs.templateForm?.clearValidate(); |
| | | }, |
| | | |
| | | /** 配置变更处理 */ |
| | |
| | | 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; |
| | | }, |
| | | |
| | | /** 更新变更状态 */ |
| | |
| | | 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) { |
| | | this.handleSaveSuccess(question); |
| | | } else { |
| | | throw new Error(response.msg || "保存失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("保存失败:", error); |
| | | question.saveStatus = { |
| | | type: "error", |
| | | message: error.message || "保存失败,请稍后重试", |
| | | }; |
| | | 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) |
| | |
| | | setTimeout(() => { |
| | | question.saveStatus = null; |
| | | }, 5000); |
| | | } else { |
| | | question.saveStatus = { |
| | | type: "error", |
| | | message: response.msg || "保存失败", |
| | | }; |
| | | this.$message.error(response.msg || "保存失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("保存失败:", error); |
| | | question.saveStatus = { |
| | | type: "error", |
| | | message: "保存失败,请稍后重试", |
| | | }; |
| | | this.$message.error("保存失败,请稍后重试"); |
| | | } finally { |
| | | question.saving = false; |
| | | } |
| | | }, |
| | | |
| | | /** 重置单个题目配置 */ |
| | | /** 重置单个题目配置 */ |
| | | resetSingleConfig(question) { |
| | | this.$confirm("确定要重置当前题目的配置吗?", "提示", { |
| | |
| | | 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(); |
| | |
| | | |
| | | /** 搜索 */ |
| | | 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 { |
| | |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | |
| | |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .template-info { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 16px; |
| | | } |
| | |
| | | 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> |