vuejs+.netcore开发使用kind-editor富文本编辑

目标:

1.vue中支持kindeditor

2.kindeditor上传及文件管理接口要使用.net core api,并需要支持token

一、下载kindeditor

二、将kindeditor整个文件夹复制到vue项目的public/static文件夹下(可以只保留其中的css和img文件),将kindeditor的关联主题样式拷贝到assets下

三、新建vue-kind-editor组件,将kindeditor中的lang、themes、kindeditor-all.js、kindeditor-all.min.js 拷贝到组件文件夹下,新建index.vue

index.vue代码,注意:token(新增)、pluginsPath(需指向static文件夹)、uploadJson、fileManagerJson属性

<template>
  <textarea :id="id" name="content" v-model="outContent"></textarea>
</template>

<script>
import "@/assets/kindeditor/themes/default/default.css";
import "./kindeditor-all.min.js";
import "./lang/zh-CN.min.js";
import store from "@/store";
export default {
  name: "vue-kind-editor",
  data() {
    return {
      editor: null,
      outContent: this.content,
    };
  },
  props: {
    header: {
      type: Object,
      default: () => ({}),
    },
    token: {
      type: String,
      default: store.getters.token,
    },
    content: {
      type: String,
      default: "",
    },
    id: {
      type: String,
      required: true,
      default: "vue-kind-editor-" + new Date().valueOf(),
    },
    width: {
      type: String,
      default: "100%",
    },
    height: {
      type: String,
      default: "500px",
    },
    minWidth: {
      type: Number,
      default: 650,
    },
    minHeight: {
      type: Number,
      default: 100,
    },
    items: {
      type: Array,
      default: function () {
        return [
          "source",
          "|",
          "undo",
          "redo",
          "|",
          "preview",
          "print",
          "template",
          "code",
          "cut",
          "copy",
          "paste",
          "plainpaste",
          "wordpaste",
          "|",
          "justifyleft",
          "justifycenter",
          "justifyright",
          "justifyfull",
          "insertorderedlist",
          "insertunorderedlist",
          "indent",
          "outdent",
          "subscript",
          "superscript",
          "clearhtml",
          "quickformat",
          "selectall",
          "|",
          "fullscreen",
          "/",
          "formatblock",
          "fontname",
          "fontsize",
          "|",
          "forecolor",
          "hilitecolor",
          "bold",
          "italic",
          "underline",
          "strikethrough",
          "lineheight",
          "removeformat",
          "|",
          "image",
          "multiimage",
          "flash",
          "media",
          "insertfile",
          "table",
          "hr",
          "emoticons",
          "baidumap",
          "pagebreak",
          "anchor",
          "link",
          "unlink",
          "|",
          "about",
        ];
      },
    },
    noDisableItems: {
      type: Array,
      default: function () {
        return ["source", "fullscreen"];
      },
    },
    filterMode: {
      type: Boolean,
      default: true,
    },
    htmlTags: {
      type: Object,
      default: function () {
        return {
          font: ["color", "size", "face", ".background-color"],
          span: ["style"],
          div: ["class", "align", "style"],
          table: [
            "class",
            "border",
            "cellspacing",
            "cellpadding",
            "width",
            "height",
            "align",
            "style",
          ],
          "td,th": [
            "class",
            "align",
            "valign",
            "width",
            "height",
            "colspan",
            "rowspan",
            "bgcolor",
            "style",
          ],
          a: ["class", "href", "target", "name", "style"],
          embed: [
            "src",
            "width",
            "height",
            "type",
            "loop",
            "autostart",
            "quality",
            "style",
            "align",
            "allowscriptaccess",
            "/",
          ],
          img: [
            "src",
            "width",
            "height",
            "border",
            "alt",
            "title",
            "align",
            "style",
            "/",
          ],
          hr: ["class", "/"],
          br: ["/"],
          "p,ol,ul,li,blockquote,h1,h2,h3,h4,h5,h6": ["align", "style"],
          "tbody,tr,strong,b,sub,sup,em,i,u,strike": [],
        };
      },
    },
    wellFormatMode: {
      type: Boolean,
      default: true,
    },
    resizeType: {
      type: Number,
      default: 2,
    },
    themeType: {
      type: String,
      default: "default",
    },
    langType: {
      type: String,
      default: "zh-CN",
    },
    designMode: {
      type: Boolean,
      default: true,
    },
    fullscreenMode: {
      type: Boolean,
      default: false,
    },
    basePath: {
      type: String,
    },
    themesPath: {
      type: String,
    },
    pluginsPath: {
      type: String,
      default: "/static/kindeditor/plugins/",
    },
    langPath: {
      type: String,
    },
    minChangeSize: {
      type: Number,
      default: 5,
    },
    loadStyleMode: {
      type: Boolean,
      default: true,
    },
    urlType: {
      type: String,
      default: "",
    },
    newlineTag: {
      type: String,
      default: "p",
    },
    pasteType: {
      type: Number,
      default: 2,
    },
    dialogAlignType: {
      type: String,
      default: "page",
    },
    shadowMode: {
      type: Boolean,
      default: true,
    },
    zIndex: {
      type: Number,
      default: 811213,
    },
    useContextmenu: {
      type: Boolean,
      default: true,
    },
    syncType: {
      type: String,
      default: "form",
    },
    indentChar: {
      type: String,
      default: "\t",
    },
    cssPath: {
      type: [String, Array],
    },
    cssData: {
      type: String,
    },
    bodyClass: {
      type: String,
      default: "ke-content",
    },
    colorTable: {
      type: Array,
    },
    afterCreate: {
      type: Function,
    },
    afterChange: {
      type: Function,
    },
    afterTab: {
      type: Function,
    },
    afterFocus: {
      type: Function,
    },
    afterBlur: {
      type: Function,
    },
    afterUpload: {
      type: Function,
    },
    uploadJson: {
      type: String,
      default: "/api/platform/resource/uploaddocument",
    },
    fileManagerJson: {
      type: String,
      default: "/api/platform/resource/filemanager",
    },
    allowPreviewEmoticons: {
      type: Boolean,
      default: true,
    },
    allowImageUpload: {
      type: Boolean,
      default: true,
    },
    allowFlashUpload: {
      type: Boolean,
      default: true,
    },
    allowMediaUpload: {
      type: Boolean,
      default: true,
    },
    allowFileUpload: {
      type: Boolean,
      default: true,
    },
    allowFileManager: {
      type: Boolean,
      default: true,
    },
    fontSizeTable: {
      type: Array,
      default: function () {
        return ["9px", "10px", "12px", "14px", "16px", "18px", "24px", "32px"];
      },
    },
    imageTabIndex: {
      type: Number,
      default: 0,
    },
    formatUploadUrl: {
      type: Boolean,
      default: true,
    },
    fullscreenShortcut: {
      type: Boolean,
      default: false,
    },
    extraFileUploadParams: {
      type: Object,
      default: function () {
        //注意:仅支持post
        return {
          //"key":"value"
        };
      },
    },
    filePostName: {
      type: String,
      default: "imgFile",
    },
    fillDescAfterUploadImage: {
      type: Boolean,
      default: false,
    },
    afterSelectFile: {
      type: Function,
    },
    pagebreakHtml: {
      type: String,
      default: '<hr style="page-break-after: always;" class="ke-pagebreak" />',
    },
    allowImageRemote: {
      type: Boolean,
      default: true,
    },
    autoHeightMode: {
      type: Boolean,
      default: false,
    },
    fixToolBar: {
      type: Boolean,
      default: false,
    },
    tabIndex: {
      type: Number,
    },
  },
  watch: {
    content(val) {
      this.editor && val !== this.outContent && this.editor.html(val);
    },
    outContent(val) {
      this.$emit("update:content", val);
      this.$emit("on-content-change", val);
    },
  },
  computed: {

  },
  mounted() {
    // 初始访问时创建
    this.initEditor();
  },
  /**
   * keep-alive 会用到进入时调用activated 离开时调用deactivated
   * 初始访问 created、mounted 切换时deactivated 再次进入时 activated
   */
  activated() {
    // keep-alive 进入时创建
    this.initEditor();
  },
  deactivated() {
    // keep-alive 离开时移除
    this.removeEditor();
  },
  methods: {
    removeEditor() {
      window.KindEditor.remove("#" + this.id);
    },
    initEditor() {
      var _this = this;
      _this.removeEditor();
      _this.editor = window.KindEditor.create("#" + this.id, {
        header: _this.header,
        token:_this.token,
        width: _this.width,
        height: _this.height,
        minWidth: _this.minWidth,
        minHeight: _this.minHeight,
        items: _this.items,
        noDisableItems: _this.noDisableItems,
        filterMode: _this.filterMode,
        htmlTags: _this.htmlTags,
        wellFormatMode: _this.wellFormatMode,
        resizeType: _this.resizeType,
        themeType: _this.themeType,
        langType: _this.langType,
        designMode: _this.designMode,
        fullscreenMode: _this.fullscreenMode,
        basePath: _this.basePath,
        themesPath: _this.themesPath,
        pluginsPath: _this.pluginsPath,
        langPath: _this.langPath,
        minChangeSize: _this.minChangeSize,
        loadStyleMode: _this.loadStyleMode,
        urlType: _this.urlType,
        newlineTag: _this.newlineTag,
        pasteType: _this.pasteType,
        dialogAlignType: _this.dialogAlignType,
        shadowMode: _this.shadowMode,
        zIndex: _this.zIndex,
        useContextmenu: _this.useContextmenu,
        syncType: _this.syncType,
        indentChar: _this.indentChar,
        cssPath: _this.cssPath,
        cssData: _this.cssData,
        bodyClass: _this.bodyClass,
        colorTable: _this.colorTable,
        afterCreate: _this.afterCreate,
        afterChange: function () {
          _this.outContent = this.html();
        },
        afterTab: _this.afterTab,
        afterFocus: _this.afterFocus,
        afterBlur: _this.afterBlur,
        afterUpload: _this.afterUpload,
        uploadJson: _this.uploadJson,
        fileManagerJson: _this.fileManagerJson,
        allowPreviewEmoticons: _this.allowPreviewEmoticons,
        allowImageUpload: _this.allowImageUpload,
        allowFlashUpload: _this.allowFlashUpload,
        allowMediaUpload: _this.allowMediaUpload,
        allowFileUpload: _this.allowFileUpload,
        allowFileManager: _this.allowFileManager,
        fontSizeTable: _this.fontSizeTable,
        imageTabIndex: _this.imageTabIndex,
        formatUploadUrl: _this.formatUploadUrl,
        fullscreenShortcut: _this.fullscreenShortcut,
        extraFileUploadParams: _this.extraFileUploadParams,
        filePostName: _this.filePostName,
        fillDescAfterUploadImage: _this.fillDescAfterUploadImage,
        afterSelectFile: _this.afterSelectFile,
        pagebreakHtml: _this.pagebreakHtml,
        allowImageRemote: _this.allowImageRemote,
        autoHeightMode: _this.autoHeightMode,
        fixToolBar: _this.fixToolBar,
        tabIndex: _this.tabIndex,
      });
    },
  },
};
</script>

