当前位置:   article > 正文

vue学习笔记day2——组件基础_vue组件页面

vue组件页面

 一 单页面应用程序

1.1 什么是单页面应用程序

单页面应用程序(single page application)简称SPA,顾名思义指的是一个web网站只有唯一的一个HTML页面,所有功能与交互都在这唯一的一个页面内完成。

单页面应用程序将所有功能局限于一个web页面中,仅在该web页面初始化时加载相应的资源(HTML、JavaScript和CSS)。一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转,而是利用JavaScript动态地变换HTML的内容,从而实现页面与用户的交互。

1.2 SPA的优缺点

SPA单页面应用程序最显著的3个优点:

良好的交互体验

  • 单页面应用的内容的改变不需要重新加载整个页面
  • 获取数据也是通过Ajax异步获取
  • 没有页面之间的跳转,不会出现‘白屏现象’

良好的前后端工作分离模式

  • 后端专注于提供API接口,更容易实现API接口的复用
  • 前段专注于页面的渲染,更利于前端工程化的发展

减轻服务器压力

  • 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍

SPA的缺点:

首屏加载慢,解决:路由懒加载、代码压缩、CDN加速、网络传输压缩

不利于SEO,解决:SSR服务器端渲染

1.3 如何快速创建vue的SPA项目

vue官方提供了两种快速创建工程化的SPA项目的方式:

  • 基于vite创建SPA项目
  • 基于vue-cli创建SPA项目
vitevue-cli
支持的vue版本仅支持3.x支持3.x和2.x
是否基于webpack
运行速度较慢
功能完整度小而巧大而全
是否建议在企业级开发中使用目前不建议建议在企业级开发中使用

二 vite的基本使用

2.1 创建vite的项目

通过npm init vite-app 项目名称,这里如果报错Need to install the following packages:create-vite-app,则需要先执行npm i create-vite-app去安装

项目结构初始化完成后,cd 项目名称进入项目目录之下,运行npm i 下载依赖包,最后npm run dev就把项目运行起来了。

2.2 梳理项目的结构

使用vite创建的项目结构如下:

在src这个项目源代码目录之下,包含了如下的文件和文件夹:

2.3 vite项目的运行流程

在工程化的项目中,vue要做的事情:通过main.js把App.vue渲染到index.html的指定区域中。

其中:

  1. App.vue用来编写待渲染的模板结构
  2. index.html中需要预留一个el区域
  3. main.js把App.vue渲染到index.html所预留的区域中

三 组件基础

3.1 什么是组件化开发

根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。

3.2 组件化开发的好处

  • 提高了前端代码的复用性和灵活性
  • 提升了开发效率和后期的可维护性

3.3 vue中的组件化开发

vue是一个完全支持组件化开发的框架。vue中规定组件的后缀名是.vue。

3.4 vue组件的构成

每个vue组件都是由三部分构成:

  • template 组件的模板结构
  • script 组件的JavaScript行为
  • style 组件的样式

其中,每个组件中必须包含template模板结构

3.4.1 组件的template节点

vue规定:每个组件对应的模板结构,需要定义到<template>节点中

注意:template是vue提供的容易标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素。

在组件的<template>节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前的DOM结构。

在vue2.x的版本中,<template>节点内的DOM结构仅支持单个根节点,而在vue3.x的版本中,<template>中支持定义多个根节点

3.4.2 组件的script节点

vue规定:组件内的<script>节点是可选的,开发者可以在<script>节点中封装组件的JavaScript业务逻辑。<script>节点的基本结构如下:

  1. <script>
  2. export default {
  3. // name属性指向得是当前组件的名称
  4. name:'MyApp'
  5. }
  6. </script>
3.4.2.1 name节点

其中name节点为当前组件定义一个名称,在使用vue-devtools进行项目调试的时候,自定义组件的名称可以清晰的区分每个组件

 3.4.2.2 data节点

vue组件渲染期间需要用到的数据,可以定义在data节点中:

  1. <script>
  2. export default {
  3. // name属性指向得是当前组件的名称
  4. name:'MyApp',
  5. // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
  6. data(){
  7. return {
  8. username:'admin'
  9. }
  10. }
  11. }
  12. </script>
3.4.2.3 methods节点

组件中的事件处理函数,必须定义到methods节点中,示例代码如下

  1. <template>
  2. <p>这是App.vue根组件---</p>
  3. <p>123----{{username}}</p>
  4. <hr>
  5. <p>count为{{count}}</p>
  6. <button @click="addCount">+1</button>
  7. </template>
  8. <script>
  9. export default {
  10. // name属性指向得是当前组件的名称
  11. name:'MyApp',
  12. // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
  13. data(){
  14. return {
  15. username:'admin',
  16. count:0
  17. }
  18. },
  19. methods:{
  20. addCount(){
  21. this.count+=1
  22. }
  23. }
  24. }
  25. </script>

