解決自媒體一鍵多平臺發佈,從零開發Markdown編輯器(一)

来源:https://www.cnblogs.com/hxsfx/archive/2022/09/12/16686305.html
-Advertisement-
Play Games

前言 在這個人人都是自媒體的時代,為了擴大個人影響力同時預防文章被盜版至其他平臺,多平臺發佈文章就成了創作者們的一大痛點,為瞭解決這一痛點就需要將文章的編輯到發佈無縫集成。 現在要實現這一功能,開發一個完全可控的Markdown編輯器就是第一步。 本文源碼已上傳Github:Github hxsfx ...


前言

在這個人人都是自媒體的時代,為了擴大個人影響力同時預防文章被盜版至其他平臺,多平臺發佈文章就成了創作者們的一大痛點,為瞭解決這一痛點就需要將文章的編輯到發佈無縫集成。
現在要實現這一功能,開發一個完全可控的Markdown編輯器就是第一步。
本文源碼已上傳Github:Github hxsfx MarkdownEditor

界面草圖

image

技術選型

考慮到編輯器解析渲染放在前端更合適,採用了HTML+JS+CSS實現Markdown編輯器模塊。

功能演示及代碼分享

各位小伙伴可以訪問線上演示地址:https://md.hxsfx.com/

1、標題語法

  • 功能演示
    image
  • 代碼分享
var h4_start = "#### ";
var h3_start = "### ";
var h2_start = "## ";
var h1_start = "# ";
if (textContent.startsWith(h4_start)) {
    html = textContent.substring(h4_start.length, textContent.length);
    tagName = "h4";
}//四級標題
else if (textContent.startsWith(h3_start)) {
    html = textContent.substring(h3_start.length, textContent.length);
    tagName = "h3";
}//三級標題
else if (textContent.startsWith(h2_start)) {
    html = textContent.substring(h2_start.length, textContent.length);
    tagName = "h2";
}//二級標題
else if (textContent.startsWith(h1_start)) {
    html = textContent.substring(h1_start.length, textContent.length);
    tagName = "h1";
}//一級標題

2、強調語法

  • 功能演示
    image
  • 代碼分享
//提取強調語法
function ExtractEmphasisGrammar(html) {
    //粗斜體
    var html = html.replace(/\*\*\*.*?\*\*\*/g, function (strongAndem_val) {
        var _strongAndem_val = strongAndem_val.substring(3, strongAndem_val.length - 3)
        return CreatePreviewSectionHTML("strong,em", _strongAndem_val);
    });
    //粗體
    var html = html.replace(/\*\*.*?\*\*/g, function (strong_val) {
        var _strong_val = strong_val.substring(2, strong_val.length - 2);
        return CreatePreviewSectionHTML("strong", _strong_val);
    });
    //斜體
    var html = html.replace(/\*.*?\*/g, function (em_val) {
        var _em_val = em_val.substring(1, em_val.length - 1);
        return CreatePreviewSectionHTML("em", _em_val);
    });
    return html;
}
//根據標簽和內部內容生成預覽區域內行塊html
function CreatePreviewSectionHTML(tagName, innerHTML) {
    var html = innerHTML;
    if (tagName == "code") {
        html = html.replace(/(\s)/g, " ");//.replace("/ /g"," ");
    }//將空格替換為轉義字元防止多個空格在html顯示為一個

    if (tagName == "" || tagName == null || tagName == undefined) {
    } else if (tagName == "hr") {
        html = "<hr>";
    } else {
        var start_tagName = "";
        var end_tagName = "";
        var tagNameSplit = tagName.split(",");
        for (var i = 0; i < tagNameSplit.length; i++) {
            start_tagName += "<" + tagNameSplit[i] + ">";
            end_tagName = end_tagName + "</" + tagNameSplit[i] + ">";
        }
        html = start_tagName + html + end_tagName;
    }
    return html;
}

3、引用語法

  • 功能演示
    image
  • 代碼分享
var blockquote_start = "&gt;";
if (textContent.startsWith(blockquote_start)) {
    isBlockquote = true;
    html = textContent.substring(blockquote_start.length, textContent.length);
    tagName = "blockquote";
}//引用

4、列表語法

  • 功能演示
    image

  • 代碼分享

var olli_pattern = /^ {0,3}[1-9]*\. /; //有序列表正則表達式
var ulli_pattern = /^[ ]{0,3}(\* |- |\+ )/; //有序列表正則表達式

