formItem

目录结构

 1,CRForm

<template>
  <RightClickCopy class="CRForm">
    <CRFormItem
      v-for="(item, index) in items"
      :key="index"
      :item="item"
      :row="rowMap[index]"
      :col="colMap[index]"
      v-model="model"
      :rowCountMap="rowCountMap"
      :colNumber="colNumber"
      :label-width="labelWidth"
    >
      <template v-if="item.slot" :slot="item.slot">
        <slot :name="item.slot"></slot>
      </template>
    </CRFormItem>
  </RightClickCopy>
</template>

<script lang="ts">
import Vue, { PropType } from "vue";
import CRFormItem from "@/pc/views/CreditRating/components/CRForm/CRFormItem.vue";
import RightClickCopy from "@/pc/components/RightClickCopy.vue";

export interface CRFormItemOptions {
  key: string;
  label: string;
  colSpan?: number; // 1
  rowSpan?: number; //1
  type?:
    | "text"
    | "highlightText"
    | "input"
    | "select"
    | "radio"
    | "textarea"
    | "durationInput"
    | "durationNumber";
  required?: boolean;
  message?: string; // 报错提示的信息
  validator?: (val: any) => boolean;
  highlightColor?: string; //highlightText 需要填
  options?: Array<{ value: any; label: string }>; //select|radio 需要给
  slot?: string; // 可自定义内容
  labelTitle?: string; // 标签title
  editType?: string; // 编辑的类型
  dateFormat?: string; // 日期文本格式
  bigNumber?: boolean; // 是否是大数字,大数字千分位格式化
  longText?: boolean; // 是否是长文本
  maxLength?: number; // 最大长度
  textareaMaxLength?: number; // 多行文本框最大长度
  formatter?: (val: any) => string; // 自定义格式化
  labelSlot?: string; // label slot
  ifIsError?: boolean;
  placeholder?: string;
  disabled?: boolean;
  filterable?: boolean;
  clearable?: boolean;
  labelTips?: string; // label提示
  labelTipsIcon?: string; // label提示icon
}
export default Vue.extend({
  name: "CRForm",
  components: { CRFormItem, RightClickCopy },
  props: {
    colNumber: {
      //列数
      type: Number,
      default: 3
    },
    items: {
      type: Array as PropType<Array<CRFormItemOptions>>,
      required: true
    },
    value: {
      type: Object as PropType<Record<string, any>>
    },
    labelWidth: {
      type: String,
    }
  },
  data() {
    const model = {} as Record<string, any>;
    const value = this.value;
    for (const k in value) {
      model[k] = value[k];
    }
    return {
      model: model
    };
  },
  watch: {
    value: {
      deep: true,
      handler(v: Record<string, any>) {
        for (const k in v) {
          this.model[k] = v[k];
        }
      },
      immediate: true
    },
    model: {
      deep: true,
      handler(v: Record<string, any>) {
        this.$emit("input", v);
      }
    }
  },
  computed: {
    rowMap(): Record<number, number> {
      const rowMap: Record<number, number> = {};
      let t = 0;
      let row = 0;
      this.items.forEach((d, i) => {
        const colSpan = d.colSpan ?? 1;
        if (t + colSpan > this.colNumber) {
          row++;
          t = 0;
        }
        t += colSpan;
        rowMap[i] = row;
      });
      return rowMap;
    },
    colMap(): Record<number, number> {
      const colMap: Record<number, number> = {};
      let t = 0;
      this.items.forEach((d, i, arr) => {
        const colSpan = d.colSpan ?? 1;
        colMap[i] = t + colSpan > this.colNumber ? 0 : t % this.colNumber;
        t = colMap[i] + colSpan;
      });
      return colMap;
    },
    rowCountMap(): Record<number, number> {
      const rowCountMap: Record<number, number> = {};
      let r: number | undefined = undefined;
      let count = 0;
      this.items.forEach((d, i) => {
        const row = this.rowMap[i];
        if (r === undefined) {
          r = row;
          count = 1;
          return;
        }
        if (r === row) {
          count++;
        } else {
          rowCountMap[r] = count;
          count = 1;
          r = row;
        }
      });
      if (r !== undefined) {
        rowCountMap[r] = count;
      }
      return rowCountMap;
    }
  },
  methods: {
    validate(): any {
      return new Promise((resolve, reject) => {
        const { items, model } = this;
        for (let i = 0; i < items.length; i++) {
          const value = model[items[i].key];
          let validate = true;
          if (items[i].required) {
            if (items[i].validator) {
              validate = (items[i] as any).validator(value);
            } else if (!value && value !== 0) {
              validate = false;
            }
            if (!validate) {
              const msg = items[i].message || "字段" + items[i].label + "为空或不符";
              this.$message.error(msg);
              reject();
              return;
            }
          }
          //长度校验
          const maxLength = items[i].maxLength || 0;
          if (maxLength && value && value.length > maxLength) {
            this.$message.error(`字段${items[i].label}最多输入${maxLength}个字符`);
            reject();
            return;
          }
        }
        resolve(true);
      });
    }
  }
});
</script>

