可视化大屏系列(1):基于 Vue3 + ECharts 打造智慧工地大屏: 项目初始化与响应式布局方案

可视化大屏系列(1):基于 Vue3 + ECharts 打造智慧工地大屏:项目初始化与响应式布局方案

在这里插入图片描述

文章目录:

  • 一、引言
  • 二、项目初始化
  • 三、多分辨率适配与响应式布局
  • 四、项目结构与目录规范
  • 五、ECharts 与 Vue3 集成
  • 六、数据动态更新与展示优化
  • 七、UI 样式与主题定制
  • 八、最佳实践与注意事项
  • 九、总结与展望

一、引言

在智慧工地管理系统中,大屏展示作为一个非常重要的功能模块,能够将工地实时的生产数据、设备状况、人员情况等信息直观地呈现给管理者。为了确保在不同设备上的流畅展示,选择合适的框架与技术栈至关重要。本文将通过基于 Vue3 + ECharts 的方案,详细讲解如何搭建一个可适配多分辨率、具有响应式设计的智慧工地大屏。

二、项目初始化

2.1 创建 Vue3 项目

在终端运行以下命令来初始化一个 Vue3 项目:

# 使用 Vue CLI 创建 Vue3 项目
vue create smart-site-dashboard
# 选择 Vue 3.x 版本
2.2 安装 ECharts

通过 npm 安装 ECharts 以便在项目中使用:

npm install echarts --save
2.3 安装其他必需的插件

安装 vue-router 用于页面路由管理,安装 pinia 用于状态管理:

npm install vue-router pinia --save
2.4 配置项目结构

确保目录结构清晰、模块化,方便管理与扩展:

src/
  assets/       # 静态资源,如图片、图标
  components/   # 公共组件
  views/        # 页面组件
  store/        # Pinia 状态管理
  router/       # 路由管理
  utils/        # 工具函数,如 API 请求
  assets/styles # 样式文件

三、多分辨率适配与响应式布局

3.1 响应式设计原则

响应式设计(Responsive Design)是一种使网页能够适应各种屏幕尺寸和设备类型的设计方法。通过灵活的布局和适配策略,响应式设计确保了网页在不同设备上都能拥有良好的用户体验。

响应式设计的基本原则:

  • 流式布局:元素的宽度和位置不应使用固定值,而应基于相对比例(如百分比、vw、vh)来设置,使其能够根据屏幕宽度自动调整。
  • 弹性盒子(Flexbox):通过 Flexbox 布局模型,能够轻松实现自适应的排版,保证在不同屏幕尺寸下内容能自适应地布局。
  • CSS 媒体查询(Media Queries):通过媒体查询来根据不同设备的特性(如屏幕宽度、分辨率)来应用不同的样式。
3.2 使用 Element Plus 实现响应式布局

Element Plus 是一款基于 Vue 3 的 UI 框架,提供了强大的响应式布局支持。通过使用 el-rowel-col 组件,您可以灵活地实现各种屏幕尺寸下的响应式布局。以下是一个使用 Element Plus 的栅格系统来创建响应式布局的示例。

示例:

<template>
  <el-row :gutter="20">
    <el-col :span="12" :xs="24" :sm="12" :md="8">
      <el-card>
        <div class="card-content">内容 1</div>
      </el-card>
    </el-col>
    <el-col :span="12" :xs="24" :sm="12" :md="8">
      <el-card>
        <div class="card-content">内容 2</div>
      </el-card>
    </el-col>
  </el-row>
</template>

<script>
export default {
  name: "ResponsiveLayout"
};
</script>

<style scoped>
.card-content {
  padding: 20px;
  background-color: #f4f4f4;
  border-radius: 10px;
}
</style>

解释:

  • el-row 用于创建行,gutter 控制列之间的间距。
  • el-col 用于定义列宽,可以通过 xssmmd 等属性来设置不同屏幕尺寸下的列宽。例如,在屏幕宽度小于 768px 时,xs="24" 表示每列占满 100% 宽度;在屏幕宽度大于 768px 时,sm="12" 表示每列占 50% 宽度;在屏幕宽度大于 1200px 时,md="8" 表示每列占 33.33% 宽度。

