最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#后端集成CodeBuddy CLI的完整方案

 更新時(shí)間:2026年03月15日 14:46:32   作者:newbe36524  
本文詳細(xì)介紹了如何在C#后端項(xiàng)目中集成CodeBuddyCLI,實(shí)現(xiàn)AI編程助手能力的完整方案,通過分層架構(gòu)設(shè)計(jì)和依賴注入,該方案支持多種AIProvider,并提供了流式和非流式兩種調(diào)用方式,需要的朋友可以參考下

本文將詳細(xì)介紹如何在 C# 后端項(xiàng)目中集成 CodeBuddy CLI,實(shí)現(xiàn) AI 編程助手能力的完整方案。

背景

在現(xiàn)代 AI 代碼助手開發(fā)中,單一 AI Provider 往往無法滿足復(fù)雜多變的開發(fā)場景。這就像,人生路遠(yuǎn),總不能只認(rèn)一個(gè)方向吧?HagiCode 作為一款多功能 AI 編程助手,需要支持多種 AI Provider 以提供更好的用戶體驗(yàn)。畢竟,用戶的選擇權(quán)還是要給夠的。在 2026 年初,項(xiàng)目面臨一個(gè)關(guān)鍵決策:如何在 C# 后端中恢復(fù) CodeBuddy 的 ACP(Agent Communication Protocol)集成能力。

此前項(xiàng)目中曾實(shí)現(xiàn)過 CodeBuddy 對接,但相關(guān)代碼在一次重構(gòu)中被移除了。其實(shí)也沒什么好抱怨的,代碼迭代嘛,總有東西要被遺忘。本次技術(shù)方案的目標(biāo)是完整恢復(fù)這一能力,并優(yōu)化架構(gòu)使其更加健壯和可維護(hù)。

如果你也在考慮為自己的項(xiàng)目接入多種 AI 編程助手,下面的方案或許能給你一些啟發(fā)——這可是我們踩了無數(shù)坑之后總結(jié)出來的經(jīng)驗(yàn)。或許能讓你少走點(diǎn)彎路,也算是我做過的一點(diǎn)好事吧。

關(guān)于 HagiCode

本文分享的方案來自我們在 HagiCode 項(xiàng)目中的實(shí)踐經(jīng)驗(yàn)。HagiCode 是一個(gè)開源的 AI 代碼助手項(xiàng)目,支持多種 AI Provider 和跨平臺運(yùn)行。為了滿足不同用戶的偏好,我們需要能夠靈活切換各種 AI 編程助手,這就有了本文要介紹的 CodeBuddy 集成方案。

HagiCode 采用模塊化設(shè)計(jì),AI Provider 作為可插拔的組件,這種架構(gòu)讓我們可以輕松添加新的 AI 支持,而不影響現(xiàn)有功能。這也罷了,設(shè)計(jì)這種東西,當(dāng)初做得好,后面省心不少。如果你對我們的技術(shù)架構(gòu)感興趣,可以在 GitHub 上查看完整源碼。

架構(gòu)設(shè)計(jì)

分層架構(gòu)概覽

C# 與 CodeBuddy 的對接采用清晰的分層架構(gòu),這種設(shè)計(jì)讓代碼職責(zé)分明,后期維護(hù)起來也更加方便:

┌─────────────────────────────────────────────┐
│           Provider 契約層                    │
│   AIProviderType 枚舉 + 擴(kuò)展方法             │
├─────────────────────────────────────────────┤
│           Provider 工廠層                    │
│   AIProviderFactory 依賴注入工廠             │
├─────────────────────────────────────────────┤
│           Provider 實(shí)現(xiàn)層                    │
│   CodebuddyCliProvider 具體實(shí)現(xiàn)              │
├─────────────────────────────────────────────┤
│           ACP 基礎(chǔ)設(shè)施層                     │
│  ACPSessionManager / StdioAcpTransport      │
│  AcpRpcClient / AcpAgentClient              │
└─────────────────────────────────────────────┘

這種分層的好處是什么呢?簡單說就是各層之間互不打擾。假設(shè)以后要換一種通信方式(比如從 stdio 改成 WebSocket),你只需要改最下面那一層,上面的業(yè)務(wù)代碼完全不用動。畢竟,誰也不想牽一發(fā)而動全身,改個(gè)通信方式還要改半天業(yè)務(wù)代碼,那也太慘了。