<style scoped lang="less">
.CRForm {
  display: flex;
  flex-wrap: wrap;
}
</style>

2. CRFormItem

<template>
  <div
    class="CRFormItem"
    :style="itemStyles"
    :data-row="row"
    :data-col="col"
    :class="{ 'is-error': isError }"
  >
    <div class="label" :title="item.labelTitle" :style="{width: labelWidth}">
      <span class="text">{{ item.label }}</span>
      <span v-if="item.labelTips" class="required" :title="item.labelTips">
        <i :class="item.labelTipsIcon || 'el-icon-question'" />
      </span>
      <template v-if="type !== 'text' && type !== 'highlightText' && type !== 'tag'">
        <span v-if="item.required" class="required" title="必填项">
          <i class="el-icon-warning" />
        </span>

<!--        <span v-else class="not-required" title="选填项">-->
<!--          <i class="el-icon-info" />-->
<!--        </span>-->
      </template>
    </div>
    <div class="field" :id="'field-' + item.key">
      <template v-if="item.slot">
        <slot :name="item.slot"></slot>
      </template>
      <template v-else>
        <template v-if="type === 'text'">
          <div :title="item.longText ? '' : itemText" class="field-text" :style="getLineClamp(item)" @dblclick="showTextDialog" :data-text="itemText">
            {{ itemText }}
          </div>
        </template>

        <template v-if="type === 'highlightText'">
          <div class="field-text highlight" :style="getLineClamp(item)" :title="item.longText ? '' : itemText" @dblclick="showTextDialog" :data-text="itemText">
            <span :style="{ fontWeight: 700, color: item.highlightColor }">{{itemText}}</span>
          </div>
        </template>

        <template v-if="type === 'tag'">
          <div
            :title="item.longText ? '' : itemText"
            class="field-text"
            :style="getLineClamp(item)"
            @dblclick="showTextDialog"
            :data-text="itemText"
          >
            <el-tag size="mini" color="#ff0000" style="font-weight: bold; color: #ffffff">
              {{ itemText }}
            </el-tag>
          </div>
        </template>

        <template v-if="type === 'number'">
          <el-input-number :controls="false"  size="mini" v-model="model[item.key]" :precision="item.precision" clearable
            :placeholder="item.placeholder || '请输入'"
            :disabled="item.disabled || false"
          >
          </el-input-number>

        </template>


        <template v-if="type === 'input'">
          <el-input size="mini" v-model="model[item.key]" clearable :placeholder="item.placeholder || '请输入'" :disabled="item.disabled || false">
            <template slot="append" v-if="item.append">{{ item.append }}</template>
          </el-input>
        </template>
        <template v-if="type === 'date'">
          <el-date-picker
            v-model="model[item.key]"
            type="date"
            size="mini"
            format="yyyy-MM-dd"
            :value-format="item.valueFormat"
            placeholder="选择日期"
            :disabled="item.disabled || false"
            clearable>
          </el-date-picker>
        </template>
        <template v-if="type === 'textarea'">
          <el-input
            type="textarea"
            size="mini"
            resize="none"
            v-model="model[item.key]"
            :disabled="item.disabled || false"
            :autosize="getTextAreaRows(item.rowSpan)"
            :maxLength="item.textareaMaxLength || null"
          />
        </template>
        <template v-if="type === 'select'">
          <el-select size="mini" v-model="model[item.key]" :filterable="item.filterable || false" :clearable="item.clearable" :placeholder="item.placeholder" :disabled="item.disabled || false">
            <el-option
              v-for="(optionItem, index) in item.options"
              :key="index"
              :label="optionItem.label"
              :value="optionItem.value"
            />
          </el-select>
        </template>

        <template v-if="type === 'multiselect'">
          <el-select collapse-tags size="mini" filterable multiple v-model="model[item.key]" clearable :disabled="item.disabled || false">
            <el-option
              v-for="(optionItem, index) in item.options"
              :key="index"
              :label="optionItem.label"
              :value="optionItem.value"
            />
          </el-select>
        </template>

        <template v-if="type === 'radio'">
          <el-radio-group size="mini" v-model="model[item.key]" :disabled="item.disabled || false">
            <el-radio
              v-for="(optionItem, index) in item.options"
              :key="index"
              :label="optionItem.value"
            >
              {{ optionItem.label }}
            </el-radio>
          </el-radio-group>
        </template>
        <template v-if="type === 'durationInput'">
          <div class="durationInput">
            <el-input size="mini" v-model="model[item.key][0]" clearable />
            <span>~</span>
            <el-input size="mini" v-model="model[item.key][1]" clearable />
          </div>
        </template>
        <template v-if="type === 'durationNumber'">
          <div class="durationNumber">
            <el-input-number
              v-model="model[item.key][0]"
              :min="0"
              size="mini"
              clearable
            ></el-input-number>
            <!-- <el-input size="mini" v-model="model[item.key][0]" clearable /> -->
            <span>~</span>
            <el-input-number
              v-model="model[item.key][1]"
              :min="0"
              size="mini"
              clearable
            ></el-input-number>
          </div>
        </template>

        <!--      <Renderer-->
        <!--        :render="fieldRender"-->
        <!--        :props="item"-->
        <!--      />-->
      </template>
    </div>
    <el-dialog :title="item.label" :visible.sync="textDialogVisible">
      <div class="long-text-content" @contextmenu.prevent.stop="onRightClick">
        {{ textDialogContent }}
      </div>
    </el-dialog>
  </div>
