主题
完成更新业务与完成删除业务(Day3 2/2)
本页对应 day3,共 6 段视频,约 1 小时 49 分。
学习目标
- 能使用 Element Plus 表单的
validator自定义校验规则,完成品牌名称至少两位汉字、品牌 Logo 地址非空等校验,并返回Error提示信息。 - 能通过
formRef.validate()在提交前触发整体表单校验,并根据返回 Promise 的成功或失败结果控制后续请求是否执行。 - 能在打开添加/更新对话框前或图片上传成功后,使用
resetFields、clearValidate清除指定字段的校验结果,并能用nextTick或可选链处理组件未渲染问题。 - 能根据表格行数据回填更新表单,依据是否存在 ID 区分添加与更新,动态设置对话框标题、成功/失败提示及更新后的分页页码。
- 能调用删除品牌接口完成删除业务,使用
ElMessageBox.confirm进行二次确认,删除成功后给出提示并刷新当前页品牌数据。 - 能引入
xlsx插件,将品牌列表 JSON 数据转换为工作表数据并导出为 Excel 文件。
知识点
更新业务的目标与前置认知
更新业务要解决的问题是:表格里已经有一条品牌数据,点击“修改”后,把这条数据回填到弹窗表单里,用户改完再提交,后端根据品牌 ID 完成更新。转写稿里反复强调“不要只看页面,要盯数据”,因为页面展示只是结果,真正决定更新是否成功的是:row 里的已有数据有没有被正确复制到表单对象 tradeMark 中,以及提交时有没有带上 id。接口层面,更新通常是 PUT 请求,需要携带品牌 id、品牌名称和品牌 logo 地址。
先回填,再提交:为什么需要 row
为什么需要:点击表格某一行的“修改”按钮时,必须知道“改的是哪一条”。这个信息就在表格当前行的 row 里。
怎么想:row 是当前品牌对象,里面包含 id、tmName、logoURL 等字段。弹窗表单绑定的 tradeMark 初始是空对象,所以点击修改后要先把 row 的值复制给 tradeMark。
怎么做:在修改按钮回调里接收 row,先打印确认,再赋值。转写稿中调试时就是先 console.log(row),确认拿到已有品牌数据,再复制给 tradeMark。关键片段按转写稿整理如下:
js
// 点击“修改”时回填已有品牌数据
const updateTradeMark = (row) => {
tradeMark.id = row.id
tradeMark.tmName = row.tmName
tradeMark.logoURL = row.logoURL
dialogVisible.value = true
}易错点:只复制了名称和 logo,忘记复制 id。名称和 logo 能让页面看起来“有数据”,但提交更新时没有 id,后端就不知道更新谁。转写稿里特别提醒:id 虽然不展示,但将来更新必须用。
复用添加弹窗:标题要动态
为什么需要:添加和更新往往共用一个弹窗。如果标题写死“添加品牌”,用户点修改时还显示“添加品牌”,体验和语义都不对。
怎么想:已有品牌有 id,新增品牌没有 id。所以可以用 tradeMark.id 是否存在来判断当前是更新还是添加。
怎么做:弹窗标题写成动态表达式:
html
:title="tradeMark.id ? '更新品牌' : '添加品牌'"提交成功后的提示也要分情况:有 id 提示“更新成功/更新失败”,没有 id 提示“添加成功/添加失败”。转写稿中一开始提示写死为“添加成功”,调试后发现更新时也提示添加,属于状态没区分。
易错点:只改标题不改提示;或者判断条件用错,把 id 为 0 当成有值。转写稿中清空时把 id 设为 0,所以判断“是否有 ID”要结合业务约定,不能把字符串空值随便塞进去,因为接口需要 Number 类型。
清空数据时要把 id 一起清掉
为什么需要:弹窗关闭后如果只清了名称和 logo,没有清 id,下一次点“添加品牌”时,tradeMark.id 还保留上一次修改的 ID,提交时就会被当成更新。
怎么想:转写稿中的调试现象是:点修改后再关闭,再点添加,弹窗标题仍可能变成“更新品牌”,因为 id 没清。
怎么做:清空函数里同时清 id、tmName、logoURL,id 按接口要求设为 0 或符合类型的空值:
js
tradeMark.id = 0
tradeMark.tmName = ''
tradeMark.logoURL = ''易错点:给 id 赋空字符串导致类型飘红;或者只清展示字段,不清隐藏的 id。
更新后刷新列表:添加回第一页,更新留当前页
为什么需要:添加成功后通常回第一页看最新数据;更新成功后用户希望留在当前页,而不是被强行拉回第一页。
怎么想:刷新列表函数 getTradeMark 接收页码参数。提交时根据 tradeMark.id 是否存在分流:更新传当前页,添加传 1。
怎么做:关键逻辑可整理为:
js
// 伪代码:按转写稿逻辑整理
getTradeMark(tradeMark.id ? 当前页 : 1)易错点:刷新列表不传页码,默认永远第一页;更新后页码丢失,用户还要重新翻页。
提交与校验的衔接
更新弹窗复用了添加表单,所以表单校验规则、图片上传等非表单组件的校验也要保持一致。转写稿中提到,普通表单元素可靠 change、blur 触发校验;图片上传这类非表单组件无法自然触发时,要通过表单实例的 validate() 在点击确定、发请求前统一校验。只有校验通过才发更新请求,失败则中断,不关闭弹窗、不提示成功。清空校验结果时可用 resetFields 或 clearValidate,但要注意调用时机和表单实例是否已渲染。
易错点清单
- 点击修改只打印
row不回填,弹窗仍是空数据。 - 回填时漏掉
id,导致更新接口缺少主键。 - 标题、成功提示写死为“添加”,更新时语义错误。
- 关闭弹窗只清名称和 logo,不清
id,下次添加被误判为更新。 - 刷新列表永远回第一页,更新后没有留在当前页。
- 提交前没有统一校验,图片等非表单组件校验未通过也发请求。
本节笔记未附图片,故未插入图片;以上代码为转写稿中明确出现的关键逻辑整理。
先看接口:删除必须带品牌 ID
为什么需要:删除不是“把页面这一行删掉”这么简单,真正要删的是数据库里的那条品牌数据,因此请求必须明确告诉后端删谁。
怎么想:转写稿里先手动验证接口,找到“删除品牌”的请求,发现它需要传 id,并且返回数据为空,所以前端调用时重点是把当前行 row.id 传过去。
怎么做:在 API 文件中补删除方法,函数参数用 number,不要写成字符串。示意代码如下,路径按项目实际接口填写:
ts
export function reqDeleteById(id: number) {
return request.delete(`/product/trademark/${id}`)
}易错点:不要凭感觉写请求方法,删除一般是 DELETE;不要把 id 拼错或传成 row 整个对象;返回值为空时不要硬接一个不存在的数据结构。
用 Popconfirm 做删除确认
为什么需要:删除是不可逆操作,用户点“删除”时不能立刻发请求,必须先问一句“确定要删除吗”。
怎么想:转写稿中使用的是 Element Plus 的 Popconfirm 气泡确认框,它适合放在表格操作列里。标题可以动态显示品牌名,让用户确认自己删的是哪一条。
怎么做:在表格最后一列的操作列中包一层 el-popconfirm,把真正的删除按钮放到 #reference 插槽里,在 @confirm 时调用删除方法:
vue
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-popconfirm
:title="`确定要删除${row.tmName}吗?`"
@confirm="deleteTradeMark(row.id)"
>
<template #reference>
<el-button type="danger" :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>易错点:@confirm 才是用户点“确定”的事件,不要误写成普通按钮的 @click;按钮文字、图标颜色可以调,但核心是确认后才发请求;row.id 的类型要与接口要求一致。
删除方法:请求成功后再刷新列表
为什么需要:只发删除请求,页面上的表格不会自动少一行;只前端删掉,数据库又没变。正确顺序是“请求成功 → 提示成功 → 重新获取列表”。
怎么想:转写稿中调试时先打印 id,确认参数有值,再发请求;请求成功后调用获取品牌列表的方法。删除后应停留在当前页,所以刷新时要传当前页码,而不是永远回第一页。
怎么做:
ts
async function deleteTradeMark(id: number) {
try {
await reqDeleteById(id)
ElMessage.success('删除成功')
await getTradeMark(page.value)
} catch (e) {
ElMessage.error('删除失败')
}
}易错点:不要先刷新再删除;失败时不要提示“删除成功”;刷新列表别忘了传当前页;如果接口报错,先看 Network 里请求地址和 id 是否正确。
调试顺序:先手动请求,再点按钮打印
为什么需要:删除业务看起来简单,但错误常出在“接口没通”“参数没传”“刷新没执行”三处,盲目改页面效率低。
怎么想:转写稿的调试过程是先找接口、手动删一条数据,确认后端接口可用;再回到页面,在删除回调里打印 row.id;最后观察请求和列表变化。
怎么做:第一步在接口工具里验证删除;第二步在 @confirm 中打印 row.id;第三步看浏览器 Network 的请求方法、URL、参数;第四步看删除成功后 getTradeMark 是否带着当前页码执行。
易错点:不要只盯着页面提示,要盯请求和响应;如果列表没变,先确认刷新函数有没有执行,再确认刷新参数是不是当前页。
提示与当前页:删除完成后的收尾
为什么需要:用户需要知道操作结果;同时删除后如果直接回第一页,会打断用户操作。
怎么想:转写稿中删除成功后用 ElMessage.success,失败用 ElMessage.error;重新获取列表时保留当前页。
怎么做:成功和失败分开提示;刷新列表时传 page.value;如果项目里删除后总数变化导致当前页为空,再按业务决定是否回退页码。
易错点:不要所有情况都提示成功;不要把“删除失败”的异常吞掉;刷新列表时不要漏掉当前页参数。这样删除业务才算闭环:确认、请求、提示、刷新、停留当前页。
课堂画面






