拼图小游戏

拼图小游戏

采用主技术

GUI Java多线程  IO流

效果展示:

启动界面:
在这里插入图片描述

主界面:(游戏界面内容功能见名知意)
在这里插入图片描述

阅前须知

源码分为两部分
1、图片image
2、代码实现
在导入之前需要修改IO文件路径
如需修改拼图照片内容,需要将主图像素大小调至450 x 600
然后拆分为6行5列作为拼图区
注意图片命名格式一定要一致
附上一个拆分图片网站:https://www.qtool.net/piccutting
创作不易,如果能帮到您,请点赞支持,感谢!

源码部分

ResourceUtil

package jinhuan.pink;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class ResourceUtil {
    public static BufferedImage getImage(String imageName){
        BufferedImage bufferedImage = null;
        try {
//            这里的路径需要修改为自己的文件路径
            bufferedImage = ImageIO.read(new File("Xingle\\src\\image\\"+imageName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  bufferedImage;
    }
}

MyTime

package jinhuan.pink;

/**
 * 自定义计时器
 * 实现时间的递增
 * */
public class MyTime implements Runnable {
    static String myTime;
    static int  myMinute = 0;
    static int  mySecond = 0;
    static int  myHour = 0;
    @Override
    public void run() {
        while (true){
            try {
                Thread.currentThread().sleep(1000);
                mySecond++;
                if(mySecond >= 60){
                    mySecond = 0;
                    myMinute++;
                }
                if(myMinute >= 60){
                    myMinute++;
                }
                if(myHour >= 24){
                    myMinute++;
                }
                myTime = myHour+":"+myMinute+":"+mySecond;
                MainJFrame.buttonAreaJPanel.yourtime.setText("时间:"+myTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

GameStart

package jinhuan.pink;
//主启动类

public class GameStart {
    public static void main(String[] args) {
        new BeginJFrame();
    }
}

BeginJFrame

package jinhuan.pink;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

/**
 * 游戏的开始界面
 * */
public class BeginJFrame extends JFrame {
    private BeginJFrame beginJFrame = this;

   private BufferedImage titleIcon = ResourceUtil.getImage("icon.png");
   private BufferedImage startBu = ResourceUtil.getImage("startBu.png");
   private JButton start = new JButton(new ImageIcon(startBu));
   private Container contentPane = getContentPane();

    /**
     * 定义主窗口的组件
     */
    private JLabel title = new JLabel("拼图小游戏",JLabel.CENTER );
    private JLabel hhh = new JLabel("开始游戏",JLabel.CENTER );

    /**
     * 定义主窗体的子面板
     * */
    private JPanel mainJpanel = new JPanel(null);

    //    获取本窗体的自带面板并将其属化
    public BeginJFrame(){
        addComponent();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        setTitle("拼图小游戏");
        setSize(600,300);
        setIconImage(titleIcon);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public void addComponent(){
        contentPane.setBackground(Color.getHSBColor(12,12,12));
        title.setFont(new Font("宋体",Font.BOLD,60));
        contentPane.add(title,BorderLayout.NORTH);

        /**
         * 给两个按钮设置属性并添加监听事件
         * */
        start.setBounds(200,100,200,76);
        hhh.setFont(new Font("幼圆",Font.BOLD,20));
        start.setFocusPainted(false);
        start.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new MainJFrame();
                beginJFrame.dispose();
            }
        });

        hhh.setBounds(200,50,200,76);
        mainJpanel.add(hhh);
        mainJpanel.add(start);
        mainJpanel.setBackground(Color.WHITE);
        contentPane.add(mainJpanel,BorderLayout.CENTER);
    }
}

MainJFrame

package jinhuan.pink;

import java.awt.*;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;

public class MainJFrame extends JFrame {
    /**
     * 创建整个面板的属性变量
     *     窗体的图标:
     *          iconImage-->创建工具类,利用IO进行批量的读入
     *      两个子面板:
     *          buttonAreaJPanel-->代表按钮面板
     *          imageAreaJPanel-->代表下部图片区域
     *
     * */
    BufferedImage iconImage = ResourceUtil.getImage("icon.png");// 代表整个游戏的图标
    static ButtonAreaJPanel buttonAreaJPanel = new ButtonAreaJPanel();//按钮区域对象
    static ImageAreaJPanel imageAreaJPanel = new ImageAreaJPanel();// 创建图片区域对象

    /**
     * 提供无参的构造方法:
     *          添加事件监听
     *          设置窗口属性(可见设置在最后)
     * */
    public MainJFrame(){
        //调用监听的方法
        this.setIconImage(iconImage);
        this.setTitle("拼图小游戏");
        this.setSize(1200, 720);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);// 设置窗体居中
        this.setResizable(false);// 设置窗体的大小不可以改变
        this.add(buttonAreaJPanel, BorderLayout.NORTH);
        this.add(imageAreaJPanel, BorderLayout.CENTER);
        this.setVisible(true);
    }
}

ButtonAreaJPanel

package jinhuan.pink;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;

public class ButtonAreaJPanel extends JPanel{

    /**
     * 为当前面板添加两个子面板:
     *       1、左面板--leftJPanel
     *       2、右面板--rightJPanel
     * */

    JPanel leftJPanel = new JPanel();
    JPanel rightJPanel = new JPanel();
    
    /**
     * 构造方法来初始化面板
     *      1、设置此面板的属性
     *      2、将左边的面板和右边的面板添加到按钮区域面板中
     * */
    public  ButtonAreaJPanel() {
        this.setLayout(new GridLayout(1, 2));
        addLeftJPanel();
        addRightJPanel();
    }
    /**
     * 1、设置左边面板的属性
     * 2、定义左边面板的组件:
     *      a、imageName代表左边的图片名称
     *      b、step代表计步区域
     * */
    static JTextField imageName;
    static JTextField step;
    static JTextField yourtime;
    public void addLeftJPanel() {
        leftJPanel.setBorder(new TitledBorder("游戏数据区"));
        leftJPanel.setLayout(new GridLayout(1,2));
        /**
         * 图片显示的文本框
         * */
        imageName = new JTextField("图片名称:"+pictures[0]);
        imageName.setBackground(Color.CYAN);
        imageName.setEditable(false);
        /**
         * 步数的文本显示框
         * */
        step = new JTextField("步数:"+0);// 
        step.setBackground(Color.magenta);
        step.setEditable(false);
        /**
         * yourtime
         * */
        yourtime = new JTextField("花费时间:"+0);//
        yourtime.setBackground(Color.PINK);
        yourtime.setEditable(false);

//        添加至左面板
        leftJPanel.add(imageName,BorderLayout.WEST);
        leftJPanel.add(yourtime,BorderLayout.CENTER);
        leftJPanel.add(step, BorderLayout.EAST);
//        将leftJPanel添加到ButtonAreaJPanel
        this.add(leftJPanel, BorderLayout.WEST);
    }

    /**
     * 定义右边面板的组件:
     *      1、单选按钮:——>分组
     *              a、showNumber代表显示序号
     *              b、hideNumber代表隐藏序号-->默认被选中
     *      2、下拉列表——>来选择图片
     *      3、开始按钮
     * */
    static String[] pictures = {"波妞和宗介_1","波妞和宗介_2","宗介"};//作为下拉列表的选项
    static String[] models = {"简单","中等","困难"};
    static JRadioButton showNum;
    static JRadioButton hideNum;
    static JComboBox<String> changePic;
    static JComboBox<String> changeMod;
    static JButton start;
    static int modIndex;
    static int indexBegin;
    static long beginTime;

    MyTime myTime = new MyTime();
    Thread thread = new Thread(myTime);

    public void addRightJPanel() {
        rightJPanel.setBorder(new TitledBorder("功能区"));//设置 面板的边框的标题
        /**
         * 完成 两个单选框的初始化
         * */
        showNum = new JRadioButton("显示序号",false);
        showNum.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                ImageAreaJPanel.myPuzzle.showNum();
            }
        });
        
        hideNum = new JRadioButton("隐藏序号",true);
        hideNum.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ImageAreaJPanel.myPuzzle.hideNum();
            }
        });
//        将两个按钮绑定为一组
        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(showNum);
        buttonGroup.add(hideNum);

//        添加到右边的面板上
        rightJPanel.add(showNum);
        rightJPanel.add(hideNum);


//       对下拉列表进行设置
        changePic = new JComboBox<String>(pictures);//将图片名称的数组进行传入
        changeMod = new JComboBox<String>(models);//设置难度

        changePic.addItemListener(new ItemListener(){
            @Override
            public void itemStateChanged(ItemEvent e) {
                if(e.getStateChange() == ItemEvent.SELECTED) {
                    hideNum.setSelected(false);// 切换图片默认不显示序号
                    int index = changePic.getSelectedIndex();//获取选中的图片的索引 index  第一个选项 0
                    PreviewAreaJPanel.picId = index + 1;// 通过获取的索引 确定图片的id ----> 修改图片的id
                    ImageAreaJPanel.myPreview.repaint(); //重新绘制
                    imageName.setText("图片名称:"+changePic.getSelectedItem());//设置功能区显示的图片名称
                    PreviewAreaJPanel.stepCount = 0;//当游戏切换图片之后,步数清空,重新赋值为0
                    //将总共用了多少步数  赋值给文本框
                    step.setText("当前步数:"+PreviewAreaJPanel.stepCount);
                    MainJFrame.buttonAreaJPanel.yourtime.setText("时间:"+MyTime.myTime);
//                    thread.interrupt();
                    ImageAreaJPanel.myPuzzle.reload();
                }
            }
        });
        changeMod.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                modIndex = changeMod.getSelectedIndex();
            }
        });
        changePic.setBackground(Color.WHITE);
        changeMod.setBackground(Color.WHITE);
        //将下拉列表添加到右边的面板上
        rightJPanel.add(new JLabel("选择图片:"));
        rightJPanel.add(changePic);
        rightJPanel.add(new JLabel("选择难度:"));
        rightJPanel.add(changeMod);
        /**
         * 完成开始按钮的初始化
         *  并给开始按钮添加监听事件
         *  */

        start = new JButton("开始游戏");
        start.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                beginTime = System.currentTimeMillis();
                PreviewAreaJPanel.stepCount = 0;
                PreviewAreaJPanel.wasteTime = "";
                MainJFrame.buttonAreaJPanel.step.setText("步数:"+PreviewAreaJPanel.stepCount);
                myTime.mySecond = 0;
                myTime.myMinute = 0;
                myTime.myHour = 0;
                if(indexBegin == 0){
                    thread.start();
                }
                indexBegin++;
                ImageAreaJPanel.myPuzzle.upset();
            }
        });
        start.setFocusPainted(false);//去除获取焦点时的边框线
