cherry_markdown.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. $(function () {
  2. window.editormdLocales = {
  3. 'zh-CN': {
  4. placeholder: '本编辑器支持 Markdown 编辑,左边编写,右边预览。',
  5. contentUnsaved: '编辑内容未保存,需要保存吗?',
  6. noDocNeedPublish: '没有需要发布的文档',
  7. loadDocFailed: '文档加载失败',
  8. fetchDocFailed: '获取当前文档信息失败',
  9. cannotAddToEmptyNode: '空节点不能添加内容',
  10. overrideModified: '文档已被其他人修改确定覆盖已存在的文档吗?',
  11. confirm: '确定',
  12. cancel: '取消',
  13. contentsNameEmpty: '目录名称不能为空',
  14. addDoc: '添加文档',
  15. edit: '编辑',
  16. delete: '删除',
  17. loadFailed: '加载失败请重试',
  18. tplNameEmpty: '模板名称不能为空',
  19. tplContentEmpty: '模板内容不能为空',
  20. saveSucc: '保存成功',
  21. serverExcept: '服务器异常',
  22. paramName: '参数名称',
  23. paramType: '参数类型',
  24. example: '示例值',
  25. remark: '备注',
  26. },
  27. 'en': {
  28. placeholder: 'This editor supports Markdown editing, writing on the left and previewing on the right.',
  29. contentUnsaved: 'The edited content is not saved, need to save it?',
  30. noDocNeedPublish: 'No Document need to be publish',
  31. loadDocFailed: 'Load Document failed',
  32. fetchDocFailed: 'Fetch Document info failed',
  33. cannotAddToEmptyNode: 'Cannot add content to empty node',
  34. overrideModified: 'The document has been modified by someone else, are you sure to overwrite the document?',
  35. confirm: 'Confirm',
  36. cancel: 'Cancel',
  37. contentsNameEmpty: 'Document Name cannot be empty',
  38. addDoc: 'Add Document',
  39. edit: 'Edit',
  40. delete: 'Delete',
  41. loadFailed: 'Failed to load, please try again',
  42. tplNameEmpty: 'Template name cannot be empty',
  43. tplContentEmpty: 'Template content cannot be empty',
  44. saveSucc: 'Save success',
  45. serverExcept: 'Server Exception',
  46. paramName: 'Parameter',
  47. paramType: 'Type',
  48. example: 'Example',
  49. remark: 'Remark',
  50. }
  51. };
  52. var CustomHookA = Cherry.createSyntaxHook('codeBlock', Cherry.constants.HOOKS_TYPE_LIST.PAR, {
  53. makeHtml(str) {
  54. console.warn('custom hook', 'hello');
  55. return str;
  56. },
  57. rule(str) {
  58. const regex = {
  59. begin: '',
  60. content: '',
  61. end: '',
  62. };
  63. regex.reg = new RegExp(regex.begin + regex.content + regex.end, 'g');
  64. return regex;
  65. },
  66. });
  67. /**
  68. * 自定义一个自定义菜单
  69. * 点第一次时,把选中的文字变成同时加粗和斜体
  70. * 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本
  71. */
  72. var customMenuA = Cherry.createMenuHook('加粗斜体', {
  73. iconName: 'font',
  74. onClick: function (selection) {
  75. // 获取用户选中的文字,调用getSelection方法后,如果用户没有选中任何文字,会尝试获取光标所在位置的单词或句子
  76. let $selection = this.getSelection(selection) || '同时加粗斜体';
  77. // 如果是单选,并且选中内容的开始结束内没有加粗语法,则扩大选中范围
  78. if (!this.isSelections && !/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
  79. this.getMoreSelection('***', '***', () => {
  80. const newSelection = this.editor.editor.getSelection();
  81. const isBoldItalic = /^\s*(\*\*\*)[\s\S]+(\1)/.test(newSelection);
  82. if (isBoldItalic) {
  83. $selection = newSelection;
  84. }
  85. return isBoldItalic;
  86. });
  87. }
  88. // 如果选中的文本中已经有加粗语法了,则去掉加粗语法
  89. if (/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
  90. return $selection.replace(/(^)(\s*)(\*\*\*)([^\n]+)(\3)(\s*)($)/gm, '$1$4$7');
  91. }
  92. /**
  93. * 注册缩小选区的规则
  94. * 注册后,插入“***TEXT***”,选中状态会变成“***【TEXT】***”
  95. * 如果不注册,插入后效果为:“【***TEXT***】”
  96. */
  97. this.registerAfterClickCb(() => {
  98. this.setLessSelection('***', '***');
  99. });
  100. return $selection.replace(/(^)([^\n]+)($)/gm, '$1***$2***$3');
  101. }
  102. });
  103. /**
  104. * 定义一个空壳,用于自行规划cherry已有工具栏的层级结构
  105. */
  106. var customMenuB = Cherry.createMenuHook('发布', {
  107. iconName: 'publish',
  108. onClick: releaseDocument,
  109. });
  110. var customMenuC = Cherry.createMenuHook("返回", {
  111. iconName: 'back',
  112. onClick: backWard,
  113. })
  114. var customMenuD = Cherry.createMenuHook('保存', {
  115. id: "markdown-save",
  116. iconName: 'save',
  117. onClick: saveDocument,
  118. });
  119. var customMenuE = Cherry.createMenuHook('边栏', {
  120. iconName: 'sider',
  121. onClick: siderChange,
  122. });
  123. var customMenuF = Cherry.createMenuHook('历史', {
  124. iconName: 'history',
  125. onClick: showHistory,
  126. });
  127. let customMenuTools = Cherry.createMenuHook('工具', {
  128. iconName: '',
  129. subMenuConfig: [
  130. {
  131. iconName: 'word',
  132. name: 'Word转笔记',
  133. onclick: ()=>{
  134. let converter = new WordToHtmlConverter();
  135. converter.handleFileSelect(function (response) {
  136. if (response.messages.length) {
  137. let messages = response.messages.map((item)=>{
  138. return item.message + "<br/>";
  139. }).join('\n');
  140. layer.msg(messages);
  141. }
  142. converter.replaceHtmlBase64(response.value).then((html)=>{
  143. window.editor.insertValue(html);
  144. });
  145. })
  146. }
  147. },
  148. {
  149. noIcon: true,
  150. name: 'Htm转Markdown',
  151. onclick: ()=>{
  152. let converter = new HtmlToMarkdownConverter();
  153. converter.handleFileSelect(function (response) {
  154. window.editor.insertValue(response);
  155. })
  156. }
  157. }
  158. ]
  159. });
  160. var basicConfig = {
  161. id: 'manualEditorContainer',
  162. externals: {
  163. echarts: window.echarts,
  164. katex: window.katex,
  165. MathJax: window.MathJax,
  166. },
  167. isPreviewOnly: false,
  168. fileUpload: myFileUpload,
  169. engine: {
  170. global: {
  171. urlProcessor(url, srcType) {
  172. //console.log(`url-processor`, url, srcType);
  173. return url;
  174. },
  175. },
  176. syntax: {
  177. codeBlock: {
  178. theme: 'twilight',
  179. },
  180. table: {
  181. enableChart: false,
  182. // chartEngine: Engine Class
  183. },
  184. fontEmphasis: {
  185. allowWhitespace: false, // 是否允许首尾空格
  186. },
  187. strikethrough: {
  188. needWhitespace: false, // 是否必须有前后空格
  189. },
  190. mathBlock: {
  191. engine: 'MathJax', // katex或MathJax
  192. src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins,则需要使用该url通过script标签引入
  193. },
  194. inlineMath: {
  195. engine: 'MathJax', // katex或MathJax
  196. },
  197. emoji: {
  198. useUnicode: false,
  199. customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8',
  200. upperCase: true,
  201. },
  202. // toc: {
  203. // tocStyle: 'nested'
  204. // }
  205. // 'header': {
  206. // strict: false
  207. // }
  208. },
  209. customSyntax: {
  210. // SyntaxHookClass
  211. CustomHook: {
  212. syntaxClass: CustomHookA,
  213. force: false,
  214. after: 'br',
  215. },
  216. },
  217. },
  218. toolbars: {
  219. toolbar: [
  220. 'customMenuCName',
  221. 'customMenuDName',
  222. 'customMenuBName',
  223. 'customMenuEName',
  224. 'undo',
  225. 'redo',
  226. 'bold',
  227. 'italic',
  228. {
  229. strikethrough: ['strikethrough', 'underline', 'sub', 'sup', 'ruby', 'customMenuAName'],
  230. },
  231. 'size',
  232. '|',
  233. 'color',
  234. 'header',
  235. '|',
  236. 'drawIo',
  237. '|',
  238. 'ol',
  239. 'ul',
  240. 'checklist',
  241. 'panel',
  242. 'detail',
  243. '|',
  244. 'formula',
  245. {
  246. insert: ['image', 'audio', 'video', 'link', 'hr', 'br', 'code', 'formula', 'toc', 'table', 'pdf', 'word', 'ruby'],
  247. },
  248. 'graph',
  249. 'togglePreview',
  250. 'settings',
  251. 'switchModel',
  252. 'export',
  253. 'customMenuFName',
  254. 'customMenuToolsName'
  255. ],
  256. bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false
  257. sidebar: ['mobilePreview', 'copy', 'codeTheme', 'theme'],
  258. customMenu: {
  259. customMenuAName: customMenuA,
  260. customMenuBName: customMenuB,
  261. customMenuCName: customMenuC,
  262. customMenuDName: customMenuD,
  263. customMenuEName: customMenuE,
  264. customMenuFName: customMenuF,
  265. customMenuToolsName: customMenuTools,
  266. },
  267. },
  268. drawioIframeUrl: '/static/cherry/drawio_demo.html',
  269. editor: {
  270. defaultModel: 'edit&preview',
  271. height: "100%",
  272. },
  273. previewer: {
  274. // 自定义markdown预览区域class
  275. // className: 'markdown'
  276. },
  277. keydown: [],
  278. //extensions: [],
  279. //callback: {
  280. //changeString2Pinyin: pinyin,
  281. //}
  282. };
  283. fetch('').then((response) => response.text()).then((value) => {
  284. //var markdownarea = document.getElementById("markdown_area").value
  285. var config = Object.assign({}, basicConfig);// { value: markdownarea });// { value: value });不显示获取的初始化值
  286. window.editor = new Cherry(config);
  287. window.editor.getCodeMirror().on('change', (e, detail)=>{
  288. resetEditorChanged(true);
  289. });
  290. openLastSelectedNode();
  291. });
  292. /***
  293. * 加载指定的文档到编辑器中
  294. * @param $node
  295. */
  296. window.loadDocument = function ($node) {
  297. var index = layer.load(1, {
  298. shade: [0.1, '#fff'] // 0.1 透明度的白色背景
  299. });
  300. $.get(window.editURL + $node.node.id).done(function (res) {
  301. layer.close(index);
  302. if (res.errcode === 0) {
  303. window.isLoad = true;
  304. try {
  305. window.editor.setTheme(res.data.markdown_theme);
  306. window.editor.setMarkdown(res.data.markdown);
  307. } catch (e) {
  308. console.log(e);
  309. }
  310. var node = { "id": res.data.doc_id, 'parent': res.data.parent_id === 0 ? '#' : res.data.parent_id, "text": res.data.doc_name, "identify": res.data.identify, "version": res.data.version };
  311. pushDocumentCategory(node);
  312. window.selectNode = node;
  313. pushVueLists(res.data.attach);
  314. setLastSelectNode($node);
  315. } else {
  316. layer.msg(editormdLocales[lang].loadDocFailed);
  317. }
  318. }).fail(function () {
  319. layer.close(index);
  320. layer.msg(editormdLocales[lang].loadDocFailed);
  321. });
  322. };
  323. /**
  324. * 保存文档到服务器
  325. * @param $is_cover 是否强制覆盖
  326. */
  327. function saveDocument($is_cover, callback) {
  328. var index = null;
  329. var node = window.selectNode;
  330. var content = window.editor.getMarkdown();
  331. var html = window.editor.getHtml(true);
  332. var markdownTheme = window.editor.getTheme();
  333. var version = "";
  334. if (!node) {
  335. layer.msg(editormdLocales[lang].fetchDocFailed);
  336. return;
  337. }
  338. if (node.a_attr && node.a_attr.disabled) {
  339. layer.msg(editormdLocales[lang].cannotAddToEmptyNode);
  340. return;
  341. }
  342. var doc_id = parseInt(node.id);
  343. for (var i in window.documentCategory) {
  344. var item = window.documentCategory[i];
  345. if (item.id === doc_id) {
  346. version = item.version;
  347. break;
  348. }
  349. }
  350. $.ajax({
  351. beforeSend: function () {
  352. index = layer.load(1, { shade: [0.1, '#fff'] });
  353. window.saveing = true;
  354. },
  355. url: window.editURL,
  356. data: { "identify": window.book.identify, "doc_id": doc_id, "markdown": content, "html": html, "markdown_theme": markdownTheme, "cover": $is_cover ? "yes" : "no", "version": version },
  357. type: "post",
  358. timeout: 30000,
  359. dataType: "json",
  360. success: function (res) {
  361. if (res.errcode === 0) {
  362. resetEditorChanged(false);
  363. for (var i in window.documentCategory) {
  364. var item = window.documentCategory[i];
  365. if (item.id === doc_id) {
  366. window.documentCategory[i].version = res.data.version;
  367. break;
  368. }
  369. }
  370. $.each(window.documentCategory, function (i, item) {
  371. var $item = window.documentCategory[i];
  372. if (item.id === doc_id) {
  373. window.documentCategory[i].version = res.data.version;
  374. }
  375. });
  376. if (typeof callback === "function") {
  377. callback();
  378. }
  379. } else if (res.errcode === 6005) {
  380. var confirmIndex = layer.confirm(editormdLocales[lang].overrideModified, {
  381. btn: [editormdLocales[lang].confirm, editormdLocales[lang].cancel] // 按钮
  382. }, function () {
  383. layer.close(confirmIndex);
  384. saveDocument(true, callback);
  385. });
  386. } else {
  387. layer.msg(res.message);
  388. }
  389. },
  390. error: function (XMLHttpRequest, textStatus, errorThrown) {
  391. layer.msg(window.editormdLocales[window.lang].serverExcept + errorThrown);
  392. },
  393. complete: function () {
  394. layer.close(index);
  395. window.saveing = false;
  396. }
  397. });
  398. }
  399. /**
  400. * 设置编辑器变更状态
  401. * @param $is_change
  402. */
  403. function resetEditorChanged($is_change) {
  404. if ($is_change && !window.isLoad) {
  405. $("#markdown-save").removeClass('disabled').addClass('change');
  406. } else {
  407. $("#markdown-save").removeClass('change').addClass('disabled');
  408. }
  409. window.isLoad = false;
  410. }
  411. /**
  412. * 返回上一个页面
  413. */
  414. function backWard() {
  415. if (document.referrer == "") { // 没有上一级
  416. var homepage = window.location.origin;
  417. window.location.href = homepage; // 返回首页
  418. return;
  419. }
  420. window.location.href = document.referrer;
  421. }
  422. /**
  423. * 发布文档
  424. */
  425. function releaseDocument() {
  426. if (Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0) {
  427. if ($("#markdown-save").hasClass('change')) {
  428. var confirm_result = confirm(editormdLocales[lang].contentUnsaved);
  429. if (confirm_result) {
  430. saveDocument(false, releaseBook);
  431. return;
  432. }
  433. }
  434. releaseBook();
  435. return
  436. }
  437. layer.msg(editormdLocales[lang].noDocNeedPublish)
  438. }
  439. /**
  440. * 显示/隐藏边栏
  441. */
  442. function siderChange() {
  443. $("#manualCategory").toggle(0, "swing", function () {
  444. var $then = $("#manualEditorContainer");
  445. var left = parseInt($then.css("left"));
  446. if (left > 0) {
  447. window.editorContainerLeft = left;
  448. $then.css("left", "0");
  449. } else {
  450. $then.css("left", window.editorContainerLeft + "px");
  451. }
  452. });
  453. }
  454. /**
  455. * 显示文档历史
  456. */
  457. function showHistory() {
  458. window.documentHistory();
  459. }
  460. /**
  461. * 添加文档
  462. */
  463. $("#addDocumentForm").ajaxForm({
  464. beforeSubmit: function () {
  465. var doc_name = $.trim($("#documentName").val());
  466. if (doc_name === "") {
  467. return showError(editormdLocales[lang].contentsNameEmpty, "#add-error-message")
  468. }
  469. $("#btnSaveDocument").button("loading");
  470. return true;
  471. },
  472. success: function (res) {
  473. if (res.errcode === 0) {
  474. var data = {
  475. "id": res.data.doc_id,
  476. 'parent': res.data.parent_id === 0 ? '#' : res.data.parent_id,
  477. "text": res.data.doc_name,
  478. "identify": res.data.identify,
  479. "version": res.data.version,
  480. state: { opened: res.data.is_open == 1 },
  481. a_attr: { is_open: res.data.is_open == 1 }
  482. };
  483. var node = window.treeCatalog.get_node(data.id);
  484. if (node) {
  485. window.treeCatalog.rename_node({ "id": data.id }, data.text);
  486. $("#sidebar").jstree(true).get_node(data.id).a_attr.is_open = data.state.opened;
  487. } else {
  488. window.treeCatalog.create_node(data.parent, data);
  489. window.treeCatalog.deselect_all();
  490. window.treeCatalog.select_node(data);
  491. }
  492. pushDocumentCategory(data);
  493. $("#markdown-save").removeClass('change').addClass('disabled');
  494. $("#addDocumentModal").modal('hide');
  495. } else {
  496. showError(res.message, "#add-error-message");
  497. }
  498. $("#btnSaveDocument").button("reset");
  499. }
  500. });
  501. /**
  502. * 文档目录树
  503. */
  504. $("#sidebar").jstree({
  505. 'plugins': ["wholerow", "types", 'dnd', 'contextmenu'],
  506. "types": {
  507. "default": {
  508. "icon": false // 删除默认图标
  509. }
  510. },
  511. 'core': {
  512. 'worker':true,
  513. 'check_callback': true,
  514. "multiple": false,
  515. 'animation': 0,
  516. "data": window.documentCategory
  517. },
  518. "contextmenu": {
  519. show_at_node: false,
  520. select_node: false,
  521. "items": {
  522. "添加文档": {
  523. "separator_before": false,
  524. "separator_after": true,
  525. "_disabled": false,
  526. "label": window.editormdLocales[window.lang].addDoc,//"添加文档",
  527. "icon": "fa fa-plus",
  528. "action": function (data) {
  529. var inst = $.jstree.reference(data.reference),
  530. node = inst.get_node(data.reference);
  531. openCreateCatalogDialog(node);
  532. }
  533. },
  534. "编辑": {
  535. "separator_before": false,
  536. "separator_after": true,
  537. "_disabled": false,
  538. "label": window.editormdLocales[window.lang].edit,
  539. "icon": "fa fa-edit",
  540. "action": function (data) {
  541. var inst = $.jstree.reference(data.reference);
  542. var node = inst.get_node(data.reference);
  543. openEditCatalogDialog(node);
  544. }
  545. },
  546. "删除": {
  547. "separator_before": false,
  548. "separator_after": true,
  549. "_disabled": false,
  550. "label": window.editormdLocales[window.lang].delete,
  551. "icon": "fa fa-trash-o",
  552. "action": function (data) {
  553. var inst = $.jstree.reference(data.reference);
  554. var node = inst.get_node(data.reference);
  555. openDeleteDocumentDialog(node);
  556. }
  557. }
  558. }
  559. }
  560. }).on("ready.jstree", function () {
  561. window.treeCatalog = $("#sidebar").jstree(true);
  562. //如果没有选中节点则选中默认节点
  563. // openLastSelectedNode();
  564. }).on('select_node.jstree', function (node, selected) {
  565. if ($("#markdown-save").hasClass('change')) {
  566. if (confirm(window.editormdLocales[window.lang].contentUnsaved)) {
  567. saveDocument(false, function () {
  568. loadDocument(selected);
  569. });
  570. return true;
  571. }
  572. }
  573. //如果是空目录则直接出发展开下一级功能
  574. if (selected.node.a_attr && selected.node.a_attr.disabled) {
  575. selected.instance.toggle_node(selected.node);
  576. return false
  577. }
  578. loadDocument(selected);
  579. }).on("move_node.jstree", jstree_save).on("delete_node.jstree", function ($node, $parent) {
  580. openLastSelectedNode();
  581. });
  582. /**
  583. * 打开文档模板
  584. */
  585. $("#documentTemplateModal").on("click", ".section>a[data-type]", function () {
  586. var $this = $(this).attr("data-type");
  587. if ($this === "customs") {
  588. $("#displayCustomsTemplateModal").modal("show");
  589. return;
  590. }
  591. var body = $("#template-" + $this).html();
  592. if (body) {
  593. window.isLoad = true;
  594. window.editor.clear();
  595. window.editor.insertValue(body);
  596. window.editor.setCursor({ line: 0, ch: 0 });
  597. resetEditorChanged(true);
  598. }
  599. $("#documentTemplateModal").modal('hide');
  600. });
  601. document.addEventListener('keydown', function(event) {
  602. if (event.ctrlKey && event.key === 's') {
  603. event.preventDefault();
  604. saveDocument(true, null);
  605. }
  606. });
  607. });
  608. function myFileUpload(file, callback) {
  609. // 创建 FormData 对象以便包含要上传的文件
  610. var formData = new FormData();
  611. formData.append("editormd-file-file", file); // "file" 是与你的服务端接口相对应的字段名
  612. var layerIndex = 0;
  613. // AJAX 请求
  614. $.ajax({
  615. url: window.fileUploadURL, // 确保此 URL 是文件上传 API 的正确 URL
  616. type: "POST",
  617. async: false, // 3xxx 20240609这里修改为同步,保证cherry批量上传图片时,插入的图片名称是正确的,否则,插入的图片名称都是最后一个名称
  618. dataType: "json",
  619. data: formData,
  620. processData: false, // 必须设置为 false,因为数据是 FormData 对象,不需要对数据进行序列化处理
  621. contentType: false, // 必须设置为 false,因为是 FormData 对象,jQuery 将不会设置内容类型头
  622. beforeSend: function () {
  623. layerIndex = layer.load(1, {
  624. shade: [0.1, '#fff'] // 0.1 透明度的白色背景
  625. });
  626. },
  627. error: function () {
  628. layer.close(layerIndex);
  629. layer.msg(locales[lang].uploadFailed);
  630. },
  631. success: function (data) {
  632. layer.close(layerIndex);
  633. // 验证data是否为数组
  634. if (data.errcode !== 0) {
  635. layer.msg(data.message);
  636. } else {
  637. callback(data.url); // 假设返回的 JSON 中包含上传文件的 URL,调用回调函数并传入 URL
  638. }
  639. }
  640. });
  641. }