核心組件解析

Provider 契約層 是整個(gè)架構(gòu)的基石。我們定義了 AIProviderType 枚舉,其中 CodebuddyCli = 3 作為枚舉值,通過擴(kuò)展方法實(shí)現(xiàn)字符串與枚舉的雙向映射。這樣配置文件中的字符串可以很方便地轉(zhuǎn)成枚舉,調(diào)試時(shí)枚舉也能轉(zhuǎn)成字符串輸出。這也罷了,其實(shí)就是個(gè)映射關(guān)系,但做好了就是省心。

Provider 工廠層 負(fù)責(zé)根據(jù)配置創(chuàng)建對應(yīng)的 Provider 實(shí)例。這里使用了 .NET 的依賴注入機(jī)制,配合 ActivatorUtilities.CreateInstance 實(shí)現(xiàn)動態(tài)創(chuàng)建。工廠模式的好處在于,新增一個(gè) Provider 時(shí)只需要添加創(chuàng)建邏輯,不用修改已有的代碼。這和寫文章差不多,想加個(gè)新章節(jié),就加個(gè)新章節(jié),不用把前面的都重寫一遍。

Provider 實(shí)現(xiàn)層 是真正干活的地方。CodebuddyCliProvider 實(shí)現(xiàn)了 IAIProvider 接口,提供 ExecuteAsync(非流式)和 StreamAsync(流式)兩種調(diào)用方式。

ACP 基礎(chǔ)設(shè)施層 則是通信的底層支撐。這一層處理所有的協(xié)議細(xì)節(jié),包括進(jìn)程管理、消息序列化、響應(yīng)解析等。就像房子的地基,上面蓋得再漂亮,底下的東西得穩(wěn)才行。

通信機(jī)制

Stdio 傳輸模式

CodeBuddy 使用 Stdio(標(biāo)準(zhǔn)輸入輸出) 方式與外部進(jìn)程通信。啟動命令很簡單:

codebuddy --acp

然后通過標(biāo)準(zhǔn)輸入輸出進(jìn)行 JSON-RPC 消息交換。這種方式的優(yōu)勢在于:

  1. 啟動迅速:本地進(jìn)程通信沒有網(wǎng)絡(luò)延遲
  2. 配置簡單:只需要指定可執(zhí)行文件路徑
  3. 環(huán)境隔離:每個(gè)會話獨(dú)立進(jìn)程,互不影響

通信過程中支持環(huán)境變量注入,常用的包括:

  • CODEBUDDY_API_KEY:API 密鑰認(rèn)證
  • CODEBUDDY_INTERNET_ENVIRONMENT:網(wǎng)絡(luò)環(huán)境配置

這就像,人與人之間的溝通,找個(gè)方便的方式,才能說得上話。

消息協(xié)議

ACP 基于 JSON-RPC 2.0 協(xié)議,消息格式大概是醬紫的:

// 請求消息
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "agent/prompt",
  "params": {
    "prompt": "幫我寫一個(gè)排序算法",
    "sessionId": "session-123"
  }
}
// 響應(yīng)消息
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": "這里是 AI 的回復(fù)..."
  }
}

實(shí)際實(shí)現(xiàn)中,我們把這些協(xié)議細(xì)節(jié)都封裝好了,上層業(yè)務(wù)代碼只需要關(guān)注 prompt 和 response 就行。這也罷了,封裝得好,后面的人用起來就舒服點(diǎn)。

核心實(shí)現(xiàn)

1. Provider 契約恢復(fù)

首先在枚舉文件中恢復(fù) CodeBuddy 類型:

// PCode.Models/AIProviderType.cs
public enum AIProviderType
{
    ClaudeCodeCli = 0,
    CodexCli = 1,
    GitHubCopilot = 2,
    CodebuddyCli = 3,  // 恢復(fù)這個(gè)枚舉值
    OpenCodeCli = 4,
    IFlowCli = 5,
}

然后在擴(kuò)展方法中添加字符串映射,這樣配置文件就可以用字符串指定 Provider:

// AIProviderTypeExtensions.cs
private static readonly Dictionary<string, AIProviderType> _typeMap = new(
    StringComparer.OrdinalIgnoreCase)
{
    ["CodebuddyCli"] = AIProviderType.CodebuddyCli,
    ["Codebuddy"] = AIProviderType.CodebuddyCli,
    ["codebuddy"] = AIProviderType.CodebuddyCli,
    // ... 其他 provider 的映射
};

2. Provider 工廠集成

在工廠類中添加 CodeBuddy 的創(chuàng)建分支:

// AIProviderFactory.cs
private IAIProvider? CreateProvider(AIProviderType providerType, ProviderConfiguration config)
{
    return providerType switch
    {
        AIProviderType.CodebuddyCli =>
            ActivatorUtilities.CreateInstance<CodebuddyCliProvider>(
                _serviceProvider,
                Options.Create(config)),
        // ... 其他 provider
        _ => throw new NotSupportedException($"Provider {providerType} not supported")
    };
}

這里用了依賴注入的 ActivatorUtilities,它會自動處理構(gòu)造函數(shù)的參數(shù)注入,非常方便。這也罷了,.NET 的東西,用對了就是省心。

3. 完整的 Provider 實(shí)現(xiàn)

下面是 CodebuddyCliProvider 的核心實(shí)現(xiàn),包含了流式和非流式兩種調(diào)用方式:

public class CodebuddyCliProvider : IAIProvider
{
    private readonly ILogger<CodebuddyCliProvider> _logger;
    private readonly IACPSessionManager _sessionManager;
    private readonly ProviderConfiguration _config;

    public string Name => "CodebuddyCli";
    public bool SupportsStreaming => true;
    public ProviderCapabilities Capabilities { get; }

    public CodebuddyCliProvider(
        ILogger<CodebuddyCliProvider> logger,
        IACPSessionManager sessionManager,
        IOptions<ProviderConfiguration> config)
    {
        _logger = logger;
        _sessionManager = sessionManager;
        _config = config.Value;

        // 定義當(dāng)前 Provider 的能力
        Capabilities = new ProviderCapabilities
        {
            SupportsStreaming = true,
            SupportsTools = true,
            SupportsSystemMessages = true,
            SupportsArtifacts = false,
            MaxTokens = 8192
        };
    }

    // 非流式調(diào)用:等所有結(jié)果一起返回
    public async Task<AIResponse> ExecuteAsync(
        AIRequest request,
        CancellationToken cancellationToken = default)
    {
        // 為請求創(chuàng)建獨(dú)立會話
        var session = await _sessionManager.CreateSessionAsync(
            "CodebuddyCli",
            request.WorkingDirectory,
            cancellationToken,
            request.SessionId);

        try
        {
            var fullPrompt = BuildPrompt(request);
            await session.SendPromptAsync(fullPrompt, cancellationToken);

            var responseBuilder = new StringBuilder();
            var toolCalls = new List<AIToolCall>();

            // 收集所有響應(yīng)塊
            await foreach (var chunk in StreamFromSession(session, cancellationToken))
            {
                if (!string.IsNullOrEmpty(chunk.Content))
                {
                    responseBuilder.Append(chunk.Content);
                }
                // 處理工具調(diào)用...
            }

            return new AIResponse
            {
                Content = AIResultContentSanitizer.SanitizeResultContent(
                    responseBuilder.ToString()),
                ToolCalls = toolCalls,
                Provider = Name,
                Model = string.Empty
            };
        }
        finally
        {
            // 釋放會話資源
            await session.DisposeAsync();
        }
    }

    // 流式調(diào)用:實(shí)時(shí)返回響應(yīng)塊
    public async IAsyncEnumerable<AIStreamingChunk> StreamAsync(
        AIRequest request,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var session = await _sessionManager.CreateSessionAsync(
            "CodebuddyCli",
            request.WorkingDirectory,
            cancellationToken);

        try
        {
            var fullPrompt = BuildPrompt(request);
            await session.SendPromptAsync(fullPrompt, cancellationToken);

            await foreach (var chunk in StreamFromSession(session, cancellationToken))
            {
                yield return chunk;
            }
        }
        finally
        {
            await session.DisposeAsync();
        }
    }