这种方法能够在不同设备上自适应地调整列宽,使得布局在各种屏幕尺寸下都能保持合理的显示效果。

3.3 配置 TailwindCSS 实现响应式设计

TailwindCSS 是一个功能强大的 CSS 框架,提供了一套高度可配置的工具类,用于构建响应式布局。它通过内置的断点类,使得开发者能够轻松为不同设备尺寸编写样式。

安装 TailwindCSS

首先,您需要安装 TailwindCSS,并配置好它的基本文件:

npm install tailwindcss postcss autoprefixer --save-dev
npx tailwindcss init

然后,在 tailwind.config.js 中配置内容:

module.exports = {
  content: ['./src/**/*.{html,vue,js}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

src/assets/styles/index.css 中引入 TailwindCSS 的基础样式:

@tailwind base;
@tailwind components;
@tailwind utilities;

示例:

<template>
  <div class="container mx-auto px-4">
    <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
      <div class="bg-gray-200 p-4 rounded-lg">内容 1</div>
      <div class="bg-gray-200 p-4 rounded-lg">内容 2</div>
      <div class="bg-gray-200 p-4 rounded-lg">内容 3</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ResponsiveGridLayout',
};
</script>

<style scoped>
/* Additional styles for this component */
</style>

解释:

  • grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3:使用 Tailwind 的网格系统。默认情况下,所有列占据 1 列(grid-cols-1);当屏幕尺寸大于 sm 时,使用 2 列(sm:grid-cols-2);当屏幕尺寸大于 md 时,使用 3 列(md:grid-cols-3)。
  • gap-4:设置网格项之间的间距为 4(单位是 0.25rem)。
  • p-4:为每个网格项添加 4 的内边距。
  • rounded-lg:设置网格项的圆角。

3.4 使用 CSS 媒体查询实现分辨率适配

除了使用框架和工具类之外,您还可以通过 CSS 的 @media 媒体查询来手动处理不同屏幕尺寸下的样式调整。通过媒体查询,您可以为不同分辨率的设备指定不同的样式规则。

示例:

/* 默认样式 */
.container {
  padding: 20px;
  width: 100%;
}

/* 屏幕宽度大于 600px 时 */
@media (min-width: 600px) {
  .container {
    padding: 30px;
    width: 80%;
  }
}

/* 屏幕宽度大于 1200px 时 */
@media (min-width: 1200px) {
  .container {
    padding: 40px;
    width: 60%;
  }
}

解释:

  • 在默认情况下,.container 使用 20px 的内边距和 100% 的宽度。
  • 当屏幕宽度大于 600px 时,.container 的宽度调整为 80%,内边距增加到 30px。
  • 当屏幕宽度大于 1200px 时,.container 的宽度调整为 60%,内边距为 40px。

3.5 使用视口单位(vw/vh)进行适配

除了传统的响应式布局方法,使用 视口单位vwvh)也是一种有效的适配方法。视口单位可以根据视口的宽度(vw)或高度(vh)动态调整元素的尺寸。

示例:

.container {
  width: 80vw;  /* 宽度为视口宽度的 80% */
  height: 40vh; /* 高度为视口高度的 40% */
  font-size: 4vw; /* 字体大小为视口宽度的 4% */
}

这种方法适合用于布局和字体大小调整,尤其是在大屏幕设备上。使用 vwvh 可以确保元素根据屏幕大小自适应。

四、项目结构与目录规范

为确保项目的可维护性与扩展性,我们采用了以下的目录结构:

src/
  assets/          # 存放所有静态资源,如图片、字体、图标
  components/      # 存放公共组件(如数据卡片、按钮、表单控件)
  views/           # 存放各个页面视图(如首页、大屏显示页)
  store/           # 存放 Pinia 状态管理模块
  router/          # 路由管理
  utils/           # 存放工具函数和 API 封装
  assets/styles/   # 存放 SCSS 或 TailwindCSS 配置文件