3.4.3 组件的style节点

vue规定:组件内的<style>节点是可选的,开发者可以在<style>节点中编写样式美化当前组件的ui结构。

  1. <style lang="css">
  2. p {
  3. color: red;
  4. }
  5. </style>

其中lang="css"属性是可选的,他表示所使用的样式语言。默认只支持普通的css语法,可选值还有less,scss等

如果希望使用less语法编写组件的样式,可以按照如下两个步骤进行配置

  1. 运行npm i less -D命令安装依赖包,从而提供less语法的编译支持
  2. 在<style>标签上添加lang="less"属性,即可使用less语法编写样式

3.5 组件的注册

组件之间可以进行相互引用,例如:

vue中组件的引用原则:先注册后使用。

3.5.1 注册组件的两种方式

vue中注册组件的方式分为“全局注册”和“局部注册”两种,其中:

  • 被全局注册的组件,可以在全局任何一个组件内使用
  • 被局部注册的组件,只能在当前注册的范围内使用
3.5.1.1 全局注册组件

在components目录下创建01.golbalReg文件夹,文件夹下包含Swiper.vue和Test.vue

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. //1.导入Swiper组件
  4. import Swiper from './components/01.globalReg/Swiper.vue'
  5. import Test from './components/01.globalReg/Test.vue'
  6. const app=createApp(App)
  7. //2.调用app实例的component()方法,在全局注册my-swiper和my-test两个组件
  8. app.component('my-swiper',Swiper)
  9. app.component('my-test',Test)
  10. app.mount('#app')

使用app.component()方法注册的全局组件,直接以标签的形式进行使用即可,例如:

  1. <template>
  2. <my-swiper></my-swiper>
  3. <my-test></my-test>
  4. </template>
3.5.1.2 局部注册组件

在components目录下创建02.privateReg文件夹,文件夹下包含Search.vue

  1. <template>
  2. <my-swiper></my-swiper>
  3. <my-test></my-test>
  4. <my-search></my-search>
  5. </template>
  6. <script>
  7. //1.导入
  8. import Search from './components/02.privateReg/Search.vue'
  9. export default {
  10. name:'MyApp',
  11. data(){
  12. return {
  13. username:'admin',
  14. count:0
  15. }
  16. },
  17. methods:{
  18. addCount(){
  19. this.count+=1
  20. }
  21. },
  22. //2.通过components节点,为当前的组件注册私有子组件
  23. components:{
  24. 'my-search':Search
  25. }
  26. }
3.5.1.3 组件注册时名称的大小写

在进行组件的注册时,定义组件注册名称的方式有两种:

使用kebab-case命名法(俗称短横线命名法,例如:my-test)

使用PasscalCase命名法(俗称帕斯卡命名法或大驼峰命名法,例如MySwiper)

短横线命名法的特点:

必须严格按照短横线名称进行使用

帕斯卡命名法的特点:

既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用

注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强

3.5.1.4 通过name属性注册组件

在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的name属性作为注册后组件的名称,如:

  1. // 1.按需导入createApp函数
  2. import { createApp } from 'vue'
  3. // 2.导入待渲染的组件App.vue
  4. import App from './App.vue'
  5. import Test from './components/01.globalReg/Test.vue'
  6. // 3.调用createApp函数,创建SPA应用的实例
  7. const app=createApp(App)
  8. app.component(Test.name,Test)
  9. // 4.通过mount()把App组件的模板结构渲染到指定的el区域中
  10. app.mount('#app')

3.6 组件样式冲突

3.6.1 了解导致组件之间样式冲突的原因

默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件之间样式冲突的根本原因:

单页面应用程序中所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的

每个组件中的样式,都会影响整个index.html页面中的DOM元素

3.6.2 style节点的scoped属性

为了提高开发效率和开发体验,vue为style节点提供了scoped属性,从而防止组件之间的样式冲突问题:

  1. <style scoped>
  2. /* style节点的scoped属性,用来自动为每个组件分配唯一的“自定义属性”,并自动为当前组件的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题 */
  3. p{
  4. color: red;
  5. }
  6. </style>

3.6.3 deep样式穿透

如果给当前组件的style节点添加了scoped属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用/deep/深度选择器,

  1. <style scoped>
  2. /deep/ p{
  3. color: red;
  4. }
  5. </style>