四、修改kindeditor.all.js,让其支持.net core的上传文件,修改完后可以进行压缩成kindeditor-all.min.js

1.修改kindeditor.all.js中的_ajax方法,来支持上传文件功能,代码如下

function _ajax(url, fn, method, formData, dataType,token) {
	method = method || 'GET';
	method = method.toUpperCase();
	dataType = dataType || 'json';
	var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');

	xhr.open(method, url, true);
	//请求头必须在open之后设置
	if(token) xhr.setRequestHeader("Authorization","Bearer "+token);
	xhr.onreadystatechange = function () {
		if (xhr.readyState == 4 && xhr.status == 200) {
			if (fn) {
				var data = _trim(xhr.responseText);
				if (dataType == 'json') {
					data = _json(data);
				}
				fn(data);
			}
		}
	};
	if (method == 'POST') {
		xhr.send(formData);
	} else {
		xhr.send(null);
	}
}

2.修改image、flash、media、file插件,修改dialog组件的yesBtn的click方法,将其中的上传文件代码即uploadbutton.submit();改为调用1中的_ajax方法。注意:每个插件原理相同,代码略有不同。

//提交上传表单
						//uploadbutton.submit();

						var formData = new FormData();
						var file = document.getElementsByClassName("ke-upload-file");
						formData.append(filePostName, file[0].files[0]);
						for(var k in extraParams){
							formData.append(K,extraParams[k]);
						}
						K.ajax(K.addParam(uploadJson, 'dir=image&' + new Date().getTime()), function (data) {
							dialog.hideLoading();
							if (data.error === 0) {
								var url = data.url;
								if (formatUploadUrl) {
									url = K.formatUrl(url, 'absolute');
								}
								if (self.afterUpload) {
									self.afterUpload.call(self, url, data, name);
								}
								if (!fillDescAfterUploadImage) {
									clickFn.call(self, url, data.title, data.width, data.height, data.border, data.align);
								} else {
									K(".ke-dialog-row #remoteUrl", div).val(url);
									K(".ke-tabs-li", div)[0].click();
									K(".ke-refresh-btn", div).click();
								}
							} else {
								alert(data.message);
							}
						}, "post", formData,null, self.token);