if (olli_pattern.test(textContent)) {
    //有序列表項
    if (textContent.startsWith(" ")) {
        isOL2 = true;
    } else {
        isOL = true;
    }
    html = textContent.replace(olli_pattern, "");
    tagName = "ol";
}
else if (ulli_pattern.test(textContent)) {
    //無序列表項
    if (textContent.startsWith(" ")) {
        isUL2 = true;
    } else {
        isUL = true;
    }
    html = textContent.replace(ulli_pattern, "");
    tagName = "ul";
}
//提取列表語法
function ExtractList(analysisResult, prevAnalysisResult) {
    var isExtractTable = true;
    if (prevAnalysisResult == null || prevAnalysisResult.ListInfo == null) {
        isExtractTable = CreateListInfo(analysisResult, isExtractTable);
    }//沒有上一行 或者 上一行不是列表
    else {
        var liHTML = analysisResult.AnalysisHTML;
        if ((prevAnalysisResult.IsOL && analysisResult.IsOL) ||
            (prevAnalysisResult.IsUL && analysisResult.IsUL)) {
            //接著上一行繼續
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.LiInfoArray.push({ LiHtml: liHTML, LiInfoArray: [] });
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//同為一級標題且標簽相同
        else if ((prevAnalysisResult.IsOL && analysisResult.IsUL) ||
            (prevAnalysisResult.IsUL && analysisResult.IsOL)) {
            isExtractTable = CreateListInfo(analysisResult, isExtractTable);
        }
        else if ((prevAnalysisResult.IsOL && (analysisResult.IsOL2 || analysisResult.IsUL2)) ||
            (prevAnalysisResult.IsUL && (analysisResult.IsOL2 || analysisResult.IsUL2))) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            var secondLevelLiInfoArray = currentFisrtLevelLiInfoArray.LiInfoArray;
            var isFindPeer = false;
            for (var i = 0; i < secondLevelLiInfoArray.length; i++) {
                var _secondLevelLiInfo = secondLevelLiInfoArray[i];
                if (_secondLevelLiInfo.TagName == analysisResult.TagName) {
                    isFindPeer = true;
                    _secondLevelLiInfo.LiHtmlList.push(liHTML);
                }
            }
            if (!isFindPeer) {
                secondLevelLiInfoArray.push({ TagName: analysisResult.TagName, LiHtmlList: [liHTML] });
            }
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }
        else if ((prevAnalysisResult.IsOL2 && (analysisResult.IsOL || analysisResult.IsUL)) ||
            (prevAnalysisResult.IsUL2 && (analysisResult.IsOL || analysisResult.IsUL))) {
            if (prevAnalysisResult.ListInfo.TagName == analysisResult.TagName) {
                prevAnalysisResult.ListInfo.LiInfoArray.push({ LiHtml: liHTML, LiInfoArray: [] });
                analysisResult.ListInfo = prevAnalysisResult.ListInfo;
                analysisResult.ListInfo.IsMergePrevHTML = true;
            }//此二級有序項對應一級項的列表項跟當前一致且為一級
            else {
                isExtractTable = CreateListInfo(analysisResult, isExtractTable);
            }//當前一級與上一個一級標簽不同無法合併,再生成一個新的
        }
        else if ((prevAnalysisResult.IsOL2 && analysisResult.IsOL2) ||
            (prevAnalysisResult.IsUL2 && analysisResult.IsUL2)) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            var secondLevelLiInfoArray = currentFisrtLevelLiInfoArray.LiInfoArray.slice(-1)[0];
            secondLevelLiInfoArray.LiHtmlList.push(liHTML);
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//同為二級同標簽,直接追加
        else if ((prevAnalysisResult.IsOL2 && analysisResult.IsUL2) ||
            (prevAnalysisResult.IsUL2 && analysisResult.IsOL2)) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            //這兒是要新添加不同的二級標簽跟上面不一樣所以不要屬性
            currentFisrtLevelLiInfoArray.LiInfoArray.push({ TagName: analysisResult.TagName, LiHtmlList: [liHTML] });
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//雖然同時二級,但標簽不同,需生成新的二級
    }
    return isExtractTable;
}
function CreateListInfo(analysisResult, isExtractTable) {
    if (analysisResult.IsUL || analysisResult.IsOL) {
        analysisResult.ListInfo = {
            IsMergePrevHTML: false,
            TagName: analysisResult.TagName,
            LiInfoArray: [
                {
                    LiHtml: analysisResult.AnalysisHTML,
                    LiInfoArray: []
                    //LiInfoArray: [{
                    // TagName: "",
                    // LiHtmlList: []
                    //}],
                },
            ]
        };
    } //識別為一級無序列表項 或者 識別為一級有序列表項
    else {
        isExtractTable = false;
    } //第一行識別為二級列表項,做普通處理
    return isExtractTable;
}