    private async IAsyncEnumerable<AIStreamingChunk> StreamFromSession(
        IACPSession session,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        // 遍歷會話中的所有更新
        await foreach (var notification in session.ReceiveUpdatesAsync(cancellationToken))
        {
            switch (notification.Update)
            {
                case AgentMessageChunkSessionUpdate agentMessage:
                    // 處理文本內(nèi)容塊
                    if (agentMessage.Content is AcpImp.TextContentBlock textContent)
                    {
                        yield return new AIStreamingChunk
                        {
                            Content = textContent.Text,
                            Type = StreamingChunkType.ContentDelta,
                            IsComplete = false
                        };
                    }
                    break;

                case ToolCallSessionUpdate toolCall:
                    // 處理工具調(diào)用
                    yield return new AIStreamingChunk
                    {
                        Content = string.Empty,
                        Type = StreamingChunkType.ToolCallDelta,
                        ToolCallDelta = new AIToolCallDelta
                        {
                            Id = toolCall.ToolCallId,
                            Name = toolCall.Kind.ToString(),
                            Arguments = toolCall.RawInput?.ToString()
                        }
                    };
                    break;

                case AcpImp.PromptCompletedSessionUpdate:
                    // 響應(yīng)完成
                    yield break;
            }
        }
    }

    // 構(gòu)建完整的提示詞
    private string BuildPrompt(AIRequest request, string? embeddedCommandPrompt = null)
    {
        var sb = new StringBuilder();

        // 嵌入命令提示詞(如果有)
        if (!string.IsNullOrEmpty(embeddedCommandPrompt))
        {
            sb.AppendLine(embeddedCommandPrompt);
            sb.AppendLine();
        }

        // 系統(tǒng)消息
        if (!string.IsNullOrEmpty(request.SystemMessage))
        {
            sb.AppendLine(request.SystemMessage);
            sb.AppendLine();
        }

        // 用戶 prompt
        sb.Append(request.Prompt);
        return sb.ToString();
    }
}

這段代碼有幾個(gè)關(guān)鍵點(diǎn):

  1. 會話管理:每個(gè)請求創(chuàng)建獨(dú)立會話,請求完成后釋放資源。這是坑踩出來的經(jīng)驗(yàn)——如果會話復(fù)用做得不好,很容易出現(xiàn)狀態(tài)污染的問題。畢竟,用過就得收拾干凈,不然下次用的人就麻煩了。
  2. 流式處理IAsyncEnumerable 讓響應(yīng)可以邊生成邊返回,不用等全部內(nèi)容生成完。這對于長文本場景特別重要,用戶體驗(yàn)會好很多。就像,等結(jié)果的人也不想一直干等著不是。
  3. 工具調(diào)用:CodeBuddy 支持工具調(diào)用(Function Calling),通過 ToolCallSessionUpdate 處理。這個(gè)能力對于復(fù)雜的代碼編輯任務(wù)很關(guān)鍵。
  4. 內(nèi)容過濾:使用 AIResultContentSanitizer 過濾 Think 塊內(nèi)容,保持輸出干凈。

4. 依賴注入配置

在模塊注冊中添加相關(guān)服務(wù):

// PCodeClaudeHelperModule.cs
public void ConfigureModule(IServiceCollection context)
{
    // 注冊 Provider
    context.Services.AddTransient<CodebuddyCliProvider>();

    // 注冊 ACP 基礎(chǔ)設(shè)施
    context.Services.AddSingleton<IACPSessionManager, ACPSessionManager>();
    context.Services.AddSingleton<IAcpPlatformConfigurationResolver, AcpPlatformConfigurationResolver>();
    context.Services.AddSingleton<IAIRequestToAcpMapper, AIRequestToAcpMapper>();
    context.Services.AddSingleton<IAcpToAIResponseMapper, AcpToAIResponseMapper>();
}

配置示例

配置文件

appsettings.json 中添加 CodeBuddy 相關(guān)配置:

AI:
  # 默認(rèn)使用的 Provider
  DefaultProvider: "CodebuddyCli"
  # Provider 配置
  Providers:
    CodebuddyCli:
      Type: "CodebuddyCli"
      WorkingDirectory: "C:/projects/my-app"
      ExecutablePath: "C:/tools/codebuddy.cmd"
  # 平臺相關(guān)配置
  PlatformConfigurations:
    CodebuddyCli:
      ExecutablePath: "C:/tools/codebuddy.cmd"
      Arguments: "--acp"
      StartupTimeoutMs: 5000
      EnvironmentVariables:
        CODEBUDDY_API_KEY: "${CODEBUDDY_API_KEY}"
        CODEBUDDY_INTERNET_ENVIRONMENT: "production"

配置模型

對應(yīng)的配置模型定義:

public class CodebuddyPlatformConfiguration : IAcpPlatformConfiguration
{
    public string ProviderName => "CodebuddyCli";
    public AcpTransportType TransportType => AcpTransportType.Stdio;
    public string ExecutablePath { get; set; } = "codebuddy";
    public string Arguments { get; set; } = "--acp";
    public int StartupTimeoutMs { get; set; } = 5000;
    public Dictionary<string, string?>? EnvironmentVariables { get; set; }
}

實(shí)踐經(jīng)驗(yàn)總結(jié)

踩坑記錄

我們在實(shí)現(xiàn)過程中遇到了幾個(gè)典型的坑,分享出來讓大家少走彎路。畢竟,別人的坑,自己能避開就是好事:

  1. 會話泄漏問題:一開始沒有正確釋放會話,導(dǎo)致進(jìn)程資源耗盡。解決方法是使用 try-finally 確保每次請求都會釋放資源。這也罷了,用過的東西得放回去,不然后面的人用什么。
  2. 環(huán)境變量傳遞:Windows 和 Linux 的環(huán)境變量語法不同,后來統(tǒng)一使用 Dictionary<string, string?> 來處理。跨平臺這種事,一開始就統(tǒng)一規(guī)范,后面就省心。
  3. 超時(shí)配置:CLI 啟動需要時(shí)間,設(shè)置了 5 秒的啟動超時(shí),避免快速請求失敗。凡事都得有個(gè)度,太急了反而辦不成事。
  4. 編碼問題:Windows 上默認(rèn)編碼可能導(dǎo)致中文亂碼,在啟動進(jìn)程時(shí)顯式指定 UTF-8 編碼。中文顯示不出來,那多難受。

性能優(yōu)化

  1. 會話池:對于頻繁的短請求,可以考慮實(shí)現(xiàn)會話池來復(fù)用進(jìn)程
  2. 連接緩存:工廠類已經(jīng)支持 Provider 實(shí)例緩存
  3. 異步優(yōu)先:全程使用異步編程,避免阻塞線程

性能這種事,能優(yōu)化就優(yōu)化,畢竟用戶等的越久,體驗(yàn)就越差。

總結(jié)

本文詳細(xì)介紹了 C# 后端集成 CodeBuddy CLI 的完整方案,涵蓋了從架構(gòu)設(shè)計(jì)到具體實(shí)現(xiàn)的全過程。通過分層架構(gòu)設(shè)計(jì),我們將協(xié)議細(xì)節(jié)與業(yè)務(wù)邏輯分離,使得代碼更加清晰和可維護(hù)。

核心要點(diǎn)回顧:

  • 采用 Provider 契約層、工廠層、實(shí)現(xiàn)層、基礎(chǔ)設(shè)施層的分層架構(gòu)
  • 使用 JSON-RPC over Stdio 方式進(jìn)行進(jìn)程間通信
  • 通過依賴注入實(shí)現(xiàn)靈活的配置和擴(kuò)展
  • 提供流式和非流式兩種調(diào)用方式

這套方案不僅適用于 CodeBuddy,添加新的 AI Provider 也遵循同樣的模式。如果你也在做類似的多 AI Provider 集成,希望這篇文章能給你一些參考。其實(shí),寫文章和寫代碼一樣,分享出來,能幫到別人就算沒白寫。