</template>

<script lang="tsx">
import Vue, { PropType } from "vue";
import { CRFormItemOptions } from "@/pc/views/CreditRating/components/CRForm/CRForm.vue";
import {copyToClipboard, EMPTY_CHAR, isEmpty} from "@/pc/utils";
import moment from "moment";
import { numberFormatter } from "@/pc/utils";

export default Vue.extend({
  name: "CRFormItem",
  components: {
    // Renderer
  },
  props: {
    item: {
      type: Object as PropType<CRFormItemOptions>,
      required: true
    },
    labelWidth: {
      type: String,
      default: "160px"
    },
    col: {
      type: Number,
      required: true
    },
    row: {
      type: Number,
      required: true
    },
    colNumber: {
      type: Number,
      required: true
    },
    value: {
      type: Object as PropType<Record<string, any>>
    },
    rowCountMap: {
      type: Object as PropType<Record<number, number>>,
      required: true
    }
  },
  data() {
    const model = {} as Record<string, any>;
    const value = this.value;
    for (const k in value) {
      model[k] = value[k];
    }
    return {
      model: model,
      textDialogVisible: false,
      textDialogContent: ""
    };
  },
  watch: {
    value: {
      deep: true,
      handler(v: Record<string, any>) {
        for (const k in v) {
          this.model[k] = v[k];
        }
      },
      immediate: true
    },
    model: {
      deep: true,
      handler(v: Record<string, any>) {
        this.$emit("input", v);
      }
    },
  },
  computed: {
    isError() {
      const value = this.model[this.item.key];
      if (this.item.required) {
        if (value === "" || value === null || value === undefined) {
          return this.item.ifIsError ? false : true;
          //return true;
        }
      }

      if (this.item.validator) {
        if (!this.item.validator(value)) {
          return true;
        }
      }
      return false;
    },
    itemStyles(): any {
      const baseHeight = 30;
      const rowSpan = this.item.rowSpan ?? 1;
      const colSpan = Math.min(this.colNumber, this.item.colSpan ?? 1);
      return {
        height: `${baseHeight * rowSpan}px`,
        width: `calc((100% - ${this.rowCountMap[this.row] + 1}px)/${this.colNumber}*${colSpan})`
      };
    },
    type(): CRFormItemOptions["type"] {
      return this.item.type ?? "text";
    },
    itemText(): string {
      const { item } = (this as any);
      const value = (this as any).model[item.key];
      if (item.formatter) {
        return item.formatter(value);
      }
      if (item.editType === "select" || item.editType === "radio") {
        const options = item.options || [];
        for (let i = 0; i < options.length; i++) {
          if (options[i].value === value) {
            return options[i].label;
          }
        }
      }
      if (item.editType === "multiselect" ) {
        const options = item.options || [];
        const labels : any = []
        if(value.length>0){
          value.forEach(item => {
            for (let i = 0; i < options.length; i++) {
              if (options[i].value === item) {
                labels.push(options[i].label);
              }
            }
          })
          return labels.join(',')
        } else {
          return "-"
        }

      }
      if (item.editType === "date") {
        if (value) {
          return moment(value).format(item.dateFormat || "YYYY-MM-DD");
        }
      }
      if (item.bigNumber) {
        return this.formatBigNumber(value);
      }
      return this.formatter(value);
    }
  },
  methods: {
    showTextDialog(): any {
      if (!this.item.longText) {
        return;
      }
      this.textDialogVisible = true;
      this.textDialogContent = this.model[this.item.key];
    },
    formatBigNumber(value: number): any {
      return numberFormatter(value);
    },
    getTextAreaRows(rowSpan: any): any {
      rowSpan = rowSpan || 1;
      rowSpan = parseInt(((rowSpan * 30 - 10) / 20) + "");
      return { minRows: rowSpan, maxRows: rowSpan }
    },
    getLineClamp(item: any): any {
      if (item.longText) {
        // 长文本加滚动条
        return {height: "100%", width: "100%", whiteSpace: "normal", overflow: "auto"};
      }
      let rowSpan = item.rowSpan || 1;
      rowSpan = parseInt(((rowSpan * 30 - 10) / 20) + "");
      const height = rowSpan * 20;
      return {"-webkit-line-clamp":  rowSpan, height: `${height}px`};
    },
    formatter(val: any) {
      if (isEmpty(val)) return EMPTY_CHAR;
      return val;
    },
    getItemText(value, item): string {
      if (item.formatter) {
        return item.formatter(value);
      }
      if (item.editType === "select" || item.editType === "radio") {
        const options = item.options || [];
        if(value == null || value === ""){
          return "-"
        }
        for (let i = 0; i < options.length; i++) {
          if (options[i].value === value) {
            return options[i].label;
          }
        }
      }
      if (item.editType === "multiselect" ) {
        const options = item.options || [];
        const labels : any = []
        if(value.length>0){
          value.forEach(item => {
            for (let i = 0; i < options.length; i++) {
              if (options[i].value === item) {
                labels.push(options[i].label);
              }
            }
          })
          return labels.join(',')
        } else {
          return "-"
        }

      }
      if (item.editType === "date") {
        if (value) {
          return moment(value).format(item.dateFormat || "YYYY-MM-DD");
        }
      }
      if (item.bigNumber) {
        return this.formatBigNumber(value);
      }
      return this.formatter(value);
    },
    onRightClick(e) {
      const item = (this as any).item;
      // 文本,双击复制
      if (item.type === "text" || item.type === "highlightText") {
        const value = this.model[item.key];
        if (value || value == 0) {
          if (copyToClipboard(value)) {
            this.$message.info("复制成功");
          } else {
            this.$message.error("复制失败");
          }
        }
      }
    },
  }
});
</script>

