vue3新拟态组件库开发流程——button组件源码

首先写最基础的button组件

<script setup>
const props = defineProps({})
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
</script>

<template>
    <button 
        class="neumorphism"
        @click="handleClick"
    >
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism {
    width: 200px;
    height: 75px;
    border: none;
    border-radius: 20px;
    background-color: #e6e6e6;
    box-shadow: 5px 5px 10px #bbbbbb, -5px -5px 10px #ffffff; 
}
.neumorphism:active {
    box-shadow: inset 5px 5px 10px #bbbbbb, inset -5px -5px 10px #ffffff;
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: #a6a6a6;
    font-size: 24px;
    text-align: center;
    line-height: 75px;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
</style>

第二步,需要颜色自定义,这里可以用prop传入,我这里采用的是跟随全局主题颜色,全局主题颜色是用户在网页上根据调色板选出的,js部分用provide/inject取出,css部分用–main-color取出。
于是这里就需要根据主题色,生产出.neumorphism的样式。相关代码如下:

// hexColor.js
export const hexColor = (hex, lum) => {
  // validate hex string
  hex = String(hex).replace(/[^0-9a-f]/gi, '')
  if (hex.length < 6) {
    hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  }
  lum = lum || 0

  // convert to decimal and change luminosity
  let rgb = '#',
    c,
    i
  for (i = 0; i < 3; i++) {
    c = parseInt(hex.substr(i * 2, 2), 16)
    c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16)
    rgb += ('00' + c).substr(c.length)
  }

  return rgb
}
export const getContrast = (hex) => {
  const r = parseInt(hex.substr(1, 2), 16),
    g = parseInt(hex.substr(3, 2), 16),
    b = parseInt(hex.substr(5, 2), 16),
    yiq = (r * 299 + g * 587 + b * 114) / 1000
  return yiq >= 128 ? '#001f3f' : '#F6F5F7'
}
<script setup>
import { inject, computed } from 'vue'
import { hexColor } from '@/sanorin/utils/hexColor'
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
const mainColor = inject('mainColor')
let leftColor =  computed(() => hexColor(mainColor.value,-0.15))
let rightColor = computed(() => hexColor(mainColor.value,0.15))
</script>

