DHTMLX-GANTT在vue2中一些实践

效果图

在这里插入图片描述

实现功能

  • 年月日维度调整
  • 回退恢复
  • 删除
  • 前进后退
  • 全屏
  • 等等…

代码

GanttComponent.vue

<template>
  <div ref="ganttContainer" style="width: 100%; height: calc(100% - 48px)"></div>
</template>

<script>
import { gantt } from "dhtmlx-gantt";
import 'dhtmlx-gantt/codebase/sources/skins/dhtmlxgantt_material.css'
export default {
  props: {
    tasks: {
      type: Object,
      default() {
        return {
          data: [

          ],
          collections: {
            links: [],
          }
        };
      },
    },
    config: {
      type: Object,
      default() {
        return {};
      },
    }
  },
  data() {
    return {
      styleName: "dhtmlxgantt_material", // 默认样式文件名称
      performAction: null,
      ganttEvent: {}
    };
  },
  watch: {
    config: {
      immediate: false,
      deep: true,
      handler(value) {
        gantt.config.scales = this.setScale(value.dataScale);
        gantt.render();
      },
    },
  },
  created() {
    this.start = performance.now();
  },
  mounted: function () {
    this.init();
  },
  methods: {
    init() {
      // 初始化事件
      this.initGanttEvents();
      // 配置
      this.setConfig();
      this.performAction = this.handleAction()
      // 初始化
      gantt.init(this.$refs.ganttContainer);
      // 解析数据
      gantt.parse(this.tasks);
      // 初始化数据处理
      this.initDataProcessor();
      this.end = performance.now();
      console.log("操作执行时间:", this.end - this.start, "毫秒");
    },
    // 配置信息
    setConfig() {
      // -------1、左侧区域--------
      gantt.config.grid_width = 300; //左侧宽
      gantt.config.autofit = false; //左侧是否自适应
      gantt.config.date_grid = "%F %d"; //左侧日期数据展示格式
      // --------1.1左侧区域头部-------
      // 设置表头
      this.setColumn();

      // 拖拽排序
      gantt.config.order_branch = true;
      gantt.config.order_branch_free = true;
      gantt.config.resize_rows = true;


      gantt.config.scales = this.setScale(this.config.dataScale);
      // gantt.config.row_height = 50;   //进度条容器高

      // 里程碑文本居右
      gantt.templates.rightside_text = function (start, end, task) {
        if (task.type == gantt.config.types.milestone) {
          return task.text;
        }
        return "";
      };
      // ===========通用配置(国际化,日期格式等)=======
      this.managePlugins();
      gantt.i18n.setLocale("cn");
      // 日期格式
      gantt.config.date_format = "%Y-%m-%d";
      gantt.config.readonly = false; //只读
      gantt.config.fit_tasks = true; //自动调整图表坐标轴区间用于适配task的长度
      gantt.config.wide_form = false; //  弹窗宽
    },
    // 设置右侧头部展示日期方式
    setScale(value) {
      gantt.config.scale_height = 100; //设置时间刻度的高度和网格的标题
      const weekScaleTemplate = function (date) {
        // 可以时使用dayjs 处理返回
        const dateToStr = gantt.date.date_to_str("%d");
        const mToStr = gantt.date.date_to_str("%M");
        const endDate = gantt.date.add(
          gantt.date.add(date, 1, "week"),
          -1,
          "day"
        );
        // 处理一下月份
        return `${dateToStr(date)} 号 - ${dateToStr(endDate)} 号 (${mToStr(
          date
        )})`;
      };
      const daysStyle = function (date) {

        if (date.getDay() === 0 || date.getDay() === 6) {
          return "weekend";
        }
        return "";
      };
      switch (value) {
        case "Days":
          return [
            { unit: "week", step: 1, date: "%Y年 %W周" },
            { unit: "day", step: 1, date: "%m-%d", css: daysStyle },
          ];
        case "Months":
          return [
            { unit: "month", step: 1, date: "%M" },
            { unit: "week", step: 1, date: "%W周" },
          ];
        case "Years":
          return [
            { unit: "year", step: 1, date: "%Y年" },
            { unit: "month", step: 1, date: "%M" },
          ];
        case "Week":
          return [
            { unit: "year", step: 1, date: "%Y" },
            { unit: "week", step: 1, format: weekScaleTemplate },
            { unit: "day", step: 1, date: "%D", css: daysStyle },
          ];
        default:
          return {};
      }
    },
    // 自定义表格列
    setColumn() {
      // gantt.config.columns = [
      //   {
      //     name: "text",
      //     label: "里程碑节点",
      //     width: 280,
      //     tree: true,
      //     template: function (obj) {
      //       return `节点:${obj.text}`; // 通过 template 回调可以指定返回内容值
      //     },
      //   },
      // ];
    },
    managePlugins() {
      // 插件
      gantt.plugins({
        click_drag: true,
        drag_timeline: true, // 拖动图
        marker: true, // 时间标记
        fullscreen: true, // 全屏
        tooltip: true, // 鼠标经过时信息
        undo: true, // 允许撤销
        multiselect: true, //eachSelectedTask可用
        auto_scheduling: true, //自动调度
        quick_info: false, //进度条点击展示快捷面板
      });
      // auto_scheduling管理
      gantt.config.work_time = true; //自动调度须设置一下
      gantt.config.min_column_width = 60;
      gantt.config.auto_scheduling = true;
      gantt.config.auto_scheduling_strict = true;

      // multiselect(如果不设置会重复触发请求)
      gantt.config.drag_multiple = false;
      gantt.config.multiselect = false;
      // 基线
      var dateToStr = gantt.date.date_to_str(gantt.config.task_date);
      let today = this.getEndOfDate(); // getEndOfDate 为获取今天结束时间的方法
      gantt.addMarker({
        start_date: today,
        css: "status_line",
        text: "今日",
      });
      var start = new Date(1907, 9, 9);
      gantt.addMarker({
        start_date: start,
        css: "projectStartDate",
        text: "开始时间",
        title: "开始时间: " + dateToStr(start),
      });
      // drag
      gantt.config.drag_links = true; //可连线
      gantt.config.drag_progress = true; // 进度条可拖拽
      // tooltip
      // 自定义tooltip内容
      gantt.templates.tooltip_text = function (start, end, task) {
        const t = gantt;
        const output = `<b>里程碑:</b>${task.text
          }<br/><b>计划开始时间:</b>${t.templates.tooltip_date_format(
            start
          )}<br/><b>计划结束时间:</b>${t.templates.tooltip_date_format(end)}`;
        return output;
      };
      gantt.ext.fullscreen.getFullscreenElement = function () {
        console.log(document.getElementById("main-gantt"))
        return document.getElementById("main-gantt");
      }
    },
    // 事件监听
    initGanttEvents: function () {
      if (!gantt.$_eventsInitialized) {
        // 选中
        this.ganttEvent.onTaskSelected = gantt.attachEvent("onTaskSelected", (id) => {
          let task = gantt.getTask(id);
          this.$emit("task-selected", task);
        });
        this.ganttEvent.onTaskIdChange = gantt.attachEvent("onTaskIdChange", (id, new_id) => {
          if (gantt.getSelectedId() == new_id) {
            let task = gantt.getTask(new_id);
            this.$emit("task-selected", task);
          }
        });
        // 线条click
        this.ganttEvent.onLinkClick = gantt.attachEvent("onLinkClick", function (id) {
          var link = this.getLink(id),
            src = this.getTask(link.source),
            trg = this.getTask(link.target),
            types = this.config.links;

          var first = "",
            second = "";
          switch (link.type) {
            case types.finish_to_start:
              first = "finish";
              second = "start";
              break;
            case types.start_to_start:
              first = "start";
              second = "start";
              break;
            case types.finish_to_finish:
              first = "finish";
              second = "finish";
              break;
            case types.start_to_finish:
              first = "start";
              second = "finish";
              break;
          }

          gantt.message(
            "Must " +
            first +
            " <b>" +
            src.text +
            "</b> to " +
            second +
            " <b>" +
            trg.text +
            "</b>"
          );
        });
        // 任务添加完成后钩子
        this.ganttEvent.onAfterTaskAdd = gantt.attachEvent("onAfterTaskAdd", function (id, item) {
          console.log(id, item);
        });
        // 任务删除后钩子
        this.ganttEvent.onAfterTaskDelete = gantt.attachEvent("onAfterTaskDelete", function (id, item) {
          console.log(id, item);
        });
        // 修改任务
        this.ganttEvent.onAfterTaskUpdate = gantt.attachEvent("onAfterTaskUpdate", (id, data) => {
          console.log(id, data);

        });
        // 监听进度拖拽事件
        this.ganttEvent.onTaskDrag = gantt.attachEvent("onTaskDrag", function (id, mode, e) {
          console.log('66666', id, mode, e)
          return true;
        });
        this.ganttEvent.onBeforeTaskDrag = gantt.attachEvent("onBeforeTaskDrag", function (id, mode, e) {
          console.log(id, mode, e)
          return true;
        });

        // 移动项目
        this.ganttEvent.onAfterTaskDrag = gantt.attachEvent("onAfterTaskDrag", function (id, mode, task, original) {
          console.log(id, mode, task, original)
          return true
        });
        // 用户完成拖动并释放鼠标
        this.ganttEvent.onAfterTaskChanged = gantt.attachEvent("onAfterTaskChanged", (id, mode, task) => {
          console.log(id, mode, task);

        });

        // 删除连接项目关系
        this.ganttEvent.onAfterLinkDelete = gantt.attachEvent("onAfterLinkDelete", (id, item) => {
          console.log(id, item);

        });
        // 修改连接项目关系
        this.ganttEvent.onAfterLinkUpdate = gantt.attachEvent("onAfterLinkUpdate", (id, item) => {
          console.log(id, item);

        });
        // 新增连接项目关系
        this.ganttEvent.onBeforeLinkAdd = gantt.attachEvent("onBeforeLinkAdd", (id, item) => {
          console.log(id, item);
        });

        // 弹窗打开前钩子(可禁用自带编辑弹窗)
        this.ganttEvent.onBeforeLightbox = gantt.attachEvent(
          "onBeforeLightbox",
          function (id) {
            console.log(id);
            return true; // 返回 false
          },
          {}
        );
        // 任务双击钩子
        this.ganttEvent.onTaskDblClick = gantt.attachEvent(
          "onTaskDblClick",
          function (id, e) {
            console.log("id", id, e);
            return true;
          },
          {}
        );
        // 展示tooltip
        this.ganttEvent.onGanttReady = gantt.attachEvent("onGanttReady", function () {
          var tooltips = gantt.ext.tooltips;
          tooltips.tooltip.setViewport(gantt.$task_data);
        });
        this.ganttEvent.onBeforeTaskAutoSchedule = gantt.attachEvent("onBeforeTaskAutoSchedule", function (task, predecessor) {
          // 在此处执行自定义操作
          console.log("Before auto-schedule for task:", task.text);
          console.log("Predecessor:", predecessor);
          // 返回 true 表示允许自动调度,返回 false 则取消自动调度
          return true;
        });
        gantt.$_eventsInitialized = true;
      }
    },
    // 数据变化监听
    initDataProcessor: function () {
      if (!gantt.$_dataProcessorInitialized) {
        gantt.createDataProcessor((entity, action, data, id) => {
          this.$emit(`${entity}-updated`, id, action, data);
        });
        gantt.$_dataProcessorInitialized = true;
      }
    },
    getEndOfDate() {
      const today = new Date();
      // 将日期设置为当天的开始
      today.setHours(23);
      today.setMinutes(59);
      today.setSeconds(59);
      today.setMilliseconds(999);
      return today;
    },
    // 进度条前进后退
    shiftTask(task_id, direction) {
      const task = gantt.getTask(task_id);
      task.start_date = gantt.date.add(task.start_date, direction, "day");
      task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
      gantt.updateTask(task.id);
    },
    handleAction() {
      const that = this
      const actions = {
        undo: function () {
          gantt.ext.undo.undo();
        },
        redo: function () {
          gantt.ext.undo.redo();
        },
        del: function (task_id) {
          if (gantt.isTaskExists(task_id)) gantt.deleteTask(task_id);
          return task_id;
        },
        moveForward: function (task_id) {
          that.shiftTask(task_id, 1);
        },
        moveBackward: function (task_id) {
          that.shiftTask(task_id, -1);
        }
      };
      const cascadeAction = {
        del: true
      };

      const singularAction = {
        undo: true,
        redo: true
      };

      gantt.performAction = function (actionName) {
        var action = actions[actionName];
        if (!action)
          return;

        if (singularAction[actionName]) {
          action();
          return;
        }

        gantt.batchUpdate(function () {

          // need to preserve order of items on indent/outdent,
          // remember order before changing anything:
          const indexes = {};
          const siblings = {};
          gantt.eachSelectedTask(function (task_id) {
            gantt.ext.undo.saveState(task_id, "task");
            indexes[task_id] = gantt.getTaskIndex(task_id);
            siblings[task_id] = {
              first: null
            };

            let currentId = task_id;
            while (gantt.isTaskExists(gantt.getPrevSibling(currentId)) && gantt.isSelectedTask(gantt.getPrevSibling(currentId))) {
              currentId = gantt.getPrevSibling(currentId);
            }
            siblings[task_id].first = currentId;
          });

          const updated = {};
          gantt.eachSelectedTask(function (task_id) {

            if (cascadeAction[actionName]) {
              if (!updated[gantt.getParent(task_id)]) {
                const updated_id = action(task_id, indexes, siblings);

                updated[updated_id] = true;
              } else {
                updated[task_id] = true;
              }
            } else {
              action(task_id, indexes);
            }
          });
        });
      };
      return gantt.performAction
    }
  },
  destroyed() {
    // 销毁gantt事件
    for (let i in this.ganttEvent) {
      gantt.detachEvent(this.ganttEvent[i])
    }
    gantt.ext.tooltips.tooltip.hide();
  }
};
</script>