//        将开始按钮添加到右边的面板中
        rightJPanel.add(start);
//        将整个右边的面板添加到按钮区
        this.add(rightJPanel, BorderLayout.EAST);
    }

}

ImageAreaJPanel

package jinhuan.pink;

import java.awt.BorderLayout;
import java.awt.GridLayout;

import javax.swing.JPanel;
import javax.swing.border.TitledBorder;

public class ImageAreaJPanel extends JPanel{
    /**
     * 下面板的两个子面板
     *          一个是原图区
     *          一个是拼图区
     * */
    static PreviewAreaJPanel myPreview = new PreviewAreaJPanel();
    static PuzzleAreaJPanel myPuzzle = new PuzzleAreaJPanel();

    /**
     * 构造方法完成图片区域的初始化
     * */
    public  ImageAreaJPanel() {
        this.setLayout(new GridLayout(1, 2));
        myPreview.setBorder(new TitledBorder("图片预览区"));
        myPuzzle.setBorder(new TitledBorder("拼图区"));
        this.add(myPreview, BorderLayout.WEST);
        this.add(myPuzzle, BorderLayout.EAST);
    }
}

PreviewAreaJPanel

package jinhuan.pink;
//图片预览区

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class PreviewAreaJPanel extends JPanel{
    //定义两个变量
    //图片的数字 ----> static 修饰的成员 可以直接通过类名.访问    静态变量:类变量 随着类的加载而 初始化的   存在于 方法区的静态区
    static int picId = 1;
    //总共用了多少步
    static int stepCount = 0;

    static String wasteTime = "";

    //预览区的图片
    BufferedImage previewImage ;

    //绘制图片
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        previewImage = ResourceUtil.getImage(picId + ".jpg");
        g.drawImage(previewImage, 70, 20, 450, 600, null);
    }
}