3.修改filemanager插件,修改reloadPage方法,调用_ajax方法的参数进行调整,代码如下

K.ajax(K.addParam(fileManagerJson, param + '&' + new Date().getTime()), function (data) {
				dialog.hideLoading();
				func(data);
			}, null, null, null, self.token);

五、.net core 接口,接口代码请根据自己系统进行修改,只需要注意返回的json格式要跟kindeditor处理方法对应。

/// <summary>
        /// 上传文件(kindeditor)
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="imgFile"></param>
        /// <returns></returns>
        [HttpPost]
        //[AllowAnonymous]
        public async Task<IActionResult> UploadDocument([FromQuery(Name = "dir")] string dir, [FromForm] IFormFile imgFile)
        {
            var req = Request;

            if (String.IsNullOrEmpty(dir))
            {
                return new JsonResult(new { error = 1, message = "目录名不正确。" });
            }
            var _config = _uploadConfig[dir];
            if (_config == null)
            {
                return new JsonResult(new { error = 1, message = "目录名不正确。" });
            }

            var date = DateTime.Today.ToString("yyyyMMdd");

            if (imgFile == null || imgFile.Length < 1)
            {
                return new JsonResult(new { error = 1, message = "请上传文件!" });
            }

            //格式限制
            //var extentions = ((string)extTable[dir]).Split(',').ToList();
            //String fileExt = Path.GetExtension(imgFile.FileName).ToLower();
            //if (fileExt.Length > 0)
            //    fileExt = fileExt.Substring(fileExt.IndexOf('.') + 1);
            if (!_config.ContentType.Contains(imgFile.ContentType))
            {
                return new JsonResult(new { error = 1, message = "文件格式错误!" });
            }

            //大小限制
            if (!(imgFile.Length <= _config.MaxSize))
            {
                return new JsonResult(new { error = 1, message = "文件过大!" });
            }

            var fileInfo = new ZzwIot.Core.Common.Files.FileInfo(imgFile.FileName, imgFile.Length)
            {
                UploadPath = _config.UploadPath + "/",
                RequestPath = _config.RequestPath + "/"
            };

            var dateTimeFormat = _config.DateTimeFormat.NotNull() ? DateTime.Now.ToString(_config.DateTimeFormat) : "";
            var format = _config.Format.NotNull() ? StringHelper.Format(_config.Format, new { Id = date }) : "";
            fileInfo.RelativePath = Path.Combine(dateTimeFormat, format).ToPath();

            if (!Directory.Exists(fileInfo.FileDirectory))
            {
                Directory.CreateDirectory(fileInfo.FileDirectory);
            }

            fileInfo.SaveName = $"{IdWorkerHelper.GenId64()}.{fileInfo.Extension}";

            await _uploadHelper.SaveAsync(imgFile, fileInfo.FilePath);

            Hashtable hash = new Hashtable();
            hash["error"] = 0;
            hash["url"] = fileInfo.RequestPath + fileInfo.FileRelativePath;

            return new JsonResult(hash);
        }

        /// <summary>
        /// 文件管理(kindeditor)
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //[AllowAnonymous]
        public IActionResult FileManager([FromQuery(Name = "dir")] string dir,
            [FromQuery(Name = "path")] string path,
            [FromQuery(Name = "order")] string order
          )
        {
            var config = _uploadConfig[dir];
            if (config == null)
            {
                return Content("目录错误");
            }

            String rootUrl = config.RequestPath + "/";


            //图片扩展名
            //String photoTypes = "gif,jpg,jpeg,png,bmp";

            String currentPath = "";
            String currentUrl = "";
            String currentDirPath = "";
            String moveupDirPath = "";

            String dirPath = config.UploadPath + "/";

            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            //根据path参数,设置各路径和URL
            path = String.IsNullOrEmpty(path) ? "" : path;
            if (path == "")
            {
                currentPath = dirPath;
                currentUrl = rootUrl;
                currentDirPath = "";
                moveupDirPath = "";
            }
            else
            {
                currentPath = dirPath + path;
                currentUrl = rootUrl + path;
                currentDirPath = path;
                moveupDirPath = Regex.Replace(currentDirPath, @"(.*?)[^\/]+\/$", "$1");
            }

            //排序形式,name or size or type

            order = String.IsNullOrEmpty(order) ? "" : order.ToLower();

            //不允许使用..移动到上一级目录
            if (Regex.IsMatch(path, @"\.\."))
            {
                return Content("拒绝访问。");
            }
            //最后一个字符不是/
            if (path != "" && !path.EndsWith("/"))
            {
                return Content("Path无效。");
            }
            //目录不存在或不是目录
            if (!Directory.Exists(currentPath))
            {
                return Content("目录不存在。");
            }

            //遍历目录取得文件信息
            string[] dirList = Directory.GetDirectories(currentPath);
            string[] fileList = Directory.GetFiles(currentPath);

            switch (order)
            {
                case "size":
                    Array.Sort(dirList, new NameSorter());
                    Array.Sort(fileList, new SizeSorter());
                    break;
                case "type":
                    Array.Sort(dirList, new NameSorter());
                    Array.Sort(fileList, new TypeSorter());
                    break;
                case "name":
                default:
                    Array.Sort(dirList, new NameSorter());
                    Array.Sort(fileList, new NameSorter());
                    break;
            }

            Hashtable result = new Hashtable();
            result["moveup_dir_path"] = moveupDirPath;
            result["current_dir_path"] = currentDirPath;
            result["current_url"] = currentUrl;
            result["total_count"] = dirList.Length + fileList.Length;
            List<Hashtable> dirFileList = new List<Hashtable>();
            result["file_list"] = dirFileList;
            for (int i = 0; i < dirList.Length; i++)
            {
                DirectoryInfo directory = new DirectoryInfo(dirList[i]);
                Hashtable hash = new Hashtable();
                hash["is_dir"] = true;
                hash["has_file"] = (directory.GetFileSystemInfos().Length > 0);
                hash["filesize"] = 0;
                hash["is_photo"] = false;
                hash["filetype"] = "";
                hash["filename"] = directory.Name;
                hash["datetime"] = directory.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss");
                dirFileList.Add(hash);
            }
            var photoTypes = "png,gif,jpg,jpeg,bmp";
            for (int i = 0; i < fileList.Length; i++)
            {
                FileInfo file = new FileInfo(fileList[i]);
                Hashtable hash = new Hashtable();
                hash["is_dir"] = false;
                hash["has_file"] = false;
                hash["filesize"] = file.Length;
                hash["is_photo"] = (Array.IndexOf(photoTypes.Split(','), file.Extension.Substring(1).ToLower()) >= 0);
                hash["filetype"] = file.Extension.Substring(1);
                hash["filename"] = file.Name;
                hash["datetime"] = file.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss");
                dirFileList.Add(hash);
            }
            return new JsonResult(result);
        }