<style>
.weekend {
  background: #f4f7f4 !important;
}

.gantt_selected .weekend {
  background: #fff3a1 !important;
}

.projectStartDate,
.projectEndDate {
  background-color: #0dd1eb !important;
}

.gantt_row_project {
  font-weight: bold;
}
</style>

index.vue

<template>
  <div class="container" id="main-gantt">
    <div class="tools-list">
      <div class="tool-button">{{ "仅供学习" }} <a
          href="https://gitee.com/lht1132950411/blog/blob/master/examples/Dhtmlx_gantt.zip">资源地址,感谢提供包的大佬</a></div>
      <div class="data-scale">
        <a-select v-model="config.dataScale" style="width: 50px">
          <a-select-option value="Days"></a-select-option>
          <a-select-option value="Week"></a-select-option>
          <a-select-option value="Months"></a-select-option>
          <a-select-option value="Years"></a-select-option>
        </a-select>
      </div>
      <a-button title="回退" @click="undo" :loading="undoLoading" class="tool-button">
        <a-icon type="undo" v-if="!undoLoading" />
      </a-button>
      <a-button title="重做" @click="redo" :loading="redoLoading" class="tool-button">
        <a-icon type="redo" v-if="!redoLoading" />
      </a-button>
      <a-button title="删除" @click="del" :loading="delLoading" class="tool-button">
        <a-icon type="delete" v-if="!delLoading" />
      </a-button>
      <a-button-group class="tool-button">
        <a-button title="左移" @click="moveBackward" :loading="moveBackwardLoading"> <a-icon type="left"
            v-if="!moveBackwardLoading" /></a-button>
        <a-button title="右移" @click="moveForward" :loading="moveForwardLoading"> <a-icon type="right"
            v-if="!moveForwardLoading" /> </a-button>
      </a-button-group>
      <a-button title="全屏" @click="fullscreen" class="tool-button">
        <a-icon type="fullscreen" />
      </a-button>
    </div>
    <!-- <a-spin :spinning="spinning">
      <div class="spin-content">
        <GanttComponent ref="gantt" :tasks="tasks" :config="config" @task-updated="taskUpdate"
          @link-updated="linkUpdate" @task-selected="selectTask">
        </GanttComponent>
      </div>
    </a-spin> -->
    <GanttComponent ref="gantt" :tasks="tasks" :config="config" @task-updated="taskUpdate" @link-updated="linkUpdate"
      @task-selected="selectTask">
    </GanttComponent>
  </div>
