主题
组件的使用与使用过滤器(Lesson8 1/3)
本页对应 Lesson08,共 6 段视频,约 1 小时 36 分。
学习目标
- 能使用
render函数通过h/createElement创建并挂载虚拟节点,说明template与render的优先级及执行效率差异。 - 能使用 Vue CLI 创建并配置脚手架项目,完成依赖安装、项目启动,以及端口、主机、自动打开浏览器等开发服务配置。
- 能在脚手架中创建主组件和子组件,完成组件引入、注册、父子属性传递、事件绑定及
scoped样式隔离。 - 能定义并使用局部过滤器与全局过滤器,并通过
Vue.use以对象install或函数形式将过滤器注册为插件。 - 能基于给定页面结构和数据,在脚手架中完成 todoList 的界面渲染,并正确引入全局样式或组件内样式。
知识点
入口文件:用 render 指定主组件
为什么需要:脚手架项目启动后,必须告诉 Vue 把哪个组件渲染到 #app 上。转写中反复调试入口文件,删掉 src 后会提示找不到入口,说明入口是项目运行的起点。
怎么想:入口不直接写一堆模板,而是引入最外层的主组件 App,通过 render 返回它。render 接收的 h 是创建元素/组件的函数。
怎么做:
js
import Vue from "vue";
import App from "@/App";
new Vue({
el:"#app",
render:h=>h(App)
})易错点:h(App) 里的 App 是组件对象,不是字符串;@ 通常代表 src。转写中还演示过:在脚手架入口直接写 template 当前版本会报警/不生效,所以入口用 render 更稳。若不用 el,需要手动挂载。
App.vue:结构层、逻辑层、样式层
为什么需要:一个 .vue 文件要把结构、逻辑、样式放在一起,便于维护。转写中删掉结构层后页面没有内容,说明模板不能省。
怎么想:template 是结构层,必须有且只能有一个根元素;script 写逻辑;style 写样式。
怎么做:
vue
<!--
结构层-不允许省略
有且只能是一个根元素
-->
<template>
<div>
<h3 @click="num++">APP:{{num}}</h3>
<hr/>
<Child :num="num"></Child>
</div>
</template>
<!--逻辑层-->
<script>
import Child from "@/components/Child";
export default {
name: "App.vue",
data(){
return {
num:1
}
},
components:{
Child
}
}
</script>
<!--样式层-->
<style lang="less" scoped>
div{
h3{
color:red;
}
}
</style>易错点:模板里写两个同级 div 会编译失败,转写中就是加一个外层 div 包裹后解决;template 不能省略,script、style 可省。lang="less" 需要项目支持 less。
子组件注册与使用:import + components + 标签
为什么需要:页面要拆分,App 作为主组件,Child 作为子组件。
怎么想:先 import Child,再在 components 中注册,最后模板里写 <Child>。转写中只引入不注册就不能用。
怎么做:App 中已写 import Child from "@/components/Child"; 和 components:{ Child },模板写 <Child :num="num"></Child>。
易错点:components 拼写;注册名与标签名对应;@/components/Child 指向 src/components/Child.vue。VS Code 中可用提示自动引入,但要看路径是否正确。
props 传值与校验:父传子
为什么需要:App 的 num 要显示在 Child 中,不能直接改子组件内部数据,要用 props 接收。
怎么想:父组件用 :num="num" 传变量;子组件声明 props 接收。转写中不声明接收时控制台报错/不显示。
怎么做:
vue
<template>
<div>
<h3>Child:{{num}}</h3>
</div>
</template>
<script>
export default {
name: "Child",
// props:["num"],
props:{
num:{
type:Number,
default:100
}
},
beforeCreate() {
console.log("1-beforeCreate");
},
created(){
console.log("2-created");
},
beforeMount() {
console.log("3-beforeMount");
},
mounted(){
console.log("4-mounted");
},
beforeUpdate() {
console.log("1-beforeUpdate");
},
updated(){
console.log("2-updated");
}
}
</script>
<style scoped>
h3{
color:green;
}
</style>易错点::num="num" 传的是变量,num="num" 传的是字符串;type:Number 与默认值 100 要匹配;不传时默认值生效。转写中演示过去掉不合适的校验后页面正常。
scoped 样式作用域:解决父子样式冲突
为什么需要:转写中 App 的 h3 红色把 Child 的 h3 绿色覆盖了,刷新后看到红色,鼠标查看子组件样式被划掉。
怎么想:给组件样式加 scoped,让样式只作用于当前组件模板。Vue 会给元素加类似 data-v-xxx 的属性,选择器带属性后就不互相覆盖。
怎么做:Child 的 <style scoped> 已加;App 的 <style lang="less" scoped> 也加。
易错点:不加 scoped 容易全局污染;scoped 只解决当前组件模板范围,不是所有深层样式都随便穿透。
生命周期观察:父数据变化触发子组件更新
为什么需要:理解组件创建、挂载、更新顺序,调试时知道日志为什么这样打。
怎么想:Child 中已有钩子日志。首次渲染会依次看到 beforeCreate → created → beforeMount → mounted;点击 App 的 h3 让 num++ 后,父数据变化传到子组件,会触发 beforeUpdate → updated。
怎么做:打开控制台,点击 APP:{{num}} 观察 Child 的日志。
易错点:beforeCreate/created 只执行一次;更新阶段不是重新 created;日志要写在 Child 中才能观察子组件更新。
先把问题定位清楚:过滤器为什么要分“局部”和“全局”
这一节的核心不是重新讲一遍管道符,而是解决一个实际报错:在 App.vue 里定义好的过滤器,放到子组件里使用时不生效。转写稿里先在主组件中写了商品价格、上架时间,用 {{ item.price | currency }}、{{ item.time | d }} 展示;随后在子组件里也想用 d,结果报错。原因不是写法坏了,而是过滤器注册位置不对:在哪个组件里注册,就只允许哪个组件使用,这叫局部过滤器。需要跨组件复用时,就要注册成全局过滤器。
局部过滤器:在哪个组件注册,就在哪个组件用
先在主组件里准备数据,用 v-for 渲染商品列表,再用管道符格式化价格和时间:
html
<div v-for="item in goodsList" :key="item.id">
<p>商品名称:{{ item.name }}</p>
<p>商品价格:{{ item.price | currency }}</p>
<p>上架时间:{{ item.time | d }}</p>
</div>js
export default {
data() {
return {
goodsList: [
{ id: 1, name: '商品一号', price: 19.123, time: '...' },
{ id: 2, name: '商品二号', price: 456, time: '...' }
]
}
},
filters: {
currency(value, n) {
// 转写稿中保留了原过滤器实现,这里不重复编造
},
d(value) {
// 时间过滤器实现保持不变
}
}
}想保留三位小数时,通过括号传参:
html
{{ item.price | currency(3) }}这里要记住:管道符左边的数据是过滤器的第一个参数,括号里的 3 是第二个参数。易错点是:在 App.vue 的 filters 里注册后,子组件 Chart 直接写 {{ t | d }} 会报“找不到过滤器”,因为局部过滤器不会自动下发给子组件。
全局过滤器的三种注册方式
第一种最直接,在 main.js 里用 Vue.filter 注册:
js
import Vue from 'vue'
import App from './App.vue'
Vue.filter('d', function (value) {
// 原时间过滤器实现
})
Vue.filter('currency', function (value, n) {
// 原价格过滤器实现
})
new Vue({
render: h => h(App)
}).$mount('#app')第二种是把过滤器集中到 src/filters/index.js,导出对象,再在 main.js 里循环注册:
js
// src/filters/index.js
export default {
d(value) { /* ... */ },
currency(value, n) { /* ... */ }
}js
// main.js
import Vue from 'vue'
import App from './App.vue'
import filters from './filters'
for (let key in filters) {
Vue.filter(key, filters[key])
}
new Vue({
render: h => h(App)
}).$mount('#app')第三种是放到 beforeCreate 里,在创建实例前完成注册:
js
new Vue({
beforeCreate() {
for (let key in filters) {
Vue.filter(key, filters[key])
}
},
render: h => h(App)
}).$mount('#app')易错点:忘记 import filters、过滤器重名、循环变量写错,都会导致“页面没报错但格式没变”。注册时机要保证在组件渲染前。
把过滤器做成插件:Vue.use 到底做了什么
如果每次都在 main.js 里写循环,重复且容易漏。转写稿把它封装成插件,用 Vue.use(filters) 安装。Vue.use 接收插件时有两种情况:如果传入的是对象,就调用对象里的 install 方法,并把 Vue 传进去;如果传入的是函数,就直接调用这个函数,并把 Vue 传进去。对象写法:
js
const filters = {
d(value) { /* ... */ },
currency(value, n) { /* ... */ }
}
export default {
install(Vue) {
for (let key in filters) {
Vue.filter(key, filters[key])
}
}
}js
// main.js
import Vue from 'vue'
import App from './App.vue'
import filters from './filters'
Vue.use(filters)
new Vue({
render: h => h(App)
}).$mount('#app')函数写法更简洁:
js
const filters = { /* ... */ }
export default function (Vue) {
for (let key in filters) {
Vue.filter(key, filters[key])
}
}易错点:export default 导出的对象如果没有 install,Vue.use 不会帮你注册;install 里拿到的是 Vue 构造函数,不是组件实例;插件写法本质仍是注册全局过滤器。
接到 TodoList 页面时,别把过滤器当成组件
转写稿最后开始搭 TodoList:把页面模板复制到 App.vue,在 main.js 里引入 App 并用 render: h => h(App) 挂载;样式可以放进组件的 style,也可以新建 src/assets/css/index.css,在 script 里 import 引入。这里要区分:过滤器负责“格式化显示”,组件负责“结构和逻辑”。易错点是模板根元素、样式作用域和 scoped 的使用;过滤器仍然按“局部注册只在当前组件可用,全局注册才跨组件可用”的规则处理。
先明确本节目标:把 todoList 的静态界面渲染出来
为什么需要:todoList 后面要接增加、删除、全选、反选和缓存,如果一上来就写逻辑,页面结构和入口文件容易混在一起。先把“壳”渲染出来,后面才有稳定的调试基础。
怎么想:转写稿的思路是先从已有 todoList 页面里复制 id="root" 所包裹的 div 结构,放进 App.vue 的模板;再新建入口 main.js,把 App.vue 挂到 #app 上;样式可以复制进组件的 style,也可以放到 src/assets/css/index.css 再引入。
怎么做:入口文件写成下面这样,render 返回主组件 App,最后手动挂载:
js
import Vue from 'vue'
import App from './App.vue'
import './assets/css/index.css'
new Vue({
render: h => h(App)
}).$mount('#app')主组件先保留三段式骨架:
vue
<template>
<div>
<!-- 这里放从 todoList 页面复制来的结构 -->
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
/* 也可以把样式放到 assets/css/index.css 中引入 */
</style>易错点:挂载点要和 public/index.html 里的 id="app" 对应;样式放 assets/css/index.css 时,入口里要用 import 引入,@ 在脚手架中通常代表 src。转写稿里反复强调“先呈现界面,再加功能”,不要在这一步提前写增删改查。
render 是脚手架入口文件的写法
为什么需要:脚手架入口没有直接写 el 和 template,而是用 render 指定主组件。不理解它,就看不懂 main.js 为什么是 h => h(App)。
怎么想:render 接收一个创建节点的函数,通常命名为 h;h(App) 表示用 App 组件生成虚拟节点,最终替换挂载点内容。转写稿中先用 h('div', '你好') 演示,再改成 h(App),就是为了说明“简单结构可以直接写,复杂页面要交给组件”。
怎么做:入口统一用:
js
new Vue({
render: h => h(App)
}).$mount('#app')易错点:template 和 render 同时存在时,render 优先级更高;转写稿里在脚手架中直接写 template 会报“当前版本不支持这种直接渲染”,所以不要照搬旧写法。render 里写 h('div', '你好') 只适合演示,真实页面应返回组件。
单文件组件与样式作用域
为什么需要:todoList 有结构、逻辑和样式,单文件组件把三部分放在一个 .vue 文件里,主组件和子组件职责更清楚。
怎么想:App.vue 是最外层主组件,子组件放 components 目录;模板负责结构,script 写数据和方法,style 写样式。转写稿中演示两个 H3 样式互相覆盖,解决方式是给 style 加 scoped,或者用 lang="less" 并加作用域。
怎么做:组件模板只能有一个根节点;样式冲突时写:
vue
<style scoped>
h3 { color: red; }
</style>需要 Less 时写 <style lang="less" scoped>。易错点:template 不能省略,script 和 style 可以省略;模板出现两个同级根节点会编译失败,要先套一个外层 div;scoped 会给元素加 data-v-* 属性,使样式只作用当前组件。
过滤器:局部与全局,以及作为插件安装
为什么需要:todoList 后面会涉及价格、时间等显示格式,过滤器可以把格式化逻辑从模板里抽出来。
怎么想:在某个组件里写的 filters 是局部过滤器,只在该组件模板中可用;多个组件都要用,就注册全局过滤器,或把过滤器做成插件,用 Vue.use() 安装。
怎么做:局部写法是在组件里写 filters: { ... };全局可以用 Vue.filter('currency', fn)。转写稿中把过滤器放到 src/filters/index.js,并导出插件:
js
const filters = {
// 这里放前面写好的过滤器函数
}
export default {
install(Vue) {
for (let key in filters) {
Vue.filter(key, filters[key])
}
}
}入口中安装:
js
import Vue from 'vue'
import App from './App.vue'
import filters from './filters'
Vue.use(filters)
new Vue({
render: h => h(App)
}).$mount('#app')易错点:Vue.use(filters) 时,如果 filters 是对象,对象里必须有 install 方法,否则转写稿中会出现“过滤器没生效、控制台报错”;如果导出的是函数,Vue.use 会直接调用该函数并传入 Vue。局部过滤器只在注册它的组件里有效,换到子组件就用不了,这是转写稿中专门调试过的点。
渲染 todoList 页面:先静态,后数据
为什么需要:todoList 的完整功能依赖列表数据,但本节转写稿只做到“把页面结构渲染出来、样式引入成功”,所以要先保证静态页面可见,再逐步接 v-for、事件和缓存。
怎么想:先把复制来的 todoList 结构放进 App.vue,样式可以走两条路:一是直接放进组件 style;二是放到 src/assets/css/index.css,在入口或组件中 import。转写稿最后验证的是页面能正常显示,而不是一次性写完所有交互。
怎么做:保留 App.vue 作为主组件,入口用 render 挂载;后续列表数据放进 data() 返回的对象,用 v-for 渲染,key 用唯一 id。本节先不要凭空补全增删改查代码,转写稿明确说这些功能后面再做。
易错点:data 在组件中必须写成函数并返回对象;v-for 的 key 不要用索引凑合;样式文件路径不要写错。转写稿的调试结论是:先把静态壳和样式跑通,再进入 todoList 的交互逻辑,否则报错时很难判断是入口、组件还是样式的问题。
课堂画面