5、代碼塊語法

  • 功能演示
    image
  • 代碼分享
var isCodeBlock = false;
if (analysisResult.IsCodeBlock) {
    isCodeBlock = analysisResult.IsCodeBlock;
    //當前是代碼塊結束或開始
    if (prevAnalysisResult == null) {
        //代碼塊開始
        analysisResult.TextContent = "";
        previewHTMLArray.push(CreatePreviewSectionHTML("pre", analysisResult.TextContent));
    }
    else {
        if (prevAnalysisResult.IsCodeBlock) {
            //結束
            analysisResult.IsCodeBlock = false;
            analysisResult.TextContent = prevAnalysisResult.TextContent;
            previewHTMLArray[previewHTMLArray.length - 1] = CreatePreviewSectionHTML("pre", analysisResult.TextContent);
        }
        else {
            //開始
            analysisResult.TextContent = "";
            previewHTMLArray.push(CreatePreviewSectionHTML("pre", analysisResult.TextContent));
        }
    }
}

if (prevAnalysisResult.IsCodeBlock) {
    //當前在代碼塊內
    isCodeBlock = true;
    analysisResult.IsCodeBlock = true;
    var _textContent = "";
    if (analysisResult.TextContent != "" && analysisResult.TextContent != null) {
        _textContent = CreatePreviewSectionHTML("code", analysisResult.TextContent);
    }
    analysisResult.TextContent = prevAnalysisResult.TextContent + _textContent;
    previewHTMLArray[previewHTMLArray.length - 1] = CreatePreviewSectionHTML("pre", analysisResult.TextContent);
}

6、分隔線語法

  • 功能演示
    image
  • 代碼分享
var separator_pattern = /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/;//分隔線正則表達式
if (separator_pattern.test(textContent)) {
    tagName = "hr";
}//分隔線

7、鏈接語法

  • 功能演示
    image
  • 代碼分享
