【案例1】el-dialog点击空白不允许关闭,只能点击关闭和取消按钮才消失

本文介绍了如何在Element UI的Dialog组件中阻止通过点击空白区域或按ESC键关闭,提供了两种方法:一是设置Dialog属性,二是全局修改Element UI的默认配置。适合调整某些业务场景下的Dialog行为。

这里写自定义目录标题

使用场景

在使用Dialog组件时,当点击弹框外的空白处时,仍然会触发关闭弹框事件,一些业务场景不适合使用这种交互,需要只能点击关闭和取消按钮才消失。
在这里插入图片描述

方法一

  // close-on-click-modal	是否可以通过点击 modal 关闭 Dialog
  // close-on-press-escape	是否可以通过按下 ESC 关闭 Dialog
  <el-dialog
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      v-if="businessReview"
      :visible.sync="businessReview"
      title="业务"
      top="25vh"
      width="320px"
      class="business-review-dialog">
  </el-dialog>

方法二

	// main.js 中可以全局设置 点击空白处、按下ESC不能关闭Dialog弹窗
	// 首先你得保证在main.js里面引入了 element-ui
	import ElementUI from 'element-ui'
	
	// 全局修改默认配置,点击空白处不能关闭弹窗
	ElementUI.Dialog.props.closeOnClickModal.default = false
	// 全局修改默认配置,按下ESC不能关闭弹窗
	ElementUI.Dialog.props.closeOnPressEscape.default = false
	
	Vue.use(ElementUI)


