【Vue】组件化开发

在这里插入图片描述

个人主页:Guiat
归属专栏:Vue

在这里插入图片描述

正文

1. 组件基础

组件是 Vue 的核心功能,允许我们将 UI 拆分为独立可复用的代码片段。

1.1 组件注册

// 全局组件注册
const app = Vue.createApp({})

// 全局注册组件
app.component('my-component', {
  // 组件选项
  template: '<div>全局组件</div>',
  data() {
    return {
      count: 0
    }
  }
})

// 局部注册组件
const ComponentA = {
  template: '<div>局部组件 A</div>'
}

const ComponentB = {
  template: '<div>局部组件 B</div>'
}

const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

1.2 组件基本用法

<div id="app">
  <!-- 使用组件 -->
  <button-counter></button-counter>
  
  <!-- 可以重复使用组件,每个实例都是独立的 -->
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

<script>
  const app = Vue.createApp({})
  
  app.component('button-counter', {
    // 组件的数据必须是函数,以确保每个实例都有独立的数据副本
    data() {
      return {
        count: 0
      }
    },
    template: `
      <button @click="count++">
        你点击了我 {{ count }} 次
      </button>
    `
  })
  
  app.mount('#app')
</script>

2. 组件通信

2.1 Props 向下传递数据

<div id="app">
  <!-- 传递静态 prop -->
  <blog-post title="Vue 组件入门"></blog-post>
  
  <!-- 动态绑定 prop -->
  <blog-post :title="post.title"></blog-post>
  <blog-post :title="post.title" :author="post.author" :publish-date="post.publishDate"></blog-post>
  
  <!-- 传递对象的所有属性 -->
  <blog-post v-bind="post"></blog-post>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        post: {
          id: 1,
          title: 'Vue 组件化开发指南',
          author: '张三',
          publishDate: '2023-05-15'
        }
      }
    }
  })
  
  app.component('blog-post', {
    // 声明 props
    props: {
      // 基础类型检查
      title: String,
      // 多种类型
      author: [String, Object],
      // 必填项
      publishDate: {
        type: String,
        required: true
      },
      // 带默认值
      comments: {
        type: Array,
        default: () => []
      },
      // 自定义验证函数
      likes: {
        validator(value) {
          return value >= 0
        }
      }
    },
    template: `
      <div class="blog-post">
        <h3>{{ title }}</h3>
        <p>作者: {{ author }}</p>
        <p>发布日期: {{ publishDate }}</p>
      </div>
    `
  })
  
  app.mount('#app')
</script>

2.2 自定义事件向上传递数据

<div id="app">
  <!-- 监听自定义事件 -->
  <blog-post
    :title="post.title"
    @enlarge-text="fontSizeIncrease"
  ></blog-post>
  
  <p :style="{ fontSize: postFontSize + 'px' }">
    文章内容示例,当前字体大小: {{ postFontSize }}px
  </p>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        post: {
          title: 'Vue 组件通信'
        },
        postFontSize: 16
      }
    },
    methods: {
      fontSizeIncrease(enlargeAmount) {
        this.postFontSize += enlargeAmount
      }
    }
  })
  
  app.component('blog-post', {
    props: ['title'],
    emits: ['enlarge-text'], // 声明组件发出的事件
    template: `
      <div class="blog-post">
        <h3>{{ title }}</h3>
        <button @click="$emit('enlarge-text', 2)">
          放大文字
        </button>
      </div>
    `
  })
  
  app.mount('#app')
</script>

2.3 插槽分发内容