var def_pattern = /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/;
if (def_pattern.test(textContent)) {
    html = "textContent";
    tagName = "a";
}//鏈接
//提取鏈接和圖片語法
function ExtractLink(html) {
    var link_pattern = /!?\[(?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?\]\(\s*(?:<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)(?:\s+(?:"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/g;
    if (link_pattern.test(html)) {
        var link_pattern2 = /^!?\[((?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/;
        html = html.replace(link_pattern, function (val) {
            var getVals = link_pattern2.exec(val);
            var text = getVals[1];
            var href = getVals[2];
            href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
            href = href.replace(/<strong><em>/, "***");
            href = href.replace(/<\/strong><\/em>/, "***");
            href = href.replace(/<strong>/, "**");
            href = href.replace(/<\/strong>/, "**");
            href = href.replace(/<em>/, "*");
            href = href.replace(/<\/em>/, "*");
            var title = getVals[3];
            title = getVals[3] ? getVals[3].slice(1, -1) : '';
            title = title.replace(/<strong><em>/, "***");
            title = title.replace(/<\/strong><\/em>/, "***");
            title = title.replace(/<strong>/, "**");
            title = title.replace(/<\/strong>/, "**");
            title = title.replace(/<em>/, "*");
            title = title.replace(/<\/em>/, "*");
            if (getVals[0].startsWith("!")) {
                return "<img src=\"" + href + "\" title=\"" + title + "\" alt=\"" + text + "\"/>";
            } else {
                var text = ExtractLink(text);
                return "<a href=\"" + href + "\" title=\"" + title + "\">" + text + "</a>";
            }
        });
    }
    return html;
}

8、圖片語法

  • 功能演示
    image
  • 代碼分享
    參考第7點的鏈接語法

9、表格語法

  • 功能演示
    image
  • 代碼分享
var table_tag_pattern = /^[ ]{0,3}((\|[ ]*?(?:[:]{0,1}- *){3,}[:]{0,1}[ ]*)+\|){1,}[ | ]{0,}$/;//表格出現的標識
if (table_tag_pattern.test(textContent)) {
    html = textContent;
}//表格
//提取表格語法
function ExtractTable(analysisResult, prevAnalysisResult) {
    var isExtractTable = true;
    if (prevAnalysisResult == null) {
        isExtractTable = false;
    }//但因為是第一行,所以不能轉為表格
    else {
        //把當前內容根據|分隔進行拆分
        var currentSplitArray = analysisResult.AnalysisHTML.split('|');
        if (analysisResult.IsTable && prevAnalysisResult.TableInfo == null) {
            if (/^[ ]{0,3}\|/.test(prevAnalysisResult.AnalysisHTML)) {
                var headerTHTextArray = prevAnalysisResult.AnalysisHTML.split('|');
                analysisResult.TableInfo = {};
                analysisResult.TableInfo.TextAlignArray = new Array();
                var columnCount = currentSplitArray.length - 2;
                if (columnCount > headerTHTextArray.length - 2) {
                    columnCount = headerTHTextArray.length - 2;
                }
                var thHTML = "";
                for (var i = 0; i < columnCount; i++) {
                    var _tagText = currentSplitArray[i + 1].trim();
                    var textAlign = "";
                    if (_tagText.startsWith("-") && _tagText.endsWith("-")) {
                        textAlign = ""
                    } else if (_tagText.startsWith(":") && _tagText.endsWith(":")) {
                        textAlign = "center";
                    } else if (_tagText.startsWith(":")) {
                        textAlign = "left";
                    } else if (_tagText.endsWith(":")) {
                        textAlign = "right";
                    }
                    var textAlignStyle = ""
                    if (textAlign != "") {
                        textAlignStyle = "style=\"text-align:" + textAlign + "\";";
                    }
                    analysisResult.TableInfo.TextAlignArray.push(textAlignStyle);
                    thHTML += "<th " + textAlignStyle + ">" + headerTHTextArray[i + 1] + "</th>"
                }
                analysisResult.TableInfo.THeadHTML = "<thead><tr>" + thHTML + "</tr></thead>";
                analysisResult.TableInfo.TBodyHTMLArray = new Array();
            }//檢查上一行是不是符合做表頭內容文本的格式條件
            else {
                isExtractTable = false;
            }//此行雖然是表格標識出現,但因為上一行格式不對,不滿足生成表格的條件
        }//當表頭還未生成的時候先生成表頭
        else if (prevAnalysisResult.TableInfo != null) {
            analysisResult.TableInfo = prevAnalysisResult.TableInfo;
            var tdHtml = "";
            for (var i = 0; i < analysisResult.TableInfo.TextAlignArray.length; i++) {
                var text = currentSplitArray[i + 1];
                if (text === undefined) {
                    text = "";
                }
                tdHtml += "<td " + analysisResult.TableInfo.TextAlignArray[i] + ">" + text + "</td>"
            }
            analysisResult.TableInfo.TBodyHTMLArray.push("<tr>" + tdHtml + "</tr>");
        }//當表頭生成後生成表體
        else {
            isExtractTable = false;
        }
    }
    if (isExtractTable == false) {
        analysisResult.TableInfo = null;
    }
    return isExtractTable;
}

10、其他功能

  • 功能演示
    image
  • 代碼分享
//通過使用localStorage實現本地緩存
//緩存輸入至localStorage
function LocalStorageInputMD(mdInputHTML){
    localStorage.setItem('ls_mdInput',mdInputHTML);
}
//實現輸入內容導出
function exportRaw(name, data) {
    var urlObject = window.URL || window.webkitURL || window;
    var export_blob = new Blob([data]);
    var save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
    save_link.href = urlObject.createObjectURL(export_blob);
    save_link.download = name;
    var ev = document.createEvent("MouseEvents");
    ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    save_link.dispatchEvent(ev);
}
//通過將輸入緩存至EditorElementRecordHistoryArray中,實現撤銷和重做功能
//點擊撤銷按鈕
UndoButtonElement.onclick = function () {
    //document.execCommand("Undo");
    if (EditorElementRecordHistoryArray === undefined ||
        EditorElementRecordHistoryArray == null) {
        EditorElementRecordHistoryArray = new Array();
    }
    else {
        if (EditorElementRecordHistoryArray.length >= 2) {
            if (EditorElementRecordHistoryArray.length <= 2) {
                UndoButtonElement.className += " disable";
            }//當記錄元素小於等於1個,就可以撤銷了
            if (EditorElementRecordHistoryArray_undo === undefined ||
                EditorElementRecordHistoryArray_undo == null) {
                EditorElementRecordHistoryArray_undo = new Array();
            }//先判斷重做隊列是否為null
            RedoButtonElement.className = RedoButtonElement.className.replace("disable", "");//將重做按鈕點亮
            EditorElementRecordHistoryArray_undo.push(EditorElementRecordHistoryArray.pop());//將保存的歷史記錄最後一條取出來(這是當前條,取出來要放到重做隊列)
            editDivElement.innerHTML = EditorElementRecordHistoryArray.pop();//將當前條的前一條取出來
            Preview();//渲染
        }
    }
}
//點擊重做按鈕
RedoButtonElement.onclick = function () {
    //document.execCommand("Redo");
    if (EditorElementRecordHistoryArray_undo !== undefined &&
        EditorElementRecordHistoryArray_undo != null &&
        EditorElementRecordHistoryArray_undo.length > 0) {
        editDivElement.innerHTML = EditorElementRecordHistoryArray_undo.pop();
        Preview();//渲染
    }
    if (EditorElementRecordHistoryArray_undo === undefined ||
        EditorElementRecordHistoryArray_undo == null ||
        EditorElementRecordHistoryArray_undo.length <= 0) {
        if (RedoButtonElement.className.indexOf("disable") < 0) {
            RedoButtonElement.className += " disable";
        }
    }
}
//只要有輸入動作就清空重做記錄
function ClearUndo() {
    EditorElementRecordHistoryArray_undo = new Array();
    if (RedoButtonElement.className.indexOf("disable") < 0) {
        RedoButtonElement.className += " disable";
    }
}

寫在最後

本次開發代碼質量只能說有手就行哈哈。接下來除了完成新功能的添加,也會預留一部分的時間來重構代碼。如果各位小伙伴有什麼建議的可以通過評論或者私信的方式告訴我,讓我們一起學習吧。

預告一下

後期將對接各平臺發佈功能(初步預計5個平臺,包括博客園、知乎、今日頭條、CSDN、簡書),預期1個月左右完成一個平臺對接,爭取春節前完成5個平臺的一鍵發佈功能。

博客園-本文作者(好先生FX http://www.cnblogs.com/hxsfx)

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Django框架(九) cookie與session簡介 網址的發展史: 1、起初網站都沒有保存用戶功能的需求,所有用戶訪問返回的結果都是一樣的。 比如:新聞網頁,博客網頁,小說... (這些網頁是不需要登錄後才能訪問的,每個人訪問的結果都一樣) 2、後來出現了一些需要保存用戶信息的網站 比如:支付 ...
  • 由於本文需要有一定的Stream基礎,所以如果不懂什麼是Stream的同學請移步:Java Stream入門 操作分類 graph LR 操作分類 中間操作 終端操作 操作分類 中間操作 有狀態 中間操作 無狀態 短路 終端操作 非短路 終端操作 中間操作只進行操作的記錄,而實際的操作是由終端操作來 ...
  • 這兩天在對一些ORM進行性能測試(涉及SqlSugar、FreeSql、Fast.Framework、Dapper.LiteSql),測試用的是Winform程式,別人第一眼看到我的程式,說,你這測試沒意義! 可能我的測試程式的某些地方寫的比較變態吧,但我認為有現實意義,並且網上有相關網站崩潰問題的 ...
  • 參考鏈接:https://www.systutorials.com/docs/linux/man/7-netlink/ #1. 監聽Netlink消息類型示例 Netlink是用戶程式與內核通信的socket方法,通過Netlink可以獲得修改內核的配置,常見的有獲得介面的IP地址列表、更改路由表或 ...
  • 0. 前言 可以臨時設置,也可以修改配置文件 1. 修改配置文件 # 打開 配置IP的文件 路徑如下 sudo vi /etc/netplan/01-network-manager-all.yaml 1.1 輸入(修改)以下內容 # This is the network config writte ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 1.結論先行 無論ibp(innodb_buffer_pool_size)是否充足,MySQL的性能都遠不如GreatSQL。 MySQL的性能平均約為 ...
  • 一.數據的存儲方式 特定的文件 / 記憶體 / 第三方雲伺服器 / 資料庫伺服器 二.什麼是資料庫 資料庫按照一定的形式來組織存儲數據,目的是為了便於操作數據 —— 增刪改查 三.資料庫發展歷史 網狀資料庫 -> 層次型資料庫 -> 關係型資料庫 -> 非關係型資料庫(NoSQL) 關係型資料庫邏輯結 ...
  • 本文介紹對vue-plugin-hiprint部分重要代碼的解析,這是一個很好的開源插件,能夠自己自定義列印模板,通過後端傳來的數據進行渲染列印,官方也提供了許多的api供開發者使用。界面採用了antdesign。實現了免預覽的直接列印。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...