五、ECharts 与 Vue3 集成,使组件更便捷

5.1 ECharts 与 Vue3 集成的基础

ECharts 提供了丰富的图表类型,如折线图、柱状图、饼图等,适合各种数据展示需求。为了让 ECharts 与 Vue3 的集成更加高效,我们首先需要安装 echarts 库。

安装 ECharts:

npm install echarts --save

然后,我们可以创建一个 Vue3 组件来封装图表的渲染过程,只通过传递数据、颜色、图表类型等简单参数,来快速生成图表。

5.2 封装 ECharts 组件

为了使图表组件更加灵活,我们将 ECharts 的初始化、更新和销毁封装到一个 Vue3 组件中。此时,我们的组件将接收 datacolorchartType 等参数,用户仅需传递数据、颜色和图表类型即可。

ECharts 组件封装:

<template>
  <div ref="chartContainer" :style="{ width: width, height: height }"></div>
</template>

<script>
import { onMounted, onBeforeUnmount, ref, watch } from 'vue';
import * as echarts from 'echarts';

export default {
  name: 'EChartsComponent',
  props: {
    data: {
      type: Array,
      required: true
    },
    chartType: {
      type: String,
      default: 'bar'  // 默认柱状图
    },
    colors: {
      type: Array,
      default: () => ['#409EFF', '#67C23A'] // 默认颜色
    },
    width: {
      type: String,
      default: '100%'
    },
    height: {
      type: String,
      default: '400px'
    }
  },
  setup(props) {
    const chartContainer = ref(null);
    let chartInstance = null;

    const initChart = () => {
      chartInstance = echarts.init(chartContainer.value);
      const option = generateOption();
      chartInstance.setOption(option);
    };

    const updateChart = () => {
      if (chartInstance) {
        const option = generateOption();
        chartInstance.setOption(option);
      }
    };

    // 根据传入的参数生成配置项
    const generateOption = () => {
      return {
        tooltip: {},
        xAxis: {
          type: 'category',
          data: props.data.map(item => item.name)
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '数据',
            type: props.chartType,
            data: props.data.map(item => item.value),
            itemStyle: {
              color: (params) => {
                return props.colors[params.dataIndex % props.colors.length];
              }
            }
          }
        ]
      };
    };

    onMounted(() => {
      initChart();
    });

    onBeforeUnmount(() => {
      if (chartInstance) {
        chartInstance.dispose();
      }
    });

    // Watch for props changes to update chart
    watch(() => props.data, updateChart);
    watch(() => props.chartType, updateChart);
    watch(() => props.colors, updateChart);

    return {
      chartContainer
    };
  }
};
</script>

<style scoped>
/* 可添加一些自定义样式 */
</style>
5.3 使用封装的 ECharts 组件

通过封装 ECharts 组件后,我们可以在其他 Vue 组件中方便地引用它,只需要传递图表数据、颜色和图表类型等参数,无需关心图表的细节实现。

使用示例:

<template>
  <div>
    <h3>工地巡检统计</h3>
    <EChartsComponent 
      :data="chartData" 
      chartType="bar" 
      :colors="['#ff7f50', '#87cefa']" 
      width="100%" 
      height="400px" 
    />
  </div>
</template>

<script>
import { ref } from 'vue';
import EChartsComponent from './components/EChartsComponent';

export default {
  name: 'InspectionStats',
  components: {
    EChartsComponent
  },
  setup() {
    const chartData = ref([
      { name: '任务1', value: 120 },
      { name: '任务2', value: 200 },
      { name: '任务3', value: 150 },
      { name: '任务4', value: 80 },
      { name: '任务5', value: 70 }
    ]);

    return {
      chartData
    };
  }
};
</script>

<style scoped>
/* 可添加一些自定义样式 */
</style>