六、vue-kind-editor调用

import KindEditor from "@/components/vue-kind-editor";

<kind-editor :content.sync="addForm.content" id="kind-editor" />

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Layui是一款基于HTML5和CSS3技术开发的前端UI框架,它提供了丰富的UI组件和独特的模块化开发方式,使得前端开发更加简单和高效。Layui可以与.NET Core MVC框架无缝集成,实现前后端分离的开发模式。通过Layui,开发者可以快速构建出美观、交互丰富的网页应用。 .NET Core是由Microsoft开发的开源跨平台框架,借助它,开发者可以在Windows、Linux和macOS等多个操作系统上构建高性能的Web应用程序。使用.NET Core MVC框架可以方便地开发和管理网站的使用逻辑,与Layui相结合,可以实现前后端分离的开发模式,提高开发效率和项目可维护性。 Oracle是一种关系型数据库管理系统,具有高性能、高可靠性和强大的功能。在.NET Core MVC项目中使用Oracle数据库可以提供可靠的数据存储和管理能力。通过Oracle提供的ADO.NET驱动程序,可以方便地与.NET Core应用程序进行集成,并进行数据的读取和写入。 综上所述,结合Layui、.NET Core MVC和Oracle可以构建出一个强大的Web应用程序。Layui提供了美观且易于使用的前端组件,.NET Core MVC框架提供了稳定且高性能的开发环境,而Oracle数据库提供了可靠的数据存储和管理能力。这样的组合可以使开发者更加便捷地开发出功能齐全且用户体验良好的Web应用程序。 ### 回答2: Layui是一款基于HTMLCSS、JavaScript的前端UI框架,它提供了一套简洁易用的界面组件,能够快速搭建美观的用户界面。对于使用Layui的.NET Core MVC项目来说,可以通过在前端页面中引入Layui的CSS和JavaScript文件来使用其提供的各种组件,比如表格、表单、弹窗等,从而实现丰富的用户交互和界面展示效果。 .NET Core是微软开发的跨平台开发框架,能够支持在Windows、Linux、Mac等操作系统上运行。它的MVC框架是一种基于模型-视图-控制器的设计模式,能够将前端页面、后端业务逻辑以及数据访问分离开来,提升代码的可维护性和扩展性。在.NET Core MVC项目中,可以很方便地通过使用C#编写控制器和模型来处理业务逻辑,并将结果通过视图呈现给用户。 Oracle是一家知名的数据库管理系统提供商,它提供了强大的关系型数据库服务。在.NET Core MVC项目中使用Oracle数据库时,可以通过使用Oracle提供的.NET数据访问技术(如ODP.NET)来实现与数据库的连接和操作。借助于.NET Core MVC的数据访问技术,可以在控制器中编写代码进行数据的增删改查操作,并将结果返回给前端页面展示给用户。 综上所述,Layui、.NET Core MVC和Oracle可以很好地结合在一起,实现一个功能强大、界面美观、数据安全可靠的Web应用。通过前端的Layui框架,可以搭建出优雅的用户界面;通过.NET Core MVC框架,可以实现清晰的分层架构和灵活的业务逻辑;通过Oracle数据库,可以实现数据的持久化和高效的数据管理。这样的组合能够满足开发者和用户对于功能、性能和用户体验的要求。 ### 回答3: layui是一款基于HTML5和CSS3的前端UI框架,提供了丰富的组件和模块化的开发方式,可以快速搭建漂亮、易用的网页界面。它的特点是简洁、轻量、易学易用,适用于各种Web应用的开发.NET Core是微软推出的开源跨平台开发框架,它可以在Windows、Linux和Mac等多种操作系统上运行,具有高性能、可扩展性好等特点。使用.NET Core可以方便地开发各种Web应用、移动应用和云服务等。 Oracle是一家全球著名的数据库解决方案提供商,其数据库产品具有高度可靠性、安全性和可扩展性。在开发Web应用时,通过使用Oracle数据库可以实现数据的持久化和高效的数据访问操作。 综上所述,当我们使用Layui、.NET Core MVC和Oracle进行开发时,我们可以通过Layui提供的丰富的UI组件和模块化开发方式来设计网页界面,使用.NET Core MVC来开发Web应用的后端逻辑,并通过Oracle数据库实现数据的持久化和访问操作。这样的组合可以使开发过程更加高效、简便,同时满足用户对于良好界面和可靠数据存储的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值