</template>

<script>
import GanttComponent from "./GanttComponent.vue";
const axios = require('axios');
export default {
  name: "app",
  components: { GanttComponent },
  computed: {
    ganttInstance() {
      return this.$refs.gantt.$refs.ganttContainer.gantt
    }
  },
  data() {
    return {
      value: "",
      tasks: {
        "data": [
          {
            "id": 5,
            "text": "Task #1.1",
            "start_date": "2017-04-07 00:00:00",
            "duration": 7,
            "progress": 0.34,
            "parent": 1,
            "sortorder": 2,
            "open": true
          },
          {
            "id": 2,
            "text": "Task #1",
            "start_date": "2017-04-11 00:00:00",
            "duration": 4,
            "progress": 0.639881,
            "parent": 1,
            "sortorder": 3,
            "open": true
          },
          {
            "id": 1,
            "text": "Project #1",
            "start_date": "2017-04-07 00:00:00",
            "duration": 6,
            "progress": 0.8,
            "parent": 0,
            "sortorder": 7,
            "open": true
          },
          {
            "id": 4,
            "text": "Task #3",
            "start_date": "2017-04-12 00:00:00",
            "duration": 4,
            "progress": 0,
            "parent": 1,
            "sortorder": 7,
            "open": true
          },
          {
            "id": 15,
            "text": "新任无",
            "start_date": "2017-04-11 00:00:00",
            "duration": 1,
            "progress": 0,
            "parent": 0,
            "sortorder": 11,
            "open": true
          },
          {
            "id": 18,
            "text": "新任",
            "start_date": "2017-04-11 00:00:00",
            "duration": 4,
            "progress": 0,
            "parent": 0,
            "sortorder": 22,
            "open": true
          }
        ],
        "collections": {
          "links": [
            {
              "id": 20,
              "source": 15,
              "target": 18,
              "type": "1"
            }
          ]
        }
      },
      selectedTask: null,
      config: {
        dataScale: "Week",
      },
      undoPageData: false,
      spinning: false,
      undoLoading: false,
      redoLoading: false,
      indentLoading: false,
      outdentLoading: false,
      delLoading: false,
      moveBackwardLoading: false,
      moveForwardLoading: false,
      loadingKey: ''
    };
  },

  mounted() {
    // this.loadData()
  },
  methods: {
    loadData() {
      const that = this
      that.spinning = true
      axios.get('api/data')
        .then(function (response) {
          // 处理成功情况
          that.tasks = response.data
          that.ganttInstance.parse(that.tasks)
          that.ganttInstance.refreshData()
        })
        .catch(function (error) {
          // 处理错误情况
          console.log(error);
        })
        .finally(function () {
          // 总是会执行
          that.spinning = false
        });
    },
    reqHandle(mode) {
      let method = ''
      let message = ''
      let errorMessage = ''
      switch (mode) {
        case "update":
          method = 'put'
          message = '更新成功'
          errorMessage = '更新失败'
          break;
        case "create":
          console.log('create')
          method = 'post'
          message = '新增成功'
          errorMessage = '新增失败'
          break;
        case "delete":
          method = 'delete'
          message = '删除成功'
          errorMessage = '删除失败'
          break;
        default:
          break;
      }
      return { method, message, errorMessage }
    },
    // 任务更新
    taskUpdate(id, mode, task) {
      // const that = this
      // that.loadingKey && (that[that.loadingKey + 'Loading'] = true)
      // // 仅回滚页面,则直接返回
      // if (that.undoPageData) {
      //   return
      // }

      // const { method, message, errorMessage } = that.reqHandle(mode)
      // axios[method](`api/data/task/${method != 'post' ? id : ''}`, task)
      //   .then(function (response) {
      //     console.log('then-----------')
      //     if (response.data.action == 'error') {
      //       throw new Error()
      //     }
      //     // 处理成功情况
      //     that.$message.success(message)

      //   })
      //   .catch(function (error) {
      //     console.log('catch-----------')
      //     // 处理错误情况
      //     that.$confirm({
      //       title: errorMessage,
      //       content: '请求失败是否回滚页面数据,以保持数据一致?',
      //       onOk() {
      //         return new Promise((resolve) => {
      //           that.undoPageData = true
      //           that.undo()
      //           resolve()
      //         }).then(() => { that.undoPageData = false });
      //       },
      //       onCancel() {
      //         that.undoPageData = false
      //       },
      //     });
      //     console.log(error);
      //   })
      //   .finally(function () {
      //     // 总是会执行
      //     that.loadingKey && (that[that.loadingKey + 'Loading'] = false)
      //     that.loadingKey = ''
      //   });

    },
    // 连线更新
    linkUpdate(id, mode, link) {
      // const that = this
      // // 仅回滚页面,则直接返回
      // if (that.undoPageData) {
      //   return
      // }
      // const { method, message, errorMessage } = that.reqHandle(mode)

      // axios[method](`api/data/link/${method != 'post' ? id : ''}`, link)
      //   .then(function (response) {
      //     console.log('then-----------')
      //     if (response.data.action == 'error') {
      //       throw new Error()
      //     }
      //     // 处理成功情况
      //     that.$message.success(message)

      //   })
      //   .catch(function (error) {
      //     console.log('catch-----------')
      //     // 处理错误情况
      //     that.$confirm({
      //       title: errorMessage,
      //       content: '请求失败是否回滚页面数据,以保持数据一致?',
      //       onOk() {
      //         return new Promise((resolve) => {
      //           that.undoPageData = true
      //           that.undo()
      //           resolve()
      //         }).then(() => { that.undoPageData = false });
      //       },
      //       onCancel() {
      //         that.undoPageData = false
      //       },
      //     });
      //     console.log(error);
      //   })
      //   .finally(function () {
      //     // 总是会执行
      //   });
    },
    // 获取当前选中任务
    selectTask: function (task) {
      console.log('selected', task)
      this.selectedTask = task;
    },
    undo() {
      this.loadingKey = 'undo'
      this.$refs.gantt.performAction('undo')
    },
    redo() {
      this.loadingKey = 'redo'
      this.$refs.gantt.performAction('redo')
    },
    indent() {
      this.loadingKey = 'indent'
      this.$refs.gantt.performAction('indent')
    },
    outdent() {
      this.loadingKey = 'outdent'
      this.$refs.gantt.performAction('outdent')
    },
    del() {
      this.loadingKey = 'del'
      this.$refs.gantt.performAction('del')
    },
    moveForward() {
      this.loadingKey = 'moveForward'
      this.$refs.gantt.performAction('moveForward')
    },
    moveBackward() {
      this.loadingKey = 'moveBackward'
      this.$refs.gantt.performAction('moveBackward')
    },
    fullscreen() {
      this.ganttInstance.ext.fullscreen.toggle()
    },
  },
};
</script>