<template>
    <button 
        class="neumorphism"
        :style="{ '--left-color':leftColor,'--right-color':rightColor }"
        @click="handleClick"
    >
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism {
    width: 200px;
    height: 75px;
    border: none;
    border-radius: 20px;
    background-color: var(--main-color);
    box-shadow:  10px 10px 30px var(--left-color),-10px -10px 30px var(--right-color);
}
.neumorphism:active {
    box-shadow: inset 10px 10px 30px var(--left-color), inset -10px -10px 30px var(--right-color);
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: var(--text-color);
    font-size: 24px;
    text-align: center;
    line-height: 75px;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
</style>

在这里插入图片描述
------------------------------------------------分割线-------------------------------------------

这样雏形就有了,接下来处理props,首先加一个type类型,有四种类型,其中第四种我写到了按下去的样式,所以也就是前三种,我将其命名为:plain,down,up。在这里插入图片描述

<script setup>
import { inject, computed } from 'vue'
import { hexColor } from '@/sanorin/utils/hexColor'
const props = defineProps({
  type: {
    type: String,
    default: 'plain',
  }
})
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
const mainColor = inject('mainColor')
let lightColor =  computed(() => [hexColor(mainColor.value,-0.15),hexColor(mainColor.value,0.15)])
let linearColor = computed(() => ((e) => ({
  'down': () => [hexColor(mainColor.value,-0.1),hexColor(mainColor.value,0.07)],
  'up': () => [hexColor(mainColor.value,0.07),hexColor(mainColor.value,-0.1)],
})[e] || (() => [mainColor.value,mainColor.value]))(props.type)())

</script>

<template>
    <button 
        class="neumorphism"
        :style="{ '--left-color':lightColor[0],'--right-color':lightColor[1], '--linear-left-color':linearColor[0], '--linear-right-color':linearColor[1] }"
        @click="handleClick"
    >
    {{linearColor}}
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism {
    width: 200px;
    height: 75px;
    border: none;
    border-radius: 20px;
    background: linear-gradient(145deg, var(--linear-left-color), var(--linear-right-color));
    box-shadow:  10px 10px 30px var(--left-color),-10px -10px 30px var(--right-color);
}
.neumorphism:active {
    box-shadow: inset 10px 10px 30px var(--left-color), inset -10px -10px 30px var(--right-color);
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: var(--text-color);
    font-size: 24px;
    text-align: center;
    line-height: 75px;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
</style>

1接下来控制光照的角度,如果允许入参是360°的话,还要用公式来计算box-shadow的值,太麻烦,懒得写了,因此只支持4个角好了。即光从左上角右上角左下角右下角照过来。我将其命名为:left-up,right-up,left-down,right-down。
2再控制物体的高度,我将其命名为:distance
3在控制的柔和程度,我将其命名为:blur

<script setup>
import { ref, inject, computed } from 'vue'
import { hexColor } from '@/sanorin/utils/hexColor'
const intensity = 0.15 // 这个参数影响颜色的变化率,模拟物体的反光程度,暂时没暴露给外部,因为仅在0.1到0.3之间模拟的比较真实,如果要暴露出去,可以设定0.1为0%,0.3为100%。
console.log(-intensity/3*2)
console.log(intensity/2)
const props = defineProps({
    type: { // 按钮凹凸参数
        type: String,
        default: 'plain',
    },
    light: { // 光射来的方向参数
        type: String,
        default: 'left-up',
    },
    distance: { // 物体的高度参数
        type: Number,
        default: 10,
    },
    blur: { // 光的柔和程度参数
        type: Number,
        default: 30,
    }
})
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
let mainColor = inject('mainColor')
let linearAngle = computed(() => ((e) => ({ // 控制linear的角度,模拟光射来的方向
    'right-up': () => '225deg',
    'left-down': () => '45deg',
    'rihgt-down': () => '315deg',
})[e] || (() => '145deg'))(props.light)())
let linearColor = computed(() => ((e) => ({ // 控制linear的颜色,模拟物体的顶板凹凸状态
    'up': () => [hexColor(mainColor.value,-intensity/3*2),hexColor(mainColor.value,intensity/2)],
    'down': () => [hexColor(mainColor.value,intensity/2),hexColor(mainColor.value,-intensity/3*2)],
})[e] || (() => [mainColor.value,mainColor.value]))(props.type)())
let lightColor =  computed(() => [hexColor(mainColor.value,-intensity),hexColor(mainColor.value,intensity)]) // 控制shadow的颜色,模拟物体的阴影深浅
let shadowSize = computed(() => new Array(4).fill(props.distance)) // 控制shadow的大小,模拟物体的高度
let lightBeam = computed(() => ((e) => ({ // 控制shadow的正负,模拟不同方向射来的光
    'right-up': () => [-1, 1, 1, -1],
    'left-down': () => [1, -1, -1, 1],
    'rihgt-down': () => [-1, -1, 1, 1],
})[e] || (() => [1, 1, -1, -1]))(props.light)())
let boxShadow = computed(() => new Array(4).fill().reduce((t,v,i) => { // 结合shadow的大小和正负,生成shadow
     t.push(shadowSize.value[i] * lightBeam.value[i] + 'px')
     return t
},[]))
let boxBlur = computed(() => props.blur + 'px') // 控制shadow的blur,模拟不同柔度的光
</script>

<template>
    <button 
        class="neumorphism"
        :style="{ 
            '--left-color':lightColor[0], '--right-color':lightColor[1], '--linear-left-color':linearColor[0], '--linear-right-color':linearColor[1], '--linear-angle':linearAngle,
            '--shadow-ah':boxShadow[0], '--shadow-av':boxShadow[1], '--shadow-bh':boxShadow[2], '--shadow-bv':boxShadow[3], '--shadow-blur':boxBlur
         }"
        @click="handleClick"
    >
    {{linearColor}}
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism {
    width: 200px;
    height: 75px;
    border: none;
    border-radius: 20px;
    background: linear-gradient(var(--linear-angle), var(--linear-left-color), var(--linear-right-color));
    box-shadow:  var(--shadow-ah) var(--shadow-av) 30px var(--left-color),var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:active {
    box-shadow: inset var(--shadow-ah) var(--shadow-av) var(--shadow-blur) var(--left-color), inset var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: var(--text-color);
    font-size: 24px;
    text-align: center;
    line-height: 75px;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
.neumorphism + .neumorphism{
    margin-left: 30px;
}
</style>

接下来写hover时候的变化,这里使用animate,鼠标从左边进入按钮就把按钮往右边挤,鼠标从下方进去就把按钮往上挤,效果图如下:在这里插入图片描述

详细计算思路看下一篇文章 链接待补充

接下来增加圆角按钮样式,很简单不展示代码了。
接下来增加icon支持

<script setup>
import { ref, reactive, inject, computed, onMounted, onUnmounted } from 'vue'
import { hexColor } from '@/sanorin/utils/hexColor'
const intensity = 0.15 // 这个参数影响颜色的变化率,模拟物体的反光程度,暂时没暴露给外部,因为仅在0.1到0.3之间模拟的比较真实,如果要暴露出去,可以设定0.1为0%,0.3为100%。
const props = defineProps({
    type: { // 按钮凹凸参数
        type: String,
        default: 'plain',
    },
    light: { // 光射来的方向参数
        type: String,
        default: 'left-up',
    },
    distance: { // 物体的高度参数
        type: Number,
        default: 10,
    },
    blur: { // 光的柔和程度参数
        type: Number,
        default: 30,
    },
    animate: { // hover时候移动的距离
        type: Number,
        default: 10,
    },
    preIcon: {
        type: String,
        default: null,
    },
    postIcon: {
        type: String,
        default: null,
    }
})
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
let mainColor = inject('mainColor')
let linearAngle = computed(() => ((e) => ({ // 控制linear的角度,模拟光射来的方向
    'right-up': () => '225deg',
    'left-down': () => '45deg',
    'rihgt-down': () => '315deg',
})[e] || (() => '145deg'))(props.light)())
let linearColor = computed(() => ((e) => ({ // 控制linear的颜色,模拟物体的顶板凹凸状态
    'up': () => [hexColor(mainColor.value,-intensity/3*2),hexColor(mainColor.value,intensity/2)],
    'down': () => [hexColor(mainColor.value,intensity/2),hexColor(mainColor.value,-intensity/3*2)],
})[e] || (() => [mainColor.value,mainColor.value]))(props.type)())
let lightColor =  computed(() => [hexColor(mainColor.value,-intensity),hexColor(mainColor.value,intensity)]) // 控制shadow的颜色,模拟物体的阴影深浅
let shadowSize = computed(() => new Array(4).fill(props.distance)) // 控制shadow的大小,模拟物体的高度
let lightBeam = computed(() => ((e) => ({ // 控制shadow的正负,模拟不同方向射来的光
    'right-up': () => [-1, 1, 1, -1],
    'left-down': () => [1, -1, -1, 1],
    'rihgt-down': () => [-1, -1, 1, 1],
})[e] || (() => [1, 1, -1, -1]))(props.light)())
let boxShadow = computed(() => new Array(4).fill().reduce((t,v,i) => { // 结合shadow的大小和正负,生成shadow
     t.push(shadowSize.value[i] * lightBeam.value[i] + 'px')
     return t
},[]))
let boxBlur = computed(() => props.blur + 'px') // 控制shadow的blur,模拟不同柔度的光
// 定义按钮位置
const btn = ref() // 与html中ref=""对应,定位dom元素
const btnState = reactive({x:0,y:0})
// 定义[鼠标距离按钮的]相对位置
const mouseBtnRange = reactive({x:0,y:0})
const update = e=>{
    let x = e.pageX - btnState.x // [鼠标距离按钮的相对位置的]两条直角边的边长, e.pageX是鼠标绝对位置
    let y = e.pageY - btnState.y
    mouseBtnRange.x = -(props.animate * x / Math.sqrt(x**2 + y**2)).toFixed(2) + 'px' // 当斜边为animate时,等比缩小,求缩小后的[鼠标距离按钮的相对位置的]两条直角边边长。
    mouseBtnRange.y = -(props.animate * y / Math.sqrt(x**2 + y**2)).toFixed(2) + 'px'
}
onMounted(() => {
    // 计算按钮位置
    let btnDom = btn.value.getBoundingClientRect()
    btnState.x = btnDom.x + btnDom.width/2
    btnState.y = btnDom.y + btnDom.height/2
    // 监控鼠标位置
    window.addEventListener('mousemove',update)
}) 
onUnmounted(()=>{
    // 卸载监控鼠标位置
	window.removeEventListener('mousemove',update);
})
</script>

<template>
    <button 
        ref="btn"
        class="neumorphism"
        :style="{ 
            '--left-color':lightColor[0], '--right-color':lightColor[1], '--linear-left-color':linearColor[0], '--linear-right-color':linearColor[1], '--linear-angle':linearAngle,
            '--shadow-ah':boxShadow[0], '--shadow-av':boxShadow[1], '--shadow-bh':boxShadow[2], '--shadow-bv':boxShadow[3], '--shadow-blur':boxBlur,
            '--hover-sin':mouseBtnRange.x, '--hover-cos':mouseBtnRange.y,
         }"
        @click="handleClick"
    >
        <i :class="[preIcon, 'iconfont']" v-if="preIcon"></i>
        <span v-if="$slots.default"><slot></slot></span>
        <i :class="[postIcon, 'iconfont']" v-if="postIcon"></i>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism.round{
    border-radius: 20px;
}
.neumorphism {
    min-width: 40px;
    height: 40px;
    border: none;
    border-radius: 10px;
    background: linear-gradient(var(--linear-angle), var(--linear-left-color), var(--linear-right-color));
    box-shadow:  var(--shadow-ah) var(--shadow-av) 30px var(--left-color),var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:active {
    box-shadow: inset var(--shadow-ah) var(--shadow-av) var(--shadow-blur) var(--left-color), inset var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:hover{
    transform: translate(var(--hover-sin),var(--hover-cos));
    transition: all .2s ease;
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: var(--text-color);
    font-size: 14px;
    text-align: center;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
.neumorphism + .neumorphism{
    margin-left: 30px;
}
</style>

最后就是这样,还有一个loading功能和disable功能没写,以后再补充。
现在提取一下公共代码出来,因为假设我接下来写个table组件,其中有一部分代码是可以复用的,总不能复制过去吧。
首先判断一下是阴影那部分能复用,怎么提取出来呢?
假设我提取到总页面,写一个方法,在总页面时候加载入,生成对应的css,这样也可以,但是整个页面就必须是统一的阴影样式,不太合理。
假设我提取到一个基底组件,然后每次写新组件的时候都用基底组件包裹,还要在自定义组件中接prop,再传入基底,要引入组件,还要在自定义组件中多写代码,没解决问题。

--------------------------接下来都是优化部分-------------------------

只能写一个mixin,vue3中mixin建议用函数式编程代替,我这边修改一下代码,使得更合适函数式编程,修改完后再提取出。
提取后的代码展示:

// neumorphism.js
import { defineProps, inject, computed } from 'vue'
import { hexColor } from '@/sanorin/utils/hexColor'
export const useProp = {
  type: { // 按钮凹凸参数
    type: String,
    default: 'plain',
  },
  light: { // 光射来的方向参数
    type: String,
    default: 'left-up',
  },
  distance: { // 物体的高度参数
    type: Number,
    default: 10,
  },
  blur: { // 光的柔和程度参数
    type: Number,
    default: 30,
  }
}
export function useNeumorphism(props) {
  const intensity = 0.15 // 这个参数影响颜色的变化率,模拟物体的反光程度,暂时没暴露给外部,因为仅在0.1到0.3之间模拟的比较真实,如果要暴露出去,可以设定0.1为0%,0.3为100%。
  console.log(props)
  let mainColor = inject('mainColor')
  let linearAngle = computed(() => ((e) => ({ // 控制linear的角度,模拟光射来的方向
      'right-up': () => '225deg',
      'left-down': () => '45deg',
      'rihgt-down': () => '315deg',
  })[e] || (() => '145deg'))(props.light)())
  let linearColor = computed(() => ((e) => ({ // 控制linear的颜色,模拟物体的顶板凹凸状态
      'up': () => [hexColor(mainColor.value,-intensity/3*2),hexColor(mainColor.value,intensity/2)],
      'down': () => [hexColor(mainColor.value,intensity/2),hexColor(mainColor.value,-intensity/3*2)],
  })[e] || (() => [mainColor.value,mainColor.value]))(props.type)())
  let lightColor =  computed(() => [hexColor(mainColor.value,-intensity),hexColor(mainColor.value,intensity)]) // 控制shadow的颜色,模拟物体的阴影深浅
  let shadowSize = computed(() => new Array(4).fill(props.distance)) // 控制shadow的大小,模拟物体的高度
  let lightBeam = computed(() => ((e) => ({ // 控制shadow的正负,模拟不同方向射来的光
      'right-up': () => [-1, 1, 1, -1],
      'left-down': () => [1, -1, -1, 1],
      'rihgt-down': () => [-1, -1, 1, 1],
  })[e] || (() => [1, 1, -1, -1]))(props.light)())
  let boxShadow = computed(() => new Array(4).fill().reduce((t,v,i) => { // 结合shadow的大小和正负,生成shadow
      t.push(shadowSize.value[i] * lightBeam.value[i] + 'px')
      return t
  },[]))
  let boxBlur = computed(() => props.blur + 'px') // 控制shadow的blur,模拟不同柔度的光
  let baseStyleObject =  computed(() => ({ 
    '--left-color':lightColor.value[0], '--right-color':lightColor.value[1], '--linear-left-color':linearColor.value[0], '--linear-right-color':linearColor.value[1], '--linear-angle':linearAngle.value,
    '--shadow-ah':boxShadow.value[0], '--shadow-av':boxShadow.value[1], '--shadow-bh':boxShadow.value[2], '--shadow-bv':boxShadow.value[3], '--shadow-blur':boxBlur.value,
  }))
  return { baseStyleObject }
}

// button.vue
<script setup>
import { ref, reactive, inject, computed, onMounted, onUnmounted } from 'vue'
import { useProp, useNeumorphism } from '../mixin/neumorphism'
const props = defineProps({
    ...useProp,
    ...{
        animate: { // hover时候移动的距离
            type: Number,
            default: 10,
        },
        preIcon: {
            type: String,
            default: null,
        },
        postIcon: {
            type: String,
            default: null,
        }
    }
})
const { baseStyleObject } = useNeumorphism(props)
const emit = defineEmits(["click"])
const handleClick = (evt) => { emit("click", evt) }
// 定义按钮位置
const btn = ref() // 与html中ref=""对应,定位dom元素
const btnState = reactive({x:0,y:0})
// 定义[鼠标距离按钮的]相对位置
const mouseBtnRange = reactive({x:0,y:0})
const update = e=>{
    let x = e.pageX - btnState.x // [鼠标距离按钮的相对位置的]两条直角边的边长, e.pageX是鼠标绝对位置
    let y = e.pageY - btnState.y
    mouseBtnRange.x = -(props.animate * x / Math.sqrt(x**2 + y**2)).toFixed(2) + 'px' // 当斜边为animate时,等比缩小,求缩小后的[鼠标距离按钮的相对位置的]两条直角边边长。
    mouseBtnRange.y = -(props.animate * y / Math.sqrt(x**2 + y**2)).toFixed(2) + 'px'
}
onMounted(() => {
    // 计算按钮位置
    let btnDom = btn.value.getBoundingClientRect()
    btnState.x = btnDom.x + btnDom.width/2
    btnState.y = btnDom.y + btnDom.height/2
    // 监控鼠标位置
    window.addEventListener('mousemove',update)
}) 
onUnmounted(()=>{
    // 卸载监控鼠标位置
	window.removeEventListener('mousemove',update);
})
let styleObject =  computed(() => ({ 
   '--hover-sin':mouseBtnRange.x, '--hover-cos':mouseBtnRange.y,
}))
</script>

<template>
    <button 
        ref="btn"
        class="neumorphism"
        :style="{...baseStyleObject,...styleObject}"
        @click="handleClick"
    >
        <i :class="[preIcon, 'iconfont']" v-if="preIcon"></i>
        <span v-if="$slots.default"><slot></slot></span>
        <i :class="[postIcon, 'iconfont']" v-if="postIcon"></i>
    </button>
</template>

<script>
export default {
  name: 'sanorin-button',
}
</script>

<style>
.neumorphism.round{
    border-radius: 20px;
}
.neumorphism {
    min-width: 40px;
    height: 40px;
    border: none;
    border-radius: 10px;
    background: linear-gradient(var(--linear-angle), var(--linear-left-color), var(--linear-right-color));
    box-shadow:  var(--shadow-ah) var(--shadow-av) 30px var(--left-color),var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:active {
    box-shadow: inset var(--shadow-ah) var(--shadow-av) var(--shadow-blur) var(--left-color), inset var(--shadow-bh) var(--shadow-bv) var(--shadow-blur) var(--right-color);
}
.neumorphism:hover{
    transform: translate(var(--hover-sin),var(--hover-cos));
    transition: all .2s ease;
}
.neumorphism:focus {
    outline: none;
}
.neumorphism span {
    display: block;
    user-select: none;
    color: var(--text-color);
    font-size: 14px;
    text-align: center;
    transition: 0.1s;
}
.neumorphism:active span {
    transform: scale(0.95);
}
.neumorphism + .neumorphism{
    margin-left: 30px;
}
</style>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值