解释:

  • EChartsComponent 被作为子组件引入,并传入了 datachartTypecolors 等参数来定义图表的内容。
  • data 参数包含了图表所需的数据,包括每个任务的名称和完成情况。
  • chartType 用于选择图表类型,这里设置为 bar(柱状图)。
  • colors 参数允许我们自定义图表的颜色,使得不同数据系列之间可以有不同的显示颜色。
  • widthheight 控制图表的尺寸,您可以根据需要调整这些属性。
5.4 优化 ECharts 性能

随着数据量的增加,图表的渲染性能可能会受到影响。为了提高性能,我们可以采取以下优化措施:

  • 懒加载图表数据:只有在用户需要查看图表时才加载数据,避免一次性加载大量数据。
  • 数据虚拟化:使用虚拟化技术,仅渲染当前视口内的数据,减少页面渲染压力。
  • 图表缓存:对于重复渲染的图表,缓存已渲染的内容,避免不必要的重新渲染。

六、数据动态更新与展示优化

6.1 数据动态更新的需求

通过以下两种方式来进行数据的动态更新:

  • 定时轮询:定期请求后台接口获取最新的数据,适用于数据更新频率相对较低的场景。
  • WebSocket:通过 WebSocket 协议与后台建立持久连接,实时接收后台推送的数据,适用于数据更新频率较高的场景。
6.2 定时轮询实现数据更新

定时轮询是最常见的数据更新方式,通常用于数据变化频率较低的场景。通过 setInterval 定时请求后台接口,获取最新数据并更新视图。

实现步骤:

  1. 创建定时任务:使用 setInterval 定期调用后台接口,获取最新数据。
  2. 更新数据:在获取到新的数据后,更新页面中显示的数据。

代码实现:

<template>
  <div>
    <h3>巡检任务完成情况</h3>
    <EChartsComponent 
      :data="chartData" 
      chartType="bar" 
      :colors="['#ff7f50', '#87cefa']" 
      width="100%" 
      height="400px" 
    />
  </div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getRequest } from '@/utils/request';
import EChartsComponent from './components/EChartsComponent';

export default {
  name: 'InspectionStats',
  components: {
    EChartsComponent
  },
  setup() {
    const chartData = ref([]);
    let intervalId = null;

    // 获取巡检任务数据
    const fetchInspectionData = async () => {
      try {
        const data = await getRequest('/inspection/task');
        chartData.value = data.map(item => ({
          name: item.taskName,
          value: item.completedCount
        }));
      } catch (error) {
        console.error('获取数据失败', error);
      }
    };

    // 定时请求后台数据
    const startPolling = () => {
      fetchInspectionData(); // 获取一次初始数据
      intervalId = setInterval(fetchInspectionData, 30000); // 每30秒请求一次数据
    };

    // 组件销毁时清除定时器
    onBeforeUnmount(() => {
      clearInterval(intervalId);
    });

    // 页面初始化时启动定时任务
    onMounted(() => {
      startPolling();
    });

    return {
      chartData
    };
  }
};
</script>

<style scoped>
/* 可添加一些自定义样式 */
</style>

解释:

  • fetchInspectionData 函数是一个异步函数,调用后台接口 /inspection/task 获取巡检任务的数据。
  • 使用 setInterval 每 30 秒自动请求一次数据,更新页面展示的图表数据。
  • clearInterval(intervalId) 确保组件销毁时清除定时器,避免内存泄漏。

这种方式适用于数据更新频率较低的场景,比如巡检任务的完成情况、设备的状态等。

6.3 使用 WebSocket 实现实时数据推送

通过 WebSocket,前端和后台建立了长连接,后台可以随时向前端推送更新的数据。

实现步骤:

  1. 与后台建立 WebSocket 连接:前端通过 WebSocket 协议与后台建立连接。
  2. 接收推送的数据:前端通过 WebSocket 监听数据变化,当数据发生变化时,立即更新界面。
  3. 处理断线重连:如果 WebSocket 连接中断,需要重新建立连接。

代码实现:

<template>
  <div>
    <h3>设备运行状态</h3>
    <EChartsComponent 
      :data="chartData" 
      chartType="pie" 
      :colors="['#ff7f50', '#87cefa']" 
      width="100%" 
      height="400px" 
    />
  </div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { showError } from '@/utils/request';