<template> <div class="app-container"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px" > <el-form-item label="seasonNo" prop="seasonNo" label-width="140"> <el-input v-model="queryParams.seasonNo" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item label="colorwayid" prop="colorwayid" label-width="140"> <el-input v-model="queryParams.colorwayid" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item label="modelNo" prop="modelNo" label-width="140"> <el-input v-model="queryParams.modelNo" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item> <el-button type="primary" icon="Search" @click="handleQuery" >搜索</el-button > <el-button icon="Refresh" @click="resetQuery">重置</el-button> </el-form-item> <el-form-item> <el-button type="primary" plain @click="handleTest">测试</el-button> </el-form-item> </el-form> <el-table v-loading="loading" :data="qsList"> <el-table-column label="seasonNo" align="center" prop="seasonNo"> </el-table-column> <el-table-column label="stage" align="center" prop="stage"> </el-table-column> <el-table-column label="modelNo" align="center" prop="modelNo"> </el-table-column> <el-table-column label="Colorway Name" align="center" prop="colorwayid"> </el-table-column> <el-table-column label="pattern" align="center" prop="pattern"> </el-table-column> <el-table-column label="firstsource" align="center" prop="firstsource"> </el-table-column> <el-table-column label="Operate" align="center" class-name="small-padding fixed-width" > <template #default="scope"> <el-button link type="primary" icon="Plus" @click="handleCreate(scope.row)" ></el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </div> <el-dialog title="测试" v-model="testDialog" width="1600px" append-to-body> <div> <el-form :model="queryParams" ref="testRef" :inline="true" label-width="108px" > <el-form-item label="seasonNo" prop="seasonNo" label-width="140"> <el-input v-model="queryParams.seasonNo" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item label="colorwayid" prop="colorwayid" label-width="140"> <el-input v-model="queryParams.colorwayid" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item label="modelNo" prop="modelNo" label-width="140"> <el-input v-model="queryParams.modelNo" placeholder="please input" clearable @keyup.enter="handleQueryTest" /> </el-form-item> <el-form-item> <el-button type="primary" icon="Search" @click="handleQueryTest" >Search</el-button > <el-button icon="Refresh" @click="resetQueryTest">Reset</el-button> </el-form-item> </el-form> <el-table v-loading="loading" :data="ptrList"> <el-table-column label="seasonNo" align="center" prop="seasonNo"> </el-table-column> <el-table-column label="stage" align="center" prop="stage"> </el-table-column> <el-table-column label="modelNo" align="center" prop="modelNo"> </el-table-column> <el-table-column label="Colorway Name" align="center" prop="colorwayid"> </el-table-column> <el-table-column label="pattern" align="center" prop="pattern"> </el-table-column> <el-table-column label="firstsource" align="center" prop="firstsource"> </el-table-column> <el-table-column label="Operate" align="center" class-name="small-padding fixed-width" > <template #default="scope"> <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" ></el-button> </template> </el-table-column> </el-table> <pagination v-show="totalTest > 0" :total="totalTest" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getTestList" /> </div> </el-dialog> <el-dialog title="创建" v-model="createDialog" width="1800px" append-to-body> <div> <el-tabs v-model="activeTab" type="card"> <el-tab-pane v-for="(item, index) in mainList" :key="index" :label="getTabLabel(item)" :name="index.toString()" > <!-- 每个标签页使用独立的表单数据结构 --> <div v-if="currentTabData[index]"> <!-- 基本信息区域(只读) --> <el-card header="基本信息" style="margin-bottom: 20px"> <el-form :model="currentTabData[index].baseInfo" label-width="120px" :inline="true" > <el-form-item label="Dev Style Name"> <el-input v-model="currentTabData[index].baseInfo.modelNo" readonly style="width: 100px" class="readonly-input" /> <el-input v-model="currentTabData[index].baseInfo.modelName" readonly style="width: 150px" class="readonly-input" /> </el-form-item> <el-form-item label="Color Way Name" label-width="125"> <el-input v-model="currentTabData[index].baseInfo.colorwayid" readonly style="width: 100px" class="readonly-input" /> <el-input v-model="currentTabData[index].baseInfo.colorwayname" readonly style="width: 400px" class="readonly-input" /> <el-input v-model="currentTabData[index].baseInfo.bomid" readonly style="width: 100px" class="readonly-input" /> </el-form-item> <el-form-item label="Stage" label-width="60"> <el-input v-model="currentTabData[index].baseInfo.stage" readonly class="readonly-input" style="width: 120px" /> </el-form-item> <el-form-item label="DPA/STYLE" prop="prodCd" label-width="100"> <el-input v-model="currentTabData[index].baseInfo.prodCd" readonly class="readonly-input" /> </el-form-item> <el-form-item label="Season"> <el-input v-model="currentTabData[index].baseInfo.seasonNo" readonly class="readonly-input" style="width: 100px" /> </el-form-item> <el-form-item label="Version" prop="pfcVersion" label-width="80" > <el-input v-model="currentTabData[index].baseInfo.pfcVersion" readonly style="width: 120px" class="readonly-input" /> </el-form-item> <el-form-item label="Tool Code" prop="pattern" label-width="100" > <el-input v-model="currentTabData[index].baseInfo.pattern" readonly class="readonly-input" /> </el-form-item> <el-form-item label="PFC手册号" prop="pfcNo" label-width="100"> <el-input v-model="currentTabData[index].baseInfo.pfcNo" readonly style="width: 100px" class="readonly-input" /> </el-form-item> <el-form-item label="Page Number"> <el-input v-model="currentTabData[index].baseInfo.pageNumber" readonly class="readonly-input" style="width: 80px" /> <el-input v-model="currentTabData[index].baseInfo.stepno" readonly class="readonly-input" style="width: 80px" /> </el-form-item> <el-form-item label="鞋码类型" prop="sizeType" label-width="100" > <el-input v-model="currentTabData[index].baseInfo.sizeType" readonly class="readonly-input" /> </el-form-item> </el-form> </el-card> <!-- 图片上传区域 --> <el-card header="图片上传" style="margin-bottom: 20px"> </el-card> <!-- 可编辑的表单区域 --> <el-card header="编辑信息"> <!-- <el-form :model="currentTabData[index].formData" :rules="getTabRules(index)" label-width="120px" > <el-row> <el-col :span="8"> <el-form-item label="STYLE NO" prop="styleNo"> <el-input v-model="currentTabData[index].formData.styleNo" placeholder="Please input" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="DPA" prop="dpa"> <el-input v-model="currentTabData[index].formData.dpa" placeholder="Please input" /> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="TD CODE" prop="tdCode"> <el-input v-model="currentTabData[index].formData.tdCode" placeholder="Please input" /> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="FACTORY" prop="factory"> <el-input v-model="currentTabData[index].formData.factory" placeholder="Please input" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="8"> <el-form-item label="LAB TEST" prop="labTest"> <el-input v-model="currentTabData[index].formData.labTest" placeholder="Please input" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="APPROVED BY" prop="approvedBy" label-width="130" > <el-input v-model="currentTabData[index].formData.approvedBy" placeholder="Please input" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="APPROVED DATE" prop="approvedDate" label-width="180" > <el-date-picker v-model="currentTabData[index].formData.approvedDate" type="date" placeholder="选择日期" style="width: 50%" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="16"> <el-form-item label="具体描述" prop="description"> <el-input v-model="currentTabData[index].formData.description" placeholder="Please input" type="textarea" :rows="2" /> </el-form-item> </el-col> </el-row> </el-form> --> <!-- 动态数据行区域(根据标签页类型显示同的字段) --> <div style="margin-top: 20px"> <!-- 将模板选择器列表标题放在同一行 --> <div style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; " > <!-- 左侧:模板选择器 --> <div style="display: flex; align-items: center; flex: 1"> <span style=" font-weight: bold; margin-right: 10px; min-width: 100px; " >测试用例列表</span > <el-cascader v-model="currentTabData[index].selectedTemplate" :options="currentTabData[index].templateOptions" :props="getCascaderProps(index)" placeholder="请选择测试用例模板" clearable filterable style="width: 350px; margin-right: 10px" @change="handleTemplateSelect(index)" /> </div> <!-- 右侧:按钮标题 --> <!-- <div style="display: flex; align-items: center;"> <el-button type="primary" size="small" @click="addDataRow(index)" >添加空白用例</el-button> </div> --> </div> <el-table :data="currentTabData[index].dataRows" border v-loading="currentTabData[index].loading" > <!-- 根据页面类型动态显示列 --> <el-table-column v-for="column in getTableColumns(index)" :key="column.prop" :label="column.label" :prop="column.prop" :width="column.width" > <template #default="scope"> <el-input v-model="scope.row[column.prop]" size="small" /> </template> </el-table-column> <el-table-column label="Operate" width="80"> <template #default="scope"> <el-button link type="danger" size="small" @click="removeDataRow(index, scope.$index)" >delete</el-button > </template> </el-table-column> </el-table> </div> </el-card> </div> </el-tab-pane> </el-tabs> <!-- 底部操作按钮 --> <div style="text-align: center; margin-top: 20px"> <!-- <el-button type="primary" @click="handleSaveAll">保存所有</el-button> --> <el-button type="success" @click="handleSaveCurrent" >保存当前</el-button > <el-button @click="createDialog = false">取消</el-button> </div> </div> </el-dialog> </template> <script setup name="User"> import { ptrInfo, addQualityStand, qsInfo, getQsMain, getTemplateTree, saveQualityStandards, getSavedQualityStandards, uploadImages, getImages, deleteImage, } from "@/api/quality/standard/quasd.js"; import { getSrc } from "@/utils/interceptSrc"; const { proxy } = getCurrentInstance(); const qsList = ref([]); const ptrList = ref([]); const mainList = ref([]); const loading = ref(true); const showSearch = ref(true); const testDialog = ref(false); const createDialog = ref(false); const total = ref(0); const totalTest = ref(0); const activeTab = ref("0"); // 为每个标签页存储独立的数据 const currentTabData = ref({}); const data = reactive({ queryParams: { pageNum: 1, pageSize: 10, stage: null, modelNo: null, colorwayid: null, seasonNo: null, pfcNo: null, pattern: null, firstsource: null, pageNumber: null, stepno: null, pfcCaption: null, }, rules: { styleNo: [{ required: true, message: "STYLE NO能为空", trigger: "blur" }], dpa: [{ required: true, message: "DPA能为空", trigger: "blur" }], tdCode: [{ required: true, message: "TD CODE能为空", trigger: "blur" }], factory: [{ required: true, message: "FACTORY能为空", trigger: "blur" }], labTest: [{ required: true, message: "LAB TEST能为空", trigger: "blur" }], approvedBy: [ { required: true, message: "APPROVED BY能为空", trigger: "blur" }, ], approvedDate: [ { required: true, message: "APPROVED DATE能为空", trigger: "blur" }, ], // 其他验证规则... }, }); const { queryParams, rules } = toRefs(data); // 初始化标签页数据 async function initTabData(tabIndex, sourceData) { currentTabData.value[tabIndex] = { baseInfo: { ...sourceData }, formData: { styleNo: "", dpa: "", tdCode: "", factory: "", labTest: "", approvedBy: "", approvedDate: "", description: "", }, dataRows: [], templateOptions: [], selectedTemplate: [], currentTemplateData: null, loading: false, // 添加加载状态 }; // 加载模板数据 await loadTemplateOptions(tabIndex, sourceData.pageNumber); // 加载已保存的测试用例数据 await loadSavedDataRows(tabIndex, sourceData.pfcNo, sourceData.pageNumber); } // 加载已保存的测试用例数据 async function loadSavedDataRows(tabIndex, pfcNo, pageNumber) { const currentTab = currentTabData.value[tabIndex]; try { currentTab.loading = true; const response = await getSavedQualityStandards(pfcNo, pageNumber); const savedData = response.data || []; if (savedData.length > 0) { const currentTab = currentTabData.value[tabIndex]; // 转换已保存数据为表格行格式 currentTab.dataRows = savedData.map((item) => { const row = { templateId: item.templateId, // 保存模板ID // 复制表单数据到当前标签页的表单中(只复制一次,取第一条数据) ...(currentTab.dataRows.length === 0 ? { // 这些字段会被表单数据覆盖,所以这里主要处理测试用例特定字段 } : {}), }; // 根据页面类型设置同的字段 if (pageNumber == "P42" || pageNumber == "P124") { // P42/P124字段 row.bondTest = item.bondTest || ""; row.testItem = item.testItem || ""; row.pictureRef = item.pictureRef || ""; row.materialOne = item.materialOne || ""; row.materialTwo = item.materialTwo || ""; row.speed = item.speed || ""; row.minSpec = item.minSpec || ""; row.peSpec = item.peSpec || ""; } else if (pageNumber == "P43" || pageNumber == "P125") { // P43/P125字段 row.testMethod = item.testMethod || ""; row.testMethodName = item.testMethodName || ""; row.pictureRef = item.pictureRef || ""; row.flmc = item.flmc || ""; row.minSpec = item.minSpec || ""; if (pageNumber == "P43") { row.testParameter = item.testParameter || ""; } else if (pageNumber == "P125") { row.speed = item.speed || ""; } } // 设置可选字段 row.csan = item.csan || ""; row.pead = item.pead || ""; row.peSpec = item.peSpec || ""; return row; }); // 设置表单数据(取第一条数据的表单信息) if (savedData.length > 0) { const firstItem = savedData[0]; currentTab.formData = { styleNo: firstItem.styleNo || "", dpa: firstItem.dpa || "", tdCode: firstItem.tdCode || "", factory: firstItem.factory || "", labTest: firstItem.labTest || "", approvedBy: firstItem.approvedBy || "", approvedDate: firstItem.approvedDate || "", description: firstItem.description || "", }; } console.log(`标签页 ${tabIndex} 加载了 ${savedData.length} 条已保存数据`); } } catch (error) { console.error("加载已保存数据失败:", error); } finally { currentTab.loading = false; } } // 加载模板选项 async function loadTemplateOptions(tabIndex, pageNumber) { try { const response = await getTemplateTree(pageNumber); currentTabData.value[tabIndex].templateOptions = response.data || []; } catch (error) { console.error("加载模板数据失败:", error); proxy.$modal.msgError("加载测试用例模板失败"); } } // 获取级联选择器配置 function getCascaderProps(tabIndex) { const pageNumber = mainList.value[tabIndex]?.pageNumber; // 所有页面使用相同的配置 return { value: "value", label: "label", children: "children", checkStrictly: false, emitPath: true, expandTrigger: "hover", }; } // 处理模板选择 function handleTemplateSelect(tabIndex) { const currentTab = currentTabData.value[tabIndex]; const selectedValue = currentTab.selectedTemplate; const pageNumber = mainList.value[tabIndex]?.pageNumber; if (selectedValue && selectedValue.length > 0) { // 找到选中的模板数据 const templateData = findTemplateData( currentTab.templateOptions, selectedValue ); if (templateData && templateData.data) { currentTab.currentTemplateData = templateData.data; // 自动添加数据行 addDataRowFromTemplate(tabIndex); } else { // 如果是叶子节点,清空选择,让用户继续选择下级 if ( templateData && templateData.children && templateData.children.length > 0 ) { currentTab.currentTemplateData = null; } else { // 没有数据也没有子节点,清空选择 currentTab.selectedTemplate = []; currentTab.currentTemplateData = null; proxy.$modal.msgWarning("请选择有效的测试用例模板"); } } } else { currentTab.currentTemplateData = null; } } // 从模板添加数据行 function addDataRowFromTemplate(tabIndex) { const currentTab = currentTabData.value[tabIndex]; if (currentTab.currentTemplateData) { const columns = getTableColumns(tabIndex); const newRow = {}; // 初始化所有字段为空 columns.forEach((col) => { newRow[col.prop] = ""; }); // 用模板数据填充 fillRowWithTemplateData(newRow, currentTab.currentTemplateData, tabIndex); currentTab.dataRows.push(newRow); // 清空选择器 currentTab.selectedTemplate = []; currentTab.currentTemplateData = null; proxy.$modal.msgSuccess("已从模板添加测试用例"); } } // 在模板树中查找数据 function findTemplateData(options, path) { if (!options || !path || path.length == 0) return null; let currentLevel = options; let result = null; for (let i = 0; i < path.length; i++) { const currentValue = path[i]; const found = currentLevel.find((item) => item.value == currentValue); if (!found) return null; if (i == path.length - 1) { result = found; } else { currentLevel = found.children; } } return result; } // 修改原有的添加数据行方法 function addDataRow(tabIndex) { const currentTab = currentTabData.value[tabIndex]; const columns = getTableColumns(tabIndex); // 创建新行 const newRow = {}; columns.forEach((col) => { newRow[col.prop] = ""; }); currentTab.dataRows.push(newRow); proxy.$modal.msgSuccess("已添加空白的测试用例行"); } // 用模板数据填充行 function fillRowWithTemplateData(row, templateData, tabIndex) { const pageNumber = mainList.value[tabIndex]?.pageNumber; // 保存模板ID,用于后续保存操作 row.templateId = templateData.id; if (pageNumber == "P42" || pageNumber == "P124") { // P42/P124模板数据映射 const bondingData = templateData; // 映射所有字段 row.bondTest = bondingData.bondTest || ""; row.testItem = bondingData.testItem || ""; row.pictureRef = bondingData.pictureRef || ""; row.materialOne = bondingData.materialOne || ""; row.materialTwo = bondingData.materialTwo || ""; row.speed = bondingData.speed || ""; row.minSpec = bondingData.minSpec || ""; row.peSpec = bondingData.peSpec || ""; row.csan = bondingData.csan || ""; row.pead = bondingData.pead || ""; } else if (pageNumber == "P43") { // P43模板数据映射 const customData = templateData; row.testMethod = customData.testMethod || ""; row.testMethodName = customData.testMethodName || ""; row.pictureRef = customData.pictureRef || ""; row.flmc = customData.flmc || ""; row.testParameter = customData.testParameter || ""; // P43特有 row.minSpec = customData.minSpec || ""; row.peSpec = customData.peSpec || ""; row.csan = customData.csan || ""; row.pead = customData.pead || ""; } else if (pageNumber == "P125") { // P125模板数据映射 const customData = templateData; row.testMethod = customData.testMethod || ""; row.testMethodName = customData.testMethodName || ""; row.pictureRef = customData.pictureRef || ""; row.flmc = customData.flmc || ""; row.speed = customData.speed || ""; // P125特有 row.minSpec = customData.minSpec || ""; row.peSpec = customData.peSpec || ""; row.csan = customData.csan || ""; row.pead = customData.pead || ""; } } /** 查询【请填写功能名称】列表 */ function getList() { loading.value = true; qsInfo(queryParams.value).then((response) => { qsList.value = response.rows; total.value = response.total; loading.value = false; }); } function handleTest() { testDialog.value = true; getTestList(); } // 根据页面类型获取表格列配置 function getTableColumns(tabIndex) { const pageNumber = mainList.value[tabIndex]?.pageNumber; // 根据同的pageNumber返回同的列配置 switch (pageNumber) { case "P42": // Finished Shoe Bonding页面 return [ { prop: "bondTest", label: "BOND TEST", width: 120 }, { prop: "testItem", label: "TEST ITEM", width: 100 }, { prop: "pictureRef", label: "PICTURE REF.#", width: 130 }, { prop: "materialOne", label: "MATERIAL 1", width: 120 }, { prop: "materialTwo", label: "MATERIAL 2", width: 120 }, { prop: "speed", label: "Speed(mm/min)", width: 150 }, { prop: "minSpec", label: "Min.Spec(kgf/cm)", width: 150 }, { prop: "peSpec", label: "Product Exc.Spec", width: 150 }, { prop: "csan", label: "COMMENT SECTION Approver Name / Product Exception Detail...", width: 250, }, { prop: "pead", label: "Product Exception Approved Date", width: 150 }, ]; case "P43": // Finished Shoe Custom页面 return [ { prop: "testMethod", label: "Nike Test Method #", width: 120 }, { prop: "testMethodName", label: "Nike Test Method Name", width: 130 }, { prop: "pictureRef", label: "PICTURE REF.#", width: 100 }, { prop: "flmc", label: "FOCUSED LOCATION/MATERIAL/COLOR", width: 220 }, { prop: "testParameter", label: "Test Parameter", width: 120 }, // P43特有 { prop: "minSpec", label: "Min.Spec", width: 120 }, { prop: "peSpec", label: "Product Exc.Spec", width: 120 }, { prop: "csan", label: "COMMENT SECTION Approver Name / Product Exception Detail...", width: 250, }, { prop: "pead", label: "Product Exception Approved Date", width: 150 }, ]; case "P124": //Fuse & No Sew Upper return [ { prop: "bondTest", label: "BOND TEST", width: 120 }, { prop: "testMethod", label: "Nike Test Method #", width: 120 }, { prop: "testItem", label: "TEST ITEM", width: 100 }, { prop: "pictureRef", label: "PICTURE REF.#", width: 100 }, { prop: "materialOne", label: "MATERIAL 1", width: 120 }, { prop: "materialTwo", label: "MATERIAL 2", width: 120 }, { prop: "speed", label: "Speed(mm/min)", width: 150 }, { prop: "minSpec", label: "Min.Spec(kgf/cm)", width: 150 }, { prop: "peSpec", label: "Product Exc.Spec", width: 150 }, { prop: "csan", label: "COMMENT SECTION Approver Name / Product Exception Detail...", width: 250, }, { prop: "pead", label: "Product Exception Approved Date", width: 150 }, ]; case "P125": // Sockliner页面 return [ { prop: "testMethod", label: "Nike Test Method #", width: 120 }, { prop: "testMethodName", label: "Nike Test Method Name", width: 130 }, { prop: "pictureRef", label: "PICTURE REF.#", width: 100 }, { prop: "flmc", label: "FOCUSED LOCATION/MATERIAL/COLOR", width: 220 }, { prop: "speed", label: "Speed(mm/min)", width: 150 }, // P125特有 { prop: "minSpec", label: "Min.Spec(kgf/cm)", width: 150 }, { prop: "peSpec", label: "Product Exc.Spec", width: 150 }, { prop: "csan", label: "COMMENT SECTION Approver Name / Product Exception Detail...", width: 250, }, { prop: "pead", label: "Product Exception Approved Date", width: 150 }, ]; default: return []; } } // 获取标签页显示名称 function getTabLabel(item) { return `${item.pfcCaption} (${item.pageNumber})`; } // 删除数据行 function removeDataRow(tabIndex, rowIndex) { currentTabData.value[tabIndex].dataRows.splice(rowIndex, 1); } // 获取对应标签页的验证规则 function getTabRules(tabIndex) { // 可以根据同的标签页返回同的验证规则 return data.rules; } async function handleCreate(row) { createDialog.value = true; try { const response = await getQsMain(row.pfcNo); mainList.value = response.data; // 为每个标签页初始化独立的数据 for (let index = 0; index < mainList.value.length; index++) { const item = mainList.value[index]; await initTabData(index, item); } // 设置默认激活第一个标签页 if (mainList.value.length > 0) { activeTab.value = "0"; } } catch (error) { console.error("加载数据失败:", error); proxy.$modal.msgError("加载数据失败"); } } // 保存当前标签页 async function handleSaveCurrent() { const currentIndex = parseInt(activeTab.value); const currentData = currentTabData.value[currentIndex]; const baseInfo = currentData.baseInfo; try { // 构建保存数据 const saveDataList = []; // 遍历当前标签页的所有测试用例 for (const dataRow of currentData.dataRows) { const saveData = { pageNumber: baseInfo.pageNumber, // 当前标签页的页码 pfcNo: baseInfo.pfcNo, // PFC手册号 templateId: dataRow.templateId, // 模板树第七层data的id // 其他表单数据 // styleNo: currentData.formData.styleNo, // dpa: currentData.formData.dpa, // tdCode: currentData.formData.tdCode, // factory: currentData.formData.factory, // labTest: currentData.formData.labTest, // approvedBy: currentData.formData.approvedBy, // approvedDate: currentData.formData.approvedDate, // description: currentData.formData.description }; // 添加测试用例数据 // const pageNumber = baseInfo.pageNumber; // if (pageNumber == 'P42' || pageNumber == 'P124') { // // P42/P124字段 // saveData.testItem = dataRow.testItem || ''; // saveData.pictureRef = dataRow.pictureRef || ''; // saveData.materialOne = dataRow.materialOne || ''; // saveData.materialTwo = dataRow.materialTwo || ''; // saveData.speed = dataRow.speed || ''; // saveData.minSpec = dataRow.minSpec || ''; // saveData.bondTest = dataRow.bondTest || ''; // } else if (pageNumber == 'P43' || pageNumber == 'P125') { // // P43/P125字段 // saveData.testMethod = dataRow.testMethod || ''; // saveData.testMethodName = dataRow.testMethodName || ''; // saveData.pictureRef = dataRow.pictureRef || ''; // saveData.flmc = dataRow.flmc || ''; // saveData.minSpec = dataRow.minSpec || ''; // if (pageNumber == 'P43') { // saveData.testParameter = dataRow.testParameter || ''; // } else if (pageNumber == 'P125') { // saveData.speed = dataRow.speed || ''; // } // } // 如果csanpead为空,也添加到保存数据中 if (dataRow.csan && dataRow.csan.trim() !== "") { saveData.csan = dataRow.csan; } if (dataRow.pead && dataRow.pead.trim() !== "") { saveData.pead = dataRow.pead; } saveDataList.push(saveData); } console.log("保存数据:", saveDataList); // 调用API保存数据 const response = await saveQualityStandards(saveDataList); proxy.$modal.msgSuccess( `保存 ${mainList.value[currentIndex].pfcCaption} 成功,共保存 ${saveDataList.length} 个测试用例` ); // 保存成功后重新加载数据,确保数据一致性 await loadSavedDataRows(currentIndex, baseInfo.pfcNo, baseInfo.pageNumber); } catch (error) { console.error("保存失败:", error); proxy.$modal.msgError("保存失败: " + (error.message || "未知错误")); } } // 监听弹窗关闭 watch(createDialog, (newVal) => { if (!newVal) { // 关闭弹窗时清理数据 currentTabData.value = {}; mainList.value = []; activeTab.value = "0"; } }); // 保存所有标签页 function handleSaveAll() { console.log("保存所有标签页数据:", currentTabData.value); // 遍历所有标签页数据并保存 Object.keys(currentTabData.value).forEach((index) => { const tabData = currentTabData.value[index]; console.log(`标签页 ${index} 数据:`, tabData); // 调用API保存每个标签页的数据 }); proxy.$modal.msgSuccess("所有数据保存成功"); createDialog.value = false; } function getTestList() { loading.value = true; ptrInfo(queryParams.value).then((response) => { ptrList.value = response.rows; totalTest.value = response.total; loading.value = false; }); } function handleAdd(row) { addQualityStand(row).then((response) => { proxy.$modal.msgSuccess("新增成功"); getList(); }); } /** 搜索按钮操作 */ function handleQuery() { queryParams.value.pageNum = 1; getList(); } function handleQueryTest() { queryParams.value.pageNum = 1; getTestList(); } /** 重置按钮操作 */ function resetQuery() { proxy.resetForm("queryRef"); handleQuery(); } function resetQueryTest() { proxy.resetForm("testRef"); handleQuery(); } getList(); </script> <style scoped> /* 为级联选择器添加样式,确保显示完整 */ :deep(.el-cascader) { width: 450px; /* 增加宽度以显示更长的路径 */ margin-right: 10px; } :deep(.el-cascader__dropdown) { max-height: 400px; /* 增加下拉框高度 */ } .readonly-input :deep(.el-input__inner) { background-color: #fff7e6; } /* 为级联选择器的标签添加更好的显示 */ :deep(.el-cascader-node__label) { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 300px; } /* 模板选择区域样式 */ .template-selector { display: flex; align-items: center; margin-bottom: 15px; padding: 10px; background-color: #f8f9fa; border-radius: 4px; } .template-selector-label { font-weight: bold; margin-right: 10px; min-width: 120px; } .template-selector-hint { margin-left: 10px; color: #666; font-size: 12px; } /* 图片上传样式 */ :deep(.el-upload-list--picture-card .el-upload-list__item) { width: 100px; height: 100px; } :deep(.el-upload--picture-card) { width: 100px; height: 100px; line-height: 100px; } :deep(.el-upload-list--picture-card .el-upload-list__item-thumbnail) { object-fit: contain; } .upload-tip { margin-top: 10px; color: #909399; font-size: 12px; } </style> 这是我目前的页面,需要在图片上传区域加一个照片墙用于上传图片回显,我希望可以参考刚刚我提供的案例那样去实现,后端的接口我已经写好了@PostMapping("/images/upload") public AjaxResult uploadImages(@RequestParam("files") MultipartFile[] files, @RequestParam String pfcNo, @RequestParam String pageNumber) { try { if (files == null || files.length == 0) { return AjaxResult.error("上传文件能为空"); } int successCount = qualityStandardService.uploadImages(files, pfcNo, pageNumber); return AjaxResult.success("上传成功,共上传 " + successCount + " 张图片"); } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("上传失败: " + e.getMessage()); } }@Override public Integer uploadImages(MultipartFile[] files, String pfcNo, String pageNumber) { if (files == null || files.length == 0) { throw new ServiceException("上传文件能为空"); } int successCount = 0; String username = SecurityUtils.getUsername(); Date now = MyDateUtils.getNowDate(); for (MultipartFile file : files) { try { // 跳过空文件 if (file.isEmpty()) { continue; } QualityShoeDesign qualityShoeDesign = new QualityShoeDesign(); // 执行上传 R<ToolsFile> fileResult = remoteFileService.upload(file, MinioBucketNameConstants.SHOEDESIGN, SecurityUtils.getFact(), file.getOriginalFilename(), FileEnum.LOCAL, SecurityConstants.INNER); if (MyStringUtils.isNull(fileResult) || MyStringUtils.isNull(fileResult.getData())) { throw new ServiceException("文件服务器异常"); } qualityShoeDesign.setPfcNo(pfcNo); qualityShoeDesign.setPageNumber(pageNumber); qualityShoeDesign.setFilePath(fileResult.getData().getUrl()); qualityShoeDesign.setOriginalName(file.getOriginalFilename()); qualityShoeDesign.setCreateBy(username); qualityShoeDesign.setCreateTime(now); // 插入记录 int result = qualityStandardMapper.insertQualityShoeDesign(qualityShoeDesign); if (result > 0) { successCount++; } } catch (Exception e) { // 记录错误日志,但继续处理其他文件 log.error("上传文件失败: {}", file.getOriginalFilename(), e); } } if (successCount == 0) { throw new ServiceException("所有文件上传失败"); } return successCount; }@GetMapping("/images/list") public AjaxResult getImages(@RequestParam String pfcNo, @RequestParam String pageNumber) { try { List<QualityShoeDesign> images = qualityStandardService.getImagesByPfcNoAndPage(pfcNo, pageNumber); return AjaxResult.success(images); } catch (Exception e) { return AjaxResult.error("获取图片列表失败: " + e.getMessage()); } }这些是上传代码获取图片代码,请帮我生成合适的前端vue代码,图片上传我要可多选的
最新发布
10-15
<template> <div class="app-container"> <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> <el-form-item label="投诉标题" prop="title"> <el-input v-model="queryParams.title" placeholder="请输入投诉标题" clearable @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="投诉类型" prop="complaintType"> <el-select v-model="queryParams.complaintType" placeholder="请选择投诉类型" clearable> <el-option v-for="dict in dict.type.incorrupt_complaint_type" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="处理状态" prop="processingStatus"> <el-select v-model="queryParams.processingStatus" placeholder="请选择处理状态" clearable> <el-option v-for="dict in dict.type.incorrupt_complaint_status" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="投诉时间" prop="complaintTime"> <el-date-picker clearable v-model="queryParams.complaintTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择投诉时间"> </el-date-picker> </el-form-item> <el-form-item label="转为案例" prop="complaintExample"> <el-select v-model="queryParams.complaintExample" placeholder="请选择转为案例" clearable> <el-option v-for="dict in dict.type.incorrupt_case_type" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> </el-form-item> </el-form> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['incorruptible:ComplaintTs:add']" >新增</el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['incorruptible:ComplaintTs:edit']" >修改</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['incorruptible:ComplaintTs:remove']" >删除</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['incorruptible:ComplaintTs:export']" >导出</el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row> <!-- 投诉记录列表板块 --> <el-table v-loading="loading" :data="ComplaintTsList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="投诉ID" align="center" prop="complaintId" /> <el-table-column label="投诉标题" align="center" prop="title" /> <el-table-column label="投诉类型" align="center" prop="complaintType"> <template slot-scope="scope"> <dict-tag :options="dict.type.incorrupt_complaint_type" :value="scope.row.complaintType"/> </template> </el-table-column> <el-table-column label="被投诉人姓名" align="center" prop="bcomplaintName" /> <el-table-column label="被投诉人公司" align="center" prop="bcomplaintCompany" /> <el-table-column label="被投诉人部门" align="center" prop="bcomplaintDept" /> <el-table-column label="处理状态" align="center" prop="processingStatus"> <template slot-scope="scope"> <dict-tag :options="dict.type.incorrupt_complaint_status" :value="scope.row.processingStatus"/> </template> </el-table-column> <el-table-column label="投诉时间" align="center" prop="complaintTime" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.complaintTime, '{y}-{m}-{d}') }}</span> </template> </el-table-column> <!-- 修改转为案例为单选框 --> <el-table-column label="转为案例" align="center" prop="complaintExample"> <template slot-scope="scope"> <el-radio-group v-model="scope.row.complaintExample" @change="handleCaseChange(scope.row)" :disabled="isProcessing(scope.row.processingStatus)" size="mini" > <el-radio :label="dict.type.incorrupt_case_type[0].value">是</el-radio> <el-radio :label="dict.type.incorrupt_case_type[1].value">否</el-radio> </el-radio-group> </template> </el-table-column> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)" v-hasPermi="['incorruptible:ComplaintTs:view']" >详情</el-button> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> <!-- 添加或修改投诉记录汇总对话框 --> <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item label="投诉标题" prop="title"> <el-input v-model="form.title" placeholder="请输入投诉标题" /> </el-form-item> <el-form-item label="投诉类型" prop="complaintType"> <el-select v-model="form.complaintType" placeholder="请选择投诉类型"> <el-option v-for="dict in dict.type.incorrupt_complaint_type" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> <el-form-item label="被投诉人姓名" prop="bcomplaintName"> <el-input v-model="form.bcomplaintName" placeholder="请输入被投诉人姓名" /> </el-form-item> <el-form-item label="被投诉人公司" prop="bcomplaintCompany"> <el-input v-model="form.bcomplaintCompany" placeholder="请输入被投诉人公司" /> </el-form-item> <el-form-item label="被投诉人部门" prop="bcomplaintDept"> <el-input v-model="form.bcomplaintDept" placeholder="请输入被投诉人部门" /> </el-form-item> <el-form-item label="投诉内容" prop="bcomplaintContent"> <el-input v-model="form.bcomplaintContent" type="textarea" placeholder="请输入内容" /> </el-form-item> <el-form-item label="处理状态" prop="processingStatus"> <el-radio-group v-model="form.processingStatus"> <el-radio v-for="dict in dict.type.incorrupt_complaint_status" :key="dict.value" :label="dict.value" >{{dict.label}}</el-radio> </el-radio-group> </el-form-item> <el-form-item label="投诉时间" prop="complaintTime"> <el-date-picker clearable v-model="form.complaintTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择投诉时间"> </el-date-picker> </el-form-item> <el-form-item label="附件" prop="fileUrl"> <file-upload v-model="form.fileUrl"/> </el-form-item> <el-form-item label="处理结果" prop="complaintResult"> <el-input v-model="form.complaintResult" type="textarea" placeholder="请输入内容" /> </el-form-item> <el-form-item label="转为案例" prop="complaintExample"> <el-radio-group v-model="form.complaintExample"> <el-radio v-for="dict in dict.type.incorrupt_case_type" :key="dict.value" :label="dict.value" >{{dict.label}}</el-radio> </el-radio-group> </el-form-item> <el-form-item label="投诉人" prop="complaintPerson"> <el-input v-model="form.complaintPerson" placeholder="请输入投诉人" /> </el-form-item> <el-form-item label="投诉人电话" prop="phoneNumber"> <el-input v-model="form.phoneNumber" placeholder="请输入投诉人电话" /> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="form.email" placeholder="请输入邮箱" /> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog> </div> </template> <script> import { listComplaintTs, getComplaintTs, delComplaintTs, addComplaintTs, updateComplaintTs } from "@/api/incorruptible/ComplaintTs" export default { name: "ComplaintTs", watch: { '$route': { handler(to, from) { // 首次进入页面时,from 为 undefined,需先判断 if (from && from.name === 'ComplaintDetail') { // 当从详情页返回时刷新列表 this.getList(); } // 处理刷新参数 if (to.query.refresh === 'true') { this.getList(); // 清除刷新标记 this.$router.replace({ query: {} }); } }, immediate: true // 立即执行 } }, dicts: ['incorrupt_complaint_type', 'incorrupt_complaint_status', 'incorrupt_case_type'], data() { return { // 遮罩层 loading: true, // 选中数组 ids: [], // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 显示搜索条件 showSearch: true, // 总条数 total: 0, // 投诉记录汇总表格数据 ComplaintTsList: [], // 弹出层标题 title: "", // 是否显示弹出层 open: false, // 查询参数 queryParams: { pageNum: 1, pageSize: 10, title: null, complaintType: null, processingStatus: null, complaintTime: null, complaintExample: null, }, // 表单参数 form: {}, // 表单校验 rules: { } } }, created() { this.getList() }, methods: { /** 查询投诉记录汇总列表 */ getList() { this.loading = true listComplaintTs(this.queryParams).then(response => { this.ComplaintTsList = response.rows this.total = response.total this.loading = false }) }, // 取消按钮 cancel() { this.open = false this.reset() }, // 表单重置 reset() { this.form = { complaintId: null, title: null, complaintType: null, bcomplaintName: null, bcomplaintCompany: null, bcomplaintDept: null, bcomplaintContent: null, processingStatus: null, complaintTime: null, fileUrl: null, complaintResult: null, complaintExample: null, complaintPerson: null, phoneNumber: null, email: null, createBy: null, createTime: null, updateBy: null, updateTime: null } this.resetForm("form") }, /** 搜索按钮操作 */ handleQuery() { this.queryParams.pageNum = 1 this.getList() }, /** 重置按钮操作 */ resetQuery() { this.resetForm("queryForm") this.handleQuery() }, // 多选框选中数据 handleSelectionChange(selection) { this.ids = selection.map(item => item.complaintId) this.single = selection.length!==1 this.multiple = !selection.length }, /** 新增按钮操作 */ handleAdd() { this.reset() this.open = true this.title = "添加投诉记录汇总" }, /** 修改按钮操作 */ handleUpdate(row) { this.reset() const complaintId = row.complaintId || this.ids getComplaintTs(complaintId).then(response => { this.form = response.data this.open = true this.title = "修改投诉记录汇总" }) }, /** 详情按钮操作 - 跳转到详情页面 */ handleDetail(row) { // 跳转到ComplainTsFrom.vue页面,传递投诉ID参数 this.$router.push({ name: 'ComplaintDetail', // 确保路由配置中有此名称的路由 params: { complaintId: row.complaintId } }) }, /** 提交按钮 */ submitForm() { this.$refs["form"].validate(valid => { if (valid) { const action = this.form.complaintId ? updateComplaintTs : addComplaintTs; action(this.form).then(response => { this.$modal.msgSuccess(this.form.complaintId ? "修改成功" : "新增成功"); this.open = false; this.getList(); // 提交成功后刷新列表 }).catch(error => { console.error('提交失败:', error); this.$modal.msgError('操作失败,请重试'); }); } }) }, /** 删除按钮操作 */ handleDelete(row) { const complaintIds = row.complaintId || this.ids this.$modal.confirm('是否确认删除投诉记录汇总编号为"' + complaintIds + '"的数据项?').then(function() { return delComplaintTs(complaintIds) }).then(() => { this.getList() this.$modal.msgSuccess("删除成功") }).catch(() => {}) }, /** 导出按钮操作 */ handleExport() { this.download('incorruptible/ComplaintTs/export', { ...this.queryParams }, `ComplaintTs_${new Date().getTime()}.xlsx`) }, /** 处理转为案例的状态变更 */ handleCaseChange(row) { // 保存原始值,用于更新失败时恢复 const originalValue = row.complaintExample; // 显示加载状态 this.loading = true; // 调用更新接口 updateComplaintTs({ complaintId: row.complaintId, complaintExample: row.complaintExample }).then(response => { this.$modal.msgSuccess("更新成功"); this.getList(); // 刷新列表 }).catch(error => { console.error('更新转为案例状态失败:', error); this.$modal.msgError('更新失败,请重试'); // 恢复原始值 row.complaintExample = originalValue; this.loading = false; }); }, /** 判断是否处于特定处理状态,用于控制是否可编辑 */ isProcessing(status) { // 如果需要在某些状态下禁止修改,可以在这里添加判断逻辑 // 例如:return status === '2'; // 处理中状态可修改 return false; // 所有状态都可修改 } } } </script> 为什么点击详情跳转的页面一片空白
08-31
<!-- src/views/incorruptible/case/caseForm.vue --> <template> <div class="app-container"> <!-- 页面标题 --> <div class="page-title">{{ title }}</div> <el-form ref="form" :model="form" :rules="rules" label-width="80px" class="form-container" > <el-form-item label="案列标题" prop="title"> <el-input v-model="form.title" placeholder="请输入案列标题" /> </el-form-item> <el-form-item label="发布日期" prop="publishDate"> <el-date-picker clearable v-model="form.publishDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择发布日期" popper-append-to-body="false" class="date-picker-zindex" /> </el-form-item> <el-form-item label="案例内容"> <!-- Quill富文本编辑器 --> <div class="quill-editor-container"> <quill-editor ref="quillEditor" v-model="form.content" :options="editorOptions" @ready="onEditorReady" /> </div> </el-form-item> <el-form-item label="状态" prop="status"> <el-radio-group v-model="form.status"> <el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value" >{{dict.label}}</el-radio> </el-radio-group> </el-form-item> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-form> </div> </template> <script> import { getCase, addCase, updateCase } from "@/api/incorruptible/case"; import { quillEditor } from 'vue-quill-editor'; import 'quill/dist/quill.core.css'; import 'quill/dist/quill.snow.css'; import 'quill/dist/quill.bubble.css'; import { getToken } from '@/utils/auth'; import axios from 'axios'; export default { name: "CaseForm", components: { quillEditor }, dicts: ['sys_normal_disable'], data() { return { articleId: null, // 改为在data中存储 title: "", form: { title: null, publishDate: null, content: null, status: null }, // Quill编辑器配置 editorOptions: { placeholder: '请输入内容', theme: 'snow', modules: { toolbar: { container: [ ['bold', 'italic', 'underline', 'strike'], ['blockquote', 'code-block'], [{ 'header': 1 }, { 'header': 2 }], [{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'script': 'sub'}, { 'script': 'super' }], [{ 'indent': '-1'}, { 'indent': '+1' }], [{ 'direction': 'rtl' }], [{ 'size': ['small', false, 'large', 'huge'] }], [{ 'header': [1, 2, 3, 4, 5, 6, false] }], [{ 'color': [] }, { 'background': [] }], [{ 'font': [] }], [{ 'align': [] }], ['clean'], ['link', 'image'] ], handlers: { 'image': this.handleImageButtonClick } } } }, quillInstance: null, // 表单校验规则 rules: { title: [{ required: true, message: '案列标题能为空', trigger: 'blur' }], publishDate: [{ required: true, message: '发布日期能为空', trigger: 'change' }], status: [{ required: true, message: '状态能为空', trigger: 'change' }] } }; }, created() { // 从路由参数获取articleId this.articleId = this.$route.params.articleId; // 根据是否有ID确定是新增还是修改 this.title = this.articleId ? "修改廉洁标准案例" : "添加廉洁标准案例"; // 如果是修改,加载案例数据 if (this.articleId) { this.loadCaseData(); } }, methods: { // 加载案例数据 async loadCaseData() { try { const response = await getCase(this.articleId); this.form = response.data; // 反转义富文本内容 this.form.content = this.unescapeHtmlFromJson(this.form.content); } catch (error) { console.error('加载案例数据失败:', error); this.$modal.msgError('加载案例数据失败'); } }, // 编辑器准备就绪 onEditorReady(editor) { this.quillInstance = editor; }, // 图片按钮点击事件处理 handleImageButtonClick() { if (!this.quillInstance) return; const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', 'image/*'); input.style.display = 'none'; document.body.appendChild(input); input.click(); input.onchange = () => { if (!input.files || !input.files[0]) return; const file = input.files[0]; if (file.size > 5 * 1024 * 1024) { this.$modal.msgError('图片大小能超过5MB'); return; } this.uploadImage(file) .then(imageUrl => { const range = this.quillInstance.getSelection(); if (range) { this.quillInstance.insertEmbed(range.index, 'image', imageUrl); this.quillInstance.setSelection(range.index + 1); } }) .catch(error => { console.error('图片上传失败:', error); this.$modal.msgError('图片上传失败'); }) .finally(() => { document.body.removeChild(input); }); }; }, // 图片上传方法 uploadImage(file) { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append('file', file); const uploadUrl = process.env.VUE_APP_BASE_API + '/file/upload'; axios.post(uploadUrl, formData, { headers: { 'Authorization': 'Bearer ' + getToken(), 'Content-Type': 'multipart/form-data' } }).then(response => { const res = response.data; let imageUrl = ''; if (res.code === 200 && res.data) { if (typeof res.data === 'string') { imageUrl = res.data; } else if (res.data.url) { imageUrl = res.data.url; } else if (res.data.value) { imageUrl = String(res.data.value); } if (imageUrl) { if (!imageUrl.startsWith('http')) { imageUrl = process.env.VUE_APP_BASE_API + imageUrl; } resolve(imageUrl); } else { reject('无法解析图片URL'); } } else { reject(res.msg || '图片上传失败'); } }).catch(error => { console.error('图片上传失败:', error); reject('上传失败: ' + (error.message || '网络错误')); }); }); }, // 富文本内容JSON转义处理 escapeHtmlForJson(html) { if (!html) return ''; let processedHtml = html.replace(/ /g, '\\u00a0'); processedHtml = processedHtml.replace(/^[ \t]+/gm, (match) => { return '\\u00a0'.repeat(match.length); }); processedHtml = processedHtml.replace(/([^ \t\n\r])([ \t]{2,})/g, (match, prefix, spaces) => { return prefix + '\\u00a0'.repeat(spaces.length); }); processedHtml = processedHtml .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\//g, '\\/') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\f/g, '\\f') .replace(/</g, '\\u003c') .replace(/>/g, '\\u003e'); return processedHtml; }, // JSON转义内容反转义处理 unescapeHtmlFromJson(escapedHtml) { if (!escapedHtml) return ''; return escapedHtml .replace(/\\u00a0/g, ' ') .replace(/\\u003c/g, '<') .replace(/\\u003e/g, '>') .replace(/\\"/g, '"') .replace(/\\\//g, '/') .replace(/\\\\/g, '\\') .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\t/g, '\t') .replace(/\\f/g, '\f'); }, // 提交表单 submitForm() { this.$refs.form.validate(valid => { if (valid) { // 检查富文本内容 if (!this.form.content || this.form.content === '<p><br></p>') { this.$modal.msgWarning('案例内容能为空'); return; } // 准备数据 const formData = { ...this.form, content: this.escapeHtmlForJson(this.form.content) }; // 确定是新增还是修改 const action = this.articleId ? updateCase : addCase; action(formData) .then(() => { this.$modal.msgSuccess(this.articleId ? "修改成功" : "新增成功"); this.$router.back(); // 返回上一页 }) .catch(error => { console.error('操作失败:', error); this.$modal.msgError('操作失败'); }); } }); }, // 取消操作 cancel() { this.$router.back(); // 返回上一页 } } }; </script> <style scoped> .app-container { padding: 20px; background-color: #f9f9f9; min-height: 100vh; } .page-title { font-size: 24px; font-weight: bold; margin-bottom: 20px; color: #2c3e50; padding-bottom: 10px; border-bottom: 1px solid #eee; } .form-container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .quill-editor-container { border: 1px solid #DCDFE6; border-radius: 4px; overflow: hidden; background: #fff; margin-bottom: 20px; } .quill-editor-container:hover { border-color: #C0C4CC; } .ql-toolbar { border-bottom: 1px solid #DCDFE6 !important; } .ql-container { min-height: 300px; border: none !important; } .dialog-footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; } .date-picker-zindex { z-index: 3000 !important; } </style> 这里面的富文本,没有正确转译tab空格空格,我tab空格后刷新tab空格没有了,空格变成了\
08-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值