代码示例
示例一:脚手架中组件的使用(main.js + App.vue + Child.vue)
js
// src/main.js
import Vue from "vue";
import App from "@/App";
new Vue({
el: "#app",
render: h => h(App)
});vue
<!-- src/App.vue -->
<!--
结构层-不允许省略
有且只能是一个根元素
-->
<template>
<div>
<h3 @click="num++">APP:{{ num }}</h3>
<hr />
<Child :num="num"></Child>
</div>
</template>
<!--逻辑层-->
<script>
import Child from "@/components/Child";
export default {
name: "App.vue",
data() {
return {
num: 1
};
},
components: {
Child
}
};
</script>
<!--样式层-->
<style lang="less" scoped>
div {
h3 {
color: red;
}
}
</style>vue
<!-- src/components/Child.vue -->
<template>
<div>
<h3>Child:{{ num }}</h3>
</div>
</template>
<script>
export default {
name: "Child",
// props:["num"],
props: {
num: {
type: Number,
default: 100
}
},
beforeCreate() {
console.log("1-beforeCreate");
},
created() {
console.log("2-created");
},
beforeMount() {
console.log("3-beforeMount");
},
mounted() {
console.log("4-mounted");
},
beforeUpdate() {
console.log("1-beforeUpdate");
},
updated() {
console.log("2-updated");
}
};
</script>
<style scoped>
h3 {
color: green;
}
</style>关键行与易错点
render: h => h(App):脚手架入口不用template,而是用 render 函数把主组件App渲染出来;el: "#app"对应public/index.html里<div id="app">的挂载点。若把el写成#root之类不存在的选择器,页面就是空白。template与render同时存在时,template优先级更低,最终仍由 render 生成虚拟节点——这也是入口文件统一写 render 的原因。<template>里有且只能有一个根元素。把两个div并列写在模板顶层会直接编译失败,必须先包一层外层div。- 结构层(template)不允许省略,
script与style都可以省。 data在组件里必须是函数并 return 一个对象,写成对象形式会导致多个实例共享同一份数据。- 父传子:
<Child :num="num">中的:是v-bind简写,不加冒号传的是字符串"num"而不是变量值。子组件用props接收,对象写法可声明type、default;若父组件传了值又设了default,default不生效;type: Number时传字符串会在控制台报警告。 scoped:不加 scoped 时父子同名选择器(都写h3)会互相覆盖,父组件的红色样式会把子组件的绿色干掉;加上 scoped 后 Vue 会给元素加data-v-xxx属性选择器来隔离作用域。使用less需要在<style>上写lang="less"。- 生命周期执行顺序:父 beforeCreate → created → beforeMount → 子 beforeCreate → created → beforeMount → mounted → 父 mounted;父组件数据变化只会触发子组件的
beforeUpdate/updated,不会再走created、mounted。
示例二:把过滤器作为插件安装(全局过滤器)
js
// src/filters/index.js
// 导出一个函数:Vue.use() 时会被直接调用,并收到 Vue 构造函数
export default function (V) {
let filters = {
// 价格过滤器:第二个参数是保留的小数位数
currency(value, n = 2) {
return "¥" + Number(value).toFixed(n);
},
// 时间过滤器
d(value) {
let date = new Date(value);
let y = date.getFullYear();
let m = ("0" + (date.getMonth() + 1)).slice(-2);
let day = ("0" + date.getDate()).slice(-2);
return `${y}-${m}-${day}`;
}
};
// 遍历过滤器对象,逐个注册成全局过滤器
for (let key in filters) {
V.filter(key, filters[key]);
}
}js
// src/main.js
import Vue from "vue";
import App from "@/App";
import filters from "@/filters";
// 安装插件:传入的是函数 → 直接调用该函数并传入 Vue
Vue.use(filters);
new Vue({
el: "#app",
render: h => h(App)
});关键行与易错点
Vue.use(插件)干了什么:插件是对象时,调用对象里的install(Vue)方法;插件是函数时,直接调用该函数并把Vue传进去。示例里filters是函数,所以Vue.use(filters)等价于执行filters(Vue)。- 函数参数
V就是 Vue 构造函数本身,因此函数内部可以省掉import Vue from "vue",直接用V.filter(...)注册。 - 如果只
export default { currency(){}, d(){} }而没有 install 方法,Vue.use什么都不会执行,页面上会报「找不到过滤器」,这是最常见的坑。 - 局部过滤器写在某个组件的
filters选项里,只在当前组件生效;在子组件(如 Child.vue)里用{{ t | d }}会报错,必须像本例这样注册成全局过滤器。 - 注册时机:
Vue.use要写在new Vue(...)之前(或写在beforeCreate里),否则实例创建时过滤器还没注册。 - 模板中过滤器写法:
{{ price | currency }}冒号左边是被处理的数据,括号里传的是第二个参数,如{{ price | currency(3) }}。
示例三:列表渲染 + 过滤器(todoList / 商品列表界面)
vue
<!-- src/App.vue -->
<template>
<div>
<h3 @click="num++">APP:{{ num }}</h3>
<hr />
<ul>
<li v-for="item in goodsList" :key="item.id">
<span>商品名称:{{ item.goodsName }}</span>
<span>价格:{{ item.price | currency(3) }}</span>
<span>上架时间:{{ item.time | d }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: 1,
goodsList: [
{ id: 1, goodsName: "商品一号", price: 19.123, time: Date.now() },
{ id: 2, goodsName: "商品二号", price: 456, time: Date.now() }
]
};
}
};
</script>
<style scoped>
ul {
list-style: none;
}
li {
height: 40px;
line-height: 40px;
}
</style>关键行与易错点
v-for="item in goodsList"必须配:key,这里用数据里唯一的item.id,不要用index当 key(后续做删除、选中、全选反选时会出现错乱)。- 过滤器只能写在插值表达式
{{ }}或v-bind里,{{ item.price | currency(3) }}中的3是传给过滤器的第二个参数,第一个参数永远是被过滤的数据本身。 - 过滤器里的
value是原始数据,函数必须return处理结果,只做打印不返回,页面会显示空。 - 静态资源与全局样式建议放到
src/assets(图片)和src/assets/css/index.css,在script里import "@/assets/css/index.css"引入;另一种做法是把页面样式直接抄进<style>里,两者效果相同,区别是前者是全局样式、后者受scoped限制。 - 列表数据变化(新增、删除、勾选)时只会触发
beforeUpdate/updated,不会重新走created、mounted,需要缓存的逻辑要写在updated或对应的操作函数里。
常见错误
- 现象:
App.vue的<template>下写两个同级<div>,编译直接失败。→ 原因:模板结构层有且只能有一个根元素。→ 修法:外层补一个包裹容器,把同级节点收进去。 - 现象:
Child.vue里h3被父组件App.vue的红色样式覆盖。→ 原因:父组件样式未隔离,选择器命中了子组件节点。→ 修法:父、子组件样式都加scoped;需要穿透时再单独处理。 - 现象:父组件传
:num="num",子组件收到字符串或默认值不生效。→ 原因:props声明方式与传入值不匹配,或漏写type/default。→ 修法:用对象形式声明props,明确type、default,动态绑定保留冒号。 - 现象:子组件里直接改
props.num,控制台警告且数据流混乱。→ 原因:props是单向数据流,子组件不应直接修改。→ 修法:子组件用data/computed接收派生值,或通过$emit通知父组件修改。 - 现象:入口文件同时写
template和render,页面显示的不是预期模板。→ 原因:render优先级高于template。→ 修法:入口统一用render: h => h(App),不要在入口再写template。 - 现象:局部过滤器在另一个组件中使用时报“未定义”。→ 原因:局部过滤器只在注册它的组件作用域内有效。→ 修法:改为全局过滤器,或通过插件安装统一注册。
- 现象:
Vue.use(filters)后过滤器仍不生效。→ 原因:插件导出形式与use调用规则不匹配:对象需提供install,函数会被直接调用。→ 修法:按一种规范导出:export default { install(Vue){...} }或export default function(Vue){...}。 - 现象:
vue.config.js配了host却启动失败或访问异常。→ 原因:host里误加协议,或端口/域名与 hosts 配置不一致。→ 修法:host只写域名/IP,协议单独由https等配置控制;端口、hosts、访问地址三者对齐。
练习
- 把入口文件改为
new Vue({ render: h => h(App) }).$mount('#app'),再分别测试“只写template”和“同时写render + template”的显示结果,记录优先级差异。 - 给
Child.vue的num增加type: Number、default: 100、required三种校验组合,分别测试父组件传数字、传字符串、不传值时的控制台表现。 - 在
App.vue和Child.vue中各写一个h3样式,先不加scoped复现冲突,再加scoped修复,并用开发者工具观察属性选择器变化。 - 把笔记中的局部过滤器改造成
src/filters/index.js,分别用“对象 +install”和“函数”两种插件写法通过Vue.use()注册,验证效果一致。 - 基于 todoList 页面完成:新增任务、全选/反选、删除单条、删除选中项;要求组件拆分清晰,父子通信用
props + $emit。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 渲染函数render | 12:49 |
| 2 | 脚手架项目创建 | 34:57 |
| 3 | 组件的使用 | 18:02 |
| 4 | 过滤器的基本使用 | 15:28 |
| 5 | 将过滤器作为插件进行安装 | 09:39 |
| 6 | 渲染todoList | 05:39 |
小结
- 单文件组件先保证结构层合法:
template不可省略,且只能有一个根元素。 - 入口文件的核心是
render: h => h(App)加手动挂载,render与template不要混用。 - 父子组件通信优先保持单向数据流:父传
props,子通过事件回传,不在子组件直接改props。 - 样式冲突优先用
scoped解决;父组件不要随意命中子组件内部选择器。 - 过滤器有作用域边界:局部过滤器只在当前组件可用,跨组件使用应注册为全局或做成插件。
Vue.use()的插件导出必须符合约定:对象走install,函数直接执行,二者不要混写。- 脚手架项目要避开中文、特殊字符、C 盘路径;
host/port配置要与 hosts 和访问地址一致。 - 调试顺序固定为:先看编译错误,再看控制台警告,最后查样式作用域和数据流向。