import EChartsComponent from './components/EChartsComponent';

export default {
  name: 'DeviceStatus',
  components: {
    EChartsComponent
  },
  setup() {
    const chartData = ref([]);
    let ws = null;

    // 连接 WebSocket 服务
    const connectWebSocket = () => {
      ws = new WebSocket('wss://example.com/device/status'); // 后台 WebSocket 地址

      ws.onopen = () => {
        console.log('WebSocket 连接成功');
      };

      ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        chartData.value = [
          { name: '在线设备', value: data.online },
          { name: '离线设备', value: data.offline }
        ];
      };

      ws.onerror = (error) => {
        showError('WebSocket 连接出错');
      };

      ws.onclose = () => {
        console.log('WebSocket 连接关闭');
        // 处理连接关闭后的逻辑,可能需要重连
      };
    };

    // 页面加载时初始化 WebSocket
    onMounted(() => {
      connectWebSocket();
    });

    // 组件销毁时关闭 WebSocket 连接
    onBeforeUnmount(() => {
      if (ws) {
        ws.close();
      }
    });

    return {
      chartData
    };
  }
};
</script>

<style scoped>
/* 可添加一些自定义样式 */
</style>

解释:

  • 使用 WebSocket 创建一个长连接,与后台进行实时数据交换。
  • 当后台发送新的设备状态数据时,onmessage 事件会触发,更新图表中的数据。
  • ws.close() 在组件销毁时关闭 WebSocket 连接,防止内存泄漏。

WebSocket 适用于实时性要求较高的场景,比如设备状态监控、实时施工进度等。

6.4 展示优化与性能提升

随着数据量的增加,图表的渲染性能可能会受到影响。为了解决这个问题,我们可以采用以下优化策略:

  • 数据节流与防抖:对于高频率的数据更新,采用节流和防抖机制,减少不必要的渲染和请求。
  • 虚拟化图表:在数据量非常大的情况下,可以通过虚拟化技术,仅渲染当前视口内的图表数据。
  • 图表缓存:对于重复渲染的图表,采用缓存机制,避免每次都重新渲染。

示例:图表更新防抖优化

import { debounce } from 'lodash';

// 防抖更新图表
const updateChartDebounced = debounce(updateChart, 300);  // 延迟300ms更新

通过防抖机制,可以避免频繁的图表更新,减轻浏览器的渲染负担。


七、UI 样式与主题定制

7.1 UI 样式的基本原则
  • 简洁易懂:界面应尽量简洁,避免冗余信息,保证用户可以快速找到所需操作。
  • 一致性:不同模块和页面的样式设计要保持一致,确保整体视觉风格统一,避免混乱。
  • 响应式设计:确保界面能够适配不同分辨率和设备,提供良好的移动端体验。
  • 品牌元素:UI 样式应融入项目的品牌元素,如颜色、字体、图标等,使系统具有独特的视觉识别性。
7.2 使用 uView UI 组件库进行样式定制

uView UI 是 UniApp 中常用的UI组件库,它提供了大量常用的组件,如按钮、输入框、表单、弹窗、导航栏等,且支持丰富的定制选项。我们可以通过uView UI来快速构建符合需求的页面,同时利用其样式定制功能进行外观调整。

配置 uView UI 主题

uView UI 提供了内置的主题配置选项,我们可以通过修改 uView 的主题配置文件,调整全局样式,包括颜色、字体等。

  1. 安装 uView UI 组件库

    首先,确保已经在项目中安装了 uView UI,并在 main.js 中引入:

    import uView from 'uview-ui';
    import { createApp } from 'vue';
    
    const app = createApp(App);
    app.use(uView);
    app.mount('#app');
    
  2. 修改主题配置

    uView 中,可以通过修改 uni.scss 文件,快速自定义样式。你可以自定义基本颜色、字体、间距等样式。

    示例:修改 uni.scss 主题配置

    /* uni.scss */
    $primary-color: #42b983;  // 修改主色调为绿色
    $font-size-base: 14px;     // 设置默认字体大小
    $font-family: "Helvetica Neue", "Arial", sans-serif;  // 设置全局字体
    

    通过这些配置,所有使用 $primary-color$font-size-base 的组件和样式都会自动生效,保持全局一致性。

