[Ext JS 4] 实战Chart 协调控制(单一的坐标,两个坐标)

前言


在Extjs 中。 单一的 Column Chart 的展示效果如上。

定义的过程例如以下:

1.  创建一个 Ext.chart.Chart

2. 创建两个坐标轴。 axes

一个 Category 类型的横坐标用来显示日期

一个Numeric 类型的纵坐标用来显示数据

3. 配置显示的图 series

配置 column 类型的柱状图。

详细代码例如以下:

<!--
 Author : oscar999
 Date : 
 ALL RIGHTS RESERVED
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

<script type="text/javascript" src="../lib/extjs/ext-all.js"></script>
<link rel="stylesheet" type="text/css" href="../lib/extjs/resources/ext-theme-neptune/ext-theme-neptune-all.css" />
<script>
Ext.onReady(function(){
    window.generateData = function(n, floor){
        var data = [],
            p = (Math.random() *  11) + 1,
            i;
            
        floor = (!floor && floor !== 0)?

20 : floor; for (i = 0; i < (n || 12); i++) { data.push({ name: Ext.Date.monthNames[i % 12], data1: Math.floor(Math.max((Math.random() * 100), floor)), data2: Math.floor(Math.max((Math.random() * 100), floor)), data3: Math.floor(Math.max((Math.random() * 100), floor)), data4: Math.floor(Math.max((Math.random() * 100), floor)), data5: Math.floor(Math.max((Math.random() * 100), floor)), data6: Math.floor(Math.max((Math.random() * 100), floor)), data7: Math.floor(Math.max((Math.random() * 100), floor)), data8: Math.floor(Math.max((Math.random() * 100), floor)), data9: Math.floor(Math.max((Math.random() * 100), floor)) }); } return data; }; var store1 = Ext.create('Ext.data.JsonStore', { fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5', 'data6', 'data7', 'data9', 'data9'], data: generateData() }); var chart = Ext.create('Ext.chart.Chart', { style: 'background:#fff', animate: true, shadow: true, store: store1, //maxWidth: 500, //columnWidth : 0.1, axes: [{ type: 'Numeric', position: 'left', fields: ['data1'], label: { renderer: Ext.util.Format.numberRenderer('0,0') }, title: 'Number of Hits', grid: true, minimum: 0 }, { type: 'Category', position: 'bottom', fields: ['name'], //categoryNames:new String("111"), title: 'Month of the Year' }], series: [{ type: 'column', axis: 'left', highlight: true, tips: { trackMouse: true, width: 140, height: 28, renderer: function(storeItem, item) { this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $'); } }, label: { display: 'insideEnd', 'text-anchor': 'middle', field: 'data1', //renderer: Ext.util.Format.numberRenderer('0'), orientation: 'vertical', color: '#FFF' }, style:{ opacity: 0.95 //,width:100 }, //xPadding:{left:100,right:100}, xField: 'name', yField: 'data1' }] }); var win = Ext.create('Ext.window.Window', { width: 800, height: 600, minHeight: 400, minWidth: 550, hidden: false, maximizable: true, title: 'Column Chart', autoShow: true, layout: 'fit', tbar: [{ text: 'Save Chart', handler: function() { Ext.MessageBox.confirm('Confirm Download', 'Would you like to download the chart as an image?', function(choice){ if(choice == 'yes'){ chart.save({ type: 'image/png' }); } }); } }, { text: 'Reload Data', handler: function() { // Add a short delay to prevent fast sequential clicks window.loadTask.delay(100, function() { store1.loadData(generateData()); }); } }], items: chart }); }); </script> </head> <body> </body> </html>



怎样设置坐标轴的长度

针对上面的样例, 坐标轴的长度是Extjs依据数据大小自己主动运算并设置的。

假设须要手动定义这个长度的话,改怎样设置呢?

对于Numeric这样的坐标轴来说, 有maximum 和 minimun 这样的參数能够配置。

并且配置也非常easy了, 就不多介绍了。

这里仅仅是对红色的部分做一个伏笔(这样的设置对于配置 堆叠的图形不使用)



多个图形。 两个纵坐标轴

有的状况下, 可能会使用多个图形,

由于图形的范围不同,可能须要使用两个纵坐标轴。

相似的情景能够是这样:

有三个图, 两个柱状图。 一个折线图

折线图和柱状图的数据范围或是单位可能不同。


这里看上去好像是一个折线图和一个柱状图。

事实上是有两个柱状图, 仅仅只是一个被还有一个盖住了。

出现这样的状况的原因是在定义的时候, 往series 中加入了两个column 的chart.

这样的状况的源代码是:

<!--
 Author : oscar999
 Date : 
 ALL RIGHTS RESERVED
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="../lib/extjs/ext-all.js"></script>
<link rel="stylesheet" type="text/css" href="../lib/extjs/resources/ext-theme-neptune/ext-theme-neptune-all.css" />
<script>
Ext.onReady(function(){
    window.generateData = function(n, floor){
        var data = [],
            p = (Math.random() *  11) + 1,
            i;
            
        floor = (!floor && floor !== 0)? 20 : floor;
        
        for (i = 0; i < (n || 12); i++) {
            data.push({
                name: Ext.Date.monthNames[i % 12],
                data1: (i+1)*8,
                data2: (i+1)*10,
                data3: (i+1)*8           
            });
        }
        return data;
    };
    
    var store1 = Ext.create('Ext.data.JsonStore', {
        fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5', 'data6', 'data7', 'data9', 'data9'],
        data: generateData()
    });
    
    var chart = Ext.create('Ext.chart.Chart', {
        style: 'background:#fff',
        animate: true,
        shadow: true,
        store: store1,
        legend:'right',
        axes: [{
            type: 'Numeric',
            position: 'left',
            fields: ['data1','data2'],
            label: {
                renderer: Ext.util.Format.numberRenderer('0,0')
            },
            title: 'Number of Hits',
            grid: true
        },{
            type: 'Numeric',
            position: 'right',
            fields: ['data3'],
            label: {
                renderer: Ext.util.Format.numberRenderer('0,0')
            },
            //title: 'Number of Hits',
            grid: true
        }, {
            type: 'Category',
            position: 'bottom',
            fields: ['name'],
            title: 'Month of the Year'
        }],
        series: [{
            type: 'column',
            axis: 'left',
            highlight: true,
            tips: {
              trackMouse: true,
              width: 140,
              height: 28,
              renderer: function(storeItem, item) {
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
              }
            },
            label: {
              display: 'insideEnd',
              'text-anchor': 'middle',
                field: 'data1',
                //renderer: Ext.util.Format.numberRenderer('0'),
                orientation: 'vertical',
                color: '#FFF'
            },
            style:{
            	opacity: 0.95
            	//,width:100
            },
            //xPadding:{left:100,right:100},
            xField: 'name',
            yField: ['data1']
        }
        ,{
            type: 'column',
            axis: 'left',
            highlight: true,
            tips: {
              trackMouse: true,
              width: 140,
              height: 28,
              renderer: function(storeItem, item) {
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data2') + ' $');
              }
            },
            label: {
              display: 'insideEnd',
              'text-anchor': 'middle',
                field: ['data1','data2'],
                //renderer: Ext.util.Format.numberRenderer('0'),
                orientation: 'vertical',
                color: '#FFF'
            },
            style:{
            	opacity: 0.95
            	//,width:100
            },
            //xPadding:{left:100,right:100},
            xField: 'name',
            yField: ['data2']
        }
        ,
        {
            type: 'line',
            axis: 'right',
            highlight: true,
            tips: {
              trackMouse: true,
              width: 140,
              height: 28,
              renderer: function(storeItem, item) {
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data3') + ' $');
              }
            },
            label: {
              display: 'insideEnd',
              'text-anchor': 'middle',
                field: 'data3',
                //renderer: Ext.util.Format.numberRenderer('0'),
                orientation: 'vertical',
                color: '#FFF'
            },
            style:{
            	opacity: 0.95
            	//,width:100
            },
            //xPadding:{left:100,right:100},
            xField: 'name',
            yField: 'data3'
        }]
    });


var win = Ext.create('Ext.window.Window', {
    width: 800,
    height: 600,
    minHeight: 400,
    minWidth: 550,
    hidden: false,
    maximizable: true,
    title: 'Column Chart',
    autoShow: true,
    layout: 'fit',
    tbar: [{
        text: 'Save Chart',
        handler: function() {
            Ext.MessageBox.confirm('Confirm Download', 'Would you like to download the chart as an image?', function(choice){
                if(choice == 'yes'){
                    chart.save({
                        type: 'image/png'
                    });
                }
            });
        }
    }, {
        text: 'Reload Data',
        handler: function() {
            // Add a short delay to prevent fast sequential clicks
            window.loadTask.delay(100, function() {
                store1.loadData(generateData());
            });
        }
    }],
    items: chart    
});
    
});
</script>
</head>
<body>

</body>
</html>

基本上这不是我们想要的效果。


我们要的效果应该是这样:

这是完美的呈现方式。

完美方式实现的思想是:

1  创建两个坐标轴。 axes

一个 Category 类型的横坐标用来显示日期

一个Numeric 类型的纵坐标用来显示柱状图数据

一个Numeric 类型的纵坐标用来显示折线图数据

2.  series 加入一个 column Chart.  stacked.

以上定义完毕之后。 会发生基本上都正常, 可是有一点就是, 左右的两个纵坐标的尺度可能不同。

通过配置maximum 和maximum 来设置坐标并不会生效。

这里就要提到上面框出的maximum配置的红色部分了, 由于这个配置对于堆叠的图已经不适用了。


堆叠图性的左右坐标一致

为什么会出现左右坐标不一致的状况, 看一看Extjs 的 Ext.chart.axis.Numeric 的定义



原来是这个地方有限制。这应该是Extjs 有意为之了, 至于原因是什么,尚不可知。

无论这样。先去除这个限制得到想要的效果。

解决方式就是定义一个和  Ext.chart.axis.Numeric 相似的坐标定义

/**
 * Add by Oscar999
 */
Ext.define('Ext.chart.axis.StackedNumeric', {

    /* Begin Definitions */

    extend: 'Ext.chart.axis.Axis',

    alternateClassName: 'Ext.chart.StackedNumericAxis',

    /* End Definitions */

    type: 'StackedNumeric',

    // @private
    isNumericAxis: true,

    alias: 'axis.stackednumeric',

    uses: ['Ext.data.Store'],

    constructor: function(config) {
        var me = this,
            hasLabel = !!(config.label && config.label.renderer),
            label;

        me.callParent([config]);
        label = me.label;

        if (config.constrain == null) {
            me.constrain = (config.minimum != null && config.maximum != null);
        }

        if (!hasLabel) {
            label.renderer = function(v) {
                return me.roundToDecimal(v, me.decimals);
            };
        }
    },

    roundToDecimal: function(v, dec) {
        var val = Math.pow(10, dec || 0);
        return Math.round(v * val) / val;
    },

    /**
     * @cfg {Number} minimum
     * The minimum value drawn by the axis. If not set explicitly, the axis
     * minimum will be calculated automatically. It is ignored for stacked charts.
     */
    minimum: NaN,

    /**
     * @cfg {Number} maximum
     * The maximum value drawn by the axis. If not set explicitly, the axis
     * maximum will be calculated automatically. It is ignored for stacked charts.
     */
    maximum: NaN,

    /**
     * @cfg {Boolean} constrain
     * If true, the values of the chart will be rendered only if they belong between minimum and maximum.
     * If false, all values of the chart will be rendered, regardless of whether they belong between minimum and maximum or not.
     * Default's true if maximum and minimum is specified. It is ignored for stacked charts.
     */
    constrain: true,

    /**
     * @cfg {Number} decimals
     * The number of decimals to round the value to.
     */
    decimals: 2,

    /**
     * @cfg {String} scale
     * The scaling algorithm to use on this axis. May be "linear" or
     * "logarithmic".  Currently only linear scale is implemented.
     * @private
     */
    scale: "linear",

    // @private constrains to datapoints between minimum and maximum only
    doConstrain: function() {
        var me = this,
            chart = me.chart,
            store = chart.getChartStore(),
            items = store.data.items,
            d, dLen, record,
            series = chart.series.items,
            fields = me.fields,
            ln = fields.length,
            range = me.calcEnds(),
            min = range.from, max = range.to, i, l,
            useAcum = false,
            value, data = [],
            addRecord;

        for (d = 0, dLen = items.length; d < dLen; d++) {
            addRecord = true;
            record = items[d];
            for (i = 0; i < ln; i++) {
                value = record.get(fields[i]);
                if (me.type == 'Time' && typeof value == "string") {
                    value = Date.parse(value);
                }
                if (+value < +min) {
                    addRecord = false;
                    break;
                }
                if (+value > +max) {
                    addRecord = false;
                    break;
                }
            }
            if (addRecord) {
                data.push(record);
            }
        }
        
        chart.setSubStore(new Ext.data.Store({
            model: store.model,
            data: data
        }));
    },
    /**
     * @cfg {String} position
     * Indicates the position of the axis relative to the chart
     */
    position: 'left',

    /**
     * @cfg {Boolean} adjustMaximumByMajorUnit
     * Indicates whether to extend maximum beyond data's maximum to the nearest
     * majorUnit.
     */
    adjustMaximumByMajorUnit: false,

    /**
     * @cfg {Boolean} adjustMinimumByMajorUnit
     * Indicates whether to extend the minimum beyond data's minimum to the
     * nearest majorUnit.
     */
    adjustMinimumByMajorUnit: false,

    // applying constraint
    processView: function() {
        var me = this;

        if (me.constrain) {
            me.doConstrain();
        }
    },

    // @private apply data.
    applyData: function() {
        this.callParent();
        return this.calcEnds();
    }
});

这一段建议是单独放在一个js 文件里。 通过导入的方式使用。

以下给出一个放在同一份文件里完整的源代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

<script type="text/javascript" src="../lib/extjs/ext-all.js"></script>
<script>
/*Ext.define('mtk.chart.axis.Numeric',
{
	extend : 'Ext.chart.axis.Numeric',
	type: 'stackedNumeric',
	initComponent: function(config) {
		this.processView = function() {
		    var me = this,
		        chart = me.chart,
		        series = chart.series.items,
		        i, l;

		    for (i = 0, l = series.length; i < l; i++) {
		        if (series[i].stacked) {
		            // Do not constrain stacked charts (bar, column, or area).
		            delete me.minimum;
		            delete me.maximum;
		            me.constrain = false;
		            break;
		        }
		    }

		    if (me.constrain) {
		        me.doConstrain();
		    }
		};
		this.callParent([config]);
	}
	
});*/


Ext.define('Ext.chart.axis.StackedNumeric', {

    /* Begin Definitions */

    extend: 'Ext.chart.axis.Axis',

    alternateClassName: 'Ext.chart.StackedNumericAxis',

    /* End Definitions */

    type: 'StackedNumeric',

    // @private
    isNumericAxis: true,

    alias: 'axis.stackednumeric',

    uses: ['Ext.data.Store'],

    constructor: function(config) {
        var me = this,
            hasLabel = !!(config.label && config.label.renderer),
            label;

        me.callParent([config]);
        label = me.label;

        if (config.constrain == null) {
            me.constrain = (config.minimum != null && config.maximum != null);
        }

        if (!hasLabel) {
            label.renderer = function(v) {
                return me.roundToDecimal(v, me.decimals);
            };
        }
    },

    roundToDecimal: function(v, dec) {
        var val = Math.pow(10, dec || 0);
        return Math.round(v * val) / val;
    },

    /**
     * @cfg {Number} minimum
     * The minimum value drawn by the axis. If not set explicitly, the axis
     * minimum will be calculated automatically. It is ignored for stacked charts.
     */
    minimum: NaN,

    /**
     * @cfg {Number} maximum
     * The maximum value drawn by the axis. If not set explicitly, the axis
     * maximum will be calculated automatically. It is ignored for stacked charts.
     */
    maximum: NaN,

    /**
     * @cfg {Boolean} constrain
     * If true, the values of the chart will be rendered only if they belong between minimum and maximum.
     * If false, all values of the chart will be rendered, regardless of whether they belong between minimum and maximum or not.
     * Default's true if maximum and minimum is specified. It is ignored for stacked charts.
     */
    constrain: true,

    /**
     * @cfg {Number} decimals
     * The number of decimals to round the value to.
     */
    decimals: 2,

    /**
     * @cfg {String} scale
     * The scaling algorithm to use on this axis. May be "linear" or
     * "logarithmic".  Currently only linear scale is implemented.
     * @private
     */
    scale: "linear",

    // @private constrains to datapoints between minimum and maximum only
    doConstrain: function() {
        var me = this,
            chart = me.chart,
            store = chart.getChartStore(),
            items = store.data.items,
            d, dLen, record,
            series = chart.series.items,
            fields = me.fields,
            ln = fields.length,
            range = me.calcEnds(),
            min = range.from, max = range.to, i, l,
            useAcum = false,
            value, data = [],
            addRecord;

        for (d = 0, dLen = items.length; d < dLen; d++) {
            addRecord = true;
            record = items[d];
            for (i = 0; i < ln; i++) {
                value = record.get(fields[i]);
                if (me.type == 'Time' && typeof value == "string") {
                    value = Date.parse(value);
                }
                if (+value < +min) {
                    addRecord = false;
                    break;
                }
                if (+value > +max) {
                    addRecord = false;
                    break;
                }
            }
            if (addRecord) {
                data.push(record);
            }
        }
        
        chart.setSubStore(new Ext.data.Store({
            model: store.model,
            data: data
        }));
    },
    /**
     * @cfg {String} position
     * Indicates the position of the axis relative to the chart
     */
    position: 'left',

    /**
     * @cfg {Boolean} adjustMaximumByMajorUnit
     * Indicates whether to extend maximum beyond data's maximum to the nearest
     * majorUnit.
     */
    adjustMaximumByMajorUnit: false,

    /**
     * @cfg {Boolean} adjustMinimumByMajorUnit
     * Indicates whether to extend the minimum beyond data's minimum to the
     * nearest majorUnit.
     */
    adjustMinimumByMajorUnit: false,

    // applying constraint
    processView: function() {
        /*var me = this,
            chart = me.chart,
            series = chart.series.items,
            i, l;

        for (i = 0, l = series.length; i < l; i++) {
            if (series[i].stacked) {
                // Do not constrain stacked charts (bar, column, or area).
                delete me.minimum;
                delete me.maximum;
                me.constrain = false;
                break;
            }
        }

        if (me.constrain) {
            me.doConstrain();
        }*/
        var me = this;

        if (me.constrain) {
            me.doConstrain();
        }
    },

    // @private apply data.
    applyData: function() {
        this.callParent();
        return this.calcEnds();
    }
});

</script>
<link rel="stylesheet" type="text/css" href="../lib/extjs/resources/ext-theme-neptune/ext-theme-neptune-all.css" />
<script>
Ext.onReady(function(){
    window.generateData = function(n, floor){
        var data = [],
            p = (Math.random() *  11) + 1,
            i;
            
        floor = (!floor && floor !== 0)? 20 : floor;
        
        for (i = 0; i < (n || 12); i++) {
            data.push({
                name: Ext.Date.monthNames[i % 12],
                /*data1: Math.floor(Math.max((Math.random() * 100), floor)),
                data2: Math.floor(Math.max((Math.random() * 100), floor)),
                data3: Math.floor(Math.max((Math.random() * 100), floor)),
                data4: Math.floor(Math.max((Math.random() * 100), floor)),
                data5: Math.floor(Math.max((Math.random() * 100), floor)),
                data6: Math.floor(Math.max((Math.random() * 100), floor)),
                data7: Math.floor(Math.max((Math.random() * 100), floor)),
                data8: Math.floor(Math.max((Math.random() * 100), floor)),
                data9: Math.floor(Math.max((Math.random() * 100), floor))*/
                data1: (i+1)*8,
                data2: (i+1)*8,
                data3: (i+1)*8,
                data4: (i+1)*8,
                data5: (i+1)*8,
                data6: (i+1)*8,
                data7: (i+1)*8,
                data8: (i+1)*8,
                data9: (i+1)*8               
            });
        }
        return data;
    };
    
    var store1 = Ext.create('Ext.data.JsonStore', {
        fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5', 'data6', 'data7', 'data9', 'data9'],
        data: generateData()
    });
    
    var chart = Ext.create('Ext.chart.Chart', {
        style: 'background:#fff',
        animate: true,
        shadow: true,
        store: store1,
        //maxWidth: 500,
        //columnWidth : 0.1,
        legend:'right',
        axes: [{
            type: 'StackedNumeric',
            position: 'left',
            fields: ['data1','data2'],
            label: {
                renderer: Ext.util.Format.numberRenderer('0,0')
            },
            title: 'Number of Hits',
            grid: true,
            minimum: 0,
            maximum:200
        },{
            type: 'StackedNumeric',
            position: 'right',
            fields: ['data3'],
            label: {
                renderer: Ext.util.Format.numberRenderer('0,0')
            },
            //title: 'Number of Hits',
            grid: true,
            minimum: 0,
            maximum:200
        }, {
            type: 'Category',
            position: 'bottom',
            fields: ['name'],
            //categoryNames:new String("111"),
            title: 'Month of the Year'
        }],
        series: [{
            type: 'column',
            axis: 'left',
            //stacked:false,
            stacked:true,
            highlight: true,
            tips: {
              trackMouse: true,
              width: 140,
              height: 28,
              renderer: function(storeItem, item) {
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
              }
            },
            label: {
              display: 'insideEnd',
              'text-anchor': 'middle',
                field: ['data1','data2'],
                //renderer: Ext.util.Format.numberRenderer('0'),
                orientation: 'vertical',
                color: '#FFF'
            },
            style:{
            	opacity: 0.95
            	//,width:100
            },
            //xPadding:{left:100,right:100},
            xField: 'name',
            yField: ['data1','data2']
        }
        ,
        {
            type: 'line',
            axis: 'right',
            highlight: true,
            tips: {
              trackMouse: true,
              width: 140,
              height: 28,
              renderer: function(storeItem, item) {
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data3') + ' $');
              }
            },
            label: {
              display: 'insideEnd',
              'text-anchor': 'middle',
                field: 'data3',
                //renderer: Ext.util.Format.numberRenderer('0'),
                orientation: 'vertical',
                color: '#FFF'
            },
            style:{
            	opacity: 0.95
            	//,width:100
            },
            //xPadding:{left:100,right:100},
            xField: 'name',
            yField: 'data3'
        }]
    });


var win = Ext.create('Ext.window.Window', {
    width: 800,
    height: 600,
    minHeight: 400,
    minWidth: 550,
    hidden: false,
    maximizable: true,
    title: 'Column Chart',
    autoShow: true,
    layout: 'fit',
    tbar: [{
        text: 'Save Chart',
        handler: function() {
            Ext.MessageBox.confirm('Confirm Download', 'Would you like to download the chart as an image?', function(choice){
                if(choice == 'yes'){
                    chart.save({
                        type: 'image/png'
                    });
                }
            });
        }
    }, {
        text: 'Reload Data',
        handler: function() {
            // Add a short delay to prevent fast sequential clicks
            window.loadTask.delay(100, function() {
                store1.loadData(generateData());
            });
        }
    }],
    items: chart    
});
    
});
</script>
</head>
<body>

</body>
</html>





版权声明:本文博主原创文章。博客,未经同意不得转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值