<style scoped lang="less">
@border-style: 1px solid rgb(235, 238, 245);
.CRFormItem {
  display: flex;
  align-items: stretch;
  font-size: 12px;
  line-height: 14px;
  flex-wrap: nowrap;
  //box-sizing: border-box;
  border: @border-style;
  //border-top: none;
  //border-left: none;
  &[data-row="0"] {
    margin-top: 0;
  }
  &[data-col="0"] {
    margin-left: 0;
  }
  /deep/.el-input-number .el-input__inner{
    text-align: left;
  }
  margin-top: -1px;
  margin-left: -1px;
  &.is-error {
    ::v-deep .el-input__inner,
    ::v-deep .el-textarea__inner {
      border-color: #731e00 !important;
    }
  }
  > .label {
    background: #f8f8f8;
    color: #731e00;
    padding: 0 10px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    flex-shrink: 0;
    flex-grow: 0;
    .text {
      flex-grow: 1;
      min-width: 0;
    }
    .required {
      color: red;
      flex-shrink: 0;
    }
    .not-required {
      color: rgb(245, 169, 75);
      flex-shrink: 0;
    }
  }
  > .field {
    border-left: @border-style;
    padding: 0 5px;
    min-width: 0;
    flex-grow: 1;
    display: flex;
    align-items: center;
    > * {
      width: 100%;
    }
    .field-text {
      line-height: 20px;
      overflow: hidden;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      height: calc(100% - 10px);
    }
    .highlight {
      color: #731e00;
    }
  }
  .durationInput {
    display: inline-block;
    > span {
      text-align: center;
      margin: 0 4px;
    }
    ::v-deep .el-input {
      width: 30%;
    }
  }
  ::v-deep .durationNumber {
    display: flex;
    align-items: center;
    & > div {
      flex: 1;
      & > span {
        display: none;
      }
    }
    & > span {
      padding: 0 5px;
    }
    .el-input__inner {
      padding-left: 4px;
      padding-right: 4px;
    }
  }
}
.long-text-content {
  font-size: 12px;
  line-height: 16px;
  max-height: 600px;
  overflow: auto;
}
</style>