以上就是C#后端集成CodeBuddy CLI的完整方案的詳細(xì)內(nèi)容,更多關(guān)于C#集成CodeBuddy CLI的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C#微信公眾號開發(fā)之使用MessageHandler簡化消息處理流程

    C#微信公眾號開發(fā)之使用MessageHandler簡化消息處理流程

    這篇文章介紹了C#微信公眾號開發(fā)之使用MessageHandler簡化消息處理流程,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • Unity3D實(shí)現(xiàn)警報(bào)燈

    Unity3D實(shí)現(xiàn)警報(bào)燈

    這篇文章主要為大家詳細(xì)介紹了Unity3D實(shí)現(xiàn)警報(bào)燈效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • C# DataTable與Model互轉(zhuǎn)的示例代碼

    C# DataTable與Model互轉(zhuǎn)的示例代碼

    這篇文章主要介紹了C#DataTable與Model互轉(zhuǎn)的示例代碼,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2020-12-12
  • C#基于DBContext(EF)實(shí)現(xiàn)通用增刪改查的REST方法實(shí)例

    C#基于DBContext(EF)實(shí)現(xiàn)通用增刪改查的REST方法實(shí)例

    這篇文章主要介紹了C#基于DBContext(EF)實(shí)現(xiàn)通用增刪改查的REST方法實(shí)例,是C#程序設(shè)計(jì)中非常實(shí)用的技巧,需要的朋友可以參考下
    2014-10-10
  • c#互斥鎖Mutex類用法介紹

    c#互斥鎖Mutex類用法介紹

    本文詳細(xì)講解了c#互斥鎖Mutex類的用法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02
  • C#簡單實(shí)現(xiàn)表達(dá)式目錄樹(Expression)

    C#簡單實(shí)現(xiàn)表達(dá)式目錄樹(Expression)

    表達(dá)式目錄樹以數(shù)據(jù)形式表示語言級別代碼。數(shù)據(jù)存儲在樹形結(jié)構(gòu)中。表達(dá)式目錄樹中的每個(gè)節(jié)點(diǎn)都表示一個(gè)表達(dá)式。這篇文章給大家介紹C#簡單實(shí)現(xiàn)表達(dá)式目錄樹(Expression),需要的朋友參考下吧
    2017-11-11
  • C# 使用模式匹配的好處總結(jié)

    C# 使用模式匹配的好處總結(jié)

    模式匹配的這些用途展示了它在簡化代碼、提高可讀性和靈活處理不同類型和條件的強(qiáng)大能力,隨著C#語言的發(fā)展,模式匹配的功能和應(yīng)用場景將會進(jìn)一步擴(kuò)展和深化,這篇文章主要介紹了C# 使用模式匹配的好處,需要的朋友可以參考下
    2024-07-07
  • c#?如何將字符串轉(zhuǎn)換為大寫或小寫

    c#?如何將字符串轉(zhuǎn)換為大寫或小寫

    這篇文章主要介紹了c#?如何將字符串轉(zhuǎn)換為大寫或小寫,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • DevExpress實(shí)現(xiàn)GridView當(dāng)無數(shù)據(jù)行時(shí)提示消息

    DevExpress實(shí)現(xiàn)GridView當(dāng)無數(shù)據(jù)行時(shí)提示消息

    這篇文章主要介紹了DevExpress實(shí)現(xiàn)GridView當(dāng)無數(shù)據(jù)行時(shí)提示消息,需要的朋友可以參考下
    2014-08-08
  • C#?Chart控件標(biāo)記問題詳解

    C#?Chart控件標(biāo)記問題詳解

    這篇文章主要介紹了C#?Chart控件標(biāo)記問題詳解,在做項(xiàng)目的時(shí)候,遇到一個(gè)需求,需要我對Chart圖標(biāo)標(biāo)記數(shù)據(jù)正在運(yùn)行,實(shí)現(xiàn)數(shù)據(jù)可視化,文章通過圍繞主題展開詳情,需要的朋友可以參考一下
    2022-08-08

最新評論

牙克石市| 陵川县| 枝江市| 乡城县| 基隆市| 怀柔区| 台湾省| 库车县| 普定县| 宁津县| 包头市| 河津市| 宁陕县| 民县| 松阳县| 正镶白旗| 叶城县| 竹山县| 安国市| 永德县| 河源市| 丹阳市| 同仁县| 芜湖县| 华池县| 安阳县| 江源县| 桐梓县| 平原县| 全州县| 丰原市| 虎林市| 慈利县| 隆尧县| 贵阳市| 庆元县| 汕头市| 平山县| 宜丰县| 马尔康县| 宣武区|