<el-radio-group v-model="form.type">
时间: 2024-04-26 09:07:09 浏览: 151
这是一段 Vue.js 代码,用于渲染一个单选框组。其中,`v-model="form.type"` 绑定了一个数据模型 `form.type`,用于存储用户选择的单选框的值。当用户选择一个单选框时,`form.type` 的值会被更新。`el-radio-group` 是一个由 Element UI 提供的 Vue.js 组件,用于渲染单选框组。
相关问题
<template> <!-- 模板部分保持不变 --> <div class="app-container"> <!-- 物料类型数据 --> <el-row :gutter="20"> <!--物料类型数据--> <splitpanes :horizontal="appStore.device === 'mobile'" class="default-theme"> <pane size="16"> <el-col> <div class="head-container"> <el-input v-model="materiaTypelName" placeholder="请输入物料类型名称" clearable prefix-icon="Search" style="margin-bottom: 20px"/> </div> <div class="head-container"> <el-tree :data="typeOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" ref="treeRef" node-key="id" highlight-current default-expand-all @node-click="handleNodeClick"/> </div> </el-col> </pane> <!-- 属性数据--> <pane size="84"> <el-col> <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"> <el-form-item label="编号" prop="number"> <el-input v-model="queryParams.number" placeholder="请输入编号" clearable @keyup.enter.native="handleQuery" style="width: 240px"/> </el-form-item> <el-form-item label="名称" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" style="width: 240px"/> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 240px"> <el-option v-for="dict in mdm_common_status_list" :key="dict.value" :label="dict.label" :value="dict.value"/> </el-select> </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> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['mdm:typeAttr:add']" >新增 </el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="edit" :disabled="single" @click="handleUpdate" v-hasPermi="['mdm:typeAttr:edit']" >修改 </el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['mdm:typeAttr:remove']" >删除 </el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['mdm:typeAttr:export']" >导出 </el-button> </el-col> <el-col :span="1.5"> <el-button type="info" plain icon="upload" @click="handleImport" v-hasPermi="['mdm:typeAttr:import']" >导入 </el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="edit" @click="handleUrlUpdate" v-hasPermi="['mdm:mdmMaterialType:edit']"> 编辑命名规则 </el-button> </el-col> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </el-row> <el-table v-loading="loading" :data="typeAttrList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="45" align="center" fixed/> <el-table-column label="编号" align="center" prop="number"/> <el-table-column label="名称" align="center" prop="name"/> <el-table-column label="类型名称" align="center" prop="typemgrName" width="140"/> <el-table-column label="类型编号" align="center" prop="typemgrNum" width="140"/> <el-table-column label="字段类型" align="center" prop="dataType"> <template #default="scope"> <dict-tag :options="mdm_common_column_type" :value="scope.row.dataType"/> </template> </el-table-column> <el-table-column label="文本最大长度" align="center" prop="length" width="120"/> <el-table-column label="数值最大值" align="center" prop="maxVal" width="120"/> <el-table-column label="数值最小值" align="center" prop="minVal" width="120"/> <el-table-column label="默认值" align="center" prop="defaultVal"/> <el-table-column label="枚举ID" align="center" prop="enumerateId"/> <el-table-column label="枚举表" align="center" prop="enumerateTable"/> <el-table-column label="单位" align="center" prop="unit"/> <el-table-column label="是否必填" align="center" prop="isNull"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isNull"/> </template> </el-table-column> <el-table-column label="弹窗编辑器ID" align="center" prop="popupEditId" width="120"/> <el-table-column label="URL地址" align="center" prop="href"/> <el-table-column label="是否可查询" align="center" prop="isQuery" width="120"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isQuery"/> </template> </el-table-column> <el-table-column label="是否表单显示" align="center" prop="isShowForm" width="120"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isShowForm"/> </template> </el-table-column> <el-table-column label="是否列表显示" align="center" prop="isShowList" width="120"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isShowList"/> </template> </el-table-column> <el-table-column label="是否只读" align="center" prop="isReadOnly"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isReadOnly"/> </template> </el-table-column> <el-table-column label="表单排序" align="center" prop="orderNum"/> <el-table-column label="是否支持排序" align="center" prop="sortFlag" width="120"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.sortFlag"/> </template> </el-table-column> <el-table-column label="数值是否有公差" align="center" prop="isTolerance" width="120"> <template #default="scope"> <dict-tag :options="mdm_common_flag_list" :value="scope.row.isTolerance"/> </template> </el-table-column> <el-table-column label="正则表达式校验" align="center" prop="regularCheck" width="120"/> <el-table-column label="状态" align="center" prop="status"> <template #default="scope"> <dict-tag :options="mdm_common_status_list" :value="scope.row.status"/> </template> </el-table-column> <el-table-column label="合法值" align="center" prop="validStr" width="150"/> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right"> <template #default="scope"> <el-button link icon="edit" type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['mdm:typeAttr:edit']" >修改 </el-button> <el-button link type="primary" icon="delete" @click="handleDelete(scope.row)" v-hasPermi="['mdm:typeAttr:remove']" >删除 </el-button> <el-button link type="primary" icon="edit" @click="eidtValid(scope.row)" v-hasPermi="['mdm:typeAttr:remove']" >编辑合法值 </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"/> </el-col> </pane> </splitpanes> </el-row> <!-- 添加或修改属性规则对话框 --> <el-dialog :title="title" v-model="open" width="850px" append-to-body> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> <!-- 基本信息 --> <el-row> <el-col :span="12"> <el-form-item label="编号" prop="number"> <el-input v-model="form.number" placeholder="请输入编号" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="名称" prop="name"> <el-input v-model="form.name" placeholder="请输入名称" style="width: 240px"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="类型ID" prop="typemgrId"> <el-tree-select v-model="form.typemgrId" :data="typeOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" :current-node-key="form.typemgrId" placeholder="请选择物料类型" check-strictly style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="字段类型" prop="dataType"> <el-select v-model="form.dataType" placeholder="请选择字段类型" style="width: 240px"> <el-option v-for="dict in mdm_common_column_type" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> </el-row> <!-- 数值与文本配置 --> <el-row> <el-col :span="12"> <el-form-item label="文本最大长度" prop="length"> <el-input v-model="form.length" placeholder="请输入文本最大长度" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="数值最大值" prop="maxVal"> <el-input v-model="form.maxVal" placeholder="请输入数值最大值" style="width: 240px"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="数值最小值" prop="minVal"> <el-input v-model="form.minVal" placeholder="请输入数值最小值" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="默认值" prop="defaultVal"> <el-input v-model="form.defaultVal" placeholder="请输入默认值" style="width: 240px"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="枚举ID" prop="enumerateId"> <el-input v-model="form.enumerateId" placeholder="请输入枚举ID" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="正则表达式校验" prop="regularCheck"> <el-input v-model="form.regularCheck" placeholder="请输入正则表达式校验" style="width: 240px"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="枚举表" prop="enumerateTable"> <el-input v-model="form.enumerateTable" placeholder="请输入枚举表" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="单位" prop="unit"> <el-input v-model="form.unit" placeholder="请输入单位" style="width: 240px"/> </el-form-item> </el-col> </el-row> <!-- 状态与布尔选项 --> <el-row> <el-col :span="12"> <el-form-item label="是否必填" prop="isNull"> <el-select v-model="form.isNull" placeholder="请选择是否必填" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="是否可查询" prop="isQuery"> <el-select v-model="form.isQuery" placeholder="请选择是否可查询" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="是否表单显示" prop="isShowForm"> <el-select v-model="form.isShowForm" placeholder="请选择是否表单显示" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list " :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="是否列表显示" prop="isShowList"> <el-select v-model="form.isShowList" placeholder="请选择是否列表显示" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="是否只读" prop="isReadOnly"> <el-select v-model="form.isReadOnly" placeholder="请选择是否只读" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="是否支持排序" prop="sortFlag"> <el-select v-model="form.sortFlag" placeholder="请选择是否支持排序" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="数值是否有公差" prop="isTolerance"> <el-select v-model="form.isTolerance" placeholder="请选择数值是否有公差" style="width: 240px"> <el-option v-for="dict in mdm_common_flag_list" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="表单排序" prop="orderNum"> <el-input v-model="form.orderNum" placeholder="请输入表单排序" style="width: 240px"/> </el-form-item> </el-col> </el-row> <!-- 高级设置 --> <el-row> <el-col :span="12"> <el-form-item label="弹窗编辑器ID" prop="popupEditId"> <el-input v-model="form.popupEditId" placeholder="请输入弹窗编辑器ID" style="width: 240px"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="URL地址" prop="href"> <el-input v-model="form.href" placeholder="请输入URL地址" style="width: 240px"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="状态" prop="status"> <el-radio-group v-model="form.status"> <el-radio v-for="dict in mdm_common_status_list" :key="dict.value" :value="dict.value" >{{ dict.label }} </el-radio> </el-radio-group> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </template> </el-dialog> <!-- 修改命名规则对话框--> <el-dialog title="修改命名规则" v-model="openUrledit" width="500px" append-to-body> <el-form ref="urlformRef" :model="urlform" :rules="urlEditrules" label-width="80px"> <el-form-item label="命名规则" prop="namingrule"> <el-input v-model="urlform.namingrule" placeholder="请输入命名规则"/> </el-form-item> </el-form> <template #footer> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitFormNamingrule">确 定</el-button> <el-button @click="cancelSubmitUrlEdit">取 消</el-button> </div> </template> </el-dialog> <!-- 合法值列表对话框 --> <el-dialog :title="validtitle" v-model="validOpen" width="650px" append-to-body> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="plus" @click="handleValidAdd" v-hasPermi="['mdm:validlist:add']" >新增 </el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="edit" :disabled="validsingle" @click="handleValidUpdate" v-hasPermi="['mdm:validlist:edit']" >修改 </el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="delete" :disabled="validMultiple" @click="handleValidDeleteBtn" v-hasPermi="['mdm:validlist:remove']" >删除 </el-button> </el-col> </el-row> <el-table v-loading="validTableLoading" :data="validlistList" @selection-change="handleValidSelectionChange"> <el-table-column type="selection" width="55" align="center"/> <!-- <el-table-column label="ID" align="center" prop="id" />--> <el-table-column label="合法值名称" align="center" prop="name"/> <el-table-column label="排序" align="center" prop="orderNum"/> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template #default="scope"> <el-button link icon="edit" @click="handleValidUpdate(scope.row)" v-hasPermi="['mdm:validlist:edit']" >修改 </el-button> <el-button link icon="delete" @click="handleValidDelete(scope.row)" v-hasPermi="['mdm:validlist:remove']" >删除 </el-button> </template> </el-table-column> </el-table> <template #footer> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="validSubmitForm">关 闭</el-button> </div> </template> </el-dialog> <!-- 添加或修改合法值列表对话框 --> <el-dialog :title="validAddtitle" v-model="validAddOpen" width="500px" append-to-body> <el-form ref="validformRef" :model="validform" :rules="validRules" label-width="80px"> <el-form-item label="合法值名称" prop="name" label-width="95px"> <el-input v-model="validform.name" placeholder="请输入合法值名称"/> </el-form-item> <el-form-item label="排序" prop="orderNum" label-width="95px"> <el-input v-model="validform.orderNum" placeholder="请输入排序"/> </el-form-item> </el-form> <template #footer> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="validAddSubmitForm">确 定</el-button> <el-button @click="validAddCancel">取 消</el-button> </div> </template> </el-dialog> </div> </template> <script setup name="typeAtrr"> import {listTypeAttr, getTypeAttr, delTypeAttr, addTypeAttr, updateTypeAttr} from "@/api/mdm/typeAttr"; import {getMdmMaterialType, updateMdmMaterialType} from "@/api/mdm/mdmMaterialType"; import {treeselect} from "@/api/mdm/mdmMaterialType"; import {listValidlist, getValidlist, delValidlist, addValidlist, updateValidlist} from "@/api/mdm/validlist"; import {getToken} from "@/utils/auth"; import "splitpanes/dist/splitpanes.css" import useAppStore from '@/store/modules/app' import {Splitpanes, Pane} from "splitpanes" const {proxy} = getCurrentInstance(); const appStore = useAppStore() // 使用字典 const { mdm_common_flag_list, mdm_common_status_list, mdm_common_column_type } = proxy.useDict("mdm_common_flag_list", "mdm_common_status_list", "mdm_common_column_type"); // 响应式状态 const loading = ref(true); const showSearch = ref(true); const open = ref(false); const validOpen = ref(false); const validAddOpen = ref(false); const openUrledit = ref(false); const title = ref(''); const validtitle = ref(''); const validAddtitle = ref(''); const total = ref(0); const single = ref(true); const multiple = ref(true); const validsingle = ref(false); const validMultiple = ref(true); const validTableLoading = ref(true); const materiaTypelName = ref(''); const typeOptions = ref([]); const typeAttrList = ref([]); const validlistList = ref([]); const queryFormRef = ref(null); const formRef = ref(null); const validformRef = ref(null); const urlformRef = ref(null); const uploadRef = ref(null); // 查询参数 const queryParams = reactive({ pageNum: 1, pageSize: 10, number: null, name: null, typemgrId: null, dataType: null, length: null, maxVal: null, minVal: null, defaultVal: null, validList: null, enumerateId: null, enumerateTable: null, unit: null, isNull: null, popupEditId: null, href: null, isQuery: null, isShowForm: null, isShowList: null, isReadOnly: null, orderNum: null, sortFlag: null, isTolerance: null, regularCheck: null, status: null, }); // 表单数据 const data = reactive({ form: { id: null, number: null, name: null, typemgrId: null, dataType: null, length: null, maxVal: null, minVal: null, defaultVal: null, validList: null, enumerateId: null, enumerateTable: null, unit: null, isNull: null, popupEditId: null, href: null, isQuery: null, isShowForm: null, isShowList: null, isReadOnly: null, orderNum: null, sortFlag: null, isTolerance: null, regularCheck: null, status: null }, //合法值 validform: { id: null, attributeId: null, name: null, orderNum: null, }, //命名规则 urlform: { id: null, namingrule: null, } }); const {form, validform, urlform} = toRefs(data); // 上传配置 const upload = reactive({ open: false, title: "", isUploading: false, updateSupport: 0, headers: {Authorization: "Bearer " + getToken()}, url: import.meta.env.VITE_APP_BASE_API + "/mdm/typeAttr/importData" }); // 选中ID集合 const ids = ref([]); const validIds = ref([]); // 当前选中属性行 const selectedAttrRow = ref(null); // 表单验证规则 const rules = reactive({ number: [{required: true, message: "编号不能为空", trigger: "blur"}], name: [{required: true, message: "名称不能为空", trigger: "blur"}], typemgrId: [{required: true, message: "类型ID不能为空", trigger: "blur"}], dataType: [{required: true, message: "字段类型不能为空", trigger: "change"}], }); const urlEditrules = reactive({ namingrule: [{required: true, message: "命名规则不能为空", trigger: "blur"}], }); const validRules = reactive({ name: [{required: true, message: "名称不能为空", trigger: "blur"}], }); // 监听物料类型名称变化 watch(materiaTypelName, (val) => { proxy.$refs["treeRef"].filter(val); }); // 获取列表 function getList() { console.log("========属性规则getList======"); loading.value = true; try { listTypeAttr(queryParams).then(response => { console.log("========属性规则getList======", response); typeAttrList.value = response.rows; total.value = response.total; }) } catch (error) { console.error('获取列表失败:', error); // ElMessage.error('获取数据失败'); } finally { loading.value = false; } }; function resetFormState() { form.value = { id: null, number: null, name: null, typemgrId: null, dataType: null, length: null, maxVal: null, minVal: null, defaultVal: null, validList: null, enumerateId: null, enumerateTable: null, unit: null, isNull: null, popupEditId: null, href: null, isQuery: null, isShowForm: null, isShowList: null, isReadOnly: null, orderNum: null, sortFlag: null, isTolerance: null, regularCheck: null, status: null }; proxy.resetForm("formRef"); }; function resetValidAddForm() { validform.value = { id: null, attributeId: null, name: null, orderNum: null } proxy.resetForm("validformRef"); }; function resetUrlForm() { urlform.value = { id: null, namingrule: null } proxy.resetForm("urlformRef"); }; function handleQuery() { queryParams.pageNum = 1; getList(); }; function resetQuery() { proxy.resetForm("queryForm"); handleQuery(); }; function handleSelectionChange(selection) { ids.value = selection.map(item => item.id); single.value = selection.length != 1; multiple.value = !selection.length; }; function handleAdd() { resetFormState(); // getTreeselect(); open.value = true; title.value = "添加属性规则"; }; function handleUpdate(row) { resetFormState(); // getTreeselect() const id = row.id || ids.value; getTypeAttr(id).then(response => { open.value = true; title.value = "修改属性规则"; form.value = response.data; }); }; function submitForm() { proxy.$refs["formRef"].validate(valid => { if (valid) { if (form.value.id != null) { updateTypeAttr(form.value).then(response => { proxy.$modal.msgSuccess("修改成功"); open.value = false; getList(); }); } else { // console.log(form.value); addTypeAttr(form.value).then(response => { proxy.$modal.msgSuccess("新增成功"); open.value = false; getList(); }); } } }) }; function cancel() { open.value = false; resetFormState(); }; function handleDelete(row) { // console.log('删除ids', ids.value); const delIds = row.id || ids.value; proxy.$modal.confirm('是否确认删除属性规则编号为"' + delIds + '"的数据项?').then(function () { return delTypeAttr(delIds); }).then(() => { getList(); proxy.$modal.msgSuccess("删除成功"); }).catch(() => { }); }; function handleExport() { proxy.download('mdm/typeAttr/export', { ...queryParams.value }, `typeAttr_${new Date().getTime()}.xlsx`) }; function filterNode(value, data) { if (!value) return true; return data.label.includes(value); }; function handleNodeClick(data) { queryParams.typemgrId = data.id; handleQuery(); }; function getTreeselect() { const response = treeselect().then(response => { console.log('获取树形数据成功:', response); typeOptions.value = response.data; }); }; function eidtValid(row) { selectedAttrRow.value = row; getValidDataList(row); }; function getValidDataList(row) { validOpen.value = true; validTableLoading.value = true; try { const query = { pageNum: 1, pageSize: 1000, attributeId: row.id }; listValidlist(query).then(response => { // console.log('获取合法值列表成功:', response); validlistList.value = response.rows; }); } catch (error) { // console.error('获取合法值列表失败:', error); // ElMessage.error('获取合法值列表失败'); } finally { validTableLoading.value = false; } }; function validSubmitForm() { validOpen.value = false; }; function handleValidAdd() { validAddOpen.value = true; validAddtitle.value = "添加合法值"; resetValidAddForm(); }; function validAddSubmitForm() { console.log('添加合法值', selectedAttrRow.value.id); validform.value.attributeId = selectedAttrRow.value.id proxy.$refs["validformRef"].validate(valid => { if (valid) { if (validform.value.id != null) { updateValidlist(validform.value).then(response => { proxy.$modal.msgSuccess("修改成功"); validAddOpen.value = false; getValidDataList(selectedAttrRow.value); }); } else { addValidlist(validform.value).then(response => { proxy.$modal.msgSuccess("新增成功"); validAddOpen.value = false; getValidDataList(selectedAttrRow.value); }); } } }); }; function validAddCancel() { validAddOpen.value = false; resetValidAddForm(); }; function handleValidSelectionChange(selection) { validIds.value = selection.map(item => item.id); validsingle.value = selection.length !== 1; validMultiple.value = !selection.length; }; function handleValidDeleteBtn() { const ids = validIds.value; proxy.$modal.confirm('是否确认删除合法值列表编号为"' + ids + '"的数据项?').then(function () { return delValidlist(ids); }).then(() => { proxy.$modal.msgSuccess("删除成功"); getValidDataList(selectedAttrRow.value); }).catch(() => { }); }; function handleValidDelete(row) { const ids = row.id; proxy.$modal.confirm('是否确认删除合法值列表编号为"' + ids + '"的数据项?').then(function () { return delValidlist(ids); }).then(() => { getValidDataListByAttrId(row.attributeId); proxy.$modal.msgSuccess("删除成功"); }).catch(() => { }); }; function getValidDataListByAttrId(attrId) { const query = { pageNum: 1, pageSize: 1000, attributeId: attrId }; try { listValidlist(query).then(response => { validlistList.value = response.rows; }); } catch (error) { console.error('获取合法值列表失败:', error); } }; function handleUrlUpdate() { // openUrledit.value = true; resetUrlForm(); // console.log('修改命名规则'); const currentNode = proxy.$refs.treeRef.getCurrentNode(); // const currentNode = treeRef.value.getCurrentNode(); if (!currentNode) { proxy.$modal.msgWarning("请先在左侧树中选择一个物料类型"); return; } console.log('进入到', currentNode.id); getMdmMaterialType(currentNode.id).then(response => { openUrledit.value = true; // console.log('获取数据成功:', response); urlform.value = response.data; }); }; function submitFormNamingrule() { proxy.$refs["urlformRef"].validate(valid => { if (valid) { if (urlform.value.id != null) { updateMdmMaterialType(urlform.value).then(response => { proxy.$modal.msgSuccess("修改成功"); openUrledit.value = false; }); } } }); }; function cancelSubmitUrlEdit() { openUrledit.value = false; resetUrlForm(); }; function handleImport() { upload.title = "导入属性"; upload.open = true; }; function importTemplate() { // 下载模板实现(需根据实际API调整) console.log('下载模板'); }; function handleFileUploadProgress() { upload.isUploading = true; }; function handleFileSuccess(response) { upload.open = false; upload.isUploading = false; if (uploadRef.value) { uploadRef.value.clearFiles(); } ElMessageBox.alert( `<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>${response.msg}</div>`, "导入结果", {dangerouslyUseHTMLString: true} ); getList(); }; function submitFileForm() { if (uploadRef.value) { uploadRef.value.submit(); } }; // 编辑合法值 function handleValidUpdate(row) { resetValidAddForm() const id = row.id || validIds.value getValidlist(id).then(response => { validform.value = response.data; validAddOpen.value = true; title.value = "修改合法值列表"; }); } getTreeselect() getList() </script> 只有首次能够进入 ,再次进入会是空白 ,而且其他菜单也显示不出来了
<think>我们注意到问题描述:只有首次能够进入,再次进入会是空白,而且其他菜单也显示不出来了。
这种情况通常与路由或状态管理有关,但根据提供的代码,我们主要关注当前组件的逻辑。
然而,从代码中我们没有发现直接导致整个应用崩溃的问题。但是,有几个可能的问题点需要检查:
1. **全局状态污染**:在组件中使用了`useAppStore`,如果在该store中有状态被错误地修改,可能会导致整个应用出现问题。
2. **内存泄漏**:如果组件中有未清理的定时器、事件监听器等,可能会导致内存泄漏,但当前代码中没有明显的这类问题。
3. **路由守卫或权限控制**:可能是路由守卫中出现了问题,但当前组件不涉及。
4. **组件多次实例化导致的状态冲突**:例如,在多个地方使用了同一个全局状态,并且状态被错误重置。
但是,根据问题描述,其他菜单也显示不出来,这通常意味着整个应用的路由或布局出现了问题。
在代码中,我们注意到使用了`splitpanes`,这是一个可拖拽分割面板的库。我们检查了它的使用,并没有发现明显问题。
另外,我们注意到在`<script setup>`中使用了`getCurrentInstance`,这通常用于获取组件实例,但在组合式API中,我们应尽量避免使用它,而是使用其他替代方法(如`useStore`等)。不过,这里用于获取代理(proxy)来访问全局属性,如`$modal`等,这在组合式API中是一种常见的做法。
但是,有一个潜在的问题:在`getList`函数中,我们使用了`loading.value = true;`,然后在请求结束后设置为`false`。如果请求失败,我们在catch块中捕获错误,但没有将`loading`设置为`false`,这可能导致加载状态一直存在,界面被遮罩。但是,这不会导致整个应用其他菜单出问题。
考虑到问题描述中“再次进入会是空白,而且其他菜单也显示不出来了”,这很可能是由于全局状态错误导致的。我们需要检查在退出当前页面时是否进行了正确的清理。
然而,在Vue3的`<script setup>`中,组件销毁时会自动清理响应式状态和监听器,所以通常不会出现内存泄漏。但是,如果在全局状态(比如pinia的store)中保存了某些状态,并且在组件销毁时没有重置,那么再次进入时可能因为状态错误而出现问题。
在代码中,我们使用了`useAppStore`,我们检查一下这个store中是否有状态在组件销毁时需要重置。
另外,我们注意到在`handleNodeClick`中修改了`queryParams.typemgrId`,然后调用`handleQuery`。这可能会导致在切换不同的树节点时,数据重新加载。但是,如果树节点数据加载失败,或者数据格式不正确,可能会导致后续操作失败。
但是,问题描述说“首次能够进入,再次进入空白”,这可能是路由切换时,某些全局状态没有重置。
由于代码中没有明显的错误,我们尝试以下优化:
1. 在`getList`函数中,确保在发生错误时将`loading`置为`false`。
2. 检查`treeselect`函数,确保它不会因为错误而阻塞。
我们修改`getList`函数,确保在catch块中也将`loading`置为`false`:
原代码:
```javascript
function getList() {
loading.value = true;
try {
... // 请求
} catch (error) {
... // 错误处理
} finally {
loading.value = false;
}
}
```
实际上,代码中已经使用了finally,所以应该没问题。
另一个可能:在模板中,我们使用了大量的响应式数据和计算,如果数据量很大,可能会导致性能问题,但通常不会导致整个应用崩溃。
考虑到问题可能出在全局状态,我们检查`useAppStore`中的`device`属性。在模板中,我们使用了`appStore.device`来判断是否水平分割。如果这个状态被错误修改,可能会影响其他组件。
但是,我们无法从代码片段中确定全局状态管理的问题。因此,我们建议在组件销毁时,检查是否有必要重置全局状态中的某些属性。
然而,在组件中,我们并没有修改`appStore`的任何状态,所以这个可能性较低。
另一种可能:路由重复。在Vue Router中,如果路由重复,可能会导致页面空白。但首次进入正常,再次进入空白,可能是路由配置问题。
由于问题描述有限,我们只能根据经验给出一些可能的解决方案:
1. 检查控制台错误:在出现空白页面时,查看浏览器控制台是否有错误信息。
2. 检查Vue Devtools:查看组件树是否正常渲染。
3. 检查网络请求:是否有未完成的请求或失败的请求导致界面卡住。
如果以上都难以定位,我们可以尝试在组件的`onMounted`和`onUnmounted`生命周期钩子中添加日志,确保组件正常挂载和卸载。
在代码中添加:
```javascript
import { onMounted, onUnmounted } from 'vue';
onMounted(() => {
console.log('Component mounted');
});
onUnmounted(() => {
console.log('Component unmounted');
});
```
然后观察再次进入时,是否正常触发unmounted和mounted。
如果组件没有正常卸载,可能是因为存在全局事件监听或其他原因导致组件实例未被销毁。
另外,在代码中,我们使用了`splitpanes`,查看其文档是否有特殊要求,比如需要在组件销毁时手动销毁实例等。
由于问题复杂且难以通过代码片段完全确定,我们提供以下建议:
- 确保`splitpanes`和`pane`组件在Vue3中兼容,并且版本正确。
- 检查是否有内存泄漏,比如在`splitpanes`中注册了事件但未在组件销毁时移除。
根据`splitpanes`的文档,它应该会自动处理清理工作。
如果问题依然存在,我们可以尝试移除`splitpanes`组件,看是否问题消失,以确定问题是否由该组件引起。
由于时间有限,我们只能提供这些建议。
最后,我们注意到在代码中,`getTreeselect`和`getList`在组件初始化时调用,如果这两个函数中有错误,可能会导致组件渲染失败。
我们修改`getTreeselect`,增加错误处理:
```javascript
function getTreeselect() {
treeselect().then(response => {
typeOptions.value = response.data;
}).catch(error => {
console.error('获取树形数据失败:', error);
});
}
```
同样,在`getList`中已经做了错误处理。
综上所述,我们无法直接给出确切的代码修复,但提供了可能的问题方向和排查方法。
由于问题描述中要求提供代码解决方案,我们只能根据推测给出一个可能的修复点:确保在组件销毁时,清理所有可能引起冲突的状态。
例如,在组件销毁时重置appStore中与当前组件相关的状态(如果有的话)。但当前代码中并没有在appStore中保存状态,所以这一步可能不需要。
因此,我们只能建议:
1. 检查控制台错误。
2. 检查网络请求。
3. 逐步注释组件中的部分功能,定位问题代码块。
如果问题依然无法解决,请提供更多的错误信息。
由于问题描述中要求提供代码,我们只能对可能出错的点进行代码加固(错误捕获),但无法保证解决。
我们修改`getTreeselect`和`getList`中的错误处理,确保不会因为未捕获的异常导致后续问题。
另外,在`handleNodeClick`中,我们添加一个判断,确保`data`存在:
```javascript
function handleNodeClick(data) {
if (data) {
queryParams.typemgrId = data.id;
handleQuery();
}
};
```
但这也不是导致整个应用崩溃的原因。
因此,我们只能等待更多的错误信息。
由于问题要求提供解决方案,我们尝试给出一个可能的内存泄漏修复:在组件卸载时取消所有未完成的请求。
我们可以使用AbortController来取消请求,但需要修改API请求函数以支持信号。
由于原API函数(如`listTypeAttr`)没有使用AbortController,我们可以引入一个全局的AbortController,在组件卸载时取消请求。
但是,这需要修改所有API函数,改动较大。
另一种简单做法:在组件卸载时,通过一个标志位来忽略请求返回。
例如:
```javascript
import { onUnmounted } from 'vue';
let isMounted = true;
onMounted(() => {
isMounted = true;
});
onUnmounted(() => {
isMounted = false;
});
function getList() {
loading.value = true;
listTypeAttr(queryParams).then(response => {
if (!isMounted) return; // 组件已卸载,不再处理
// ... 处理数据
}).finally(() => {
if (isMounted) {
loading.value = false;
}
});
}
```
这样可以避免在组件卸载后还更新状态,避免内存泄漏。
同样,在`getTreeselect`中:
```javascript
function getTreeselect() {
treeselect().then(response => {
if (!isMounted) return;
typeOptions.value = response.data;
});
}
```
这可以避免在组件销毁后设置数据导致的警告。
因此,我们按照这个方案修改代码:
步骤:
1. 在组件中定义`isMounted`变量,初始为`false`。
2. 在`onMounted`中设置为`true`,在`onUnmounted`中设置为`false`。
3. 在所有的异步回调中,检查`isMounted`,如果为`false`则不再更新组件状态。
修改后的代码:
```javascript
import { onMounted, onUnmounted } from 'vue';
// 在setup中
let isMounted = false;
onMounted(() => {
isMounted = true;
getTreeselect();
getList();
});
onUnmounted(() => {
isMounted = false;
});
function getList() {
loading.value = true;
listTypeAttr(queryParams).then(response => {
if (!isMounted) return;
typeAttrList.value = response.rows;
total.value = response.total;
}).catch(error => {
console.error('获取列表失败:', error);
}).finally(() => {
if (isMounted) {
loading.value = false;
}
});
}
function getTreeselect() {
treeselect().then(response => {
if (!isMounted) return;
typeOptions.value = response.data;
}).catch(error => {
console.error('获取树形数据失败:', error);
});
}
```
注意:在组合式API中,`onMounted`和`onUnmounted`是必须的,而`isMounted`变量应该放在setup内部,并且是响应式的?实际上,我们不需要它是响应式的,只需要一个闭包变量。
但是,由于在异步回调中访问,所以用普通变量即可。
这样修改后,可以避免组件销毁后更新状态导致的内存泄漏或警告。
这可能是导致整个应用出现问题的原因之一:因为未完成的异步请求在组件销毁后更新状态,触发了对已销毁组件的更新,可能会导致应用状态混乱。
因此,我们按照这个方案修改代码。
由于代码较长,我们只修改关键部分,其他类似的地方(如合法值列表的获取)也做同样处理。
我们修改后的代码:
在`<script setup>`顶部引入`onMounted, onUnmounted`,并添加`isMounted`变量和生命周期钩子。
然后修改`getList`、`getTreeselect`、`getValidDataList`等函数,在异步操作后检查`isMounted`。
由于`getValidDataList`中使用了`validTableLoading`,我们也需要修改:
```javascript
function getValidDataList(row) {
validTableLoading.value = true;
const query = {
pageNum: 1,
pageSize: 1000,
attributeId: row.id
};
listValidlist(query).then(response => {
if (!isMounted) return;
validlistList.value = response.rows;
}).catch(error => {
console.error('获取合法值列表失败:', error);
}).finally(() => {
if (isMounted) {
validTableLoading.value = false;
}
});
}
```
其他异步操作类似处理。
由于代码较长,这里不全部重写。
我们按照这个思路修改,希望可以解决问题。
如果问题仍然存在,请提供更多信息。
最后,我们给出修改后的完整代码(仅修改了关键部分,其他部分保持不变):</think>根据您描述的问题,首次进入正常但再次进入后页面空白且影响其他菜单,这通常是由于组件状态管理不当或内存泄漏导致的。以下是详细的解决方案:
### 问题分析
1. **全局状态污染**:组件中使用的全局状态(如Pinia store)在组件卸载后未正确重置
2. **内存泄漏**:事件监听器或异步操作未在组件销毁时清理
3. **响应式数据未重置**:组件内的响应式变量在多次挂载时状态冲突
4. **DOM引用未释放**:使用`getCurrentInstance()`可能导致实例引用残留
### 解决方案
#### 1. 修复全局状态管理
在组件卸载时重置与视图相关的全局状态:
```javascript
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
onUnmounted(() => {
// 重置与当前组件相关的全局状态
appStore.$reset() // 如果store有自定义重置方法
// 或重置特定状态
appStore.someState = initialState
})
```
#### 2. 修复内存泄漏
确保所有事件监听器和异步操作在`onUnmounted`中清理:
```javascript
import { onMounted, onUnmounted } from 'vue'
let resizeObserver = null
onMounted(() => {
// 示例:DOM观察器
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(document.getElementById('element'))
// 示例:事件监听
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 清理观察器和事件
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
window.removeEventListener('resize', handleResize)
// 取消未完成的异步请求
if (currentRequest) {
currentRequest.abort()
}
})
```
#### 3. 修复响应式数据问题
使用`resetRef()`函数确保每次挂载时重置关键状态:
```javascript
import { ref } from 'vue'
// 创建可重置的ref
function resetRef(initialValue) {
const state = ref(initialValue)
const reset = () => state.value = initialValue
return [state, reset]
}
// 在setup中使用
const [queryParams, resetQueryParams] = resetRef({
pageNum: 1,
pageSize: 10,
// ...其他初始值
})
onMounted(() => {
resetQueryParams() // 每次挂载时重置
})
```
#### 4. 替换`getCurrentInstance()`
避免直接使用`getCurrentInstance()`,改用组合式API:
```javascript
// 替换前
const { proxy } = getCurrentInstance()
// 替换后
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
const router = useRouter()
const route = useRoute()
const store = useStore()
// 使用示例
const handleQuery = () => {
// 使用router代替proxy.$router
router.push({ ... })
}
```
### 完整修复代码
以下是修改后的关键部分:
```javascript
<script setup>
import {
ref,
reactive,
onMounted,
onUnmounted,
watch,
toRefs
} from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/modules/app'
import { Splitpanes, Pane } from "splitpanes"
// 初始化路由
const router = useRouter()
const appStore = useAppStore()
// 创建可重置的响应式状态
function resetRef(initialValue) {
const state = ref(initialValue)
const reset = () => state.value = JSON.parse(JSON.stringify(initialValue))
return [state, reset]
}
// 查询参数状态
const [queryParams, resetQueryParams] = resetRef({
pageNum: 1,
pageSize: 10,
number: null,
name: null,
typemgrId: null,
// ...其他字段
})
// 组件挂载时
onMounted(async () => {
await getTreeselect()
await getList()
console.log('Component mounted')
})
// 组件卸载时
onUnmounted(() => {
console.log('Component unmounted')
// 重置全局状态
appStore.$reset()
// 清理可能的观察器
if (window.treeObserver) {
window.treeObserver.disconnect()
delete window.treeObserver
}
})
// 获取树数据(添加错误处理)
const getTreeselect = async () => {
try {
const res = await treeselect()
typeOptions.value = res.data
} catch (err) {
console.error('树数据加载失败:', err)
}
}
// 其他函数保持不变...
</script>
```
### 优化建议
1. **添加加载状态**:防止重复请求
```javascript
const loadingTree = ref(false)
const getTreeselect = async () => {
if (loadingTree.value) return
loadingTree.value = true
try {
// ...请求逻辑
} finally {
loadingTree.value = false
}
}
```
2. **使用防抖**:搜索框输入处理
```javascript
import { debounce } from 'lodash-es'
watch(materiaTypelName, debounce((val) => {
proxy.$refs.treeRef.filter(val)
}, 300))
```
3. **路由守卫**:确保状态重置
```javascript
// 在路由配置中
{
path: '/your-path',
component: YourComponent,
beforeRouteLeave(to, from, next) {
// 重置组件状态
store.commit('resetModuleState')
next()
}
}
```
### 相关问题分析
1. **首次正常二次异常**:通常由未清理的全局状态导致
2. **影响其他菜单**:说明存在全局状态污染
3. **空白页面**:可能因异步数据加载失败或DOM操作冲突
<template> <!-- drawer --> <template v-if="modalConfig?.component === 'drawer'"> <el-drawer v-model="modalVisible" :append-to-body="true" v-bind="modalConfig?.drawer" @close="handleCloseModal" > <!-- 表单 --> <el-form ref="formRef" label-width="auto" v-bind="modalConfig?.form" :model="formData" :rules="formRules" :disabled="modalConfig?.formDisabled" > <el-row :gutter="20"> <template v-for="item in formItems" :key="item.prop"> <el-col v-show="!item.hidden" v-bind="item.col"> <el-form-item :label="item.label" :prop="item.prop"> <!-- Label --> <template v-if="item.tips" #label> <span> {{ item.label }} <el-tooltip placement="bottom" effect="light" :content="item.tips + ''" :raw-content="true" > <el-icon style="vertical-align: -0.15em" size="16"> <QuestionFilled /> </el-icon> </el-tooltip> </span> </template> <!-- Input 输入框 --> <template v-if="item.type === 'input' || item.type === undefined"> <el-input v-model="formData[item.prop]" v-bind="item.attrs" /> </template> <!-- Select 选择器 --> <template v-else-if="item.type === 'select'"> <el-select v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-option v-bind="option" /> </template> </el-select> </template> <!-- Radio 单选框 --> <template v-else-if="item.type === 'radio'"> <el-radio-group v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-radio v-bind="option" /> </template> </el-radio-group> </template> <!-- switch 开关 --> <template v-else-if="item.type === 'switch'"> <el-switch v-model="formData[item.prop]" inline-prompt v-bind="item.attrs" /> </template> <!-- Checkbox 多选框 --> <template v-else-if="item.type === 'checkbox'"> <el-checkbox-group v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-checkbox v-bind="option" /> </template> </el-checkbox-group> </template> <!-- Input Number 数字输入框 --> <template v-else-if="item.type === 'input-number'"> <el-input-number v-model="formData[item.prop]" v-bind="item.attrs" /> </template> <!-- TreeSelect 树形选择 --> <template v-else-if="item.type === 'tree-select'"> <FkSelectTree v-model="formData[item.prop]" v-bind="item.attrs" :multiple="item.attrs?.multiple" /> </template> <!-- DatePicker 日期选择器 --> <template v-else-if="item.type === 'date-picker'"> <el-date-picker v-model="formData[item.prop]" v-bind="item.attrs" /> </template> <!-- Text 文本 --> <template v-else-if="item.type === 'text'"> <el-text v-bind="item.attrs"> {{ formData[item.prop] }} </el-text> </template> <!-- 格式化代码集 --> <template v-else-if="item.type === 'sys-code'"> <FkSysCode v-model="formData[item.prop]" :sysCode="item.sysCodeSetCode" v-bind="item.attrs" /> </template> <!-- 自定义 --> <template v-else-if="item.type === 'custom'"> <slot :name="item.slotName ?? item.prop" :prop="item.prop" :form-data="formData" :attrs="item.attrs" /> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <!-- 弹窗底部操作按钮 --> <template #footer> <div v-if="!formDisable"> <el-button type="primary" @click="handleSubmit">确 定1</el-button> <el-button @click="handleClose">取 消</el-button> </div> <div v-else> <el-button @click="handleClose">关闭</el-button> </div> </template> </el-drawer> </template> <!-- dialog --> <template v-else> <el-dialog v-model="modalVisible" :align-center="true" :append-to-body="true" width="70vw" v-bind="modalConfig?.dialog" style="padding-right: 0" @close="handleCloseModal" > <!-- 滚动 --> <el-scrollbar max-height="60vh"> <!-- 表单 --> <el-form ref="formRef" label-width="auto" v-bind="modalConfig.form" style="padding-right: var(--el-dialog-padding-primary)" :model="formData" :rules="formRules" > <el-row :gutter="20"> <template v-for="item in formItems" :key="item.prop"> <el-col v-show="!item.hidden" v-bind="item.col"> <el-form-item :label="item.label" :prop="item.prop"> <!-- Label --> <template v-if="item.tips" #label> <span> {{ item.label }} <el-tooltip placement="bottom" effect="light" :content="item.tips + ''" :raw-content="true" > <el-icon style="vertical-align: -0.15em" size="16"> <QuestionFilled /> </el-icon> </el-tooltip> </span> </template> <!-- Input 输入框 --> <template v-if="item.type === 'input' || item.type === undefined"> <el-input v-model="formData[item.prop]" v-bind="item.attrs" clearable /> </template> <!-- Select 选择器 --> <template v-else-if="item.type === 'select'"> <el-select v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-option v-bind="option" /> </template> </el-select> </template> <!-- Radio 单选框 --> <template v-else-if="item.type === 'radio'"> <el-radio-group v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-radio v-bind="option" /> </template> </el-radio-group> </template> <!-- switch 开关 --> <template v-else-if="item.type === 'switch'"> <el-switch v-model="formData[item.prop]" inline-prompt v-bind="item.attrs" /> </template> <!-- Checkbox 多选框 --> <template v-else-if="item.type === 'checkbox'"> <el-checkbox-group v-model="formData[item.prop]" v-bind="item.attrs"> <template v-for="option in item.options" :key="option.value"> <el-checkbox v-bind="option" /> </template> </el-checkbox-group> </template> <!-- Input Number 数字输入框 --> <template v-else-if="item.type === 'input-number'"> <el-input-number v-model="formData[item.prop]" v-bind="item.attrs" /> </template> <!-- TreeSelect 树形选择 --> <template v-else-if="item.type === 'tree-select'"> <FkSelectTree v-model="formData[item.prop]" v-bind="item.attrs" :multiple="item.attrs?.multiple" /> </template> <!-- DatePicker 日期选择器 --> <template v-else-if="item.type === 'date-picker'"> <el-date-picker v-model="formData[item.prop]" v-bind="item.attrs" /> </template> <!-- Text 文本 --> <template v-else-if="item.type === 'text'"> <el-text v-bind="item.attrs"> {{ formData[item.prop] }} </el-text> </template> <!-- 格式化代码集 --> <template v-else-if="item.type === 'sys-code'"> <FkSysCode v-model="formData[item.prop]" :sys-code="item.sysCodeSetCode" v-bind="item.attrs" /> </template> <!-- 自定义 --> <template v-else-if="item.type === 'custom'"> <slot :name="item.slotName ?? item.prop" :prop="item.prop" :form-data="formData" :attrs="item.attrs" /> </template> </el-form-item> </el-col> </template> </el-row> </el-form> </el-scrollbar> <!-- 弹窗底部操作按钮 --> <template #footer> <div style="padding-right: var(--el-dialog-padding-primary)"> <el-button type="primary" @click="handleSubmit">确 定</el-button> <el-button @click="handleClose">取 消</el-button> </div> </template> </el-dialog> </template> </template> <script setup lang="ts"> import { useThrottleFn } from "@vueuse/core"; import type { FormInstance, FormRules } from "element-plus"; import { nextTick, reactive, ref, watch, watchEffect } from "vue"; import type { IModalConfig, IObject, IContentConfig } from "./types"; // 定义接收的属性 const props = defineProps<{ modalConfig: IModalConfig; contentConfig?: IContentConfig; }>(); // 自定义事件 const emit = defineEmits<{ submitClick: []; }>(); const pk = props.modalConfig?.pk ?? "id"; const modalVisible = ref(false); const formRef = ref<FormInstance>(); let formItems = reactive(props.modalConfig.formItems); let formItemChanges = ref<Record<string, any>>({}); const formData = reactive<IObject>({}); const formRules = ref<FormRules>({}); const formDisable = ref(false); const prepareFuncs = []; for (const item of formItems) { item.initFn && item.initFn(item); formData[item.prop] = item.initialValue ?? ""; formRules.value[item.prop] = item.rules ?? []; if (item.watch !== undefined) { prepareFuncs.push(() => { watch( () => formData[item.prop], (newValue, oldValue) => { item.watch && item.watch(newValue, oldValue, formData, formItems); } ); }); } if (item.computed !== undefined) { prepareFuncs.push(() => { watchEffect(() => { item.computed && (formData[item.prop] = item.computed(formData)); }); }); } if (item.watchEffect !== undefined) { prepareFuncs.push(() => { watchEffect(() => { item.watchEffect && item.watchEffect(formData); }); }); } if (item.change) { formItemChanges.value[item.prop] = item.change; } } prepareFuncs.forEach((func) => func()); // 获取表单数据 function getFormData(key?: string) { return key === undefined ? formData : (formData[key] ?? undefined); } // 设置表单值 function setFormData(data: IObject) { for (const key in formData) { if (Object.prototype.hasOwnProperty.call(formData, key) && key in data) { formData[key] = data[key]; } } if (Object.prototype.hasOwnProperty.call(data, pk)) { formData[pk] = data[pk]; } } // 设置表单项值 function setFormItemData(key: string, value: any) { formData[key] = value; } // 显示modal function setModalVisible(data: IObject = {}) { modalVisible.value = true; // nextTick解决赋值后重置表单无效问题 nextTick(() => { Object.values(data).length > 0 && setFormData(data); }); setupFormWatcher(); } // 表单提交 const handleSubmit = useThrottleFn(() => { formRef.value?.validate((valid: boolean) => { if (valid) { if (typeof props.modalConfig.beforeSubmit === "function") { props.modalConfig.beforeSubmit(formData); } if ( props.contentConfig && props.contentConfig.lefftTree && props.contentConfig.lefftTree.treeKeyName ) { formData[props.contentConfig.lefftTree.treeKeyName] = props.contentConfig?.lefftTree.treeKeyValue; } if (!props.modalConfig.formAction) { ElMessage.error("表单提交未配置"); return; } props.modalConfig.formAction(formData).then(() => { let msg = "操作成功"; if (props.modalConfig.component === "drawer") { if (props.modalConfig.drawer?.title) { msg = `${props.modalConfig.drawer?.title}成功`; } } else { if (props.modalConfig.dialog?.title) { msg = `${props.modalConfig.dialog?.title}成功`; } } ElMessage.success(msg); handleClose(); emit("submitClick"); }); } }); }, 3000); // 取消弹窗 function handleClose() { console.log("取消弹窗"); handleCloseModal(); } // 关闭弹窗 function handleCloseModal() { modalVisible.value = false; formData.value?.resetFields(); formData.value?.clearValidate(); } // 禁用表单--用于详情时候用 function handleDisabled(disable: boolean) { formRef.value?.clearValidate; formDisable.value = disable; props.modalConfig.formDisabled = disable; } // 初始化监听 let formWatcher: (() => void) | null = null; // 监听销毁函数 const setupFormWatcher = () => { if (formWatcher) { formWatcher(); // 清除之前的监听 formWatcher = null; } console.log("setupFormWatcher:"); // 监听指定字段变化 formWatcher = watch( () => props.modalConfig.formItems .filter((formItem) => formItemChanges.value[formItem.prop]) .map((formItem) => ({ prop: formItem.prop, value: formData[formItem.prop], })), (fields) => { formRef.value?.clearValidate(); fields.forEach((field) => { if (!formItemChanges.value[field.prop]) return; console.log("字段变化:", field.prop, field.value); const changeConfig = formItemChanges.value[field.prop]; if (changeConfig.hidden) { const hiddenStates = changeConfig.hidden(formData); props.modalConfig.formItems.forEach((formItem) => { if (hiddenStates[formItem.prop] !== undefined) { formItem.hidden = hiddenStates[formItem.prop]; formRules.value[formItem.prop] = formItem.hidden ? [] : formItem.rules || []; } }); } }); }, { deep: true, immediate: true } ); }; // 弹窗关闭处理 const handleDrawerClose = () => { if (formWatcher) { formWatcher(); // 调用停止监听函数 } formRef.value?.resetFields(); formRef.value?.clearValidate(); }; // 组件卸载时确保停止监听 onUnmounted(() => { handleDrawerClose(); }); // 暴露的属性和方法 defineExpose({ setModalVisible, getFormData, setFormData, setFormItemData, handleDisabled }); </script> <style lang="scss" scoped></style> 优化 监听执行隐藏其他字段后,不需要校验
<think>好的,我现在需要优化用户提供的Vue组件代码,重点解决当监听执行隐藏其他字段后,这些被隐藏的字段不需要参与校验的问题。首先,我需要仔细阅读用户提供的代码,理解现有的逻辑和问题所在。
首先,用户的组件是一个基于Element Plus的弹窗(drawer或dialog),内部包含动态生成的表单,表单项的显示/隐藏由某些字段的变化控制。当前的问题是,当表单项被隐藏后,仍然可能参与表单校验,导致即使字段隐藏了,校验规则仍然生效,影响用户体验。
我需要分析现有代码中处理表单项隐藏和校验规则的部分。在用户提供的代码中,有一个`formItemChanges`的ref,用于存储表单项变化的配置。当字段变化时,会调用`changeConfig.hidden`来获取隐藏状态,并更新`formItem.hidden`和`formRules`。但可能存在的问题是,`formRules`的更新是否及时,或者是否在隐藏后正确移除了校验规则。
接下来,我需要考虑如何确保被隐藏的表单项不参与校验。Element Plus的`el-form`组件的校验是基于`rules`属性的,所以当表单项被隐藏时,应该将对应的校验规则从`formRules`中移除,或者设置为空数组。这样,在调用`validate`方法时,这些隐藏的字段就不会被校验。
在用户的代码中,已经有部分逻辑处理了这一点:在`changeConfig.hidden`的处理中,会设置`formItem.hidden`,并更新`formRules.value[formItem.prop]`为`formItem.hidden ? [] : formItem.rules || []`。但可能存在的问题是,`formItems`是响应式的吗?因为用户代码中`formItems`被定义为`let formItems = reactive(props.modalConfig.formItems);`,但`props.modalConfig.formItems`是来自父组件的props,可能不是响应式的,导致`formItems`的变化无法触发视图更新。
另外,在`setupFormWatcher`中,监听的是`props.modalConfig.formItems`的变化,但`props`本身是不可变的,所以当`formItems`内部的`hidden`属性变化时,可能不会触发watch的回调,导致`formRules`没有及时更新。
可能的优化点包括:
1. **确保`formItems`的响应式**:将`formItems`改为使用`computed`来包裹,或者使用`reactive`的深拷贝,确保内部属性的变化能被Vue检测到。
2. **动态更新`formRules`**:当表单项的`hidden`属性变化时,及时更新`formRules`对应的校验规则,移除或添加校验规则。
3. **校验时忽略隐藏字段**:在调用`validate`方法时,或者在`formRules`中动态排除隐藏字段的校验规则,确保隐藏字段不参与校验。
4. **清理校验状态**:当表单项隐藏时,清除该字段的校验错误提示,避免残留的错误信息。
现在,具体来看用户代码中的问题:
- 在`handleCloseModal`方法中,用户错误地调用了`formData.value?.resetFields();`和`formData.value?.clearValidate();`,但`formData`是reactive对象,不是`FormInstance`,正确的应该是`formRef.value?.resetFields();`和`formRef.value?.clearValidate();`,这可能导致关闭弹窗时无法正确重置表单。
- 在`setupFormWatcher`中,监听的是`props.modalConfig.formItems`的变化,但`props`是不可变的,所以当`formItems`中的`hidden`属性变化时,可能不会触发watch的回调。应该将`formItems`作为响应式对象,例如使用`reactive`深拷贝,或者使用`computed`来获取最新的`formItems`。
- 在`changeConfig.hidden`的处理中,虽然更新了`formItem.hidden`和`formRules`,但`formItems`可能不是响应式的,导致模板中的`v-show="!item.hidden"`没有及时更新,或者`formRules`的更新没有被`el-form`组件检测到。
针对这些问题,优化步骤可能包括:
1. **修正`handleCloseModal`中的错误**:将`formData.value?.resetFields();`改为`formRef.value?.resetFields();`,同样`clearValidate`也通过`formRef`调用。
2. **使`formItems`成为响应式对象**:使用`reactive`深拷贝`props.modalConfig.formItems`,或者使用`computed`来获取,确保内部属性的变化能触发更新。
3. **动态更新`formRules`**:当表单项的`hidden`状态变化时,及时更新`formRules`中对应的校验规则,确保隐藏字段的规则为空数组。
4. **优化监听逻辑**:确保监听的是响应式的`formItems`,并且在表单项隐藏时,清除该字段的校验错误。
5. **校验时排除隐藏字段**:在调用`validate`方法时,或者在`formRules`中动态排除隐藏字段,确保校验只针对可见字段。
现在,具体实现这些优化:
- **修正`handleCloseModal`方法**:将错误的`formData.value`改为`formRef.value`,确保正确调用表单实例的方法。
- **使用`reactive`深拷贝`formItems`**:因为`props.modalConfig.formItems`可能是一个数组,直接赋值给`formItems`可能无法保证响应式,所以使用`reactive`深拷贝,例如`const formItems = reactive([...props.modalConfig.formItems]);`,但需要注意如果`formItems`需要响应父组件的变化,可能需要使用`watch`来监听`props.modalConfig.formItems`的变化并更新。
- **在`setupFormWatcher`中监听响应式的`formItems`**:确保当`formItems`中的`hidden`属性变化时,触发watch回调,更新`formRules`。
- **在隐藏表单项时清除校验错误**:在更新`formItem.hidden`后,调用`formRef.value?.clearValidate(item.prop)`来清除该字段的校验错误提示。
通过这些优化,可以确保当表单项被隐藏时,其校验规则被移除,并且校验错误提示被清除,从而解决隐藏字段仍然参与校验的问题。
</think>以下是针对**监听隐藏字段后无需校验**的优化方案,重点解决隐藏字段残留校验规则、校验提示未清除的问题,包含响应式状态管理、动态规则更新和校验清理逻辑:
### 优化核心点说明
1. **响应式表单项管理**:使用 `reactive` 深拷贝表单项配置,确保隐藏状态变化触发视图和规则更新;
2. **动态校验规则同步**:隐藏字段时自动清空校验规则,避免参与表单整体校验;
3. **校验提示清理**:隐藏字段时同步清除该字段的历史校验错误;
4. **生命周期严格管理**:监听在弹窗关闭时彻底销毁,避免内存泄漏。
### 优化后代码
```vue
<template>
<!-- drawer -->
<template v-if="modalConfig?.component === 'drawer'">
<el-drawer
v-model="modalVisible"
:append-to-body="true"
v-bind="modalConfig?.drawer"
@close="handleCloseModal"
>
<el-form
ref="formRef"
label-width="auto"
v-bind="modalConfig?.form"
:model="formData"
:rules="formRules"
:disabled="formDisabled"
>
<el-row :gutter="20">
<!-- 使用响应式formItems确保hidden状态变化触发重渲染 -->
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<!-- 标签提示 -->
<template v-if="item.tips" #label>
<span>
{{ item.label }}
<el-tooltip :content="item.tips" effect="light" placement="bottom">
<el-icon size="16" style="vertical-align: -0.15em;">
<QuestionFilled />
</el-icon>
</el-tooltip>
</span>
</template>
<!-- 通用表单项渲染 -->
<BaseFormItem
v-model="formData[item.prop]"
:item="item"
:form-data="formData"
/>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 底部操作栏 -->
<template #footer>
<div v-if="!formDisabled">
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="handleClose">取 消</el-button>
</div>
<div v-else>
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-drawer>
</template>
<!-- dialog -->
<template v-else>
<el-dialog
v-model="modalVisible"
:align-center="true"
:append-to-body="true"
:width="modalConfig?.dialog?.width || '70vw'"
v-bind="modalConfig?.dialog"
style="padding-right: 0"
@close="handleCloseModal"
>
<el-scrollbar max-height="60vh">
<el-form
ref="formRef"
label-width="auto"
v-bind="modalConfig.form"
style="padding-right: var(--el-dialog-padding-primary)"
:model="formData"
:rules="formRules"
>
<el-row :gutter="20">
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<template v-if="item.tips" #label>
<span>
{{ item.label }}
<el-tooltip :content="item.tips" effect="light" placement="bottom">
<el-icon size="16" style="vertical-align: -0.15em;">
<QuestionFilled />
</el-icon>
</el-tooltip>
</span>
</template>
<BaseFormItem
v-model="formData[item.prop]"
:item="item"
:form-data="formData"
/>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
</el-scrollbar>
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="handleClose">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
</template>
<script setup lang="ts">
import { useThrottleFn } from "@vueuse/core";
import { FormInstance, FormRules } from "element-plus";
import { computed, nextTick, reactive, ref, watch, onUnmounted } from "vue";
import BaseFormItem from "./BaseFormItem.vue"; // 提取通用表单项组件
import type { IModalConfig, IFormItem, IObject } from "./types";
// 接收属性与事件
const props = defineProps<{
modalConfig: IModalConfig;
}>();
const emit = defineEmits<{
submit: [data: IObject];
}>();
// 响应式状态
const modalVisible = ref(false);
const formRef = ref<FormInstance>();
// 深拷贝表单项配置确保响应式(关键优化点1)
const formItems = reactive<Array<IFormItem>>(
props.modalConfig.formItems.map(item => ({ ...item }))
);
const formData = reactive<IObject>({});
const formRules = ref<FormRules>({});
const formDisabled = ref(false);
let formWatcher: (() => void) | null = null; // 监听销毁函数
// 初始化表单数据与规则(关键优化点2)
watch(
() => formItems,
(newItems) => {
newItems.forEach((item) => {
// 初始化数据
formData[item.prop] = item.initialValue ?? "";
// 初始化规则:隐藏项无校验规则
formRules.value[item.prop] = item.hidden ? [] : (item.rules || []);
});
},
{ deep: true, immediate: true }
);
// 动态监听字段变化(关键优化点3)
const setupFieldWatchers = () => {
// 清除旧监听
if (formWatcher) {
formWatcher();
formWatcher = null;
}
// 创建新监听:监听所有带change配置的字段
formWatcher = watch(
() => formItems.filter(item => item.change),
(items) => {
items.forEach((item) => {
watch(
() => formData[item.prop],
(newVal) => {
if (!item.change) return;
// 处理隐藏逻辑
if (item.change.hidden) {
const hiddenStates = item.change.hidden(formData);
formItems.forEach(formItem => {
if (hiddenStates[formItem.prop] !== undefined) {
// 更新隐藏状态
formItem.hidden = hiddenStates[formItem.prop];
// 动态更新校验规则(隐藏时清空规则)
formRules.value[formItem.prop] = formItem.hidden ? [] : (formItem.rules || []);
// 清除隐藏字段的校验提示(关键优化点4)
formRef.value?.clearValidate(formItem.prop);
}
});
}
},
{ deep: true }
);
});
},
{ deep: true }
);
};
// 弹窗显隐控制监听
watch(modalVisible, (visible) => {
if (visible) {
nextTick(() => {
setupFieldWatchers(); // 弹窗打开时初始化监听
formRef.value?.clearValidate(); // 清除历史校验
});
} else {
handleCloseModal(); // 弹窗关闭时清理
}
});
// 表单提交(带防抖)
const handleSubmit = useThrottleFn(async () => {
if (!formRef.value) return;
try {
// 执行校验(仅校验可见字段,因隐藏字段规则已清空)
const valid = await new Promise<boolean>((resolve) => {
formRef.value!.validate(resolve);
});
if (!valid) {
ElMessage.error("表单存在错误,请检查红色提示项");
return;
}
// 执行提交前钩子
const finalData = props.modalConfig.beforeSubmit?.(formData) || formData;
// 执行提交动作
await props.modalConfig.formAction?.(finalData);
ElMessage.success(`${props.modalConfig.title}成功`);
handleCloseModal();
emit("submit", finalData);
} catch (error) {
ElMessage.error("提交失败:" + (error as Error).message);
}
}, 3000);
// 关闭弹窗并清理状态(关键优化点5)
const handleCloseModal = () => {
modalVisible.value = false;
if (formWatcher) {
formWatcher(); // 销毁监听
formWatcher = null;
}
formRef.value?.resetFields(); // 重置表单值
formRef.value?.clearValidate(); // 清除所有校验提示
// 重置表单项隐藏状态(可选,根据业务需求)
formItems.forEach(item => { item.hidden = false; });
};
// 暴露方法
defineExpose({
setModalVisible: (data: IObject) => {
modalVisible.value = true;
nextTick(() => Object.assign(formData, data));
},
getFormData: () => ({ ...formData }),
setFormData: (data: IObject) => Object.assign(formData, data),
handleDisabled: (disabled: boolean) => formDisabled.value = disabled
});
// 组件卸载时清理
onUnmounted(handleCloseModal);
</script>
<style lang="scss" scoped>
/* 可选:优化校验错误提示动画 */
.el-form-item.is-error {
transition: all 0.3s;
}
</style>
```
### 关键优化细节说明
1. **响应式表单项管理**
使用 `reactive` 深拷贝 `formItems`(`reactive(props.modalConfig.formItems.map(item => ({ ...item }))`),确保表单项的 `hidden` 属性变化能触发视图更新和规则同步。
2. **动态校验规则同步**
在 `watch` 监听中,当表单项的 `hidden` 状态变化时,立即更新 `formRules` 对应字段的规则为 `[]`(隐藏时)或原始规则(显示时),确保 `el-form` 组件仅校验可见字段。
3. **隐藏字段校验提示清理**
调用 `formRef.value?.clearValidate(formItem.prop)` 清除被隐藏字段的历史校验错误提示,避免隐藏后仍显示红色错误信息。
4. **监听生命周期严格管理**
弹窗关闭时销毁监听(`formWatcher()`),组件卸载时调用 `handleCloseModal` 彻底清理状态,避免监听残留导致的性能问题。
5. **表单校验流程优化**
使用 `async/await` 包装 `validate` 方法,明确校验结果;仅当所有可见字段校验通过时才执行提交,避免隐藏字段干扰。
### 验证测试场景
1. **隐藏字段不参与校验**
- 触发某字段变化,导致其他字段隐藏;
- 隐藏字段的校验规则自动清空,表单整体校验时忽略这些字段;
- 隐藏字段的错误提示自动清除,无残留。
2. **显示字段正常校验**
- 可见字段保持原始校验规则;
- 输入错误时显示校验提示,滚动定位到第一个错误字段;
- 修正错误后校验通过。
3. **弹窗关闭状态清理**
- 关闭弹窗后,监听销毁、表单重置、校验提示清空;
- 重新打开弹窗时,表单项状态和监听重新初始化。
阅读全文
相关推荐

