3. demo

<template>
  <div style="background: white;height: 100%;padding: 10px;box-sizing: border-box">
    crform
    <div>
      <template v-for="i in 4">
        <input type="radio" :id="i + 1" :value="i + 1" v-model.number="colNumber" :key="i" />
        {{ i + 1 }}
      </template>
    </div>
    <CRForm :items="items" :col-number="colNumber" :model="model" />

    <br />
    <CRForm :items="items1" :col-number="colNumber" :model="model1" />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import CRForm, { CRFormItemOptions } from "@/pc/views/CreditRating/components/CRForm/CRForm.vue";
export default Vue.extend({
  name: "CRForm_Demo",
  components: { CRForm },
  data() {
    const model: Record<string, any> = {};
    const items: Array<CRFormItemOptions> = [...new Array(11)].map((d, i) => {
      model[i] = `value${i}`;
      return {
        key: `${i}`,
        label: `label${i}`
      };
    });

    const model1: Record<string, any> = {};
    const items1: Array<CRFormItemOptions> = [...new Array(11)].map((d, i) => {
      model1[i] = `value${i}`;
      let type;
      let options;
      if (i === 4) {
        type = "textarea";
      }
      if (i === 3) {
        type = "input";
      }

      if (i === 2) {
        type = "select";
        model1[i] = 0;
        options = Array.from({ length: 4 }, (d, i) => {
          return {
            value: i,
            label: `label${i}`
          };
        });
      }

      if (i === 6) {
        type = "radio";
        model1[i] = 0;
        options = Array.from({ length: 3 }, (d, i) => {
          return {
            value: i,
            label: `label${i}`
          };
        });
      }
      let highlightColor;
      if (i === 8) {
        type = "highlightText";
        highlightColor = "blue";
      }
      let colSpan = 1;
      if (i===9) {
        colSpan = 2;
      }
      if (i===4) {
        colSpan =3;
      }
      if (i === 10) {
        console.log("aaa")
        type = "durationInput";
        model1[i] = ["", ""];
      }
      return {
        key: `${i}`,
        label: i == 4 ? "基础资产包括信托计划,私募基金,资管产品及其收权益" : `label${i}`,
        colSpan,
        type,
        rowSpan: i === 4 ? 2 : 1,
        required: i % 2 === 0,
        options,
        highlightColor
      };
    });

    return {
      colNumber: 3,
      items,
      model,
      model1,
      items1
    };
  }
});
</script>