PuzzleAreaJPanel

package jinhuan.pink;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;


public class PuzzleAreaJPanel extends JPanel implements MouseListener {
    static class PictureButton extends JButton {
        // 构造方法完成按钮的初始化
        public PictureButton(Icon icon) {
            super(icon);
            this.setSize(90, 100);
            this.setHorizontalTextPosition(CENTER);
            this.setVerticalTextPosition(CENTER);
        }
        /**
         * 定义小方格按钮的运动的方法
         * */
        public void movePictureButton(String dir) {
            switch (dir) {
                case "RIGHT":
                    this.setLocation(this.getBounds().x + 90, this.getBounds().y);
                    break;
                case "LEFT":
                    this.setLocation(this.getBounds().x - 90, this.getBounds().y);
                    break;
                case "UP":
                    this.setLocation(this.getBounds().x, this.getBounds().y - 100);
                    break;
                case "DOWN":
                    this.setLocation(this.getBounds().x , this.getBounds().y + 100);
                    break;
            }
        }
    }

    /**
     * 设置拼图区域
     *      1、设置29个按钮
     *         a、图片来添加到按钮上
     *         b、for循环创建按钮(记得减去一个按钮)
     *      2、一个为空白按钮
     * */
    static PictureButton[] picButtons;
    static Rectangle rectangle;
    static BufferedImage showImage;