<style lang="scss" scoped>
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  height: 100vh;
  width: 100%;
}

#main-gantt {
  z-index: 9999 !important;
  background-color: #fff;
}

.left-container {
  overflow: hidden;
  position: relative;
  height: 100%;
}

.right-container {
  border-right: 1px solid #cecece;
  float: right;
  height: 100%;
  width: 340px;
  box-shadow: 0 0 5px 2px #aaa;
  position: relative;
  z-index: 2;
}

.gantt-messages {
  list-style-type: none;
  height: 50%;
  margin: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding-left: 5px;
}

.gantt-messages>.gantt-message {
  background-color: #f4f4f4;
  box-shadow: inset 5px 0 #d69000;
  font-family: Geneva, Arial, Helvetica, sans-serif;
  font-size: 14px;
  margin: 5px 0;
  padding: 8px 0 8px 10px;
}

.gantt-selected-info {
  border-bottom: 1px solid #cecece;
  box-sizing: border-box;
  font-family: Geneva, Arial, Helvetica, sans-serif;
  height: 50%;
  line-height: 28px;
  padding: 10px;
}

.gantt-selected-info h2 {
  border-bottom: 1px solid #cecece;
}

.select-task-prompt h2 {
  color: #d9d9d9;
}

.data-scale {
  padding: 8px 0;
  margin-right: 16px;
}

.tools-list {
  display: flex;
  justify-content: right;
  align-items: center
}

.tool-button {
  margin-right: 16px;
}

::v-deep.ant-btn {
  padding: 0 8px !important;
}
</style>

资源地址

github地址
dhtmlx-gantt文档地址
node版本为15.0.0

声明

仅供学习参考喔

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值