一.要实现的功能展示
二.分步解决
1.vue2搭建
nodejs安装下载https://blog.csdn.net/weixin_55992854/article/details/121140754?spm=1001.2014.3001.5506
nvm安装下载
nvm安装教程
vue脚手架搭建
https://blog.csdn.net/qq_48164590/article/details/129440134
2.代码详解
2.1Vue 单文件组件(SFC)模板
<template>区域
<script>区域
<style scoped>区域
<template> 必须有且仅有一个根元素(如.component-container); 支持 Vue 核心指令:v-model(双向绑定)、v-for(循环)、v-if/v-show(条件渲染)、@事件名(事件绑定)等; 插值表达式{{ }}用于渲染响应式数据。 </template> <script> // 脚本区域:存放组件逻辑(数据、方法、生命周期等) export default { // 1. 组件名称(必填,建议 PascalCase 命名) name: 'BaseTemplate', // 2. 接收父组件传递的属性(可选) props: { // 示例:接收父组件传递的标题 title: { type: String, // 属性类型 default: '默认标题' // 默认值 } }, // 3. 响应式数据(核心) data() { return { // 组件内部标题(优先使用父组件传递的title) componentTitle: this.title, // 输入框绑定值 inputValue: '', // 其他自定义数据 count: 0 }; }, // 4. 计算属性(依赖数据自动更新,可选) computed: { // 示例:处理输入内容的长度 inputLength() { return this.inputValue.trim().length; } }, // 5. 监听属性(监听数据变化,可选) watch: { // 监听inputValue变化 inputValue(newVal, oldVal) { console.log('输入内容变化:', '新值=', newVal, '旧值=', oldVal); } }, // 6. 生命周期钩子(组件生命周期阶段执行,可选) created() { // 组件创建完成(数据已初始化,DOM未渲染) console.log('组件创建完成'); }, mounted() { // 组件挂载完成(DOM已渲染,可操作DOM) console.log('组件挂载完成'); }, // 7. 方法(业务逻辑处理,核心) methods: { /** * 按钮点击事件处理 */ handleClick() { this.count++; alert(`按钮点击了${this.count}次,输入内容长度:${this.inputLength}`); } } }; </script> <style scoped> /* 样式区域:组件样式,scoped表示样式仅作用于当前组件(避免污染) */ </style>2.2 添加用户——
<template>区域 <!-- 姓名字段:必填,失去焦点触发姓名验证 --> <div class="form-item"> <label for="name"> <span class="point">*</span>姓名: </label> <input type="text" <!-- 文本输入框 --> id="name" <!-- 与label绑定,提升可访问性 --> v-model="user.name" <!-- 双向绑定到user对象的name属性 --> placeholder="请输入姓名" <!-- 占位提示 --> @blur="checkName" <!-- 失去焦点时触发姓名验证方法 --> style="margin-bottom: 10px;" <!-- 行内样式:底部间距 --> /> <!-- 姓名验证错误提示:nameEmpty为true时显示 --> <div v-if="nameEmpty" class="point" style="margin: 0 0 15px 60px;">请输入姓名</div><script>区域 // 数据定义:响应式数据 data() { return { // 新增/编辑用户的表单数据 user: { id: 0, // 用户ID(自增) name: '', // 姓名 age: '', // 年龄 email: '', // 邮箱 selected: false,// 复选框选中状态 edit: false, // 是否处于编辑状态 tmp: {} // 编辑时保存原始数据(用于取消编辑恢复) }, list: [], // 用户列表数组,存储所有用户数据 nameEmpty: false, // 姓名验证:是否为空 ageEmpty: false, // 年龄验证:是否为空/非法 emailEmpty: false,// 邮箱验证:是否格式错误 selected: [], // 选中行的索引数组(批量操作核心) selectedAll: false,// 全选框状态 num: 0 // 自增ID计数器,用于生成唯一用户ID }; }, method(){ addUser() { // 触发所有字段验证(确保提交时强制校验) this.checkName(); this.checkAge(); this.checkEmail(); // 若任意字段验证失败,终止方法 if (this.nameEmpty || this.ageEmpty || this.emailEmpty) return; // 校验姓名是否已存在(避免重复) if (this.list.some(item => item.name === this.user.name)) { alert('姓名已存在!'); return; } // 构造新用户对象(ID自增) const newUser = { id: ++this.num, // ID自增(每次新增+1) name: this.user.name, // 姓名 age: this.user.age, // 年龄 email: this.user.email,// 邮箱 selected: false, // 初始未选中 edit: false, // 初始非编辑状态 tmp: {} // 初始空对象(用于编辑缓存) }; // 将新用户添加到列表 this.list.push(newUser); // 重置表单(清空输入框+重置验证状态) this.resetForm(); // 提示添加成功 alert('添加成功!'); }, }/** * 重置表单方法 * 1. 清空用户表单数据 * 2. 重置所有验证错误状态 */ resetForm() { this.user = { id: this.num, name: '', age: '', email: '', selected: false, edit: false, tmp: {} }; // 重置验证状态 this.nameEmpty = false; this.ageEmpty = false; this.emailEmpty = false; }, /** * 姓名验证方法(失去焦点触发) * 校验姓名是否为空(去除首尾空格) */ checkName() { this.nameEmpty = this.user.name.trim() === ''; }, /** * 年龄验证方法(失去焦点触发) * 1. 校验是否为空 * 2. 校验是否在0-120之间,非法则清空并标记错误 */ checkAge() { // 空值校验 if (this.user.age === '' || this.user.age <= 0 || this.user.age > 120) { this.ageEmpty = true; // 标记验证失败 this.user.age = ''; // 清空非法输入 } else { this.ageEmpty = false;// 验证通过 } }, /** * 邮箱验证方法(失去焦点触发) * 1. 使用正则校验邮箱格式 * 2. 去除首尾空格后校验,避免空格导致的误判 */ checkEmail() { // 邮箱正则表达式:匹配标准邮箱格式 const emailReg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/; // 校验:空值/格式错误都标记为验证失败 this.emailEmpty = !emailReg.test(this.user.email.trim()); },2.3 删除的相关方法
/** * 删除单行用户方法 * @param {Number} index - 当前行索引 * 1. 弹出确认框,取消则终止 * 2. 删除列表中对应行 * 3. 从选中数组移除当前索引(避免数据残留) */ deleteUser(index) { // 确认删除:用户取消则不执行后续逻辑 if (confirm('确定要删除该用户吗?')) { // splice删除:从index位置删除1个元素 this.list.splice(index, 1); // 从选中数组移除当前索引(避免后续批量操作出错) this.selected = this.selected.filter(i => i !== index); } }, /** * 批量删除选中用户方法 * 1. 校验是否有选中项,无则提示并终止 * 2. 确认删除,取消则终止 * 3. 倒序删除(避免索引错乱),清空选中状态 */ deleteSelected() { // 无选中项则提示并终止 if (this.selected.length === 0) { alert('请选择要删除的用户!'); return; } // 确认删除 if (confirm('确定要删除选中的用户吗?')) { // 倒序排序:避免正序删除导致索引错乱(核心!) // 例如选中[0,1],正序删0后,1变成0,再删1会删错行 this.selected.sort((a, b) => b - a).forEach(index => { this.list.splice(index, 1); // 逐个删除选中行 }); // 清空选中数组 this.selected = []; // 取消全选框状态 this.selectedAll = false; } }, /** * 删除所有用户方法 * 1. 校验列表是否为空,空则提示并终止 * 2. 确认删除,取消则终止 * 3. 清空列表,重置选中状态 */ deleteAll() { // 列表为空则提示 if (this.list.length === 0) { alert('暂无用户可删除'); return; } // 确认删除 if (confirm('确定要删除所有用户吗?')) { this.list = []; // 清空用户列表 this.selected = []; // 清空选中数组 this.selectedAll = false;// 取消全选框 } },2.4 编辑的相关方法
/** * 编辑单行用户方法 * @param {Number} index - 当前行索引 * 1. 标记当前行为编辑状态 * 2. 缓存原始数据(用于取消编辑恢复) */ editUser(index) { const item = this.list[index]; item.edit = true; // 标记为编辑状态 // 缓存原始数据:避免编辑中取消时丢失原数据 item.tmp = { name: item.name, age: item.age, email: item.email }; }, /** * 保存编辑后的用户数据 * @param {Number} index - 当前行索引 * 1. 校验姓名:不能为空 * 2. 校验年龄:0-120之间 * 3. 校验邮箱:格式正确 * 4. 校验姓名:与其他行不重复(排除自身) * 5. 验证通过则退出编辑状态,清空缓存,提示成功 */ saveUser(index) { const item = this.list[index]; // 姓名不能为空(去除首尾空格) if (item.name.trim() === '') { alert('姓名不能为空!'); return; } // 年龄必须在0-120之间 if (item.age === '' || item.age < 0 || item.age > 120) { alert('请输入0-120之间的有效年龄!'); return; } // 邮箱格式校验 const emailReg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/; if (!emailReg.test(item.email.trim())) { alert('请输入正确的邮箱格式!'); return; } // 校验姓名是否与其他行重复(排除当前行) const isDuplicate = this.list.some((i, idx) => idx !== index && i.name === item.name); if (isDuplicate) { alert('姓名已存在!'); return; } // 验证通过:退出编辑状态 item.edit = false; item.tmp = {}; // 清空缓存数据 alert('修改成功!'); // 提示修改成功 }, /** * 取消编辑方法 * @param {Number} index - 当前行索引 * 1. 退出编辑状态 * 2. 恢复缓存的原始数据 * 3. 清空缓存 */ cancelEdit(index) { const item = this.list[index]; item.edit = false; // 退出编辑状态 // 恢复原始数据(从tmp缓存中取) item.name = item.tmp.name; item.age = item.tmp.age; item.email = item.tmp.email; item.tmp = {}; // 清空缓存 }, editSelected() { // 无选中项则提示 if (this.selected.length === 0) { alert('请选择要修改的用户!'); return; } // 遍历选中行,逐个标记为编辑状态并缓存数据 this.selected.forEach(index => { const item = this.list[index]; item.edit = true; item.tmp = { name: item.name, age: item.age, email: item.email }; }); }2.5 全选/单选的相关方法
/** * 全选/取消全选方法(全选框状态变化触发) * 1. 遍历列表,同步所有行的复选框状态 * 2. 更新选中索引数组:全选时生成所有索引,取消时清空 */ selectAll() { // 遍历列表,将所有行的selected同步为全选框状态 this.list.forEach((item) => { item.selected = this.selectedAll; }); // 更新选中索引数组:全选则生成所有索引,取消则清空 this.selected = this.selectedAll ? this.list.map((_, index) => index) // map生成索引数组[0,1,2...] : []; // 取消全选则清空 }, /** * 单行复选框状态变化方法 * @param {Number} index - 当前行索引 * 1. 获取当前行数据 * 2. 选中则添加索引(去重),取消则移除索引 * 3. 取消时同步取消全选框状态 */ handleCheckboxChange(index) { // 获取当前行的用户对象 const item = this.list[index]; // 复选框选中:添加索引到选中数组(避免重复) if (item.selected) { if (!this.selected.includes(index)) { this.selected.push(index); } } else { // 复选框取消:从选中数组移除当前索引 this.selected = this.selected.filter(i => i !== index); // 只要有一行取消,全选框必为未选中 this.selectedAll = false; } },三.相关知识点汇总
一、模板语法(<template>核心)
1. 插值表达式{{ }}
- 作用:渲染响应式数据到页面(文本渲染)
- 案例对应:
{{ index + 1 }}(序号)、{{ item.name }}(用户名) - 注意:只能写表达式(如运算、方法调用),不能写语句(如
if/for)
2. 指令(Vue 特有的标签属性,以v-开头)
| 指令 | 作用 | 案例对应 |
|---|---|---|
v-model | 双向绑定(表单元素↔数据) | v-model="user.name"、v-model="item.selected" |
v-for | 循环渲染列表 | v-for="(item, index) in list" :key="item.id" |
v-if/v-else | 条件渲染(元素是否显示) | v-if="!item.edit"、v-if="list.length === 0" |
@事件名 | 绑定事件(简写,全称v-on) | @blur="checkName"、@click="addUser"、@change="selectAll" |
:属性名 | 绑定元素属性(简写,全称v-bind) | :key="item.id"(循环必须加唯一 key) |
3. 关键细节
v-for必须加:key:用唯一值(如item.id),避免列表渲染错乱;v-model适配不同表单类型:- 文本框 / 邮箱框:
v-model="xxx"(字符串); - 数字框:
v-model="xxx"(需校验数值类型); - 复选框:
v-model="xxx"(布尔值 / 数组);
- 文本框 / 邮箱框:
@blur(失去焦点):表单验证常用,避免实时校验干扰用户输入。
二、响应式数据(<script>核心)
1.data选项
- 作用:定义组件内部的响应式数据;
- 格式:必须是函数(返回对象),避免组件复用数据污染;
- 案例对应:
js
data() { return { user: { name: '', age: '' }, // 表单数据 list: [], // 列表数据 nameEmpty: false // 验证状态 }; } - 响应式特性:数据变化 → 页面自动更新(如
this.nameEmpty = true会触发错误提示显示)。
2.methods选项
- 作用:定义组件的业务方法(处理点击、验证、数据操作);
- 案例对应:
- 事件处理:
addUser()(新增)、deleteUser()(删除); - 表单验证:
checkName()、checkEmail(); - 数据操作:
selectAll()(全选)、saveUser()(保存编辑);
- 事件处理:
- 关键:方法内通过
this访问data中的数据(如this.list、this.user.name)。
3. 常用数组方法(列表操作核心)
| 方法 | 作用 | 案例对应 |
|---|---|---|
push() | 向数组末尾添加元素 | this.list.push(newUser)(新增用户) |
splice() | 删除 / 替换数组元素 | this.list.splice(index, 1)(删除行) |
filter() | 筛选数组(返回新数组) | this.selected.filter(i => i !== index)(移除索引) |
forEach() | 遍历数组 | this.list.forEach(item => { ... })(全选) |
map() | 遍历数组并返回新数组 | this.list.map((_, index) => index)(生成索引数组) |
some() | 检查是否有元素满足条件 | this.list.some(item => item.name === this.user.name)(姓名重复校验) |
三、事件处理
1. 常用事件类型
| 事件名 | 触发时机 | 案例对应 |
|---|---|---|
click | 点击元素 | @click="addUser" |
blur | 元素失去焦点 | @blur="checkName" |
change | 表单元素值变化 | @change="selectAll"(复选框) |
2. 事件传参
- 无参:
@click="addUser"→ 方法直接定义addUser() { ... }; - 有参:
@click="deleteUser(index)"→ 方法接收参数deleteUser(index) { ... }; - 传事件对象:
@input="handleInput($event)"→ 方法接收handleInput(e) { ... }。
3. 核心逻辑:批量删除的「倒序删除」
- 问题:正序删除列表元素会导致索引错乱(如删索引 0 后,原索引 1 变成 0);
- 解决:
this.selected.sort((a, b) => b - a).forEach(index => { ... })(先倒序,再删除)。
四、表单验证(实战重点)
1. 核心规则
- 姓名:
trim()去首尾空格,判断是否为空(this.user.name.trim() === ''); - 年龄:校验数值范围(0-120),非法值清空并标记错误;
- 邮箱:正则校验格式(
/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/); - 重复校验:
some()检查列表中是否已存在相同姓名(排除自身)。
2. 验证时机
- 失去焦点校验(
@blur):提升用户体验; - 提交前强制校验:
addUser()中先调用checkName()/checkAge()/checkEmail()。
五、样式(<style scoped>)
1.scoped作用
- 样式仅作用于当前组件,避免全局样式污染;
- 原理:Vue 自动为组件元素添加唯一属性,样式仅匹配该属性。
2. 常用样式技巧
- 表单元素:统一边框、圆角、内边距,聚焦时修改边框色;
- 按钮:
hover状态修改背景色,cursor: pointer显示手型; - 表格:
border-collapse: collapse合并边框,th加背景色区分表头。
六、Vue 基础高频问题
1. 为什么data是函数?
- 组件复用时,每个实例拥有独立的数据源,避免数据共享导致的错乱(如多个用户列表组件共用一个
list)。
2. 双向绑定原理?
v-model是语法糖:- 文本框:
v-model="xxx"≈:value="xxx" @input="xxx = $event.target.value"; - 复选框:
v-model="xxx"≈:checked="xxx" @change="xxx = $event.target.checked"。
- 文本框:
3. 什么是响应式?
- 数据变化 → Vue 自动更新页面(无需手动操作 DOM);
- 注意:直接修改数组下标 / 对象属性(如
this.list[0].name = 'xxx')是响应式的,但this.list[0] = { ... }需用Vue.set(Vue 2)。
4.label与input绑定的意义?
label for="name"+input id="name":- 点击标签可聚焦输入框(提升操作体验);
- 屏幕阅读器能识别标签与输入框的关联(提升可访问性)。
四.源代码
<template> <div> <div> <h2>添加用户</h2> <div class="form-item"> <label for="name"><span class="point">*</span>姓名:</label> <input type="text" id="name" v-model="user.name" placeholder="请输入姓名" @blur="checkName" style="margin-bottom: 10px;" /> </div> <div v-if="nameEmpty" class="point" style="margin: 0 0 15px 60px;">请输入姓名</div> <div class="form-item"> <label for="age"><span class="point">*</span>年龄:</label> <input type="number" id="age" v-model="user.age" placeholder="请输入年龄" @blur="checkAge" style="margin-bottom: 10px;" /> </div> <div v-if="ageEmpty" class="point" style="margin: 0 0 10px 60px;">请输入正确的年龄</div> <div class="form-item"> <label for="email"><span class="point">*</span>邮箱:</label> <input type="email" id="email" v-model="user.email" placeholder="请输入邮箱" @blur="checkEmail" style="margin-bottom: 10px;" /> </div> <div v-if="emailEmpty" class="point" style="margin: 0 0 10px 60px;">请输入正确的邮箱</div> <button @click="addUser" class="btn">提交</button> <button @click="resetForm" class="btn">重置</button> </div> <hr style="width: 700px; margin: 20px 0;"/> <div class="user-list"> <h2>用户列表</h2> <table class="user-table"> <thead> <tr> <th style="width: 23px;"> <input type="checkbox" v-model="selectedAll" @change="selectAll" class="all-check"/> </th> <th style="width: 23px;">序号</th> <th style="width: 80px;">姓名</th> <th style="width: 30px;">年龄</th> <th style="width: 120px;">邮箱</th> <th style="width: 80px;">操作</th> </tr> </thead> <tbody> <tr v-if="list.length === 0"> <td colspan="6" style="text-align: left;">暂无用户信息</td> </tr> <tr v-for="(item, index) in list" :key="item.id"> <td style="width: 23px;"> <input type="checkbox" v-model="item.selected" @change="handleCheckboxChange(index)" /> </td> <td style="width: 23px;">{{ index + 1 }}</td> <td style="width: 80px;"> <span v-if="!item.edit">{{ item.name }}</span> <input v-else type="text" v-model="item.name" style="width: 80px;"/> </td> <td style="width: 30px;"> <span v-if="!item.edit">{{ item.age }}</span> <input v-else type="number" v-model="item.age" style="width: 30px;"/> </td> <td style="width: 120px;"> <span v-if="!item.edit">{{ item.email }}</span> <input v-else type="text" v-model="item.email" style="width: 120px;"/> </td> <td style="width: 80px;"> <button @click="editUser(index)" v-if="!item.edit" class="btn">修改</button> <button @click="saveUser(index)" v-else class="btn">保存</button> <button @click="deleteUser(index)" v-if="!item.edit" class="btn">删除</button> <button @click="cancelEdit(index)" v-else class="btn">取消</button> </td> </tr> <tr> <td colspan="6"> <button @click="deleteAll" class="btn">删除所有</button> <button @click="deleteSelected" class="btn">批量删除</button> <button @click="editSelected" class="btn">批量修改</button> </td> </tr> </tbody> </table> </div> </div> </template> <script> export default { name: 'UserManager', data() { return { user: { id: 0, name: '', age: '', email: '', selected: false, edit: false, tmp: {} }, list: [], nameEmpty: false, ageEmpty: false, emailEmpty: false, selected: [], selectedAll: false, num: 0 }; }, methods: { addUser() { this.checkName(); this.checkAge(); this.checkEmail(); if (this.nameEmpty || this.ageEmpty || this.emailEmpty) return; if (this.list.some(item => item.name === this.user.name)) { alert('姓名已存在!'); return; } const newUser = { id: ++this.num, name: this.user.name, age: this.user.age, email: this.user.email, selected: false, edit: false, tmp: {} }; this.list.push(newUser); this.resetForm(); alert('添加成功!'); }, resetForm() { this.user = { id: this.num, name: '', age: '', email: '', selected: false, edit: false, tmp: {} }; this.nameEmpty = false; this.ageEmpty = false; this.emailEmpty = false; }, checkName() { this.nameEmpty = this.user.name.trim() === ''; }, checkAge() { if (this.user.age === '' || this.user.age <= 0 || this.user.age > 120) { this.ageEmpty = true; this.user.age = ''; } else { this.ageEmpty = false; } }, checkEmail() { const emailReg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/; this.emailEmpty = !emailReg.test(this.user.email.trim()); }, selectAll() { this.list.forEach((item) => { item.selected = this.selectedAll; }); this.selected = this.selectedAll ? this.list.map((_, index) => index) : []; }, handleCheckboxChange(index) { const item = this.list[index]; if (item.selected) { if (!this.selected.includes(index)) { this.selected.push(index); } } else { this.selected = this.selected.filter(i => i !== index); this.selectedAll = false; } }, deleteUser(index) { if (confirm('确定要删除该用户吗?')) { this.list.splice(index, 1); this.selected = this.selected.filter(i => i !== index); } }, deleteSelected() { if (this.selected.length === 0) { alert('请选择要删除的用户!'); return; } if (confirm('确定要删除选中的用户吗?')) { this.selected.sort((a, b) => b - a).forEach(index => { this.list.splice(index, 1); }); this.selected = []; this.selectedAll = false; } }, deleteAll() { if (this.list.length === 0) { alert('暂无用户可删除'); return; } if (confirm('确定要删除所有用户吗?')) { this.list = []; this.selected = []; this.selectedAll = false; } }, editUser(index) { const item = this.list[index]; item.edit = true; item.tmp = { name: item.name, age: item.age, email: item.email }; }, saveUser(index) { const item = this.list[index]; if (item.name.trim() === '') { alert('姓名不能为空!'); return; } if (item.age === '' || item.age < 0 || item.age > 120) { alert('请输入0-120之间的有效年龄!'); return; } const emailReg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/; if (!emailReg.test(item.email.trim())) { alert('请输入正确的邮箱格式!'); return; } const isDuplicate = this.list.some((i, idx) => idx !== index && i.name === item.name); if (isDuplicate) { alert('姓名已存在!'); return; } item.edit = false; item.tmp = {}; alert('修改成功!'); }, cancelEdit(index) { const item = this.list[index]; item.edit = false; item.name = item.tmp.name; item.age = item.tmp.age; item.email = item.tmp.email; item.tmp = {}; }, editSelected() { if (this.selected.length === 0) { alert('请选择要修改的用户!'); return; } this.selected.forEach(index => { const item = this.list[index]; item.edit = true; item.tmp = { name: item.name, age: item.age, email: item.email }; }); } } }; </script> <style scoped> .point { color: red; font-size: 12px; margin-right: 5px; } .form-item { margin: 3px 0; font-size: 14px; } input { border: 1px solid #ccc; border-radius: 4px; height: 22px; padding: 0 5px; } input:focus { outline: none; border-color: #999; } .btn { margin: 0 5px 5px 0; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; background-color: #f5f5f5; cursor: pointer; } .btn:hover { background-color: #bfbcbc; } .user-table { border-collapse: collapse; width: 700px; margin-top: 10px; } .user-table th, .user-table td { border: 1px solid #ccc; padding: 8px; text-align: left; } .user-table th { background-color: #f5f5f5; font-weight: bold; } </style>