    /**
     * 构造方法完成属性的初始化
     * */
    public PuzzleAreaJPanel() {
        picButtons = new PictureButton[6 * 5];
        this.setLayout(null);
        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 5; j++) {
                showImage = ResourceUtil.getImage(PreviewAreaJPanel.picId + "_" + (5 * i + j + 1) + ".jpg");
                picButtons[5 * i + j] = new PictureButton(new ImageIcon(showImage));
                picButtons[5 * i + j].setLocation(70 + j * 90, 20 + i * 100);
                this.add(picButtons[5 * i + j]);
            }
        }
//        删除最后一个小方格
        this.remove(picButtons[picButtons.length - 1]);
//        设置最后的空白格的坐标来将其初始化
        rectangle = new Rectangle(70 + 360, 20 + 500, 90, 100);
    }


    /**
     * 显示序号的方法
     * */
    public void showNum() {
        // 循环为每个按钮添加文本
        for (int i = 0; i < picButtons.length - 1; i++) {
            picButtons[i].setText("" + (i + 1));
        }
    }

    /**
     * 隐藏序号的方法
     * */
    public void hideNum() {
        // 循环为每个按钮添加文本
        for (int i = 0; i < picButtons.length - 1; i++) {
            picButtons[i].setText("");
        }
    }
    /**
     * 完成拼图区的图片重新加载
     *              获取当前picId  重新设置
     * */
    public void reload() {
        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 5; j++) {
                showImage = ResourceUtil.getImage(PreviewAreaJPanel.picId + "_" + (i * 5 + j + 1) + ".jpg");
                // 重新设置小方格的图片
                picButtons[i * 5 + j].setIcon(new ImageIcon(showImage));
                picButtons[5 * i + j].setLocation(70 + j * 90, 20 + i * 100);
                // 小方格的文本内容 清空
                picButtons[i * 5 + j].setText("");
            }
        }
    }
    /**
     * 完成 空白格 和 小方格 进行位置的交换
     *              a b 空白格的坐标位置
     * */
    public void movePictureButton(int rx, int ry, String dir) {
        for (int i = 0; i < picButtons.length - 1; i++) {
            if (picButtons[i].getBounds().x == rx && picButtons[i].getBounds().y == ry) {
                picButtons[i].movePictureButton(dir);
                rectangle.setLocation(rx, ry);
                break;
            }
        }
    }

    /**
     * 完成图标的打乱
     * */
    boolean isListener = false;

    public void upset() {// 打乱小方格
        // 判断当前的小方格有没有被监听
        if (!isListener) {
            // 为每一个小方格添加监听
            for (int i = 0; i < picButtons.length - 1; i++) {
                picButtons[i].addMouseListener(this);
            }
            isListener = true;
        }

            //以第一个小方格的位置来判定,只要第一个小方格的位置没有发生改变就一直打乱小方格的位置
//        for(int i=0;i<MainJFrame.buttonAreaJPanel.modIndex;i++) {
        while (picButtons[0].getBounds().x <= 70 + MainJFrame.buttonAreaJPanel.modIndex * 90 || picButtons[0].getBounds().y <= 20 + MainJFrame.buttonAreaJPanel.modIndex * 100 ) {
            //获取空白格的坐标
            int rx = rectangle.getBounds().x;
                    int ry = rectangle.getBounds().y;
                    //定义一个 0 - 3 之间的随机整数,用来表示空白格上下左右移动的方向
                    int random = (int) (Math.random() * 4);
                    switch (random) {
                        case 0:
                    //当随机数为0 的时候 空白格水平向左移动一格
                    rx -= 90;
                    //和空白个重合的小方格向右水平移动一格
                    movePictureButton(rx,ry,"RIGHT");
                    break;
                case 1:
                    //随机数为1时,空白格水平向右移动一格
                    rx += 90;
                    //和空白格重合的小方格向左水平移动一格
                    movePictureButton(rx,ry,"LEFT");
                    break;
                case 2:
                    //随机数为2的时候 空白格垂直向下移动一格
                    ry += 100;
                    //和空白格重合的小方格垂直向上移动一格
                    movePictureButton(rx,ry,"UP");
                    break;
                case 3:
                    //当随机数为3时,空白格垂直向下移动一格
                    ry -= 100;
                    movePictureButton(rx,ry,"DOWN");
                    break;
            }
        }
    }

    public void moveSwitch(int random,int rx,int ry) {
        switch (random) {
            case 0://随机数为0 --> 空白格 向左移动一格
                //改变空白格的坐标
                rx -= 90;
                //和空白格重合的小方格进行向右移动一格
                movePictureButton(rx, ry, "RIGHT");
                break;
            case 1: // ---> 空白格向右移动一格
                //改变空格格的坐标
                rx+=90;
                //和空白格重合的小方格进行向左移动一格
                movePictureButton(rx, ry, "LEFT");
                break;
            case 2:// ---> 向下
                //改变空格格的坐标
                ry+=100;
                //和空白格重合的小方格进行向上移动一格
                movePictureButton(rx, ry, "UP");
                break;
            case 3://向上
                //改变空格格的坐标
                ry-=100;
                //和空白格重合的小方格进行向左移动一格
                movePictureButton(rx, ry, "DOWN");
                break;
        }
    }
    @Override
    public void mouseClicked(MouseEvent e) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mousePressed(MouseEvent e) {
        //当鼠标按下时候 发生一些操作

        // 1 获取被点击的对象  ---> 小方格
        PictureButton p = (PictureButton) e.getSource();
        // 2 获取当前被点击的小方格的 坐标
        int px = p.getBounds().x;
        int py = p.getBounds().y;

        //3 .获取当前空方格的坐标
        int rx = rectangle.getBounds().x;
        int ry = rectangle.getBounds().y;

        //4 判断 当前被点击的小方格 周围有没有空白格
        if (px == rx && py - ry == 100) {//上边有空白格
            //小方格 移动到上边
            p.movePictureButton("UP");
        }else if (px == rx && py - ry == -100) {//下边有空白格
            //小方格移动到下边
            p.movePictureButton("DOWN");
        }else if (py == ry && px - rx == 90) {//左边有空白格
            //小方格 左移
            p.movePictureButton("LEFT");
        }else if (py == ry && px - rx == -90) {//右边有空白格
            // 小方格 右边移动
            p.movePictureButton("RIGHT");
        }else {
            return;
        }

        // 5 当小方格移动之后   空白格跟着移动
        rectangle.setLocation(px, py);// 将空白格的坐标设置为原来小方格的坐标

        // 6 重绘
        this.repaint();

        // 7 当移动了一次  步数进行 + 1
        PreviewAreaJPanel.stepCount++;
        // 8更新 文本框中的步数的内容
        ButtonAreaJPanel.step.setText("步数:"+PreviewAreaJPanel.stepCount);


        // 9 当游戏完成的时候  弹出一格框框  显示 用了多少步
        if (win()) {
            //弹出框 显示信息
            MainJFrame.buttonAreaJPanel.thread.stop();
            long endTime = System.currentTimeMillis();
            long cha = (endTime - MainJFrame.buttonAreaJPanel.beginTime)/1000;
            JOptionPane.showMessageDialog(this, "胜利! 共用了:"+PreviewAreaJPanel.stepCount+"步,"+ cha+"秒");
            // 复原游戏  取消 每个小方格的监听
            for (int i = 0; i < picButtons.length-1; i++) {
                picButtons[i].removeMouseListener(this);
            }
            isListener = false;
        }
    }

    /**
     * 判断是否游戏成功
     * */
    public boolean win() {
        //遍历所有的方格  判断每个方格是不是在它应该在的位置
        for (int i = 0; i < picButtons.length-1; i++) {
            //获取方格的位置
            int x = picButtons[i].getBounds().x;
            int y = picButtons[i].getBounds().y;
            //只要有一个方格不在自己的位置上就返回false
            if ((y-20)/100*5 + (x-20)/90 != i) {
                return false;
            }
        }
        return true;
    }
    @Override
    public void mouseReleased(MouseEvent e) {
        // TODO Auto-generated method stub

    }
    @Override
    public void mouseEntered(MouseEvent e) {
        // TODO Auto-generated method stub
    }
    @Override
    public void mouseExited(MouseEvent e) {
        // TODO Auto-generated method stub
    }
}

原文件以及源码下载链接:

链接:https://pan.baidu.com/s/19oe0AavtxNyCQfs8rbpK9A
提取码:3598
感谢支持!谢谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

尽欢Sir

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

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

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

打赏作者

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

抵扣说明:

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

余额充值