<div id="app">
  <!-- 使用插槽 -->
  <alert-box>
    发生了一个错误,请检查您的输入。
  </alert-box>
  
  <!-- 具名插槽 -->
  <base-layout>
    <template v-slot:header>
      <h1>页面标题</h1>
    </template>
    
    <template v-slot:default>
      <p>页面主体内容</p>
    </template>
    
    <template v-slot:footer>
      <p>页面底部版权信息</p>
    </template>
  </base-layout>
  
  <!-- 作用域插槽 -->
  <user-list :users="users">
    <template v-slot:user="{ user, index }">
      <div class="user-item">
        <span>{{ index + 1 }}. {{ user.name }}</span>
        <span v-if="user.online" class="online">在线</span>
      </div>
    </template>
  </user-list>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        users: [
          { id: 1, name: '张三', online: true },
          { id: 2, name: '李四', online: false },
          { id: 3, name: '王五', online: true }
        ]
      }
    }
  })
  
  // 基本插槽
  app.component('alert-box', {
    template: `
      <div class="alert-box">
        <strong>提示!</strong>
        <slot></slot>
      </div>
    `
  })
  
  // 具名插槽
  app.component('base-layout', {
    template: `
      <div class="container">
        <header>
          <slot name="header"></slot>
        </header>
        <main>
          <slot></slot>
        </main>
        <footer>
          <slot name="footer"></slot>
        </footer>
      </div>
    `
  })
  
  // 作用域插槽
  app.component('user-list', {
    props: ['users'],
    template: `
      <ul>
        <li v-for="(user, index) in users" :key="user.id">
          <slot name="user" :user="user" :index="index"></slot>
        </li>
      </ul>
    `
  })
  
  app.mount('#app')
</script>

3. 组件注册与组织

3.1 组件命名约定

// 组件命名约定

// 1. kebab-case (短横线分隔命名)
app.component('my-component', { /* ... */ })
// 使用: <my-component></my-component>

// 2. PascalCase (大驼峰命名)
app.component('MyComponent', { /* ... */ })
// 使用: <MyComponent></MyComponent> 或 <my-component></my-component>

3.2 模块化组件系统