<style scoped></style>

4. 使用

  <CRForm :items="items" :col-number="2" v-model="form" label-width="130px">
      <div slot="quotation">
        <SpecilInput
          :disabled="disabled"
          v-model="form.quotation"
          :palceHolders="['最低', '最高']"
          :percentageFlag="percentageFlag"
        />
      </div>
      <div slot="insDate">
        <AmDateRangePicker
          :disabled="disabled"
          format="yyyy-MM-dd"
          value-format="yyyy-MM-dd"
          v-model="form.insDate"
          class="label-item"
          pickerWidth="248px"
        />
      </div>
      <div slot="tradeAmt">
        <div class="tradeAmt">
          <el-input
            size="mini"
            @input="tradeAmtIput"
            v-model="form.tradeAmt"
            placeholder="输入金额"
            type="number"
          ></el-input>
        </div>
      </div>
    </CRForm>
<script>
   export default {
 computed: {
    disabled() {
      return this.diaType == "update" && this.ifDisabled;
    },
    items() {
      return [
        {
          label: "券面金额(万元)",
          key: "tradeAmt",
          colSpan: 1,
          rowSpan: 1,
          required: true,
          type: "input",
          slot: "tradeAmt",
          ifIsError: true,
        },
        {
          label: "交易方向",
          key: "insType",
          colSpan: 1,
          rowSpan: 1,
          type: "text",
          required: true,
        },
        {
          label: "报价类型",
          key: "quotationType",
          colSpan: 1,
          rowSpan: 1,
          type: "select",
          disabled: this.disabled,
          options: this.offerTypeOptions,
        },
        {
          label: "收益率",
          key: "quotation",
          colSpan: 1,
          rowSpan: 1,
          slot: "quotation",
          disabled: this.disabled,
          type: "durationNumber",
        },

        {
          label: "指令有效期",
          key: "insDate",
          colSpan: 2,
          rowSpan: 1,
          type: "date",
          slot: "insDate",
          required: true,
        },
        {
          label: "清算速度",
          key: "clearMethod",
          colSpan: 1,
          rowSpan: 1,
          type: "select",
          required: false,
          disabled: this.disabled,
          options: [{ label: "不限", value: "不限" }, ...this.fuzzyBuyOptions.DEALCLEARMETHODDEAL],
        },
        {
          label: "指定交易员",
          key: "fixincomeTrader",
          colSpan: 1,
          rowSpan: 1,
          type: "select",
          clearable: true,
          required: false,
          disabled: this.disabled,
          options: [...this.fuzzyBuyOptions.TEADERS],
        },
        {
          label: "备注",
          key: "remarks",
          colSpan: 3,
          rowSpan: 1,
          disabled: this.disabled,
          type: "input",
        },
      ];
    },
  },
  data() {
    return {
      initFlagCount: 1,
      tradeAmtFlag: false,
      insDate: [], //指令有效期
      percentageFlag: true,
      offerTypeOptions: [
        { label: "净价", value: "cleanPrice" },
        { label: "全价", value: "dirtyPrice" },
        { label: "收益率", value: "yield" },
      ],
      form: {
        insType: "卖出", //交易方向
        insDate: [], //指令有效期 insDateStart  insDateEnd
        tradeAmt: "", //券面金额
        quotationType: "yield", //报价类型,
        quotation: [], //净价  quotationTypeStart quotationTypeEnd
        clearMethod: "不限", //清算速度
        fixincomeTrader: "", //指定交易员
        remarks: "", // 备注
      },
    };
  },
}
</script>

5. 效果

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

好喝的西北风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值