判断ID不为空,怎么做?if (Request.Params ["id"] !=null)

代码展示了从请求的查询字符串中解析参数id的过程。使用int.Parse方法尝试解析,若出现异常则将id赋值为1,体现了对请求参数解析异常的处理。
int id;
try{
id=int.Parse(Request.QueryString["id"];
}
catch{
id=1;
}
<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 --> <template> <!-- 查询表单 --> <SearchForm v-show="isShowSearch" :search="_search" :reset="_reset" :columns="searchColumns" :search-param="searchParam" :search-col="searchCol" /> <!-- 表格主体 --> <div class="card table-main"> <!-- 表格头部 操作按钮 --> <div class="table-header"> <div class="header-button-lf"> <slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" /> </div> <div v-if="toolButton" class="header-button-ri"> <slot name="toolButton"> <el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="refreshData" /> <el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" /> <el-button v-if="showToolButton('search') && searchColumns?.length" :icon="Search" circle @click="isShowSearch = !isShowSearch" /> </slot> </div> </div> <!-- 表格上方的提示信息 --> <div v-if="tableTipsFlag"> <slot name="tableTips"></slot> </div> <!-- 表格主体 --> <el-table ref="tableRef" v-bind="$attrs" :data="processTableData" :border="border" :row-key="rowKey" @selection-change="selectionChange" @cell-dblclick="tableCellDblClick" @row-dblclick="tableRowDblclick"> <!-- 默认插槽 --> <slot /> <template v-for="item in tableColumns" :key="item"> <!-- selection || radio || index || expand || sort --> <el-table-column v-if="item.type && columnTypes.includes(item.type)" v-bind="item" :align="item.align ?? 'center'" :reserve-selection="item.type == 'selection'" :selectable="item.isSelectable"> <template #default="scope"> <!-- expand --> <template v-if="item.type == 'expand'"> <component :is="item.render" v-bind="scope" v-if="item.render" /> <slot v-else :name="item.type" v-bind="scope" /> </template> <!-- radio --> <el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]"> <!-- <i></i> --> </el-radio> <!-- sort --> <el-tag v-if="item.type == 'sort'" class="move"> <el-icon> <DCaret /></el-icon> </el-tag> </template> </el-table-column> <!-- other --> <TableColumn v-if="!item.type && item.prop && item.isShow && getTableColumnSetting(item.prop, props.tableName)" :column="item"> <template v-for="slot in Object.keys($slots)" #[slot]="scope"> <slot :name="slot" v-bind="scope" /> </template> </TableColumn> </template> <!-- 插入表格最后一行之后的插槽 --> <template #append> <slot name="append" /> </template> <!-- 无数据 --> <template #empty> <div class="table-empty"> <slot name="empty"> <img src="@/assets/images/notData.png" alt="notData" /> <div>暂无数据</div> </slot> </div> </template> </el-table> <!-- 分页组件 --> <slot name="pagination"> <Pagination v-if="pagination" :pageable="pageable" :handle-size-change="handleSizeChange" :handle-current-change="handleCurrentChange" /> </slot> </div> <!-- 列设置 --> <ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" v-model:tableName="props.tableName" /> </template> <script setup lang="ts" name="ProTable"> import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue"; import { ElTable } from "element-plus"; import { useTable } from "@/hooks/useTable"; import { useSelection } from "@/hooks/useSelection"; import { BreakPoint } from "@/components/Grid/interface"; import { ColumnProps, TypeProps } from "@/components/ProTable/interface"; import { Refresh, Operation, Search } from "@element-plus/icons-vue"; import { handleProp } from "@/utils"; import SearchForm from "@/components/SearchForm/index.vue"; import Pagination from "./components/Pagination.vue"; import ColSetting from "./components/ColSetting.vue"; import TableColumn from "./components/TableColumn.vue"; import Sortable from "sortablejs"; import { useUserStore } from "@/stores/modules/user"; import { getTableColumnSetting, getColSetting } from "@/utils/columnSetting"; const userStore = useUserStore(); const username = computed(() => userStore.userInfo.realname); export interface ProTableProps { columns: ColumnProps[]; // 列配置项 ==> 必传 data?: any[]; // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传 requestApi?: (params: any) => Promise<any>; // 请求表格数据的 api ==> 非必传 requestAuto?: boolean; // 是否自动执行请求 api ==> 非必传(默认为true) requestError?: (params: any) => void; // 表格 api 请求错误监听 ==> 非必传 dataCallback?: (data: any) => any; // 返回数据的回调函数,可以对数据进行处理 ==> 非必传 title?: string; // 表格标题 ==> 非必传 pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true) initParam?: any; // 初始化请求参数 ==> 非必传(默认为{}) border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为true) toolButton?: ("refresh" | "setting" | "search")[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true) rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id) searchCol?: number | Record<BreakPoint, number>; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } tableTipsFlag?: boolean; // 表格上方的提示信息 ==> 非必传 (默认为false) tableName?: string; // 表格名称,具唯一性,保存表格列设置用 非必传,不传则不保存列设置 } // 接受父组件参数,配置默认值 const props = withDefaults(defineProps<ProTableProps>(), { columns: () => [], requestAuto: true, pagination: true, initParam: {}, border: true, toolButton: true, rowKey: "id", searchCol: () => ({ xs: 1, sm: 3, md: 3, lg: 4, xl: 5 }), tableTipsFlag: false }); // table 实例 const tableRef = ref<InstanceType<typeof ElTable>>(); // column 列类型 const columnTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"]; // 是否显示搜索模块 const isShowSearch = ref(true); // 控制 ToolButton 显示 const showToolButton = (key: "refresh" | "setting" | "search") => { return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton; }; // 单选值 const radio = ref(""); // 表格多选 Hooks const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey); // 表格操作 Hooks const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError, tableRef); // 清选中数据列表 const clearSelection = () => tableRef.value!.clearSelection(); const toggleRowSelection = (row, flag?: boolean) => tableRef.value!.toggleRowSelection(row, flag); // 处理表格数据 const processTableData = computed(() => { if (!props.data) return tableData.value; if (!props.pagination) return props.data; return props.data.slice( (pageable.value.pageNum - 1) * pageable.value.pageSize, pageable.value.pageSize * pageable.value.pageNum ); }); // 监听页面 initParam 改化,重新获取表格数据 watch(() => props.initParam, () => { // 将初始化initParam参数赋值给表单 searchParam.value = { ...searchParam.value, ...props.initParam, }; getTableList() }, { deep: true }); // 接收 columns 并设置为响应式 const tableColumns = reactive<ColumnProps[]>(props.columns); // 扁平化 columns const flatColumns = computed(() => flatColumnsFunc(tableColumns)); // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择) const enumMap = ref(new Map<string, { [key: string]: any }[]>()); const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => { if (!enumValue) return; // 如果当前 enumMap 存在相同的值 return if (enumMap.value.has(prop!) && (typeof enumValue === "function" || enumMap.value.get(prop!) === enumValue)) return; // 当前 enum 为静态数据,则直接存储到 enumMap if (typeof enumValue !== "function") return enumMap.value.set(prop!, unref(enumValue!)); // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储 enumMap.value.set(prop!, []); // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap const { data } = await enumValue(); enumMap.value.set(prop!, data); }; const refreshData = () => { pageable.value.pageNum = 1; getTableList(); }; // 注入 enumMap provide("enumMap", enumMap); // 扁平化 columns 的方法 const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => { columns.forEach(async col => { if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children)); flatArr.push(col); // column 添加默认 isShow && isFilterEnum 属性值 col.isShow = col.isShow ?? true; col.isFilterEnum = col.isFilterEnum ?? true; // 设置 enumMap await setEnumMap(col); }); return flatArr.filter(item => !item._children?.length); }; // 过滤需要搜索的配置项 && 排序 const searchColumns = computed(() => { return flatColumns.value ?.filter(item => item.search?.el || item.search?.render) .sort((a, b) => a.search!.order! - b.search!.order!); }); // 设置 搜索表单默认排序 && 搜索表单项的默认值 searchColumns.value?.forEach((column, index) => { column.search!.order = column.search?.order ?? index + 2; const key = column.search?.key ?? handleProp(column.prop!); const defaultValue = column.search?.defaultValue; if (defaultValue !== undefined && defaultValue !== null) { searchInitParam.value[key] = defaultValue; searchParam.value[key] = defaultValue; } }); // 列设置 ==> 需要过滤掉不需要设置的列 const colRef = ref(); const colSetting = getColSetting(tableColumns, props.tableName); const openColSetting = () => colRef.value.openColSetting(); // 定义 emit 事件 const emit = defineEmits<{ search: []; reset: []; dargSort: [{ newIndex?: number; oldIndex?: number }]; tableCellDblClick: [{ row: any, column: any, cell: any, event: any }]; tableRowDblclick: [{ row: any, column: any, event: any }]; }>(); const _search = () => { search(); emit("search"); }; const _reset = () => { reset(); emit("reset"); }; // 拖拽排序 const dragSort = () => { const tbody = document.querySelector(".el-table__body-wrapper tbody") as HTMLElement; Sortable.create(tbody, { handle: ".move", animation: 300, onEnd({ newIndex, oldIndex }) { const [removedItem] = processTableData.value.splice(oldIndex!, 1); processTableData.value.splice(newIndex!, 0, removedItem); emit("dargSort", { newIndex, oldIndex }); } }); }; // 表格单元格双击事件 const tableCellDblClick = (row, column, cell, event) => { emit("tableCellDblClick", { row, column, cell, event }); } // 表格行双击事件 const tableRowDblclick = (row, column, event) => { emit("tableRowDblclick", { row, column, event }); } // 初始化表格数据 && 拖拽排序 onMounted(() => { dragSort(); props.requestAuto && getTableList(); props.data && (pageable.value.total = props.data.length); }); // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去) defineExpose({ element: tableRef, tableData: processTableData, radio, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange, clearSelection, toggleRowSelection, enumMap, isSelected, selectedList, selectedListIds, refreshData: getTableList // 暴露刷新方法 }); </script> <template> <div v-if="columns.length" class="card table-search"> <el-form ref="formRef" :model="searchParam"> <Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol"> <GridItem v-for="(item, index) in columns" :key="item.prop" v-bind="getResponsive(item)" :index="index"> <el-form-item> <template #label> <el-space :size="4"> <span>{{ `${item.search?.label ?? item.label}` }}</span> <el-tooltip v-if="item.search?.tooltip" effect="dark" :content="item.search?.tooltip" placement="top"> <i :class="'iconfont icon-yiwen'"></i> </el-tooltip> </el-space> </template> <SearchFormItem :column="item" :search-param="searchParam" /> </el-form-item> </GridItem> <GridItem suffix> <div class="operation"> <el-button type="primary" :icon="Search" @click="search"> 搜索 </el-button> <el-button :icon="Delete" @click="reset"> 重置 </el-button> <el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed"> {{ collapsed ? "展开" : "合并" }} <el-icon class="el-icon--right"> <component :is="collapsed ? ArrowDown : ArrowUp"></component> </el-icon> </el-button> </div> </GridItem> </Grid> </el-form> </div> </template> <script setup lang="ts" name="SearchForm"> import { computed, ref } from "vue"; import { ColumnProps } from "@/components/ProTable/interface"; import { BreakPoint } from "@/components/Grid/interface"; import { Delete, Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue"; import SearchFormItem from "./components/SearchFormItem.vue"; import Grid from "@/components/Grid/index.vue"; import GridItem from "@/components/Grid/components/GridItem.vue"; interface ProTableProps { columns?: ColumnProps[]; // 搜索配置列 searchParam?: { [key: string]: any }; // 搜索参数 searchCol: number | Record<BreakPoint, number>; search: (params: any) => void; // 搜索方法 reset: (params: any) => void; // 重置方法 } // 默认值 const props = withDefaults(defineProps<ProTableProps>(), { columns: () => [], searchParam: () => ({}) }); // 获取响应式设置 const getResponsive = (item: ColumnProps) => { return { span: item.search?.span, offset: item.search?.offset ?? 0, xs: item.search?.xs, sm: item.search?.sm, md: item.search?.md, lg: item.search?.lg, xl: item.search?.xl }; }; // 是否默认折叠搜索项 const collapsed = ref(true); // 获取响应式断点 const gridRef = ref(); const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint); // 判断是否显示 展开/合并 按钮 const showCollapse = computed(() => { let show = false; props.columns.reduce((prev, current) => { prev += (current.search![breakPoint.value]?.span ?? current.search?.span ?? 1) + (current.search![breakPoint.value]?.offset ?? current.search?.offset ?? 0); if (typeof props.searchCol !== "number") { if (prev >= props.searchCol[breakPoint.value]) show = true; } else { if (prev >= props.searchCol) show = true; } return prev; }, 0); return show; }); </script> <template> <!-- 特殊处理有效天数的自定义搜索框 --> <template v-if="column.prop === 'validityPeriod' && column.search?.el === 'custom'"> <div class="validity-period-container" style="width: 300px;"> <el-input v-model="_searchParam[column.search?.key ?? handleProp(column.prop!)].start" placeholder="请输入" :clearable="clearable" style="width: 150px; margin-right: 8px;" /> <span style="margin-right: 8px;">至</span> <el-input v-model="_searchParam[column.search?.key ?? handleProp(column.prop!)].end" placeholder="请输入" :clearable="clearable" :disabled="unlimited" style="width: 150px; margin-right: 16px;" /> <el-checkbox v-model="_searchParam[column.search?.key ?? handleProp(column.prop!)].unlimited" label="无期限" /> </div> </template> <!-- 原有默认渲染逻辑 --> <template v-else> <component :is="column.search?.render ?? `el-${column.search?.el}`" v-bind="{ ...handleSearchProps, ...placeholder, searchParam: _searchParam, clearable }" v-model="_searchParam[column.search?.key ?? handleProp(column.prop!)]" :data="column.search?.el === 'tree-select' ? columnEnum : []" collapse-tags :options="['cascader', 'select-v2'].includes(column.search?.el!) ? columnEnum : []" :filterable="true" > <template v-if="column.search?.el === 'cascader'" #default="{ data }"> <span>{{ data[fieldNames.label] }}</span> </template> <template v-if="column.search?.el === 'select'"> <component :is="`el-option`" v-for="(col, index) in columnEnum" :key="index" :label="col[fieldNames.label]" :value="col[fieldNames.value]" :title="col[fieldNames.label]" ></component> </template> <slot v-else></slot> </component> </template> </template> <script setup lang="ts" name="SearchFormItem"> import { computed, inject, ref, onMounted } from "vue"; import { handleProp } from "@/utils"; import { ColumnProps } from "@/components/ProTable/interface"; interface SearchFormItem { column: ColumnProps; searchParam: { [key: string]: any }; } const props = defineProps<SearchFormItem>(); // 接收搜索参数 const _searchParam = computed(() => props.searchParam); // 初始化有效天数的搜索参数结构 onMounted(() => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { const propKey = props.column.search?.key ?? handleProp(props.column.prop!); // 确保参数结构存在 if (!_searchParam.value[propKey]) { _searchParam.value[propKey] = { start: '', end: '', unlimited: false }; } } }); // 其他原有逻辑保持不变... const fieldNames = computed(() => { return { label: props.column.fieldNames?.label ?? "label", value: props.column.fieldNames?.value ?? "value", children: props.column.fieldNames?.children ?? "children" }; }); const enumMap = inject("enumMap", ref(new Map())); const columnEnum = computed(() => { let enumData = enumMap.value.get(props.column.prop); if (!enumData) return []; if (props.column.search?.el === "select-v2" && props.column.fieldNames) { enumData = enumData.map((item: { [key: string]: any }) => { return { ...item, label: item[fieldNames.value.label], value: item[fieldNames.value.value] }; }); } return enumData; }); const handleSearchProps = computed(() => { const label = fieldNames.value.label; const value = fieldNames.value.value; const children = fieldNames.value.children; const searchEl = props.column.search?.el; const searchElType = props.column.search?.type; let searchProps = props.column.search?.props ?? {}; if (searchEl === "tree-select") { searchProps = { ...searchProps, props: { ...searchProps.props, label, children }, nodeKey: value }; } if (searchEl === "cascader") { searchProps = { ...searchProps, props: { ...searchProps.props, label, value, children } }; } if (searchEl === "input" && searchElType === 'textarea') { searchProps = { ...searchProps, type: searchElType, autosize: { minRows: 1, maxRows: 1 } }; } return searchProps; }); const placeholder = computed(() => { const search = props.column.search; if (["datetimerange", "daterange", "monthrange"].includes(search?.props?.type) || search?.props?.isRange) { return { rangeSeparator: search?.props?.rangeSeparator ?? "至", startPlaceholder: search?.props?.startPlaceholder ?? "开始时间", endPlaceholder: search?.props?.endPlaceholder ?? "结束时间" }; } const placeholder = search?.props?.placeholder ?? (search?.el?.includes("input") ? (search?.placeholder || "请输入") : "请选择"); return { placeholder }; }); const clearable = computed(() => { const search = props.column.search; if (search?.el === "date-picker") return true; return search?.props?.clearable ?? (search?.defaultValue == null || search?.defaultValue == undefined); }); </script> <style scoped> .validity-period-container { display: flex; align-items: center; } </style> 重置后类型为custom的模块消失了,并且报错TypeError: Cannot read properties of undefined (reading 'start')
最新发布
11-05
<think>我们遇到的问题是在ProTable组件中,当有效天数(validityPeriod)的搜索项被重置后,原本自定义的搜索组件消失了,并且出现了`TypeError: Cannot read properties of undefined (reading 'start')`的错误。 根据提供的代码,我们可以分析出以下几点: 1. 在`SearchFormItem.vue`组件中,我们对`validityPeriod`这个prop做了特殊处理,使用了自定义的搜索组件(custom)。在自定义组件中,我们使用了一个包含两个输入框和一个复选框的结构,并且这个结构绑定的值是`_searchParam[propKey]`,其中`propKey`是由`column.search?.key ?? handleProp(column.prop!)`计算出来的。 2. 在`ProTable.vue`组件的`reset`方法中,我们会将`searchParam`重置为`searchInitParam`的初始值。而`searchInitParam`在初始化时,会遍历`searchColumns`,将每个搜索项的默认值(如果有)设置到`searchInitParam`和`searchParam`中。 3. 对于`validityPeriod`这个自定义搜索项,我们在`SearchFormItem`组件的`onMounted`钩子中尝试初始化它的结构(如果不存在的话)。但是在重置的时候,我们并没有重新初始化这个结构,而是直接使用了`searchInitParam`的值。如果`searchInitParam`中对应的属性没有被正确初始化(即没有初始化为一个包含`start`、`end`和`unlimited`属性的对象),那么在重置后,`_searchParam[propKey]`就会变成`undefined`,从而导致访问`start`属性时出错。 4. 另外,在`SearchFormItem`组件中,我们使用`v-if`条件渲染自定义组件,当重置后,由于`_searchParam[propKey]`可能变为`undefined`,所以条件`column.prop === 'validityPeriod' && column.search?.el === 'custom'`虽然满足,但是内部访问`_searchParam[propKey].start`就会报错。 解决方案: 1. 我们需要确保在重置时,对于自定义搜索项`validityPeriod`,其对应的`searchParam`中的属性应该被重置为一个包含`start`、`end`和`unlimited`属性的对象,而不是`undefined`。 2. 在`ProTable.vue`的`reset`方法中,重置表单时,我们不仅要重置为`searchInitParam`,还要确保自定义组件的结构存在。但是目前`searchInitParam`中并没有初始化这个结构。 3. 因此,我们需要在初始化搜索参数时(在`ProTable.vue`中遍历`searchColumns`设置默认值的地方),对于`validityPeriod`这样的自定义搜索项,我们应该初始化它的结构。 具体修改步骤: ### 修改ProTable.vue中的代码 在`ProTable.vue`中,我们遍历`searchColumns`设置默认值的地方,对于`validityPeriod`这个字段,我们需要检查它是否是自定义类型(custom),如果是,我们需要初始化一个对象。 在以下代码段中: ```javascript // 设置 搜索表单默认排序 && 搜索表单项的默认值 searchColumns.value?.forEach((column, index) => { column.search!.order = column.search?.order ?? index + 2; const key = column.search?.key ?? handleProp(column.prop!); const defaultValue = column.search?.defaultValue; if (defaultValue !== undefined && defaultValue !== null) { searchInitParam.value[key] = defaultValue; searchParam.value[key] = defaultValue; } }); ``` 我们可以在设置默认值之前,判断如果该列是自定义类型(custom)且prop为'validityPeriod',那么我们就初始化一个对象结构。 但是,这里我们更通用的做法是:在设置默认值的时候,如果该列是自定义类型且默认值不存在,则初始化一个对象(或者根据自定义组件的需要初始化结构)。但是目前我们无法在`ProTable`中知道哪些列是自定义的,所以这个逻辑最好放在`SearchFormItem`组件中,因为只有它知道哪些列需要特殊结构。 所以,我们调整策略:在`SearchFormItem`组件中,我们确保在渲染之前,该自定义组件的参数结构已经被初始化。 ### 修改SearchFormItem.vue 在`SearchFormItem`组件中,我们已经在`onMounted`中尝试初始化参数结构。但是,重置操作并不会触发`onMounted`,因为组件并没有重新挂载。因此,我们需要将初始化参数结构的逻辑放在一个响应式的地方,比如使用`watch`来监听`_searchParam`的变化,或者放在渲染过程中(比如使用计算属性或者渲染函数中)。但是注意,我们不能在模板中直接修改响应式数据,这会导致无限递归。 我们可以使用`watch`来监听`_searchParam`中对应字段的变化,如果发现它是`undefined`,我们就初始化它。但是这样可能会覆盖掉重置操作(因为重置操作就是把`searchParam`重置为`searchInitParam`,而`searchInitParam`可能没有这个结构)。 所以,我们可以在`SearchFormItem`组件的`setup`中,在组件创建时(使用`onMounted`)和每次更新时(使用`onUpdated`)都检查一下参数结构。但是这样效率不高。 另一种方法:在`reset`方法中,我们除了重置参数外,还应该重新初始化自定义组件的结构。但是重置方法在`ProTable`中,而`ProTable`并不知道哪些列是自定义的。 因此,我们选择在`SearchFormItem`组件中,每次渲染时都检查参数结构。我们可以使用一个`computed`属性来返回当前的值,并在其中做初始化,但是这样会修改响应式数据,不推荐在计算属性的getter中修改数据。 最后,我们可以在模板渲染之前,在`setup`中使用一个函数来初始化,并在模板中调用这个函数。但是这样不符合Vue的响应式原则。 ### 最终方案 我们选择在`SearchFormItem`组件中,使用`watch`来监听`props.column`和`_searchParam`,确保当`column.prop`为`validityPeriod`且搜索类型为`custom`时,对应的参数是一个对象。 具体修改如下: 在`SearchFormItem.vue`的`script`部分: ```javascript import { watch } from 'vue'; // 原有代码... // 监听列和搜索参数的变化,确保自定义组件的参数结构存在 watch(() => [props.column, _searchParam.value], () => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { const propKey = props.column.search?.key ?? handleProp(props.column.prop!); // 确保参数结构存在 if (!_searchParam.value[propKey]) { _searchParam.value[propKey] = { start: '', end: '', unlimited: false }; } } }, { immediate: true, deep: true }); ``` 但是,这样写可能会频繁触发,而且直接修改了`_searchParam`(它是从父组件传来的props),在Vue中直接修改props的值是不推荐的,而且可能会触发警告。 因此,我们应该将这个初始化逻辑放到父组件(ProTable)中,因为`searchParam`是在ProTable中管理的。但是考虑到ProTable是一个通用组件,我们不能为每个自定义搜索项都写特殊逻辑。 ### 折中方案 我们修改`ProTable`组件的`reset`方法,在重置后,遍历`searchColumns`,对于类型为`custom`的列,初始化其参数结构。 在`ProTable.vue`的`reset`方法中,我们在重置参数后,手动初始化自定义列的结构: ```javascript const reset = () => { Object.keys(searchParam.value).forEach(key => { searchParam.value[key] = searchInitParam.value[key]; }); // 针对自定义搜索项(custom)进行特殊初始化 searchColumns.value.forEach(column => { if (column.search?.el === 'custom') { const key = column.search?.key ?? handleProp(column.prop!); // 如果searchParam中该字段已被重置为undefined,则初始化 if (searchParam.value[key] === undefined) { // 这里根据不同的custom类型初始化不同的结构,我们可以通过column.prop来判断 if (column.prop === 'validityPeriod') { searchParam.value[key] = { start: '', end: '', unlimited: false }; } // 其他自定义类型... } } }); emit("reset"); }; ``` 但是,这样就把自定义组件的逻辑耦合到了`ProTable`中,不够优雅。 ### 另一种方法(推荐) 我们修改`SearchFormItem`组件,在渲染自定义组件时,使用一个计算属性来确保我们访问的对象存在。如果不存在,我们就初始化它,但是注意我们不能在计算属性的getter中修改原对象,所以我们可以使用`v-if`确保在初始化后再渲染。 在`SearchFormItem.vue`中: ```html <template v-if="column.prop === 'validityPeriod' && column.search?.el === 'custom'"> <div class="validity-period-container" style="width: 300px;" v-if="validityPeriodParam"> <!-- 绑定到validityPeriodParam.start等 --> <el-input v-model="validityPeriodParam.start" placeholder="请输入" :clearable="clearable" style="width: 150px; margin-right: 8px;" /> <span style="margin-right: 8px;">至</span> <el-input v-model="validityPeriodParam.end" placeholder="请输入" :clearable="clearable" :disabled="validityPeriodParam.unlimited" style="width: 150px; margin-right: 16px;" /> <el-checkbox v-model="validityPeriodParam.unlimited" label="无期限" /> </div> </template> ``` 在`script`中: ```javascript import { computed } from 'vue'; const validityPeriodParam = computed(() => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { const propKey = props.column.search?.key ?? handleProp(props.column.prop!); if (!_searchParam.value[propKey]) { // 初始化 _searchParam.value[propKey] = { start: '', end: '', unlimited: false }; } return _searchParam.value[propKey]; } return null; }); ``` 这样,在模板中我们通过`validityPeriodParam`这个计算属性来访问,并在访问时检查并初始化结构。注意,我们在计算属性中直接修改了`_searchParam.value[propKey]`,这属于副作用操作,但在Vue3中,计算属性通常只用于获取,不推荐修改。但是这里我们确实需要确保结构存在。 为了避免在计算属性中修改数据,我们可以使用`watch`来初始化,然后在计算属性中返回: ```javascript const propKey = computed(() => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { return props.column.search?.key ?? handleProp(props.column.prop!); } return ''; }); watch(() => _searchParam.value[propKey.value], (val) => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { if (val === undefined) { _searchParam.value[propKey.value] = { start: '', end: '', unlimited: false }; } } }, { immediate: true, deep: false }); const validityPeriodParam = computed(() => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { return _searchParam.value[propKey.value]; } return null; }); ``` 这样,我们通过`watch`立即执行一次来初始化,并在计算属性中返回这个对象。 但是请注意,我们初始化后,计算属性`validityPeriodParam`返回的就是这个对象,在模板中绑定到表单项上,这样就不会报错了。 ### 选择方案 考虑到简单性,我们选择在计算属性中初始化的方式(尽管有副作用),因为这是一个内部组件,且只针对特定列。同时,我们确保只在自定义列且为`validityPeriod`时执行。 修改`SearchFormItem.vue`文件: 1. 在模板中,将`v-model`绑定到计算属性`validityPeriodParam`的属性上。 2. 创建计算属性`validityPeriodParam`,并在其中初始化结构。 具体代码修改如下: ```html <template> <!-- 特殊处理有效天数的自定义搜索框 --> <template v-if="column.prop === 'validityPeriod' && column.search?.el === 'custom'"> <div class="validity-period-container" style="width: 300px;" v-if="validityPeriodParam"> <el-input v-model="validityPeriodParam.start" placeholder="请输入" :clearable="clearable" style="width: 150px; margin-right: 8px;" /> <span style="margin-right: 8px;">至</span> <el-input v-model="validityPeriodParam.end" placeholder="请输入" :clearable="clearable" :disabled="validityPeriodParam.unlimited" style="width: 150px; margin-right: 16px;" /> <el-checkbox v-model="validityPeriodParam.unlimited" label="无期限" /> </div> </template> <!-- 原有默认渲染逻辑 --> <!-- ... --> </template> <script> // ...其他代码 const validityPeriodParam = computed(() => { if (props.column.prop === 'validityPeriod' && props.column.search?.el === 'custom') { const propKey = props.column.search?.key ?? handleProp(props.column.prop!); // 确保参数结构存在 if (!_searchParam.value[propKey]) { _searchParam.value[propKey] = { start: '', end: '', unlimited: false }; } return _searchParam.value[propKey]; } return null; }); </script> ``` 这样,无论何时,只要这个自定义组件被渲染,我们都会检查并初始化参数结构。而且,在重置后,虽然重置操作会把`_searchParam.value[propKey]`设为默认值(如果有默认值)或者`undefined`(如果没有默认值),但我们的计算属性会再次初始化它(当它被访问时)。 注意:由于计算属性在渲染时被访问,所以初始化会在渲染之前完成,因此不会出现访问`start`属性时报错的情况。 ### 总结 我们通过在`SearchFormItem`组件中添加一个计算属性`validityPeriodParam`,在这个计算属性中,我们检查并初始化了`validityPeriod`搜索项的参数结构,从而解决了重置后参数结构不存在的问题。 同时,我们也要确保在`ProTable`的`searchInitParam`中,对于`validityPeriod`,如果有默认值,那么默认值也应该是一个包含`start`、`end`和`unlimited`属性的对象。否则,重置后会被我们的计算属性覆盖为初始结构。这需要在使用ProTable时传入正确的默认值结构。 例如,在使用ProTable时,给`validityPeriod`设置默认值: ```javascript initParam: { validityPeriod: { start: '', end: '', unlimited: false } } ``` 这样,重置时就会重置为这个结构。 因此,我们的解决方案是: 1. 修改`SearchFormItem`组件,为`validityPeriod`自定义搜索项添加计算属性,并在其中初始化参数结构。 2. 确保在使用ProTable时,给`validityPeriod`的默认值传递正确的结构。 这样,重置后就不会报错了,自定义组件也会正常显示。 由于代码较长,这里只给出关键部分的修改。请根据上述分析修改代码。 </think>根据您提供的代码,问题在于自定义的 `validityPeriod` 搜索项在重置后参数结构被清除导致出错🤔。我需要修复两个核心问题: 1. **参数结构未正确初始化**:重置后 `validityPeriod` 参数变为 undefined,无法访问其属性 2. **渲染条件缺陷**:自定义组件在参数异常时消失 ### 解决方案(修复 SearchFormItem.vue) ```html <template> <!-- 特殊处理有效天数的自定义搜索框 --> <template v-if="column.prop === 'validityPeriod' && column.search?.el === 'custom'"> <div class="validity-period-container" style="width: 300px;"> <el-input v-model="validityPeriodParam.start" placeholder="开始天数" :clearable="clearable" style="width: 150px; margin-right: 8px;" /> <span style="margin-right: 8px;">至</span> <el-input v-model="validityPeriodParam.end" placeholder="结束天数" :clearable="clearable" :disabled="validityPeriodParam.unlimited" style="width: 150px; margin-right: 16px;" /> <el-checkbox v-model="validityPeriodParam.unlimited" label="无期限" /> </div> </template> <!-- 原有默认渲染逻辑 --> <template v-else> <!-- ...保持原有代码不变 --> </template> </template> <script setup lang="ts"> // ...其他import保持不变... // 新增计算属性确保参数结构始终存在 const validityPeriodParam = computed(() => { if (props.column.prop !== 'validityPeriod') return {}; const propKey = props.column.search?.key ?? handleProp(props.column.prop!); // 确保参数结构存在 if (!_searchParam.value[propKey] || typeof _searchParam.value[propKey] !== 'object') { _searchParam.value[propKey] = { start: '', end: '', unlimited: false }; } return _searchParam.value[propKey]; }); // 移除原有的onMounted初始化逻辑 </script> ``` ### 修复说明: 1. **参数结构保证**: - 新增 `validityPeriodParam` 计算属性 - 检查并确保参数结构始终是完整对象 - 当参数不存在或类型错误时自动初始化 2. **模板绑定修正**: - 将 `v-model` 绑定到 `validityPeriodParam.start/end/unlimited` - 移除了 `v-if` 条件渲染(确保组件始终存在) 3. **错误处理**: - 当参数被意外清除时自动重建结构 - 避免直接访问嵌套属性导致的 `undefined` 错误 ### 根本原因分析: 1. **重置操作问题**: ```javascript // 原始的reset函数会清除自定义结构 reset = () => { searchParam.value = { ...searchInitParam.value }; } ``` 如果 `searchInitParam.value` 没有包含完整的 `validityPeriod` 对象结构,重置后参数会丢失 2. **渲染条件缺陷**: ```html <!-- 原有代码在参数异常时会隐藏组件 --> <div v-if="_searchParam[propKey]">...</div> ``` 3. **初始化不完整**: 原有的 `onMounted` 只会在组件挂载时初始化一次,重置操作不会重新触发初始化 ### 最佳实践建议: 1. **在ProTable中确保默认结构**: ```javascript // ProTable.vue中的searchInitParam初始化 searchColumns.value?.forEach((column) => { if (column.prop === 'validityPeriod' && column.search?.el === 'custom') { const key = column.search?.key ?? handleProp(column.prop!); searchInitParam.value[key] = { start: '', end: '', unlimited: false }; } }); ``` 2. **增强重置函数**: ```javascript // 修改ProTable.vue中的reset函数 const reset = () => { // 保留自定义结构 const customParams = {}; searchColumns.value.forEach(col => { if (col.search?.el === 'custom') { const key = col.search?.key ?? handleProp(col.prop!); customParams[key] = searchParam.value[key]; } }); // 重置并保留自定义结构 searchParam.value = { ...searchInitParam.value, ...customParams }; } ``` 3. **添加类型保护**: ```typescript // 在interface中明确定义自定义类型 interface ValidityPeriodParam { start: string; end: string; unlimited: boolean; } interface ColumnProps { // ...其他属性... search?: { // ...其他配置... defaultValue?: ValidityPeriodParam; // 指定默认结构 } } ``` ### 相关技术要点: 1. **Vue响应式原理**:使用计算属性自动维护数据结构 2. **防御性编程**:确保访问嵌套属性前的类型检查 3. **组件设计**:自包含组件应能处理边界情况 4. **状态管理**:复杂表单状态需要明确初始化策略
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值