组件样式定制

如果我们需要对某个组件的样式进行更精细的定制,可以使用 classstyle 来覆盖组件的默认样式。uView UI 组件支持通过 custom-style 属性来定义自定义样式。

示例:自定义按钮样式

<template>
  <u-button
    class="custom-btn"
    type="primary"
    :style="{ backgroundColor: btnColor, fontSize: fontSize }"
  >
    提交
  </u-button>
</template>

<script setup>
import { ref } from 'vue';

const btnColor = ref('#42b983'); // 自定义按钮背景色
const fontSize = ref('16px');   // 自定义字体大小
</script>

<style scoped>
.custom-btn {
  border-radius: 10px;  /* 设置按钮圆角 */
  padding: 12px 24px;   /* 设置按钮内边距 */
}
</style>
7.3 主题定制与动态切换

通过改变全局 CSS 变量来实现这一功能。

动态切换主题

首先,定义两个主题的样式文件,如 light-theme.scssdark-theme.scss

light-theme.scss

/* 浅色主题 */
$primary-color: #42b983;
$background-color: #ffffff;
$text-color: #333333;

dark-theme.scss

/* 深色主题 */
$primary-color: #2c3e50;
$background-color: #2c3e50;
$text-color: #ecf0f1;

在 Vue 组件中,可以通过动态切换主题样式来实现主题的变化。使用 useStateuseStorage 保存用户选择的主题,并在页面加载时应用相应的主题样式。

示例:主题切换功能

<template>
  <div :class="theme">
    <u-button @click="toggleTheme">切换主题</u-button>
    <p>当前主题: {{ theme }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const theme = ref(localStorage.getItem('theme') || 'light'); // 读取用户主题设置

// 切换主题
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light';
  localStorage.setItem('theme', theme.value); // 保存用户选择
  document.documentElement.className = theme.value; // 更新主题样式
};
</script>

<style scoped>
/* 配置浅色和深色模式的不同样式 */
.light {
  --primary-color: #42b983;
  --background-color: #ffffff;
  --text-color: #333333;
}

.dark {
  --primary-color: #2c3e50;
  --background-color: #2c3e50;
  --text-color: #ecf0f1;
}

/* 使用 CSS 变量来统一管理主题样式 */
body {
  background-color: var(--background-color);
  color: var(--text-color);
}
</style>
7.4 响应式布局与适配

为了更好地适配不同设备和分辨率,我们可以通过设置响应式栅格系统来实现内容的自适应布局。常见的响应式布局工具如 FlexboxCSS Grid 都可以帮助我们实现这一目标。

示例:使用 Flexbox 实现响应式布局

<template>
  <div class="container">
    <div class="item" v-for="(item, index) in items" :key="index">{{ item }}</div>
  </div>
</template>

<script setup>
const items = ['设备状态', '巡检数据', '施工进度'];
</script>

<style scoped>
.container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 20px;
}

.item {
  flex: 1 1 200px;
  background: #f0f0f0;
  padding: 20px;
  border-radius: 8px;
  text-align: center;
}

@media (max-width: 768px) {
  .item {
    flex: 1 1 100%; /* 小屏幕时每个 item 占满一行 */
  }
}
</style>

通过上述代码,我们使用 flex 布局来实现动态自适应。并通过媒体查询(@media)来控制小屏幕设备上的显示效果。


八、总结与展望

本文介绍了如何通过 Vue3 + ECharts 实现一个智慧工地的实时数据展示大屏,并讲解了如何实现响应式布局,确保适配各种屏幕。通过封装 ECharts 为组件,提升了代码复用性和可维护性。接下来,我们可以进一步扩展功能,增加更多数据可视化形式,并优化性能,以满足项目的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

全栈探索者chen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值