vue3中/deep/被废除,使用:deep()

  1. <style scoped>
  2. :deep(p) {
  3. color: red;
  4. }
  5. </style>

3.7 组件的props

为了提高组件的复用性,在封装vue组件时需要遵守如下的原则:

组件的DOM结构、style样式要尽量复用

组件重要展示的数据,尽量由组件的使用者提供

为了方便使用者为组件提供要展示的数据,vue组件提供了props的概念

3.7.1 什么是props

props是组件的自定义属性,组件的使用者可以通过props把数据传递到子组件内部,供子组件内部进行使用。

3.7.2 在组件中声明props

在封装vue组件时,可以把动态的数据项声明为props自定义属性。自定义属性可以在当前组件的模板结构中直接被使用。

示例:App.vue代码如下:

  1. <template>
  2. <h1>这是App根组件</h1>
  3. <hr>
  4. <my-article title="轻轻滴我走了" author="徐志摩"></my-article>
  5. </template>
  6. <script>
  7. import MyArticle from './Article.vue'
  8. export default {
  9. name:'MyApp',
  10. components:{
  11. MyArticle
  12. }
  13. }
  14. </script>

Article.vue代码如下:

  1. <template>
  2. <div>
  3. <h3>标题{{title}}</h3>
  4. <h5>作者:{{author}}</h5>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyArtical',
  10. props:['title','author']
  11. }
  12. </script>

在App中就可以使用Article中的自定义属性

3.7.3 无法使用未声明的props

如果父组件给子组件传递了未声明的props属性,则这些属性会被忽略,无法被子组件使用。

3.7.4 动态绑定props的值

可以使用v-bind属性绑定指令的形式,为组件动态绑定props的值。

  1. <template>
  2. <h1>这是App根组件</h1>
  3. <hr>
  4. <my-article :title="info.title" :author="info.author"></my-article>
  5. </template>
  6. <script>
  7. import MyArticle from './Article.vue'
  8. export default {
  9. name:'MyApp',
  10. data(){
  11. return {
  12. info:{
  13. title:'123',
  14. author:'456'
  15. }
  16. }
  17. },
  18. components:{
  19. MyArticle
  20. }
  21. }
  22. </script>

3.7.5 props的大小写命名

组件中如果使用“camelCase(驼峰命名法)”声明了props属性的名称,则有两种方式为其绑定属性的值:

  1. //短横线
  2. <my-article pub-time="1979"></my-article>
  3. //驼峰
  4. <my-article pubTime="1979"></my-article>

3.8 Class与Style绑定

在实际开发中经常遇到动态操作元素样式的需求。因此,vue允许开发者通过v-bind属性绑定指令,为元素动态绑定class属性的值和行内的style样式。

3.8.1 动态绑定HTML的class

可以通过三元表达式,动态的为元素绑定class的类名,示例如下:

  1. <template>
  2. <div>
  3. <h3 class="thin" :class="isItalic ? 'italic':''">这是Style组件</h3>
  4. <button @click="isItalic=!isItalic">Toggle Italic</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyStyle',
  10. data(){
  11. return {
  12. // 字体是否倾斜
  13. isItalic:true,
  14. }
  15. },
  16. }
  17. </script>
  18. <style lang="less">
  19. .thin {
  20. font-weight: 200;
  21. }
  22. .italic {
  23. font-style: italic;
  24. color: red;
  25. }
  26. </style>

3.8.2 以数组语法绑定HTML的class

如果元素要动态绑定多个class的类名,此时可以使用数组的语法格式:

  1. <template>
  2. <div>
  3. <h2 class="thin" :class="[isItalic ? 'italic':'', isDelete ?'delete':'']">这是Style组件</h2>
  4. <button @click="isItalic=!isItalic">Toggle Italic</button>
  5. <button @click="isDelete=!isDelete">Toggle Delete</button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name:'MyStyle',
  11. data(){
  12. return {
  13. // 字体是否倾斜
  14. isItalic:true,
  15. isDelete:true
  16. }
  17. },
  18. }
  19. </script>
  20. <style lang="less">
  21. .thin {
  22. font-weight: 200;
  23. }
  24. .italic {
  25. font-style: italic;
  26. }
  27. .delete{
  28. text-decoration: line-through;
  29. }
  30. </style>

3.8.3 以对象语法绑定HTML的class