// 文件: components/ButtonCounter.js
export default {
  name: 'ButtonCounter',
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      你点击了我 {{ count }} 次
    </button>
  `
}

// 文件: components/BlogPost.js
export default {
  name: 'BlogPost',
  props: ['title'],
  template: `
    <div class="blog-post">
      <h3>{{ title }}</h3>
      <slot></slot>
    </div>
  `
}

// 文件: main.js
import { createApp } from 'vue'
import ButtonCounter from './components/ButtonCounter.js'
import BlogPost from './components/BlogPost.js'

const app = createApp({
  components: {
    ButtonCounter,
    BlogPost
  }
})

app.mount('#app')

3.3 动态组件

<div id="app">
  <!-- 使用 component 元素和 is 特性实现动态组件 -->
  <button
    v-for="tab in tabs"
    :key="tab"
    @click="currentTab = tab"
    :class="{ active: currentTab === tab }"
  >
    {{ tab }}
  </button>
  
  <component :is="currentTabComponent" class="tab"></component>
  
  <!-- 使用 keep-alive 保持组件状态 -->
  <keep-alive>
    <component :is="currentTabComponent" class="tab"></component>
  </keep-alive>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        currentTab: 'Home',
        tabs: ['Home', 'Posts', 'Archive']
      }
    },
    computed: {
      currentTabComponent() {
        return 'tab-' + this.currentTab.toLowerCase()
      }
    }
  })
  
  app.component('tab-home', {
    template: `<div>首页内容</div>`
  })
  
  app.component('tab-posts', {
    data() {
      return {
        posts: [
          { id: 1, title: '文章一' },
          { id: 2, title: '文章二' }
        ]
      }
    },
    template: `
      <div>
        <h3>文章列表</h3>
        <ul>
          <li v-for="post in posts" :key="post.id">
            {{ post.title }}
          </li>
        </ul>
      </div>
    `
  })
  
  app.component('tab-archive', {
    template: `<div>归档内容</div>`
  })
  
  app.mount('#app')
</script>

4. 组件高级模式

4.1 组件递归

<div id="app">
  <tree-folder :folder="rootFolder"></tree-folder>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        rootFolder: {
          name: '项目文件',
          children: [
            {
              name: 'src',
              children: [
                { name: 'components', children: [
                  { name: 'App.vue' },
                  { name: 'Button.vue' }
                ] },
                { name: 'main.js' }
              ]
            },
            { name: 'public', children: [
              { name: 'index.html' },
              { name: 'favicon.ico' }
            ] },
            { name: 'package.json' }
          ]
        }
      }
    }
  })
  
  app.component('tree-folder', {
    props: ['folder'],
    template: `
      <div class="folder">
        <div class="folder-name" @click="toggle">
          <span class="icon">{{ isOpen ? '📂' : '📁' }}</span>
          {{ folder.name }}
        </div>
        <div class="folder-contents" v-if="isOpen && folder.children">
          <tree-folder 
            v-for="child in folder.children" 
            :key="child.name"
            :folder="child"
            v-if="child.children"
          ></tree-folder>
          <div class="file" v-else>
            <span class="icon">📄</span>
            {{ child.name }}
          </div>
        </div>
      </div>
    `,
    data() {
      return {
        isOpen: false
      }
    },
    methods: {
      toggle() {
        this.isOpen = !this.isOpen
      }
    }
  })
  
  app.mount('#app')
</script>

4.2 依赖注入

<div id="app">
  <parent-component></parent-component>
</div>

<script>
  const app = Vue.createApp({})
  
  app.component('parent-component', {
    // 提供数据给后代组件
    provide() {
      return {
        theme: 'dark',
        user: this.user
      }
    },
    data() {
      return {
        user: { name: '张三', role: 'admin' }
      }
    },
    template: `
      <div class="parent">
        <h2>父组件</h2>
        <child-component></child-component>
      </div>
    `
  })
  
  app.component('child-component', {
    template: `
      <div class="child">
        <h3>子组件</h3>
        <grand-child-component></grand-child-component>
      </div>
    `
  })
  
  app.component('grand-child-component', {
    // 注入祖先组件提供的数据
    inject: ['theme', 'user'],
    template: `
      <div class="grand-child" :class="theme">
        <h4>孙组件</h4>
        <p>主题: {{ theme }}</p>
        <p>用户: {{ user.name }} ({{ user.role }})</p>
      </div>
    `
  })
  
  app.mount('#app')
</script>

4.3 异步组件

// 基本异步组件
const AsyncComponent = Vue.defineAsyncComponent(() => {
  return new Promise((resolve) => {
    // 模拟异步加载
    setTimeout(() => {
      resolve({
        template: '<div>异步加载的组件</div>'
      })
    }, 1000)
  })
})

// 带选项的异步组件
const AsyncComponentWithOptions = Vue.defineAsyncComponent({
  loader: () => import('./components/HeavyComponent.js'),
  loadingComponent: {
    template: '<div>加载中...</div>'
  },
  errorComponent: {
    template: '<div>加载失败!</div>'
  },
  delay: 200,
  timeout: 3000
})

const app = Vue.createApp({
  components: {
    AsyncComponent,
    AsyncComponentWithOptions
  },
  template: `
    <div>
      <h2>异步组件示例</h2>
      <AsyncComponent />
      <AsyncComponentWithOptions />
    </div>
  `
})

5. 组件通信进阶

5.1 Provide/Inject 响应式

<div id="app">
  <theme-provider></theme-provider>
</div>

<script>
  const { ref, provide, inject } = Vue
  
  const app = Vue.createApp({})
  
  app.component('theme-provider', {
    setup() {
      // 创建响应式数据
      const theme = ref('light')
      
      // 提供响应式数据
      provide('theme', theme)
      
      // 提供切换主题的方法
      const toggleTheme = () => {
        theme.value = theme.value === 'light' ? 'dark' : 'light'
      }
      provide('toggleTheme', toggleTheme)
      
      return {
        theme,
        toggleTheme
      }
    },
    template: `
      <div :class="theme">
        <h2>当前主题: {{ theme }}</h2>
        <button @click="toggleTheme">切换主题</button>
        <theme-consumer></theme-consumer>
      </div>
    `
  })
  
  app.component('theme-consumer', {
    setup() {
      // 注入响应式数据
      const theme = inject('theme')
      const toggleTheme = inject('toggleTheme')
      
      return {
        theme,
        toggleTheme
      }
    },
    template: `
      <div class="consumer">
        <h3>消费组件</h3>
        <p>当前主题: {{ theme }}</p>
        <button @click="toggleTheme">从子组件切换主题</button>
        <deep-consumer></deep-consumer>
      </div>
    `
  })
  
  app.component('deep-consumer', {
    setup() {
      const theme = inject('theme')
      
      return { theme }
    },
    template: `
      <div class="deep-consumer">
        <h4>深层消费组件</h4>
        <p>当前主题: {{ theme }}</p>
      </div>
    `
  })
  
  app.mount('#app')
</script>

5.2 事件总线

// Vue 3 中的事件总线实现
import { createApp } from 'vue'
import mitt from 'mitt'

const app = createApp({})

// 创建事件总线
const emitter = mitt()

// 将事件总线添加到全局属性
app.config.globalProperties.$bus = emitter

// 组件 A
app.component('component-a', {
  template: `
    <div>
      <h3>组件 A</h3>
      <button @click="sendMessage">发送消息</button>
    </div>
  `,
  methods: {
    sendMessage() {
      // 发送事件
      this.$bus.emit('message', {
        text: '来自组件A的消息',
        time: new Date()
      })
    }
  }
})

// 组件 B
app.component('component-b', {
  template: `
    <div>
      <h3>组件 B</h3>
      <p v-if="message">收到消息: {{ message.text }} ({{ formatTime }})</p>
    </div>
  `,
  data() {
    return {
      message: null
    }
  },
  computed: {
    formatTime() {
      return this.message ? this.message.time.toLocaleTimeString() : ''
    }
  },
  mounted() {
    // 监听事件
    this.$bus.on('message', (data) => {
      this.message = data
    })
  },
  beforeUnmount() {
    // 移除事件监听
    this.$bus.off('message')
  }
})

app.mount('#app')

6. 组件复用技术

6.1 混入 (Mixins)

// 定义混入对象
const shareMixin = {
  data() {
    return {
      sharedData: '共享数据'
    }
  },
  created() {
    console.log('混入对象的钩子被调用')
  },
  methods: {
    sharedMethod() {
      console.log('这是一个共享方法')
    }
  }
}

// 使用混入
const app = Vue.createApp({
  mixins: [shareMixin],
  created() {
    console.log('组件钩子被调用')
    console.log('共享数据:', this.sharedData)
  }
})

// 全局混入
app.mixin({
  created() {
    console.log('全局混入的钩子被调用')
  }
})

// 组件中使用混入
app.component('custom-component', {
  mixins: [shareMixin],
  template: `
    <div>
      <h3>使用混入的组件</h3>
      <p>{{ sharedData }}</p>
      <button @click="sharedMethod">调用共享方法</button>
    </div>
  `
})

6.2 组合式 API (Composition API)

<div id="app">
  <user-profile></user-profile>
</div>

<script>
  const { ref, reactive, computed, onMounted, watch } = Vue
  
  // 可复用的组合函数
  function useUserData() {
    const user = reactive({
      name: '',
      email: '',
      role: ''
    })
    
    const isAdmin = computed(() => user.role === 'admin')
    
    const fetchUserData = async (id) => {
      // 模拟API调用
      return new Promise((resolve) => {
        setTimeout(() => {
          Object.assign(user, {
            name: '张三',
            email: 'zhangsan@example.com',
            role: 'admin'
          })
          resolve(user)
        }, 1000)
      })
    }
    
    return {
      user,
      isAdmin,
      fetchUserData
    }
  }
  
  // 另一个可复用的组合函数
  function useWindowSize() {
    const windowSize = reactive({
      width: window.innerWidth,
      height: window.innerHeight
    })
    
    const updateSize = () => {
      windowSize.width = window.innerWidth
      windowSize.height = window.innerHeight
    }
    
    onMounted(() => {
      window.addEventListener('resize', updateSize)
    })
    
    // 在组件卸载时移除事件监听
    onUnmounted(() => {
      window.removeEventListener('resize', updateSize)
    })
    
    return windowSize
  }
  
  const app = Vue.createApp({})
  
  app.component('user-profile', {
    setup() {
      // 使用组合函数
      const { user, isAdmin, fetchUserData } = useUserData()
      const windowSize = useWindowSize()
      
      // 组件特有的状态
      const loading = ref(true)
      
      // 生命周期钩子
      onMounted(async () => {
        await fetchUserData()
        loading.value = false
      })
      
      // 监听属性变化
      watch(() => user.role, (newRole) => {
        console.log(`用户角色变更为: ${newRole}`)
      })
      
      // 返回模板需要的内容
      return {
        user,
        isAdmin,
        loading,
        windowSize
      }
    },
    template: `
      <div class="user-profile">
        <div v-if="loading">加载中...</div>
        <div v-else>
          <h2>用户资料</h2>
          <p>姓名: {{ user.name }}</p>
          <p>邮箱: {{ user.email }}</p>
          <p>角色: {{ user.role }}</p>
          <p v-if="isAdmin">管理员特权已启用</p>
          <p>窗口尺寸: {{ windowSize.width }} x {{ windowSize.height }}</p>
        </div>
      </div>
    `
  })
  
  app.mount('#app')
</script>

7. 组件性能优化

7.1 组件缓存

<div id="app">
  <button 
    v-for="tab in tabs" 
    :key="tab.id"
    @click="currentTab = tab.id"
    :class="{ active: currentTab === tab.id }"
  >
    {{ tab.name }}
  </button>
  
  <!-- 使用 keep-alive 缓存组件状态 -->
  <keep-alive>
    <component :is="currentTabComponent"></component>
  </keep-alive>
  
  <!-- 带有包含/排除规则的 keep-alive -->
  <keep-alive :include="['a', 'b']" :exclude="['c']" :max="10">
    <component :is="currentTabComponent"></component>
  </keep-alive>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        tabs: [
          { id: 'home', name: '首页' },
          { id: 'posts', name: '文章' },
          { id: 'profile', name: '个人资料' }
        ],
        currentTab: 'home'
      }
    },
    computed: {
      currentTabComponent() {
        return 'tab-' + this.currentTab
      }
    }
  })
  
  app.component('tab-home', {
    template: `<div>首页内容</div>`,
    // 当组件在 keep-alive 内被切换时调用
    activated() {
      console.log('Home 组件被激活')
    },
    // 当组件在 keep-alive 内被切换出去时调用
    deactivated() {
      console.log('Home 组件被停用')
    }
  })
  
  app.component('tab-posts', {
    data() {
      return {
        searchQuery: '',
        posts: []
      }
    },
    template: `
      <div>
        <input v-model="searchQuery" placeholder="搜索文章...">
        <p>当前有 {{ posts.length }} 篇文章</p>
      </div>
    `,
    // 模拟数据加载
    created() {
      console.log('Posts 组件被创建,加载数据')
      // 这里的数据加载只会执行一次,因为组件被缓存
      setTimeout(() => {
        this.posts = Array(20).fill().map((_, i) => ({ id: i, title: `文章 ${i}` }))
      }, 1000)
    },
    activated() {
      console.log('Posts 组件被激活')
    }
  })
  
  app.component('tab-profile', {
    template: `<div>个人资料内容</div>`
  })
  
  app.mount('#app')
</script>

7.2 动态组件与异步组件结合

import { defineAsyncComponent } from 'vue'

const app = Vue.createApp({
  data() {
    return {
      currentTab: 'Home'
    }
  },
  computed: {
    currentTabComponent() {
      return `tab-${this.currentTab.toLowerCase()}`
    }
  }
})

// 注册异步组件
app.component('tab-home', {
  template: `<div>首页内容 (立即加载)</div>`
})

// 较重的组件使用异步加载
app.component('tab-posts', defineAsyncComponent({
  loader: () => {
    return new Promise((resolve) => {
      // 模拟网络请求延迟
      setTimeout(() => {
        resolve({
          template: `
            <div>
              <h3>文章列表</h3>
              <ul>
                <li v-for="i in 100" :key="i">文章 {{ i }}</li>
              </ul>
            </div>
          `,
          created() {
            console.log('Posts 组件被创建')
          }
        })
      }, 1000)
    })
  },
  loadingComponent: {
    template: `<div class="loading">加载中...</div>`
  },
  errorComponent: {
    template: `<div class="error">加载失败!</div>`
  },
  delay: 200,
  timeout: 5000
}))

app.component('tab-settings', defineAsyncComponent(() => {
  return import('./components/Settings.js')
}))

// 在模板中使用
// <keep-alive>
//   <component :is="currentTabComponent"></component>
// </keep-alive>

7.3 函数式组件

// Vue 3 中的函数式组件
const FunctionalButton = (props, { slots, emit, attrs }) => {
  return Vue.h('button', {
    ...attrs,
    class: ['btn', props.type ? `btn-${props.type}` : ''],
    onClick: () => emit('click')
  }, slots.default ? slots.default() : '按钮')
}

FunctionalButton.props = {
  type: String
}

// 使用函数式组件
const app = Vue.createApp({
  components: {
    FunctionalButton
  },
  template: `
    <div>
      <functional-button @click="handleClick">
        点击我
      </functional-button>
      <functional-button type="primary" @click="handleClick">
        主要按钮
      </functional-button>
    </div>
  `,
  methods: {
    handleClick() {
      console.log('按钮被点击')
    }
  }
})

8. 组件设计模式

8.1 容器/展示组件模式

// 展示组件 - 只负责渲染,不含业务逻辑
app.component('user-card', {
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  template: `
    <div class="user-card">
      <img :src="user.avatar" :alt="user.name">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
      <button @click="$emit('edit')">编辑</button>
    </div>
  `
})

// 容器组件 - 负责数据获取和业务逻辑
app.component('user-container', {
  data() {
    return {
      user: null,
      loading: true,
      error: null
    }
  },
  created() {
    this.fetchUser()
  },
  methods: {
    async fetchUser() {
      this.loading = true
      try {

8.2 渲染函数与JSX

// 使用渲染函数
app.component('anchored-heading', {
  props: {
    level: {
      type: Number,
      required: true
    }
  },
  render() {
    // 创建标题元素
    return Vue.h(
      'h' + this.level, // 标签名
      {}, // props/attributes
      this.$slots.default() // 子节点
    )
  }
})

// 使用JSX (需要配置JSX插件)
app.component('jsx-example', {
  data() {
    return {
      items: ['苹果', '香蕉', '橙子']
    }
  },
  render() {
    return (
      <div>
        <h2>水果列表</h2>
        <ul>
          {this.items.map(item => (
            <li key={item}>{item}</li>
          ))}
        </ul>
        {this.items.length === 0 ? (
          <p>没有水果</p>
        ) : (
          <p>共有 {this.items.length} 种水果</p>
        )}
      </div>
    )
  }
})

8.3 高阶组件模式

// 高阶组件工厂函数
function withLoading(Component) {
  return {
    props: {
      loading: {
        type: Boolean,
        default: false
      },
      ...Component.props
    },
    render(ctx) {
      return ctx.loading
        ? Vue.h('div', { class: 'loading-indicator' }, '加载中...')
        : Vue.h(Component, ctx.$props, ctx.$slots)
    }
  }
}

// 基础组件
const UserList = {
  props: {
    users: Array
  },
  template: `
    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  `
}

// 创建增强版组件
const UserListWithLoading = withLoading(UserList)

// 使用增强版组件
const app = Vue.createApp({
  components: {
    UserListWithLoading
  },
  data() {
    return {
      users: [],
      loading: true
    }
  },
  created() {
    // 模拟数据加载
    setTimeout(() => {
      this.users = [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' },
        { id: 3, name: '王五' }
      ]
      this.loading = false
    }, 2000)
  },
  template: `
    <div>
      <h2>用户列表</h2>
      <user-list-with-loading 
        :loading="loading"
        :users="users"
      ></user-list-with-loading>
    </div>
  `
})

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guiat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值