代码示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>品牌表单自定义校验</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
<el-button type="primary" @click="openDialog">打开品牌表单</el-button>
<el-dialog v-model="dialogVisible" :title="form.id ? '更新品牌' : '添加品牌'" width="500px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="品牌名称" prop="tmName">
<el-input v-model="form.tmName" placeholder="请输入品牌名称" />
</el-form-item>
<el-form-item label="品牌LOGO" prop="logoURL">
<el-upload
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="handleLogoChange"
>
<el-button type="primary">选择图片</el-button>
</el-upload>
<div v-if="form.logoURL" style="margin-top:8px;color:#67c23a">
已选择:{{ form.logoURL }}
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</div>
<script>
const { createApp, ref, reactive, nextTick } = Vue;
const { ElMessage } = ElementPlus;
createApp({
setup() {
const dialogVisible = ref(false);
const formRef = ref(null);
const form = reactive({
id: 0,
tmName: '',
logoURL: ''
});
const rules = {
tmName: [
{ required: true, message: '品牌名称不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
// 自定义规则:至少两位汉字
if (/^[\u4e00-\u9fa5]{2,}$/.test(value)) {
callback();
} else {
callback(new Error('品牌名称至少两位汉字'));
}
},
trigger: 'blur'
}
],
logoURL: [
{
validator: (rule, value, callback) => {
if (value) {
callback();
} else {
callback(new Error('请上传品牌LOGO'));
}
},
trigger: 'change'
}
]
};
const clearFormValidate = () => {
nextTick(() => {
formRef.value?.clearValidate();
});
};
const openDialog = () => {
form.id = 0;
form.tmName = '';
form.logoURL = '';
dialogVisible.value = true;
clearFormValidate();
};
const handleLogoChange = (file) => {
// 模拟上传成功后的地址
form.logoURL = file.name || 'mock-logo-url';
nextTick(() => {
formRef.value?.clearValidate('logoURL');
});
};
const handleConfirm = async () => {
try {
// validate 返回 Promise,校验失败会 reject,必须 catch/await 处理
await formRef.value.validate();
ElMessage.success('校验通过');
dialogVisible.value = false;
} catch (e) {
// 校验失败,不提交
}
};
return {
dialogVisible,
formRef,
form,
rules,
openDialog,
handleLogoChange,
handleConfirm
};
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>要点解释关键行和易错点:
validator: (rule, value, callback)是自定义校验核心,必须调用callback()放行,或callback(new Error('提示'))返回错误。- 品牌名称用
/^[\u4e00-\u9fa5]{2,}$/限制至少两位汉字。 el-upload不是普通表单元素,不能依赖blur/change自动触发校验,所以确定时统一formRef.value.validate()。validate()返回 Promise,校验失败会reject,必须try/catch或.catch,否则控制台会出现未处理异常。- 打开弹窗时用
nextTick后clearValidate()清除上一次校验结果。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>品牌更新业务</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
<el-button type="primary" @click="handleAdd">添加品牌</el-button>
<el-table :data="tableData" border style="margin-top:12px">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="tmName" label="品牌名称" />
<el-table-column prop="logoURL" label="LOGO地址" />
<el-table-column label="操作" width="160">
<template #default="{ row }">
<el-button type="primary" link @click="handleUpdate(row)">更新</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="page"
:page-size="5"
:total="total"
layout="prev, pager, next"
style="margin-top:12px"
@current-change="loadData"
/>
<el-dialog v-model="dialogVisible" :title="form.id ? '更新品牌' : '添加品牌'" width="500px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="品牌名称" prop="tmName">
<el-input v-model="form.tmName" />
</el-form-item>
<el-form-item label="LOGO地址" prop="logoURL">
<el-input v-model="form.logoURL" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</div>
<script>
const { createApp, ref, reactive, nextTick } = Vue;
const { ElMessage } = ElementPlus;
createApp({
setup() {
const page = ref(1);
const total = ref(0);
const tableData = ref([]);
const dialogVisible = ref(false);
const formRef = ref(null);
const form = reactive({
id: 0,
tmName: '',
logoURL: ''
});
const rules = {
tmName: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
logoURL: [{ required: true, message: 'LOGO地址不能为空', trigger: 'blur' }]
};
// 模拟数据库数据
const db = ref(
Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
tmName: `品牌${i + 1}`,
logoURL: `logo-${i + 1}.png`
}))
);
const loadData = () => {
const start = (page.value - 1) * 5;
tableData.value = db.value.slice(start, start + 5);
total.value = db.value.length;
};
const resetForm = () => {
form.id = 0; // 易错:必须重置为 number 0,否则下次会被误判为更新
form.tmName = '';
form.logoURL = '';
nextTick(() => {
formRef.value?.clearValidate();
});
};
const handleAdd = () => {
resetForm();
dialogVisible.value = true;
};
const handleUpdate = (row) => {
// 复制行数据,不要直接引用表格行对象
form.id = row.id;
form.tmName = row.tmName;
form.logoURL = row.logoURL;
dialogVisible.value = true;
nextTick(() => {
formRef.value?.clearValidate();
});
};
const handleConfirm = async () => {
try {
await formRef.value.validate();
// 根据 id 判断添加还是更新
const isUpdate = !!form.id;
// 模拟接口请求
await new Promise((resolve) => setTimeout(resolve, 300));
if (isUpdate) {
const index = db.value.findIndex((item) => item.id === form.id);
if (index > -1) {
db.value[index] = {
id: form.id,
tmName: form.tmName,
logoURL: form.logoURL
};
}
ElMessage.success('更新成功');
// 更新后留在当前页,不要强制回第一页
} else {
db.value.push({
id: Date.now(),
tmName: form.tmName,
logoURL: form.logoURL
});
ElMessage.success('添加成功');
page.value = 1; // 添加后回第一页
}
dialogVisible.value = false;
loadData();
} catch (e) {
// 校验失败或请求失败
}
};
loadData();
return {
page,
total,
tableData,
dialogVisible,
formRef,
form,
rules,
handleAdd,
handleUpdate,
handleConfirm,
loadData
};
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>要点解释关键行和易错点:
- 点击“更新”时,把当前行
row的id/tmName/logoURL复制到表单,用于回显和后续提交。 - 添加/更新的判断依据是
form.id是否有值,弹窗标题也根据form.id动态显示“更新品牌/添加品牌”。 - 清空表单时
form.id = 0必须重置,否则下一次添加会被误判成更新。 - 更新成功后提示“更新成功”,并保持当前页;添加成功后提示“添加成功”,并回到第一页。
- 不要直接把
row对象赋给form,否则修改表单可能影响表格行数据。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>品牌删除业务</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
<el-table :data="tableData" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="tmName" label="品牌名称" />
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-popconfirm
:title="`确定要删除${row.tmName}吗?`"
confirm-button-text="确定"
cancel-button-text="取消"
@confirm="handleDelete(row.id)"
>
<template #reference>
<el-button type="danger" link>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="page"
:page-size="5"
:total="total"
layout="prev, pager, next"
style="margin-top:12px"
@current-change="loadData"
/>
</div>
<script>
const { createApp, ref } = Vue;
const { ElMessage } = ElementPlus;
createApp({
setup() {
const page = ref(1);
const total = ref(0);
const tableData = ref([]);
const db = ref(
Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
tmName: `品牌${i + 1}`
}))
);
const loadData = () => {
const start = (page.value - 1) * 5;
tableData.value = db.value.slice(start, start + 5);
total.value = db.value.length;
// 删除最后一页最后一条后,当前页可能超出最大页数,需要修正
const maxPage = Math.max(1, Math.ceil(total.value / 5));
if (page.value > maxPage) {
page.value = maxPage;
loadData();
}
};
// 模拟删除接口:删除品牌,参数为 id
const reqDeleteById = async (id) => {
await new Promise((resolve) => setTimeout(resolve, 300));
const index = db.value.findIndex((item) => item.id === id);
if (index === -1) {
throw new Error('删除失败');
}
db.value.splice(index, 1);
};
const handleDelete = async (id) => {
try {
await reqDeleteById(id);
ElMessage.success('删除成功');
// 删除后重新获取当前页,不要固定回第一页
loadData();
} catch (e) {
ElMessage.error('删除失败');
}
};
loadData();
return {
page,
total,
tableData,
loadData,
handleDelete
};
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>要点解释关键行和易错点:
- 删除操作使用
el-popconfirm,只有点击“确定”触发@confirm后才发请求。 - 删除接口需要带品牌
id,即handleDelete(row.id)。 - 删除成功后调用
ElMessage.success('删除成功'),失败调用ElMessage.error('删除失败')。 - 删除后要重新获取数据,且应保留当前页,不要写死回第一页。
- 删除最后一页最后一条时,当前页可能超出最大页数,需要修正页码,否则会出现空页。
常见错误
现象:点“修改”后弹窗能打开,但表单为空或仍显示上一次数据。→ 原因:只打开了弹窗,没有把当前行数据回填到表单模型。→ 修法:在打开弹窗前将
row的id/name/logoUrl同步到表单对象,再展示弹窗。现象:修改完成后关闭弹窗,再点“添加品牌”,标题和提交行为仍像“更新”。→ 原因:关闭时只清了名称、图片,没有清
id;后续逻辑仍按有id走更新分支。→ 修法:关闭/初始化弹窗时同时重置id(数值型给0或undefined),并清空表单字段。现象:更新成功提示写成“添加成功”,失败提示也混用。→ 原因:成功/失败回调里没有按
id区分添加与更新。→ 修法:提交前根据id是否存在分支,成功提示分别用“添加成功/更新成功”,失败提示分别用“添加失败/更新失败”。现象:在第 5、6 页更新一条数据后,列表跳回第 1 页。→ 原因:更新后重新拉取列表时没有传当前页码,调用了默认第一页。→ 修法:添加后回第 1 页;更新后传当前页
page,刷新后留在当前页。现象:删除确认框点“确定”没反应,或删除后列表数量没变。→ 原因:
el-popconfirm的确认事件未绑定,或删除成功后未重新获取列表。→ 修法:在确认事件中传当前行id调删除接口,成功后按当前页重新拉取列表并给出成功/失败提示。现象:删除接口报错或删错数据。→ 原因:请求地址未正确拼接
id,或参数类型/传参位置不对。→ 修法:按接口文档拼接id,确认参数类型为number,删除方法接收id后直接传入请求。现象:图片上传等非表单组件校验不触发,或弹窗关闭后旧校验红字仍保留。→ 原因:非表单组件没有
change/blur触发条件;校验结果未在合适时机清除。→ 修法:用formRef.validate()在提交前统一校验;在打开弹窗前或上传成功后按字段清理校验结果。
练习
完成“更新品牌”业务:点击表格行“修改”后回填名称、图片、
id,弹窗标题根据id动态显示“添加品牌/更新品牌”。完成更新后的提示与分页:添加成功回第 1 页;更新成功留在当前页;成功/失败提示按添加或更新分别显示。
完成“删除品牌”业务:使用
el-popconfirm做二次确认,确认后按行id调删除接口,成功后按当前页刷新列表并提示。修复弹窗状态残留:关闭弹窗后清空表单字段和
id,再次打开“添加品牌”时不能带入上一次更新数据。拓展练习:将当前品牌列表导出为 Excel,要求先转换数据格式,再创建容器写入数据并导出文件。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 表单验证之自定义校验规则-上 | 14:32 |
| 2 | 表单校验之自定义校验规则-下 | 19:30 |
| 3 | 完成表单校验结果清除 | 30:25 |
| 4 | 完成品牌管理模块更新业务 | 19:26 |
| 5 | 完成删除已有品牌的业务 | 11:43 |
| 6 | 导出excel数据拓展 | 13:54 |
小结
更新业务的核心不是“再写一个表单”,而是回填当前行数据并用
id区分添加与更新。弹窗关闭时必须复位表单字段和
id,否则标题、提交分支、提示文案都会串。添加后回第 1 页,更新后留当前页;分页参数要在刷新列表时显式传递。
删除业务要抓住三件事:二次确认、传对
id、成功后刷新当前页。成功/失败提示要按业务分支区分,不能所有提交都写“添加成功”。
非表单组件的校验不要依赖
change/blur,提交前用表单实例统一校验更稳。导出 Excel 属于拓展能力,按“数据转换→创建容器→写入数据→导出文件”落地,并注意依赖安装与文档查阅。