使用数组语法动态绑定class会导致模板结构臃肿的问题。此时可以使用对象语法进行简化:

  1. <template>
  2. <div>
  3. <h3 class="thin" :class="classObj">这是Style组件</h3>
  4. <button @click="classObj.italic=!classObj.italic">Toggle Italic</button>
  5. <button @click="classObj.delete=!classObj.delete">Toggle Delete</button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name:'MyStyle',
  11. data(){
  12. return {
  13. classObj:{
  14. italic:false,
  15. delete:false
  16. }
  17. }
  18. },
  19. }
  20. </script>
  21. <style lang="less">
  22. .thin {
  23. font-weight: 200;
  24. }
  25. .italic {
  26. font-style: italic;
  27. }
  28. .delete{
  29. text-decoration: line-through;
  30. }
  31. </style>

3.8.4 以对象语法绑定内联的style

:style的对象语法十分直观——看着像css,但其实是一个JavaScript对象。css property名可以用驼峰式或短横线分隔来命名:

  1. <template>
  2. <div :style="{color:active,fontSize:fsize+'px','background-color':bgc}">我要赚大钱</div>
  3. </div>
  4. </template>
  5. <script>
  6. export default {
  7. name:'MyStyle',
  8. data(){
  9. return {
  10. active:'red',
  11. fsize:30,
  12. bgc:'pink'
  13. }
  14. },
  15. }
  16. </script>

3.9 props验证

3.9.1 什么是props验证

在封装组件时对外界传递过来的props数据进行合法性的校验,从而防止数据不合法的问题。使用props节点,可以对每个prop进行数据类型的校验。

  1. <template>
  2. <div>
  3. <p>数量{{count}}</p>
  4. <p>状态{{state}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyCount',
  10. props:{
  11. count:Number,
  12. state:Boolean
  13. }
  14. }
  15. </script>

3.9.2 props验证

对象类型的props节点提供了多种数据验证的方案,例如:

  1. 基础的类型检查
  2. 多个可能的类型
  3. 必填项校验
  4. 属性默认值
  5. 自定义验证函数
3.9.2.1 基础的类型检查

可以直接为组件的prop属性指定基础的校验类型,从而防止组件的使用者为其绑定错误类型的数据:

3.9.2.2 多个可能的类型

如果某个prop属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型,示例代码如下:

  1. export default {
  2. name:'MyCount',
  3. props:{
  4. propA:[String,Number],
  5. }
  6. }
3.9.2.3 必填项校验

如果组件的某个prop属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将其设置为必填项

  1. export default {
  2. name:'MyCount',
  3. props:{
  4. propB:{
  5. type:String,
  6. required:true
  7. }
  8. }
  9. }
3.9.2.4 属性默认值

在封装组件时,可以为某个prop属性指定默认值。

  1. export default {
  2. name:'MyCount',
  3. props:{
  4. propB:{
  5. type:String,
  6. default:'abc'
  7. }
  8. }
  9. }
3.9.2.5 自定义验证函数

在封装组件时,可以为prop属性指定自定义的验证函数,从而对prop属性的值进行更加精确的控制:

  1. export default {
  2. name:'MyCount',
  3. //通过配置对象的形式,来定义info属性的验证规则
  4. props:{
  5. info:{
  6. //通过validator函数,对info属性值进行校验,属性的值通过形参value进行接收
  7. validator(value){
  8. //info属性的值,必须匹配下列字符串中的一个
  9. return ['success','warning','danger'].indexOf(value)!==-1
  10. }
  11. }
  12. }
  13. }

3.10 计算属性

3.10.1 什么是计算属性

计算属性本质上就是一个function函数,它可以实时监听data中数据的变化,并return一个计算后的新值,供组件渲染DOM时使用。

3.10.2 如何声明计算属性

计算属性需要以function函数的形式声明到组件的computed选项中,示例如下:

  1. <template>
  2. <div>
  3. <input type="text" v-model.number="count">
  4. <p>{{count}}乘以2的值为:{{plus}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyCount',
  10. data(){
  11. return {
  12. count:1
  13. }
  14. },
  15. computed:{
  16. plus(){ //计算属性,监听data中count值的变化,自动计算出count*2之后的值
  17. return this.count*2
  18. }
  19. }
  20. }
  21. </script>

注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有return返回值!

3.10.3 计算属性和方法

想对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖发生变化时,才会重新进行运算。因此计算属性的性能更好

3.11 自定义事件

3.11.1 什么是自定义事件

在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件。

3.11.2 自定义事件的使用步骤

  1. 声明自定义事件:开发者为自定义组件封装的自定义事件,必须事先在emits节点中声明
  2. 触发自定义事件:在emits节点下声明的自定义事件,可以通过this.$emit('自定义事件的名称')方法进行触发
  3. 监听自定义事件:在使用自定义组件时,可以通过v-on的形式监听自定义事件

Counter.vue代码如下:

  1. <template>
  2. <div>
  3. <p>count的值{{count}}</p>
  4. <button @click="add">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyCounter',
  10. data(){
  11. return{
  12. count:0
  13. }
  14. },
  15. // 1.声明自定义事件
  16. emits:['countChange'],
  17. methods:{
  18. add(){
  19. this.count+=1
  20. // 2.this.$emit()触发自定义事件
  21. this.$emit('countChange')
  22. }
  23. }
  24. }
  25. </script>

App.vue代码如下:

  1. <template>
  2. <div>
  3. <h1>app根组件</h1>
  4. <!-- 3.监听自定义事件 -->
  5. <my-counter @countChange="getCount"></my-counter>
  6. </div>
  7. </template>
  8. <script>
  9. import MyCounter from './Counter.vue'
  10. export default {
  11. name:'MyApp',
  12. components:{
  13. MyCounter
  14. },
  15. methods:{
  16. // 自定义事件的处理函数
  17. getCount(){
  18. console.log('触发了countchange');
  19. }
  20. }
  21. }
  22. </script>

3.11.3 自定义事件传参

在调用this.$emit()方法触发自定义事件时,可以通过第二个参数为自定义事件传参,

  1. <template>
  2. <div>
  3. <p>count的值{{count}}</p>
  4. <button @click="add">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyCounter',
  10. data(){
  11. return{
  12. count:0
  13. }
  14. },
  15. // 1.声明自定义事件
  16. emits:['countChange'],
  17. methods:{
  18. add(){
  19. this.count+=1
  20. // 2.this.$emit()触发自定义事件
  21. this.$emit('countChange',this.count)
  22. }
  23. }
  24. }
  25. </script>
  1. <template>
  2. <div>
  3. <h1>app根组件</h1>
  4. <!-- 3.监听自定义事件 -->
  5. <my-counter @countChange="getCount"></my-counter>
  6. </div>
  7. </template>
  8. <script>
  9. import MyCounter from './Counter.vue'
  10. export default {
  11. name:'MyApp',
  12. components:{
  13. MyCounter
  14. },
  15. methods:{
  16. // 自定义事件的处理函数
  17. getCount(val){
  18. console.log('触发了countchange',val);
  19. }
  20. }
  21. }
  22. </script>

3.12 组件上的v-model

3.12.1 为什么需要在组件上使用v-model

v-model是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用v-model指令。示意图如下:

3.12.2 实现父向子同步数据

父组件通过v-bind属性绑定的形式,把数据传递给子组件,子组件中,通过props接收父组件传递过来的数据。例如将父组件的count值传给子组件,代码如下

父组件App.vue

  1. <template>
  2. <div>
  3. <h1>App根组件---{{count}}</h1>
  4. <button @click="count+=1">+1</button>
  5. <hr>
  6. <my-counter :count="count"></my-counter>
  7. </div>
  8. </template>
  9. <script>
  10. import MyCounter from './Counter.vue'
  11. export default {
  12. name:'MyApp',
  13. data(){
  14. return {
  15. count:0
  16. }
  17. },
  18. components:{
  19. MyCounter
  20. }
  21. }
  22. </script>

子组件Counter.vue

  1. <template>
  2. <div>
  3. <p>count的值{{count}}</p>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name:'MyCounter',
  9. props:['count']
  10. }
  11. </script>

3.12.3 实现子向父同步数据

在v-bind指令之前添加v-model指令,在子组件中声明emits自定义事件,格式为update:xxx,调用$emit()触发自定义事件,更新父组件中的数据。

  1. <template>
  2. <div>
  3. <h1>App根组件---{{count}}</h1>
  4. <button @click="count+=1">+1</button>
  5. <hr>
  6. <my-counter v-model:count="count"></my-counter>
  7. </div>
  8. </template>
  9. <script>
  10. import MyCounter from './Counter.vue'
  11. export default {
  12. name:'MyApp',
  13. data(){
  14. return {
  15. count:0
  16. }
  17. },
  18. components:{
  19. MyCounter
  20. }
  21. }
  22. </script>
  1. <template>
  2. <div>
  3. <p>count的值{{count}}</p>
  4. <button @click="add">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:'MyCounter',
  10. props:['count'],
  11. emits:['update:count'],
  12. methods:{
  13. add(){
  14. this.$emit("update:count",this.count+1)
  15. }
  16. }
  17. }
  18. </script>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/215383
推荐阅读
相关标签
  

闽ICP备14008679号