Commit e7dabb90 authored by 段廷瑞's avatar 段廷瑞

feat: 工具平台支持Claude Code对话(基于本地命令行)

parent 27316c1c
......@@ -59,3 +59,5 @@ bin
/AppHome/config/List`1.json
/Tools/Tools.csproj.user
/Tools/.claude/settings.local.json
/.claude/settings.local.json
/AppHome/ClaudeChat
using System;
using System.Collections.Generic;
namespace Tools.MyCommon
{
public sealed class ChatMessage
{
public string Role { get; set; }
public string Content { get; set; }
public string ThinkingContent { get; set; }
public bool IsStreaming { get; set; }
public DateTime Timestamp { get; set; } = DateTime.Now;
}
public sealed class AskUserQuestionInfo
{
public string ToolUseId { get; set; }
public List<QuestionItem> Questions { get; set; } = new List<QuestionItem>();
}
public sealed class QuestionItem
{
public string Question { get; set; }
public string Header { get; set; }
public List<OptionItem> Options { get; set; } = new List<OptionItem>();
public bool MultiSelect { get; set; }
}
public sealed class OptionItem
{
public string Label { get; set; }
public string Description { get; set; }
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Tools.MyCommon
{
public sealed class ChatSession
{
public string Id { get; set; }
public string Title { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime SavedAt { get; set; }
public List<ChatMessage> Messages { get; set; }
}
public static class HistoryManager
{
public static string EnsureHistoryDir(string workingDirectory)
{
var dir = Path.Combine(workingDirectory, "history");
Directory.CreateDirectory(dir);
return dir;
}
public static void SaveSession(string historyDir, List<ChatMessage> messages)
{
if (messages == null || messages.Count == 0) return;
var userMessages = messages.Where(m => m.Role == "user").ToList();
if (userMessages.Count == 0) return;
var session = new ChatSession
{
Id = DateTime.Now.ToString("yyyyMMdd_HHmmss"),
Title = Truncate(userMessages[0].Content, 60),
CreatedAt = messages[0].Timestamp,
SavedAt = DateTime.Now,
Messages = new List<ChatMessage>(messages)
};
var filePath = Path.Combine(historyDir, $"session_{session.Id}.json");
var json = JsonConvert.SerializeObject(session, Formatting.Indented);
File.WriteAllText(filePath, json);
}
public static ChatSession LoadSession(string filePath)
{
var json = File.ReadAllText(filePath);
return JsonConvert.DeserializeObject<ChatSession>(json);
}
public static List<SessionSummary> ListSessions(string historyDir)
{
if (!Directory.Exists(historyDir))
return new List<SessionSummary>();
var result = new List<SessionSummary>();
var files = Directory.GetFiles(historyDir, "session_*.json");
foreach (var file in files)
{
try
{
var session = LoadSession(file);
result.Add(new SessionSummary
{
FilePath = file,
Id = session.Id,
Title = session.Title,
CreatedAt = session.CreatedAt,
MessageCount = session.Messages?.Count ?? 0
});
}
catch { }
}
result.Sort((a, b) => b.CreatedAt.CompareTo(a.CreatedAt));
return result;
}
public static void DeleteSession(string filePath)
{
if (File.Exists(filePath))
File.Delete(filePath);
}
private static string Truncate(string text, int maxLen)
{
if (string.IsNullOrEmpty(text)) return "";
text = text.Replace('\r', ' ').Replace('\n', ' ');
return text.Length <= maxLen ? text : text.Substring(0, maxLen) + "...";
}
}
public sealed class SessionSummary
{
public string FilePath { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public DateTime CreatedAt { get; set; }
public int MessageCount { get; set; }
public string DisplayText =>
$"{CreatedAt:yyyy-MM-dd HH:mm} [{MessageCount}条] {Title}";
}
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Tools.MyUtils;
namespace Tools.MyCommon
{
public sealed class ToolUseInfo
{
public string ToolUseId { get; set; }
public string ToolName { get; set; }
public string InputJson { get; set; }
public string DisplayText
{
get
{
try
{
var obj = JObject.Parse(InputJson ?? "{}");
switch (ToolName)
{
case "bash":
case "Bash":
var cmd = obj["command"]?.Value<string>();
return string.IsNullOrEmpty(cmd) ? "Bash 命令" : $"Bash: {cmd}";
case "web_search":
case "WebSearch":
var q = obj["query"]?.Value<string>();
return string.IsNullOrEmpty(q) ? "Web 搜索" : $"Web 搜索: {q}";
case "web_fetch":
case "WebFetch":
var url = obj["url"]?.Value<string>();
return string.IsNullOrEmpty(url) ? "Web 请求" : $"Web 请求: {url}";
default:
return $"{ToolName}: {InputJson}";
}
}
catch
{
return ToolName ?? "未知工具";
}
}
}
}
public sealed class ClaudeCliService : IDisposable
{
public string ClaudeExecutablePath { get; set; } = "claude.cmd";
public string WorkingDirectory { get; set; }
public int ProcessTimeoutMs { get; set; } = 120_000;
public bool IsRunning { get; private set; }
public event Action<string> OnTextDelta;
public event Action<string> OnThinkingDelta;
public event Action<string, string> OnSystemInit;
public event Action OnStreamComplete;
public event Action<string> OnError;
public event Action OnProcessExited;
public event Action<ToolUseInfo> OnToolUseRequest;
public event Action<AskUserQuestionInfo> OnAskUserQuestion;
public string ModelName { get; private set; }
private Process _process;
private CancellationTokenSource _cts;
private readonly StringBuilder _textBuffer = new StringBuilder();
private readonly StringBuilder _thinkingBuffer = new StringBuilder();
private DateTime _lastFireTime = DateTime.MinValue;
private DateTime _lastThinkingFireTime = DateTime.MinValue;
private const int ThrottleMs = 80;
private volatile bool _cancelled;
private int _generation;
private StreamWriter _stdin;
// Tool use tracking
private string _currentToolUseId;
private string _currentToolUseName;
private readonly StringBuilder _currentToolUseInput = new StringBuilder();
private bool _isTrackingToolUse;
public void SendMessage(string prompt, bool isContinue, Control uiContext)
{
if (IsRunning)
throw new InvalidOperationException("A message is already being processed.");
var arguments = BuildArguments(prompt, isContinue);
var psi = new ProcessStartInfo
{
FileName = ClaudeExecutablePath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
WorkingDirectory = WorkingDirectory ?? "",
};
_process = new Process { StartInfo = psi, EnableRaisingEvents = true };
_cts = new CancellationTokenSource(ProcessTimeoutMs);
int gen = ++_generation;
IsRunning = true;
_textBuffer.Clear();
_thinkingBuffer.Clear();
_lastFireTime = DateTime.MinValue;
_lastThinkingFireTime = DateTime.MinValue;
_isTrackingToolUse = false;
_cancelled = false;
Task.Run(() =>
{
try
{
_process.Start();
_process.BeginErrorReadLine();
_stdin = _process.StandardInput;
string line;
while ((line = _process.StandardOutput.ReadLine()) != null)
ProcessJsonLine(line, uiContext);
if (_textBuffer.Length > 0 && !_cancelled)
{
string final = _textBuffer.ToString();
uiContext.AsyncUI(() => OnTextDelta?.Invoke(final));
}
if (gen == _generation)
uiContext.AsyncUI(() =>
{
IsRunning = false;
OnProcessExited?.Invoke();
});
}
catch (OperationCanceledException)
{
KillProcess();
if (gen == _generation)
uiContext.AsyncUI(() =>
{
IsRunning = false;
OnError?.Invoke("操作已取消。");
OnProcessExited?.Invoke();
});
}
catch (Exception ex)
{
KillProcess();
if (gen == _generation)
uiContext.AsyncUI(() =>
{
IsRunning = false;
OnError?.Invoke(ex.Message);
OnProcessExited?.Invoke();
});
}
});
}
public void Cancel()
{
_cancelled = true;
IsRunning = false;
try { _cts?.Cancel(); } catch { }
KillProcess();
}
public void SendResponse(string json)
{
try
{
if (_stdin != null)
{
_stdin.WriteLine(json);
_stdin.Flush();
}
}
catch { }
}
private void KillProcess()
{
try
{
if (_process != null && !_process.HasExited)
{
_process.Kill();
_process.WaitForExit(3000);
}
}
catch { }
}
private static string BuildArguments(string prompt, bool isContinue)
{
var escaped = prompt
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\r", "")
.Replace("\n", "\\n");
var args = $"-p \"{escaped}\" --output-format stream-json --include-partial-messages --verbose";
if (isContinue)
args = $"--continue {args}";
return args;
}
private bool ProcessJsonLine(string line, Control uiContext)
{
try
{
var obj = JObject.Parse(line);
string type = obj["type"]?.Value<string>();
switch (type)
{
case "stream_event":
return ProcessStreamEvent(obj, uiContext);
case "result":
ProcessResult(obj, uiContext);
break;
case "system":
ProcessSystemEvent(obj, uiContext);
break;
}
}
catch (Exception) { }
return false;
}
private bool ProcessStreamEvent(JObject obj, Control uiContext)
{
var evt = obj["event"];
if (evt == null) return false;
string eventType = evt["type"]?.Value<string>();
if (eventType == "content_block_start")
{
var contentBlock = evt["content_block"];
if (contentBlock != null && contentBlock["type"]?.Value<string>() == "tool_use")
{
_isTrackingToolUse = true;
_currentToolUseId = contentBlock["id"]?.Value<string>();
_currentToolUseName = contentBlock["name"]?.Value<string>();
_currentToolUseInput.Clear();
}
return false;
}
else if (eventType == "content_block_delta")
{
var delta = evt["delta"];
string deltaType = delta?["type"]?.Value<string>();
if (deltaType == "text_delta")
{
string text = delta["text"]?.Value<string>();
if (!string.IsNullOrEmpty(text))
{
_textBuffer.Append(text);
ThrottledFireTextDelta(uiContext);
}
}
else if (deltaType == "thinking_delta")
{
string thinking = delta["thinking"]?.Value<string>();
if (!string.IsNullOrEmpty(thinking))
{
_thinkingBuffer.Append(thinking);
ThrottledFireThinkingDelta(uiContext);
}
}
else if (deltaType == "input_json_delta" && _isTrackingToolUse)
{
string json = delta["partial_json"]?.Value<string>();
if (!string.IsNullOrEmpty(json))
_currentToolUseInput.Append(json);
}
return false;
}
else if (eventType == "content_block_stop")
{
if (_isTrackingToolUse)
{
_isTrackingToolUse = false;
string inputJson = _currentToolUseInput.ToString();
if (string.Equals(_currentToolUseName, "AskUserQuestion", StringComparison.OrdinalIgnoreCase))
{
var questionInfo = ParseAskUserQuestion(_currentToolUseId, inputJson);
if (questionInfo != null)
uiContext.AsyncUI(() => OnAskUserQuestion?.Invoke(questionInfo));
}
else
{
var toolInfo = new ToolUseInfo
{
ToolUseId = _currentToolUseId,
ToolName = _currentToolUseName,
InputJson = inputJson
};
uiContext.AsyncUI(() => OnToolUseRequest?.Invoke(toolInfo));
}
}
return false;
}
else if (eventType == "message_stop")
{
if (_textBuffer.Length > 0 && !_cancelled)
{
string final = _textBuffer.ToString();
uiContext.AsyncUI(() => OnTextDelta?.Invoke(final));
}
if (!_cancelled)
uiContext.AsyncUI(() => OnStreamComplete?.Invoke());
}
return false;
}
private void ProcessResult(JObject obj, Control uiContext)
{
string subtype = obj["subtype"]?.Value<string>();
if (subtype == "error")
{
string errorText = obj["error"]?.Value<string>() ?? "未知 API 错误。";
uiContext.AsyncUI(() => OnError?.Invoke(errorText));
}
}
private void ProcessSystemEvent(JObject obj, Control uiContext)
{
string subtype = obj["subtype"]?.Value<string>();
if (subtype == "init")
{
ModelName = obj["model"]?.Value<string>();
string version = obj["version"]?.Value<string>();
uiContext.AsyncUI(() => OnSystemInit?.Invoke(ModelName, version));
}
}
private void ThrottledFireTextDelta(Control uiContext)
{
if (_cancelled) return;
var now = DateTime.UtcNow;
if ((now - _lastFireTime).TotalMilliseconds < ThrottleMs)
return;
_lastFireTime = now;
string accumulated = _textBuffer.ToString();
uiContext.AsyncUI(() => OnTextDelta?.Invoke(accumulated));
}
private void ThrottledFireThinkingDelta(Control uiContext)
{
if (_cancelled) return;
var now = DateTime.UtcNow;
if ((now - _lastThinkingFireTime).TotalMilliseconds < ThrottleMs)
return;
_lastThinkingFireTime = now;
string accumulated = _thinkingBuffer.ToString();
uiContext.AsyncUI(() => OnThinkingDelta?.Invoke(accumulated));
}
public static string ResolveClaudeExePath()
{
try
{
var psi = new ProcessStartInfo
{
FileName = "cmd",
Arguments = "/c where claude.cmd 2>nul",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
using (var p = Process.Start(psi))
{
string output = p.StandardOutput.ReadToEnd().Trim();
p.WaitForExit(3000);
if (p.ExitCode == 0 && !string.IsNullOrEmpty(output))
{
string cmdPath = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)[0];
string npmDir = Path.GetDirectoryName(cmdPath);
string exePath = Path.Combine(npmDir, "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe");
if (File.Exists(exePath))
return exePath;
}
}
}
catch { }
return null;
}
public static string GetVersion(string executablePath = "claude.cmd")
{
return TryRunAndRead(executablePath, "--version", 5000)
?? TryRunAndRead("cmd", $"/c {executablePath} --version", 5000);
}
public static string GetModel(string executablePath = "claude.cmd")
{
return TryRunAndRead(executablePath, "config", 5000)
?? TryRunAndRead("cmd", $"/c {executablePath} config", 5000);
}
private static string TryRunAndRead(string fileName, string arguments, int timeoutMs)
{
try
{
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
};
using (var p = Process.Start(psi))
{
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit(timeoutMs);
return p.ExitCode == 0 ? output.Trim() : null;
}
}
catch
{
return null;
}
}
private static AskUserQuestionInfo ParseAskUserQuestion(string toolUseId, string inputJson)
{
try
{
var obj = JObject.Parse(inputJson);
var questionsArr = obj["questions"] as JArray;
if (questionsArr == null || questionsArr.Count == 0)
return null;
var info = new AskUserQuestionInfo { ToolUseId = toolUseId };
foreach (var q in questionsArr)
{
var question = new QuestionItem
{
Question = q["question"]?.Value<string>() ?? "",
Header = q["header"]?.Value<string>() ?? "",
MultiSelect = q["multiSelect"]?.Value<bool>() ?? false
};
var optionsArr = q["options"] as JArray;
if (optionsArr != null)
{
foreach (var o in optionsArr)
{
question.Options.Add(new OptionItem
{
Label = o["label"]?.Value<string>() ?? "",
Description = o["description"]?.Value<string>() ?? ""
});
}
}
info.Questions.Add(question);
}
return info;
}
catch
{
return null;
}
}
public static bool IsClaudeInstalled(string executablePath = "claude.cmd")
{
return TryRun(executablePath, "--version", 5000)
|| TryRun("cmd", $"/c {executablePath} --version", 5000);
}
private static bool TryRun(string fileName, string arguments, int timeoutMs)
{
try
{
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
using (var p = Process.Start(psi))
{
p.WaitForExit(timeoutMs);
return p.ExitCode == 0;
}
}
catch
{
return false;
}
}
public void Dispose()
{
Cancel();
_process?.Dispose();
_cts?.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Tools.MyCommon
{
public static class MarkdownRenderer
{
private const string RtfHeader =
@"{\rtf1\ansi\deff0" +
@"{\fonttbl" +
@"{\f0\fnil\fcharset134 Microsoft YaHei;}" +
@"{\f1\fnil\fcharset134 Courier New;}}" +
@"{\colortbl" +
@";\red0\green120\blue212" + // 1: user blue
@";\red128\green128\blue128" + // 2: timestamp gray
@";\red16\green137\blue62" + // 3: claude green
@";\red255\green0\blue0" + // 4: error red
@";\red0\green0\blue0" + // 5: black
@";\red245\green245\blue245" + // 6: code bg
@";\red30\green30\blue30" + // 7: code fg
@";\red102\green102\blue102" + // 8: blockquote gray
@";\red128\green128\blue160" + // 9: thinking purple-gray
@";\red0\green102\blue204" + // 10: hyperlink blue
@"}" +
@"\viewkind4\uc1\pard\f0\fs20";
private const string RtfSuffix = @"}";
private static readonly Regex OrderedListTest = new Regex(@"^\d+\.\s", RegexOptions.Compiled);
private static readonly Regex OrderedListCapture = new Regex(@"^(\d+)\.\s(.*)", RegexOptions.Compiled);
private static readonly Regex LinkPattern = new Regex(@"^\[([^\]]+)\]\(([^)]+)\)", RegexOptions.Compiled);
private const int ClUser = 1;
private const int ClTs = 2;
private const int ClClaude = 3;
private const int ClErr = 4;
private const int ClText = 5;
private const int ClCodeBg = 6;
private const int ClCodeFg = 7;
private const int ClBq = 8;
private const int ClThinking = 9;
private const int ClLink = 10;
// Matches Windows absolute paths: C:\..., D:\... with optional file extension
private static readonly Regex FilePathPattern = new Regex(
@"\b([A-Za-z]:[\\/](?:[^\s\\/:*?""<>|]+[\\/])*[^\s\\/:*?""<>|]+\.[a-zA-Z0-9]{1,10})\b",
RegexOptions.Compiled);
// Matches inline code that looks like a shell command
private static readonly Regex CommandPattern = new Regex(
@"`([^`]{2,200})`",
RegexOptions.Compiled);
// Common command prefixes for detection
private static readonly string[] CmdPrefixes = {
"dotnet ", "git ", "npm ", "npx ", "yarn ", "pnpm ", "docker ", "docker-compose ",
"gh ", "gcloud ", "az ", "aws ", "terraform ", "kubectl ", "helm ",
"python ", "pip ", "node ", "go ", "cargo ", "rustc ", "javac ", "java ",
"msbuild ", "dotnet.exe ", "nuget ", "choco ", "scoop ", "winget ",
"cd ", "dir ", "ls ", "copy ", "move ", "mkdir ", "rmdir ", "del ",
"type ", "find ", "findstr ", "where ", "set ", "echo ", "cls "
};
public static string BuildChatRtf(List<ChatMessage> messages, string welcomeText)
{
var sb = new StringBuilder();
sb.Append(RtfHeader);
if (messages == null || messages.Count == 0)
{
sb.Append(@"\cf");
sb.Append(ClTs);
sb.Append(@"\f0\fs20 ");
sb.Append(Escape(welcomeText));
sb.Append(@"\par");
}
else
{
for (int i = 0; i < messages.Count; i++)
{
RenderMessage(sb, messages[i]);
if (i < messages.Count - 1)
sb.Append(@"\par");
}
}
sb.Append(RtfSuffix);
return sb.ToString();
}
private static void RenderMessage(StringBuilder sb, ChatMessage msg)
{
bool isUser = msg.Role == "user";
// Role label line
sb.Append(@"\pard\cf");
sb.Append(isUser ? ClUser : ClClaude);
sb.Append(@"\b\f0\fs20 ");
sb.Append(isUser ? "You" : "Claude");
sb.Append(@"\b0\cf");
sb.Append(ClTs);
sb.Append(@"\fs16 ");
sb.Append(msg.Timestamp.ToString("HH:mm:ss"));
sb.Append(@"\par");
// Content
sb.Append(@"\pard\cf");
sb.Append(ClText);
sb.Append(@"\f0\fs20 ");
if (isUser)
{
sb.Append(Escape(msg.Content));
sb.Append(@"\par");
}
else
{
if (!string.IsNullOrEmpty(msg.ThinkingContent))
{
sb.Append(@"\cf"); sb.Append(ClThinking);
sb.Append(@"\i\fs18 ");
sb.Append("▸ Thinking...");
sb.Append(@"\par ");
sb.Append(Escape(msg.ThinkingContent));
sb.Append(@"\i0\fs20\cf"); sb.Append(ClText);
sb.Append(@"\par\par ");
}
RenderMarkdown(sb, msg.Content);
}
}
private static void RenderMarkdown(StringBuilder sb, string markdown)
{
if (string.IsNullOrEmpty(markdown))
return;
var lines = markdown.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
bool inCodeBlock = false;
var codeBuf = new StringBuilder();
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
if (line.TrimStart().StartsWith("```"))
{
if (!inCodeBlock)
{
inCodeBlock = true;
codeBuf.Clear();
}
else
{
inCodeBlock = false;
RenderCodeBlock(sb, codeBuf.ToString());
codeBuf.Clear();
}
continue;
}
if (inCodeBlock)
{
if (codeBuf.Length > 0)
codeBuf.Append(@"\line ");
codeBuf.Append(Escape(line));
continue;
}
if (string.IsNullOrWhiteSpace(line))
{
sb.Append(@"\par ");
continue;
}
string trimmed = line.TrimStart();
int indent = line.Length - trimmed.Length;
if (trimmed.StartsWith("### "))
{
sb.Append(@"\b\fs22 ");
RenderInline(sb, trimmed.Substring(4));
sb.Append(@"\b0\fs20\par ");
}
else if (trimmed.StartsWith("## "))
{
sb.Append(@"\b\fs26 ");
RenderInline(sb, trimmed.Substring(3));
sb.Append(@"\b0\fs20\par ");
}
else if (trimmed.StartsWith("# "))
{
sb.Append(@"\b\fs32 ");
RenderInline(sb, trimmed.Substring(2));
sb.Append(@"\b0\fs20\par ");
}
else if (trimmed.StartsWith("- ") || trimmed.StartsWith("* "))
{
int li = 360 + indent * 120;
sb.Append(@"\li"); sb.Append(li);
sb.Append(@"\fi-180 ");
sb.Append("• ");
RenderInline(sb, trimmed.Substring(2));
sb.Append(@"\pard\par ");
}
else if (OrderedListTest.IsMatch(trimmed))
{
var m = OrderedListCapture.Match(trimmed);
int li = 360 + indent * 120;
sb.Append(@"\li"); sb.Append(li);
sb.Append(@"\fi-180 ");
sb.Append(m.Groups[1].Value); sb.Append(". ");
RenderInline(sb, m.Groups[2].Value);
sb.Append(@"\pard\par ");
}
else if (trimmed.StartsWith("> "))
{
sb.Append(@"\cf"); sb.Append(ClBq);
sb.Append(@"\i ");
RenderInline(sb, trimmed.Substring(2));
sb.Append(@"\i0\cf"); sb.Append(ClText);
sb.Append(@"\par ");
}
else if (trimmed == "---" || trimmed == "***")
{
sb.Append(@"\brdrb\brdrs\brdrw10\brsp20 \par\pard ");
}
else
{
RenderInline(sb, line);
sb.Append(@"\par ");
}
}
if (inCodeBlock)
{
RenderCodeBlock(sb, codeBuf.ToString());
}
}
private static void RenderCodeBlock(StringBuilder sb, string code)
{
string trimmed = code.Trim();
if (!trimmed.Contains("\n") && !trimmed.Contains("\r") && IsLikelyCommand(trimmed))
{
sb.Append(@"\pard\highlight"); sb.Append(ClCodeBg);
sb.Append(@"\f1\fs18 ");
sb.Append(BuildCmdHyperlink(trimmed));
sb.Append(@"\highlight0\f0\fs20\par ");
return;
}
sb.Append(@"\pard\highlight"); sb.Append(ClCodeBg);
sb.Append(@"\cf"); sb.Append(ClCodeFg);
sb.Append(@"\f1\fs18 ");
sb.Append(code);
sb.Append(@"\highlight0\cf"); sb.Append(ClText);
sb.Append(@"\f0\fs20\par ");
}
private static string BuildFileHyperlink(string filePath)
{
string escapedPath = EscapeRtf(filePath);
string url = "file:///" + filePath.Replace("\\", "/");
string escapedUrl = EscapeRtf(url);
return $"{{\\field{{\\*\\fldinst{{HYPERLINK \"{escapedUrl}\"}}}}{{\\fldrslt{{\\cf{ClLink}\\ul {escapedPath}\\ul0\\cf{ClText}}}}}}}";
}
private static string BuildCmdHyperlink(string command)
{
string escapedCmd = EscapeRtf(command);
string url = "cmd:///" + Uri.EscapeDataString(command);
string escapedUrl = EscapeRtf(url);
return $"{{\\field{{\\*\\fldinst{{HYPERLINK \"{escapedUrl}\"}}}}{{\\fldrslt{{\\cf{ClLink}\\ul {escapedCmd}\\ul0\\cf{ClCodeFg}}}}}}}";
}
private static bool IsLikelyCommand(string text)
{
if (string.IsNullOrWhiteSpace(text) || text.Length < 3) return false;
string lower = text.ToLowerInvariant();
foreach (var prefix in CmdPrefixes)
{
if (lower.StartsWith(prefix, StringComparison.Ordinal))
return true;
}
return false;
}
private static string EscapeRtf(string text)
{
if (string.IsNullOrEmpty(text)) return "";
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
switch (c)
{
case '\\': sb.Append("\\\\"); break;
case '{': sb.Append("\\{"); break;
case '}': sb.Append("\\}"); break;
default: sb.Append(c); break;
}
}
return sb.ToString();
}
private static void RenderInline(StringBuilder sb, string text)
{
int i = 0;
while (i < text.Length)
{
// Inline code: `...`
if (text[i] == '`')
{
int end = text.IndexOf('`', i + 1);
if (end > i)
{
string code = text.Substring(i + 1, end - i - 1);
if (IsLikelyCommand(code))
{
sb.Append(@"{\f1\fs18\highlight"); sb.Append(ClCodeBg);
sb.Append(" ");
sb.Append(BuildCmdHyperlink(code));
sb.Append(@"\highlight0\f0\fs20}");
}
else
{
sb.Append(@"{\f1\fs18\highlight"); sb.Append(ClCodeBg);
sb.Append(@"\cf"); sb.Append(ClCodeFg);
sb.Append(" ");
sb.Append(Escape(code));
sb.Append(@"\highlight0\cf"); sb.Append(ClText);
sb.Append(@"\f0\fs20}");
}
i = end + 1;
continue;
}
}
// Bold+Italic: ***...***
if (i + 3 <= text.Length && text[i] == '*' && text[i + 1] == '*' && text[i + 2] == '*')
{
int end = FindClosing(text, i + 3, "***");
if (end > i)
{
sb.Append(@"\b\i ");
RenderInline(sb, text.Substring(i + 3, end - i - 3));
sb.Append(@"\i0\b0 ");
i = end + 3;
continue;
}
}
// Bold: **...**
if (i + 2 <= text.Length && text[i] == '*' && text[i + 1] == '*')
{
int end = FindClosing(text, i + 2, "**");
if (end > i)
{
sb.Append(@"\b ");
RenderInline(sb, text.Substring(i + 2, end - i - 2));
sb.Append(@"\b0 ");
i = end + 2;
continue;
}
}
// Italic: *...*
if (text[i] == '*' && (i == 0 || text[i - 1] != '*'))
{
int end = FindClosing(text, i + 1, "*");
if (end > i && (end + 1 >= text.Length || text[end + 1] != '*'))
{
sb.Append(@"\i ");
RenderInline(sb, text.Substring(i + 1, end - i - 1));
sb.Append(@"\i0 ");
i = end + 1;
continue;
}
}
// Link: [text](url)
if (text[i] == '[')
{
var m = LinkPattern.Match(text.Substring(i));
if (m.Success)
{
sb.Append(@"{\cf"); sb.Append(ClUser);
sb.Append(@"\ul ");
sb.Append(Escape(m.Groups[1].Value));
sb.Append(@"\ul0\cf"); sb.Append(ClText);
sb.Append(@"}");
i += m.Length;
continue;
}
}
// File path detection: C:\..., D:\..., etc.
var pathMatch = FilePathPattern.Match(text, i);
if (pathMatch.Success && pathMatch.Index == i)
{
sb.Append(BuildFileHyperlink(pathMatch.Value));
i += pathMatch.Length;
continue;
}
sb.Append(EscapeChar(text[i]));
i++;
}
}
private static int FindClosing(string text, int start, string marker)
{
int idx = text.IndexOf(marker, start, StringComparison.Ordinal);
return idx >= 0 ? idx : -1;
}
private static string Escape(string text)
{
if (string.IsNullOrEmpty(text)) return "";
var sb = new StringBuilder(text.Length);
foreach (char c in text)
sb.Append(EscapeChar(c));
return sb.ToString();
}
private static string EscapeChar(char c)
{
switch (c)
{
case '\\': return "\\\\";
case '{': return "\\{";
case '}': return "\\}";
case '\r': return "";
case '\n': return @"\line ";
default: return c.ToString();
}
}
}
}
......@@ -84,6 +84,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="MyCommon\Log.cs" />
<Compile Include="MyCommon\ChatMessage.cs" />
<Compile Include="MyCommon\ClaudeCliService.cs" />
<Compile Include="MyCommon\ChatSession.cs" />
<Compile Include="MyCommon\MarkdownRenderer.cs" />
<Compile Include="MyCommon\UserLog.cs" />
<Compile Include="MyCommon\controls\MyTreeView.cs">
<SubType>Component</SubType>
......@@ -245,6 +249,12 @@
<Compile Include="UI\wAutoClass.Designer.cs">
<DependentUpon>wAutoClass.cs</DependentUpon>
</Compile>
<Compile Include="UI\wClaudeChat.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\wClaudeChat.Designer.cs">
<DependentUpon>wClaudeChat.cs</DependentUpon>
</Compile>
<Compile Include="UI\Models\AppModel.cs" />
<Compile Include="MyCommon\Common.cs" />
<Compile Include="MyCommon\wBasic.cs">
......@@ -374,6 +384,10 @@
<Compile Include="wMain.Designer.cs">
<DependentUpon>wMain.cs</DependentUpon>
</Compile>
<Compile Include="wMain.ClaudeChat.cs">
<DependentUpon>wMain.cs</DependentUpon>
<SubType>Form</SubType>
</Compile>
<EmbeddedResource Include="MyCommon\controls\ucAudit.resx">
<DependentUpon>ucAudit.cs</DependentUpon>
</EmbeddedResource>
......@@ -444,6 +458,9 @@
<EmbeddedResource Include="UI\sys\wUploadUpdate.resx">
<DependentUpon>wUploadUpdate.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\wClaudeChat.resx">
<DependentUpon>wClaudeChat.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\wExcelExportSetting.resx">
<DependentUpon>wExcelExportSetting.cs</DependentUpon>
</EmbeddedResource>
......
using System.Windows.Forms;
namespace Tools.UI
{
partial class wClaudeChat
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.rtbChat = new System.Windows.Forms.RichTextBox();
this.panelMiddle = new System.Windows.Forms.Panel();
this.lblProgress = new System.Windows.Forms.Label();
this.lblFrog = new System.Windows.Forms.Label();
this.lblConfig = new System.Windows.Forms.LinkLabel();
this.lblChangeDir = new System.Windows.Forms.LinkLabel();
this.lblNewChat = new System.Windows.Forms.LinkLabel();
this.lblAttachFile = new System.Windows.Forms.LinkLabel();
this.cmbMode = new System.Windows.Forms.ComboBox();
this.lblModeLabel = new System.Windows.Forms.Label();
this.lblHistory = new System.Windows.Forms.LinkLabel();
this.panelQuestion = new System.Windows.Forms.Panel();
this.lblQuestionText = new System.Windows.Forms.Label();
this.pnlOptions = new System.Windows.Forms.Panel();
this.btnConfirmSelection = new System.Windows.Forms.Button();
this.panelPermission = new System.Windows.Forms.Panel();
this.lblPermissionText = new System.Windows.Forms.Label();
this.btnDeny = new System.Windows.Forms.Button();
this.btnApprove = new System.Windows.Forms.Button();
this.panelBottom = new System.Windows.Forms.Panel();
this.btnSend = new System.Windows.Forms.Button();
this.txtInput = new System.Windows.Forms.TextBox();
this.tmrFrog = new System.Windows.Forms.Timer(this.components);
this.panelMiddle.SuspendLayout();
this.panelQuestion.SuspendLayout();
this.panelPermission.SuspendLayout();
this.panelBottom.SuspendLayout();
this.SuspendLayout();
//
// rtbChat
//
this.rtbChat.BackColor = System.Drawing.Color.White;
this.rtbChat.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.rtbChat.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbChat.Font = new System.Drawing.Font("微软雅黑", 10F);
this.rtbChat.Location = new System.Drawing.Point(0, 0);
this.rtbChat.Margin = new System.Windows.Forms.Padding(4);
this.rtbChat.Name = "rtbChat";
this.rtbChat.ReadOnly = true;
this.rtbChat.Size = new System.Drawing.Size(817, 288);
this.rtbChat.TabIndex = 0;
this.rtbChat.Text = "";
//
// panelMiddle
//
this.panelMiddle.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(245)))), ((int)(((byte)(245)))), ((int)(((byte)(245)))));
this.panelMiddle.Controls.Add(this.lblProgress);
this.panelMiddle.Controls.Add(this.lblFrog);
this.panelMiddle.Controls.Add(this.lblConfig);
this.panelMiddle.Controls.Add(this.lblChangeDir);
this.panelMiddle.Controls.Add(this.lblNewChat);
this.panelMiddle.Controls.Add(this.lblAttachFile);
this.panelMiddle.Controls.Add(this.cmbMode);
this.panelMiddle.Controls.Add(this.lblModeLabel);
this.panelMiddle.Controls.Add(this.lblHistory);
this.panelMiddle.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelMiddle.Location = new System.Drawing.Point(0, 445);
this.panelMiddle.Margin = new System.Windows.Forms.Padding(4);
this.panelMiddle.Name = "panelMiddle";
this.panelMiddle.Size = new System.Drawing.Size(817, 51);
this.panelMiddle.TabIndex = 1;
//
// lblProgress
//
this.lblProgress.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.lblProgress.AutoSize = true;
this.lblProgress.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblProgress.ForeColor = System.Drawing.Color.Gray;
this.lblProgress.Location = new System.Drawing.Point(679, 14);
this.lblProgress.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblProgress.Name = "lblProgress";
this.lblProgress.Size = new System.Drawing.Size(0, 17);
this.lblProgress.TabIndex = 5;
this.lblProgress.Visible = false;
//
// lblFrog
//
this.lblFrog.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.lblFrog.AutoSize = true;
this.lblFrog.Cursor = System.Windows.Forms.Cursors.Hand;
this.lblFrog.Font = new System.Drawing.Font("Consolas", 12F, System.Drawing.FontStyle.Bold);
this.lblFrog.ForeColor = System.Drawing.Color.Green;
this.lblFrog.Location = new System.Drawing.Point(624, 14);
this.lblFrog.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblFrog.Name = "lblFrog";
this.lblFrog.Size = new System.Drawing.Size(49, 19);
this.lblFrog.TabIndex = 4;
this.lblFrog.Text = "(◉◉)";
//
// lblConfig
//
this.lblConfig.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblConfig.AutoSize = true;
this.lblConfig.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblConfig.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblConfig.Location = new System.Drawing.Point(512, 14);
this.lblConfig.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblConfig.Name = "lblConfig";
this.lblConfig.Size = new System.Drawing.Size(56, 17);
this.lblConfig.TabIndex = 6;
this.lblConfig.TabStop = true;
this.lblConfig.Text = "默认配置";
//
// lblChangeDir
//
this.lblChangeDir.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblChangeDir.AutoSize = true;
this.lblChangeDir.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblChangeDir.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblChangeDir.Location = new System.Drawing.Point(417, 14);
this.lblChangeDir.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblChangeDir.Name = "lblChangeDir";
this.lblChangeDir.Size = new System.Drawing.Size(80, 17);
this.lblChangeDir.TabIndex = 3;
this.lblChangeDir.TabStop = true;
this.lblChangeDir.Text = "打开工作目录";
//
// lblNewChat
//
this.lblNewChat.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblNewChat.AutoSize = true;
this.lblNewChat.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblNewChat.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblNewChat.Location = new System.Drawing.Point(346, 14);
this.lblNewChat.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblNewChat.Name = "lblNewChat";
this.lblNewChat.Size = new System.Drawing.Size(56, 17);
this.lblNewChat.TabIndex = 2;
this.lblNewChat.TabStop = true;
this.lblNewChat.Text = "重置会话";
//
// lblAttachFile
//
this.lblAttachFile.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblAttachFile.AutoSize = true;
this.lblAttachFile.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblAttachFile.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblAttachFile.Location = new System.Drawing.Point(275, 14);
this.lblAttachFile.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblAttachFile.Name = "lblAttachFile";
this.lblAttachFile.Size = new System.Drawing.Size(56, 17);
this.lblAttachFile.TabIndex = 1;
this.lblAttachFile.TabStop = true;
this.lblAttachFile.Text = "引用文件";
//
// cmbMode
//
this.cmbMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbMode.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cmbMode.Font = new System.Drawing.Font("微软雅黑", 9F);
this.cmbMode.FormattingEnabled = true;
this.cmbMode.Items.AddRange(new object[] {
"Normal",
"Plan",
"Goal"});
this.cmbMode.Location = new System.Drawing.Point(168, 11);
this.cmbMode.Margin = new System.Windows.Forms.Padding(4);
this.cmbMode.Name = "cmbMode";
this.cmbMode.Size = new System.Drawing.Size(83, 25);
this.cmbMode.TabIndex = 0;
//
// lblModeLabel
//
this.lblModeLabel.AutoSize = true;
this.lblModeLabel.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblModeLabel.ForeColor = System.Drawing.Color.Black;
this.lblModeLabel.Location = new System.Drawing.Point(104, 16);
this.lblModeLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblModeLabel.Name = "lblModeLabel";
this.lblModeLabel.Size = new System.Drawing.Size(68, 17);
this.lblModeLabel.TabIndex = 8;
this.lblModeLabel.Text = "模式选择:";
//
// lblHistory
//
this.lblHistory.ActiveLinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblHistory.AutoSize = true;
this.lblHistory.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblHistory.LinkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.lblHistory.Location = new System.Drawing.Point(9, 14);
this.lblHistory.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblHistory.Name = "lblHistory";
this.lblHistory.Size = new System.Drawing.Size(56, 17);
this.lblHistory.TabIndex = 7;
this.lblHistory.TabStop = true;
this.lblHistory.Text = "历史记录";
//
// panelQuestion
//
this.panelQuestion.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(235)))), ((int)(((byte)(245)))), ((int)(((byte)(255)))));
this.panelQuestion.Controls.Add(this.lblQuestionText);
this.panelQuestion.Controls.Add(this.pnlOptions);
this.panelQuestion.Controls.Add(this.btnConfirmSelection);
this.panelQuestion.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelQuestion.Location = new System.Drawing.Point(0, 288);
this.panelQuestion.Margin = new System.Windows.Forms.Padding(4);
this.panelQuestion.Name = "panelQuestion";
this.panelQuestion.Size = new System.Drawing.Size(817, 100);
this.panelQuestion.TabIndex = 5;
this.panelQuestion.Visible = false;
//
// lblQuestionText
//
this.lblQuestionText.AutoSize = true;
this.lblQuestionText.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold);
this.lblQuestionText.Location = new System.Drawing.Point(14, 10);
this.lblQuestionText.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblQuestionText.Name = "lblQuestionText";
this.lblQuestionText.Size = new System.Drawing.Size(56, 17);
this.lblQuestionText.TabIndex = 0;
this.lblQuestionText.Text = "请选择:";
//
// pnlOptions
//
this.pnlOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.pnlOptions.AutoScroll = true;
this.pnlOptions.BackColor = System.Drawing.Color.White;
this.pnlOptions.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlOptions.Location = new System.Drawing.Point(14, 34);
this.pnlOptions.Margin = new System.Windows.Forms.Padding(4);
this.pnlOptions.Name = "pnlOptions";
this.pnlOptions.Size = new System.Drawing.Size(1254, 59);
this.pnlOptions.TabIndex = 1;
//
// btnConfirmSelection
//
this.btnConfirmSelection.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnConfirmSelection.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.btnConfirmSelection.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnConfirmSelection.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnConfirmSelection.ForeColor = System.Drawing.Color.White;
this.btnConfirmSelection.Location = new System.Drawing.Point(1275, 34);
this.btnConfirmSelection.Margin = new System.Windows.Forms.Padding(4);
this.btnConfirmSelection.Name = "btnConfirmSelection";
this.btnConfirmSelection.Size = new System.Drawing.Size(145, 59);
this.btnConfirmSelection.TabIndex = 2;
this.btnConfirmSelection.Text = "确认选择 (Enter)";
this.btnConfirmSelection.UseVisualStyleBackColor = false;
//
// panelPermission
//
this.panelPermission.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(243)))), ((int)(((byte)(205)))));
this.panelPermission.Controls.Add(this.lblPermissionText);
this.panelPermission.Controls.Add(this.btnDeny);
this.panelPermission.Controls.Add(this.btnApprove);
this.panelPermission.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelPermission.Location = new System.Drawing.Point(0, 388);
this.panelPermission.Margin = new System.Windows.Forms.Padding(4);
this.panelPermission.Name = "panelPermission";
this.panelPermission.Size = new System.Drawing.Size(817, 57);
this.panelPermission.TabIndex = 4;
this.panelPermission.Visible = false;
//
// lblPermissionText
//
this.lblPermissionText.AutoSize = true;
this.lblPermissionText.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblPermissionText.Location = new System.Drawing.Point(14, 14);
this.lblPermissionText.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblPermissionText.Name = "lblPermissionText";
this.lblPermissionText.Size = new System.Drawing.Size(124, 17);
this.lblPermissionText.TabIndex = 0;
this.lblPermissionText.Text = "Claude 请求执行操作";
//
// btnDeny
//
this.btnDeny.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnDeny.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(50)))), ((int)(((byte)(50)))));
this.btnDeny.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnDeny.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnDeny.ForeColor = System.Drawing.Color.White;
this.btnDeny.Location = new System.Drawing.Point(719, 8);
this.btnDeny.Margin = new System.Windows.Forms.Padding(4);
this.btnDeny.Name = "btnDeny";
this.btnDeny.Size = new System.Drawing.Size(84, 40);
this.btnDeny.TabIndex = 2;
this.btnDeny.Text = "拒绝";
this.btnDeny.UseVisualStyleBackColor = false;
//
// btnApprove
//
this.btnApprove.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnApprove.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(160)))), ((int)(((byte)(80)))));
this.btnApprove.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnApprove.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnApprove.ForeColor = System.Drawing.Color.White;
this.btnApprove.Location = new System.Drawing.Point(628, 8);
this.btnApprove.Margin = new System.Windows.Forms.Padding(4);
this.btnApprove.Name = "btnApprove";
this.btnApprove.Size = new System.Drawing.Size(84, 40);
this.btnApprove.TabIndex = 1;
this.btnApprove.Text = "批准";
this.btnApprove.UseVisualStyleBackColor = false;
//
// panelBottom
//
this.panelBottom.Controls.Add(this.btnSend);
this.panelBottom.Controls.Add(this.txtInput);
this.panelBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelBottom.Location = new System.Drawing.Point(0, 496);
this.panelBottom.Margin = new System.Windows.Forms.Padding(4);
this.panelBottom.Name = "panelBottom";
this.panelBottom.Padding = new System.Windows.Forms.Padding(5, 6, 5, 6);
this.panelBottom.Size = new System.Drawing.Size(817, 142);
this.panelBottom.TabIndex = 2;
//
// btnSend
//
this.btnSend.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(212)))));
this.btnSend.Dock = System.Windows.Forms.DockStyle.Right;
this.btnSend.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnSend.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold);
this.btnSend.ForeColor = System.Drawing.Color.White;
this.btnSend.Location = new System.Drawing.Point(723, 6);
this.btnSend.Margin = new System.Windows.Forms.Padding(4);
this.btnSend.Name = "btnSend";
this.btnSend.Size = new System.Drawing.Size(89, 130);
this.btnSend.TabIndex = 1;
this.btnSend.Text = "发送";
this.btnSend.UseVisualStyleBackColor = false;
//
// txtInput
//
this.txtInput.AcceptsReturn = true;
this.txtInput.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.txtInput.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtInput.Font = new System.Drawing.Font("微软雅黑", 10F);
this.txtInput.Location = new System.Drawing.Point(5, 6);
this.txtInput.Margin = new System.Windows.Forms.Padding(4);
this.txtInput.Multiline = true;
this.txtInput.Name = "txtInput";
this.txtInput.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtInput.Size = new System.Drawing.Size(807, 130);
this.txtInput.TabIndex = 0;
//
// tmrFrog
//
this.tmrFrog.Interval = 800;
//
// wClaudeChat
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(817, 638);
this.Controls.Add(this.rtbChat);
this.Controls.Add(this.panelQuestion);
this.Controls.Add(this.panelPermission);
this.Controls.Add(this.panelMiddle);
this.Controls.Add(this.panelBottom);
this.KeyPreview = true;
this.Margin = new System.Windows.Forms.Padding(5, 6, 5, 6);
this.MinimumSize = new System.Drawing.Size(464, 409);
this.Name = "wClaudeChat";
this.Text = "Claude Chat";
this.panelMiddle.ResumeLayout(false);
this.panelMiddle.PerformLayout();
this.panelQuestion.ResumeLayout(false);
this.panelQuestion.PerformLayout();
this.panelPermission.ResumeLayout(false);
this.panelPermission.PerformLayout();
this.panelBottom.ResumeLayout(false);
this.panelBottom.PerformLayout();
this.ResumeLayout(false);
}
private System.Windows.Forms.RichTextBox rtbChat;
private System.Windows.Forms.Panel panelMiddle;
private System.Windows.Forms.Label lblFrog;
private System.Windows.Forms.Label lblProgress;
private System.Windows.Forms.ComboBox cmbMode;
private System.Windows.Forms.Label lblModeLabel;
private System.Windows.Forms.LinkLabel lblAttachFile;
private System.Windows.Forms.LinkLabel lblChangeDir;
private System.Windows.Forms.LinkLabel lblConfig;
private System.Windows.Forms.LinkLabel lblHistory;
private System.Windows.Forms.LinkLabel lblNewChat;
private System.Windows.Forms.Panel panelQuestion;
private System.Windows.Forms.Label lblQuestionText;
private System.Windows.Forms.Panel pnlOptions;
private System.Windows.Forms.Button btnConfirmSelection;
private System.Windows.Forms.Panel panelPermission;
private System.Windows.Forms.Label lblPermissionText;
private System.Windows.Forms.Button btnApprove;
private System.Windows.Forms.Button btnDeny;
private System.Windows.Forms.Panel panelBottom;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.TextBox txtInput;
private System.Windows.Forms.Timer tmrFrog;
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Tools.MyCommon;
using Tools.MyUtils;
namespace Tools.UI
{
public partial class wClaudeChat : wBasic
{
private readonly List<ChatMessage> _messages = new List<ChatMessage>();
private readonly ClaudeCliService _claudeService = new ClaudeCliService();
private bool _isFirstMessage = true;
private int _lastStreamingLength;
private string _workingDirectory;
private string _cachedVersion;
private string _cachedModel;
// History
private string _historyDir;
// Frog animation
private int _frogFrame;
private readonly string[] _frogIdle = { "(◉◉)", "(◉◉)", "(◉◉)", "(––)" };
private readonly string[] _frogActive = { "(◉◉)", "(––)", "(◉◉)", "(––)" };
private readonly string[] _frogHappy = { "(◕◕)", "(◕‿◕)" };
// Question options
private AskUserQuestionInfo _currentQuestionInfo;
private int _selectedOptionIndex;
private readonly List<Label> _optionLabels = new List<Label>();
public wClaudeChat()
{
InitializeComponent();
LoadConfig();
WireEvents();
cmbMode.SelectedIndex = 0;
PreloadVersionInfo();
RefreshChat();
StartFrogAnimation();
}
// ── Config ──────────────────────────────────────────
private string ConfigPath =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ClaudeChat", "config.json");
private void LoadConfig()
{
try
{
if (File.Exists(ConfigPath))
{
var cfg = JsonConvert.DeserializeObject<ChatConfig>(File.ReadAllText(ConfigPath));
if (cfg != null && Directory.Exists(cfg.WorkingDirectory))
_workingDirectory = cfg.WorkingDirectory;
}
}
catch { }
if (string.IsNullOrEmpty(_workingDirectory))
_workingDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ClaudeChat");
Directory.CreateDirectory(_workingDirectory);
EnsureClaudeMd(_workingDirectory);
_historyDir = HistoryManager.EnsureHistoryDir(_workingDirectory);
_claudeService.WorkingDirectory = _workingDirectory;
var exePath = ClaudeCliService.ResolveClaudeExePath();
if (exePath != null)
_claudeService.ClaudeExecutablePath = exePath;
}
private static void EnsureClaudeMd(string workingDir)
{
var claudeMdPath = Path.Combine(workingDir, "CLAUDE.md");
if (File.Exists(claudeMdPath)) return;
var content = @"# Claude Code 配置
## 角色定义
你是一个全栈软件工程师助手,使用中文回答所有问题。你的主要技术栈包括:
- **后端**: C#, .NET Framework / .NET, ASP.NET, Web API
- **数据库**: SQL Server, T-SQL, 存储过程
- **桌面**: WinForms, WPF
- **前端**: Vue.js, JavaScript, HTML, CSS
- **文档处理**: PPT, Excel (NPOI/EPPlus), Markdown, 文本文件
- **工具**: Git, Docker, CI/CD, NuGet
请始终使用中文回复,代码注释和文档也使用中文。保持回复简洁、实用、可操作。
## 权限配置
```json
{
""permissions"": {
""defaultMode"": ""auto"",
""allowedTools"": [""Read"", ""Edit"", ""Bash(*)"", ""WebSearch"", ""WebFetch""],
""disallowedTools"": [
""Bash(rm -rf *)"",
""Bash(sudo *)""
],
""dangerouslySkipPermissions"": false
}
}
```";
try { File.WriteAllText(claudeMdPath, content, System.Text.Encoding.UTF8); }
catch { }
}
private void SaveConfig()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath));
var cfg = new ChatConfig { WorkingDirectory = _workingDirectory };
File.WriteAllText(ConfigPath, JsonConvert.SerializeObject(cfg, Formatting.Indented));
}
catch { }
}
private void OpenWorkingDirectory()
{
try
{
System.Diagnostics.Process.Start("explorer.exe", _workingDirectory);
}
catch { }
}
private void ShowConfigPopup()
{
var claudeMdPath = Path.Combine(_workingDirectory, "CLAUDE.md");
string fullContent;
try
{
if (!File.Exists(claudeMdPath))
{
Utils.Toast("CLAUDE.md 文件不存在");
return;
}
fullContent = File.ReadAllText(claudeMdPath, System.Text.Encoding.UTF8);
}
catch (Exception ex)
{
Utils.Toast($"读取失败: {ex.Message}");
return;
}
string jsonText = ExtractJsonBlock(fullContent) ?? "{}";
using (var popup = new Form
{
Text = "默认配置 — 编辑后可保存",
Size = new System.Drawing.Size(680, 500),
StartPosition = FormStartPosition.CenterParent,
MinimizeBox = false,
MaximizeBox = false,
ShowIcon = false,
Font = new System.Drawing.Font("Microsoft YaHei", 9F),
})
{
var rtb = new RichTextBox
{
Dock = DockStyle.Fill,
Font = new System.Drawing.Font("Consolas", 10F),
BackColor = System.Drawing.Color.FromArgb(30, 30, 30),
ForeColor = System.Drawing.Color.FromArgb(212, 212, 212),
BorderStyle = BorderStyle.None,
WordWrap = false,
AcceptsTab = true,
};
rtb.Rtf = BuildJsonHighlightRtf(jsonText);
var panelBottom = new Panel
{
Dock = DockStyle.Bottom,
Height = 44,
BackColor = System.Drawing.Color.FromArgb(45, 45, 45),
};
var btnSave = new Button
{
Text = "保存配置",
Dock = DockStyle.Right,
Width = 100,
FlatStyle = FlatStyle.Flat,
BackColor = System.Drawing.Color.FromArgb(0, 120, 212),
ForeColor = System.Drawing.Color.White,
Font = new System.Drawing.Font("Microsoft YaHei", 9F),
};
var lblHint = new Label
{
Text = "编辑 JSON 后点击保存,将对新会话生效",
AutoSize = true,
ForeColor = System.Drawing.Color.FromArgb(160, 160, 160),
Location = new System.Drawing.Point(10, 13),
};
btnSave.Click += (s, e) =>
{
try
{
string editedJson = rtb.Text;
var parsed = Newtonsoft.Json.Linq.JToken.Parse(editedJson);
string formatted = parsed.ToString(Newtonsoft.Json.Formatting.Indented);
string newFullContent = ReplaceJsonBlock(fullContent, formatted);
File.WriteAllText(claudeMdPath, newFullContent, System.Text.Encoding.UTF8);
Utils.Toast("配置已保存,新会话生效");
}
catch (Exception ex)
{
Utils.Toast($"保存失败: {ex.Message}");
}
};
panelBottom.Controls.Add(btnSave);
panelBottom.Controls.Add(lblHint);
popup.Controls.Add(rtb);
popup.Controls.Add(panelBottom);
popup.ShowDialog(this);
}
}
private static string ExtractJsonBlock(string content)
{
int start = content.IndexOf("```json", StringComparison.OrdinalIgnoreCase);
if (start < 0) return null;
start = content.IndexOf('\n', start) + 1;
int end = content.IndexOf("```", start, StringComparison.Ordinal);
if (end < 0) return content.Substring(start).Trim();
return content.Substring(start, end - start).Trim();
}
private static string ReplaceJsonBlock(string fullContent, string newJson)
{
int start = fullContent.IndexOf("```json", StringComparison.OrdinalIgnoreCase);
if (start < 0) return fullContent;
int blockStart = fullContent.IndexOf('\n', start) + 1;
int end = fullContent.IndexOf("```", blockStart, StringComparison.Ordinal);
if (end < 0) return fullContent;
return fullContent.Substring(0, blockStart) + newJson + "\n" + fullContent.Substring(end);
}
private static string BuildJsonHighlightRtf(string json)
{
var sb = new StringBuilder();
sb.Append(@"{\rtf1\ansi\deff0");
sb.Append(@"{\fonttbl{\f0\fnil\fcharset134 Consolas;}}");
sb.Append(@"{\colortbl");
sb.Append(@";\red212\green212\blue212"); // 1: default text
sb.Append(@";\red86\green156\blue214"); // 2: key blue
sb.Append(@";\red106\green170\blue100"); // 3: string green
sb.Append(@";\red206\green145\blue120"); // 4: number orange
sb.Append(@";\red220\green220\blue170"); // 5: braces yellow
sb.Append(@";\red156\green220\blue254"); // 6: boolean cyan
sb.Append(@";\red128\green128\blue128"); // 7: punct gray
sb.Append(@"}");
sb.Append(@"\viewkind4\uc1\pard\f0\fs20 ");
var lines = json.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++)
{
if (lineIdx > 0)
sb.Append(@"\par ");
RenderJsonLine(sb, lines[lineIdx]);
}
sb.Append(@"}");
return sb.ToString();
}
private static void RenderJsonLine(StringBuilder sb, string line)
{
int i = 0;
while (i < line.Length)
{
char c = line[i];
// String (key or value)
if (c == '"')
{
int end = i + 1;
while (end < line.Length && line[end] != '"')
{
if (line[end] == '\\') end++; // skip escaped
end++;
}
if (end < line.Length) end++; // include closing quote
string token = line.Substring(i, end - i);
// Check if it's a key (followed by ':')
int after = end;
while (after < line.Length && char.IsWhiteSpace(line[after])) after++;
bool isKey = after < line.Length && line[after] == ':';
sb.Append(isKey ? @"\cf2 " : @"\cf3 "); // key=blue, value=green
sb.Append(EscapeRtf(token));
sb.Append(@"\cf1 ");
i = end;
continue;
}
// Number
if (char.IsDigit(c) || (c == '-' && i + 1 < line.Length && char.IsDigit(line[i + 1])))
{
int end = i;
while (end < line.Length && (char.IsDigit(line[end]) || line[end] == '.' || line[end] == '-' || line[end] == 'e' || line[end] == 'E'))
end++;
sb.Append(@"\cf4 ");
sb.Append(EscapeRtf(line.Substring(i, end - i)));
sb.Append(@"\cf1 ");
i = end;
continue;
}
// Boolean/Null
if (c == 't' || c == 'f' || c == 'n')
{
string[] literals = { "true", "false", "null" };
bool found = false;
foreach (var lit in literals)
{
if (i + lit.Length <= line.Length && line.Substring(i, lit.Length) == lit)
{
sb.Append(@"\cf6 ");
sb.Append(lit);
sb.Append(@"\cf1 ");
i += lit.Length;
found = true;
break;
}
}
if (found) continue;
}
// Braces
if (c == '{' || c == '}' || c == '[' || c == ']')
{
sb.Append(@"\cf5 ");
sb.Append(EscapeRtf(c.ToString()));
sb.Append(@"\cf1 ");
i++;
continue;
}
// Punctuation
if (c == ':' || c == ',')
{
sb.Append(@"\cf7 ");
sb.Append(c);
sb.Append(@"\cf1 ");
i++;
continue;
}
sb.Append(EscapeRtf(c.ToString()));
i++;
}
}
private static string EscapeRtf(string text)
{
if (string.IsNullOrEmpty(text)) return "";
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
switch (c)
{
case '\\': sb.Append("\\\\"); break;
case '{': sb.Append("\\{"); break;
case '}': sb.Append("\\}"); break;
default: sb.Append(c); break;
}
}
return sb.ToString();
}
private void OnChatLinkClicked(object sender, LinkClickedEventArgs e)
{
string link = e.LinkText;
if (string.IsNullOrEmpty(link)) return;
try
{
if (link.StartsWith("file:///", StringComparison.OrdinalIgnoreCase))
{
string filePath = link.Substring(8).Replace('/', '\\');
filePath = Uri.UnescapeDataString(filePath);
OpenFile(filePath);
}
else if (link.StartsWith("cmd:///", StringComparison.OrdinalIgnoreCase))
{
string command = link.Substring(7);
command = Uri.UnescapeDataString(command);
ExecuteCommand(command);
}
}
catch { }
}
private static void OpenFile(string filePath)
{
if (!File.Exists(filePath) && !Directory.Exists(filePath))
{
Utils.Toast($"文件不存在: {filePath}");
return;
}
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = filePath,
UseShellExecute = true
});
}
catch (Exception ex)
{
Utils.Toast($"打开失败: {ex.Message}");
}
}
private void ExecuteCommand(string command)
{
bool needsAdmin = CommandNeedsAdmin(command);
string title = needsAdmin ? "需要管理员权限" : "确认执行命令";
string msg = needsAdmin
? $"该命令需要管理员权限才能执行:\n\n{command}\n\n是否以管理员身份执行?"
: $"确认执行以下命令?\n\n{command}";
var result = MessageBox.Show(this, msg, title,
MessageBoxButtons.YesNo, needsAdmin ? MessageBoxIcon.Warning : MessageBoxIcon.Question);
if (result != DialogResult.Yes) return;
try
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/k \"{command}\"",
UseShellExecute = true,
WorkingDirectory = _workingDirectory,
};
if (needsAdmin)
psi.Verb = "runas";
System.Diagnostics.Process.Start(psi);
}
catch (Exception ex)
{
Utils.Toast($"执行失败: {ex.Message}");
}
}
private static bool CommandNeedsAdmin(string command)
{
if (string.IsNullOrEmpty(command)) return false;
string lower = command.ToLowerInvariant();
string[] adminPatterns = {
"choco install", "choco upgrade", "scoop install", "winget install",
"msiexec", "install", "setup",
@"\program files\", @"\windows\system32\",
"reg add", "reg delete", "regedit",
"sc ", "net start", "net stop",
"netsh ", "bcdedit", "icacls",
"docker ", "systemctl ",
"sudo ", "runas"
};
foreach (var pattern in adminPatterns)
{
if (lower.Contains(pattern))
return true;
}
return false;
}
private void PreloadVersionInfo()
{
_cachedVersion = ClaudeCliService.GetVersion();
}
// ── Events ──────────────────────────────────────────
private void WireEvents()
{
lblNewChat.LinkClicked += (s, e) => NewChat();
lblHistory.LinkClicked += (s, e) => ShowHistoryPopup();
lblAttachFile.LinkClicked += (s, e) => AttachFile();
lblChangeDir.LinkClicked += (s, e) => OpenWorkingDirectory();
lblConfig.LinkClicked += (s, e) => ShowConfigPopup();
btnSend.Click += (s, e) => OnSendButtonClick();
txtInput.KeyDown += OnInputKeyDown;
this.KeyDown += OnFormKeyDown;
tmrFrog.Tick += OnFrogTick;
rtbChat.LinkClicked += OnChatLinkClicked;
_claudeService.OnTextDelta += OnAssistantTextDelta;
_claudeService.OnThinkingDelta += OnAssistantThinkingDelta;
_claudeService.OnStreamComplete += OnAssistantComplete;
_claudeService.OnError += OnServiceError;
_claudeService.OnProcessExited += OnServiceExited;
_claudeService.OnSystemInit += OnSystemInitReceived;
_claudeService.OnToolUseRequest += OnToolUseRequested;
_claudeService.OnAskUserQuestion += OnAskUserQuestionReceived;
btnConfirmSelection.Click += (s, e) => ConfirmSelection();
pnlOptions.KeyDown += OnOptionsKeyDown;
this.FormClosing += (s, e) =>
{
SaveCurrentSession();
_claudeService.Cancel();
_claudeService.Dispose();
tmrFrog.Stop();
};
this.Load += (s, e) =>
{
if (!ClaudeCliService.IsClaudeInstalled())
Utils.Toast("Claude CLI 未找到,请确保已安装并添加到 PATH 环境变量");
};
}
// ── Welcome Placeholder ─────────────────────────────
private string BuildWelcomeText()
{
var parts = new List<string> { "我是一个全栈软件工程师,输入消息开始对话 (Ctrl+Enter 发送)" };
if (!string.IsNullOrEmpty(_cachedVersion))
parts.Add($"Claude CLI: {_cachedVersion}");
if (!string.IsNullOrEmpty(_cachedModel))
parts.Add($"模型: {_cachedModel}");
parts.Add($"工作目录: {_workingDirectory}");
return string.Join("\n", parts);
}
// ── Frog Animation ──────────────────────────────────
private void StartFrogAnimation()
{
_frogFrame = 0;
tmrFrog.Interval = 1200;
tmrFrog.Start();
}
private void OnFrogTick(object s, EventArgs e)
{
string[] frames;
if (_claudeService.IsRunning)
{
frames = _frogActive;
tmrFrog.Interval = 500;
lblProgress.Visible = true;
if (_messages.Count > 0)
{
var last = _messages[_messages.Count - 1];
if (!string.IsNullOrEmpty(last.ThinkingContent) && string.IsNullOrEmpty(last.Content))
lblProgress.Text = "Claude 思考中...";
else
lblProgress.Text = "Claude 回复中...";
}
}
else
{
frames = _frogIdle;
tmrFrog.Interval = 1200;
lblProgress.Visible = false;
}
_frogFrame = (_frogFrame + 1) % frames.Length;
lblFrog.Text = frames[_frogFrame];
}
private void ShowFrogHappy()
{
lblFrog.Text = _frogHappy[0];
lblProgress.Text = "";
lblProgress.Visible = false;
}
// ── Input Handling ──────────────────────────────────
private void OnInputKeyDown(object s, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
OnSendButtonClick();
return;
}
if (e.KeyCode == Keys.Tab)
{
string text = txtInput.Text;
int pos = txtInput.SelectionStart;
int atIdx = text.LastIndexOf('@', pos > 0 ? pos - 1 : 0);
if (atIdx >= 0 && (atIdx == 0 || text[atIdx - 1] == ' '))
{
e.SuppressKeyPress = true;
AttachFile(atIdx);
}
}
}
private void OnFormKeyDown(object s, KeyEventArgs e)
{
// Question panel: allow up/down/enter even when input is disabled
if (panelQuestion.Visible && _optionLabels.Count > 0)
{
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
{
e.Handled = true;
e.SuppressKeyPress = true;
NavigateOption(e.KeyCode == Keys.Up ? -1 : 1);
return;
}
if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
e.SuppressKeyPress = true;
ConfirmSelection();
return;
}
}
if (e.KeyCode == Keys.Escape)
{
e.Handled = true;
if (panelQuestion.Visible)
{
HideQuestionPanel();
return;
}
if (_claudeService.IsRunning)
CancelSend();
}
}
// ── Send / Cancel ───────────────────────────────────
private void OnSendButtonClick()
{
if (_claudeService.IsRunning)
{
CancelSend();
return;
}
SendMessage();
}
private void CancelSend()
{
HideQuestionPanel();
// Mark last assistant message as cancelled before killing process
if (_messages.Count > 0)
{
var last = _messages[_messages.Count - 1];
if (last.Role == "assistant" && last.IsStreaming)
{
last.IsStreaming = false;
last.Content = string.IsNullOrEmpty(last.Content)
? "[已取消]"
: last.Content + "\n\n[已取消]";
}
}
_claudeService.Cancel();
SetInputEnabled(true);
SetButtonState(false);
txtInput.Focus();
RefreshChat();
}
private void SetButtonState(bool isRunning)
{
if (isRunning)
{
btnSend.Text = "取消";
btnSend.BackColor = System.Drawing.Color.FromArgb(220, 50, 50);
}
else
{
btnSend.Text = "发送";
btnSend.BackColor = System.Drawing.Color.FromArgb(0, 120, 212);
}
}
// ── Chat Logic ─────────────────────────────────────
private void RefreshChat()
{
string rtf = MarkdownRenderer.BuildChatRtf(_messages, BuildWelcomeText());
rtbChat.Rtf = rtf;
rtbChat.Select(rtbChat.TextLength, 0);
rtbChat.ScrollToCaret();
}
private void NewChat()
{
if (_claudeService.IsRunning)
_claudeService.Cancel();
SaveCurrentSession();
HideQuestionPanel();
_messages.Clear();
_isFirstMessage = true;
_lastStreamingLength = 0;
ResetInputState();
RefreshChat();
}
private void SendMessage()
{
string text = txtInput.Text.Trim();
if (string.IsNullOrEmpty(text)) return;
if (_claudeService.IsRunning) return;
if (text.StartsWith("/"))
{
HandleSlashCommand(text);
return;
}
// Apply mode prefix
string displayText = text;
string prompt = ApplyMode(text);
txtInput.Text = "";
SetInputEnabled(false);
SetButtonState(true);
_messages.Add(new ChatMessage { Role = "user", Content = displayText });
var assistantMsg = new ChatMessage { Role = "assistant", Content = "", IsStreaming = true };
_messages.Add(assistantMsg);
_lastStreamingLength = 0;
RefreshChat();
bool isContinue = !_isFirstMessage;
_isFirstMessage = false;
_claudeService.SendMessage(prompt, isContinue, this);
}
private string ApplyMode(string userText)
{
string mode = cmbMode.SelectedItem?.ToString() ?? "Normal";
switch (mode)
{
case "Plan":
return $"Please create a detailed implementation plan for the following task. Include step-by-step breakdown, file changes needed, and architectural considerations:\n\n{userText}";
case "Goal":
return $"My goal is: {userText}\n\nHelp me achieve this goal. Break it down into actionable steps and guide me through the implementation.";
default:
return userText;
}
}
// ── Slash Commands ─────────────────────────────────
private void HandleSlashCommand(string text)
{
string[] parts = text.Split(new[] { ' ' }, 2, StringSplitOptions.None);
string cmd = parts[0].ToLowerInvariant();
string arg = parts.Length > 1 ? parts[1] : "";
// Show command as user message first
txtInput.Text = "";
_messages.Add(new ChatMessage { Role = "user", Content = text });
switch (cmd)
{
case "/clear":
NewChat();
break;
case "/help":
AddSystemMessage(
"**可用命令**\n" +
"- `/clear` - 清空当前对话\n" +
"- `/help` - 显示此帮助\n" +
"- `/model` / `/version` - 查看版本和模型\n" +
"- `/plan <描述>` - 让 Claude 制定实施计划\n" +
"- `/goal <目标>` - 设定开发目标\n" +
"- `/skill <名称>` - 调用技能\n" +
"\n**快捷操作**\n" +
"- `Ctrl+Enter` - 发送\n" +
"- `ESC` - 取消\n" +
"- `@` + `Tab` - 引用文件\n" +
"- 模式选择: Normal / Plan / Goal");
break;
case "/model":
case "/version":
ShowSettingsInfo();
break;
case "/plan":
if (string.IsNullOrWhiteSpace(arg))
{
AddSystemMessage("用法: `/plan <描述>` — 让 Claude 为指定任务制定实施计划");
}
else
{
cmbMode.SelectedItem = "Plan";
txtInput.Text = arg;
txtInput.Focus();
txtInput.SelectionStart = arg.Length;
}
break;
case "/goal":
if (string.IsNullOrWhiteSpace(arg))
{
AddSystemMessage("用法: `/goal <目标>` — 设定开发目标并让 Claude 帮助实现");
}
else
{
cmbMode.SelectedItem = "Goal";
txtInput.Text = arg;
txtInput.Focus();
txtInput.SelectionStart = arg.Length;
}
break;
case "/skill":
if (string.IsNullOrWhiteSpace(arg))
{
AddSystemMessage("用法: `/skill <名称>` — 调用指定的技能\n可用技能: plan, goal, code-review, tdd");
}
else
{
string skillPrompt = BuildSkillPrompt(arg.ToLowerInvariant());
if (skillPrompt == null)
{
AddSystemMessage($"未知技能: {arg}\n可用技能: plan, goal, code-review, tdd, refactor, debug, explain");
}
else
{
SendSlashPrompt(skillPrompt);
}
}
break;
default:
AddSystemMessage($"未知命令: {cmd}\n输入 `/help` 查看可用命令");
break;
}
}
private string BuildSkillPrompt(string skill)
{
switch (skill)
{
case "plan":
return "Act as an implementation planner. Create a detailed step-by-step plan with file changes, architectural decisions, and risk assessment.";
case "goal":
return "Act as a goal-oriented development assistant. Help me define clear goals and break them into actionable tasks.";
case "code-review":
return "Act as a code reviewer. Review the code for bugs, security issues, performance problems, and code quality concerns.";
case "tdd":
return "Act as a TDD guide. Help me write tests first, then implement to pass them, following the Red-Green-Refactor cycle.";
case "refactor":
return "Act as a refactoring expert. Identify code smells, suggest improvements, and help restructure code for better maintainability.";
case "debug":
return "Act as a debugging assistant. Help analyze the problem systematically, identify root causes, and propose fixes.";
case "explain":
return "Act as a code explainer. Explain the code clearly with examples. Break down complex logic step by step.";
default:
return null;
}
}
private void SendSlashPrompt(string prompt)
{
if (_claudeService.IsRunning) return;
SetInputEnabled(false);
SetButtonState(true);
txtInput.Text = "";
_messages.Add(new ChatMessage { Role = "user", Content = prompt });
var assistantMsg = new ChatMessage { Role = "assistant", Content = "", IsStreaming = true };
_messages.Add(assistantMsg);
_lastStreamingLength = 0;
RefreshChat();
bool isContinue = !_isFirstMessage;
_isFirstMessage = false;
_claudeService.SendMessage(prompt, isContinue, this);
}
private void AddSystemMessage(string content)
{
_messages.Add(new ChatMessage { Role = "assistant", Content = content });
RefreshChat();
}
// ── Settings ────────────────────────────────────────
private void ShowSettingsInfo()
{
string version = _cachedVersion ?? ClaudeCliService.GetVersion() ?? "未获取到版本信息";
if (!string.IsNullOrEmpty(version) && _cachedVersion == null)
_cachedVersion = version;
var info = $"**Claude CLI 版本**: {version}";
if (!string.IsNullOrEmpty(_cachedModel))
info += $"\n**当前模型**: {_cachedModel}";
if (!string.IsNullOrEmpty(_claudeService.ModelName))
info += $"\n**当前模型**: {_claudeService.ModelName}";
info += $"\n**工作目录**: {_workingDirectory}";
info += $"\n**当前模式**: {cmbMode.SelectedItem ?? "Normal"}";
_messages.Add(new ChatMessage { Role = "assistant", Content = info });
RefreshChat();
}
// ── File Attachment ────────────────────────────────
private void AttachFile(int atIndex = -1)
{
using (var dlg = new OpenFileDialog
{
Title = "选择要引用的文件",
Filter = "所有文件|*.*",
Multiselect = false,
InitialDirectory = _workingDirectory
})
{
if (dlg.ShowDialog(this) == DialogResult.OK)
{
string insertText = $"@\"{dlg.FileName}\" ";
if (atIndex >= 0)
{
int len = txtInput.SelectionStart - atIndex;
if (len > 0)
txtInput.Text = txtInput.Text.Remove(atIndex, len);
txtInput.Text = txtInput.Text.Insert(atIndex, insertText);
txtInput.SelectionStart = atIndex + insertText.Length;
}
else
{
int pos = txtInput.SelectionStart;
txtInput.Text = txtInput.Text.Insert(pos, insertText);
txtInput.SelectionStart = pos + insertText.Length;
}
txtInput.Focus();
}
}
}
// ── Streaming Handlers ─────────────────────────────
private void OnSystemInitReceived(string model, string version)
{
this.AsyncUI(() =>
{
if (!string.IsNullOrEmpty(model))
{
_cachedModel = model;
this.Text = $"Claude Chat - {model}";
RefreshChat();
}
if (!string.IsNullOrEmpty(version) && _cachedVersion == null)
{
_cachedVersion = version;
RefreshChat();
}
});
}
private void OnAssistantTextDelta(string accumulatedText)
{
if (_messages.Count == 0) return;
var lastMsg = _messages[_messages.Count - 1];
if (lastMsg.Role != "assistant") return;
lastMsg.Content = accumulatedText;
_lastStreamingLength = accumulatedText.Length;
this.AsyncUI(() => RefreshChat());
}
private void OnAssistantThinkingDelta(string accumulatedThinking)
{
if (_messages.Count == 0) return;
var lastMsg = _messages[_messages.Count - 1];
if (lastMsg.Role != "assistant") return;
lastMsg.ThinkingContent = accumulatedThinking;
this.AsyncUI(() => RefreshChat());
}
private void OnAssistantComplete()
{
if (_messages.Count == 0) return;
var lastMsg = _messages[_messages.Count - 1];
if (lastMsg.Role == "assistant")
lastMsg.IsStreaming = false;
_lastStreamingLength = 0;
this.AsyncUI(() =>
{
RefreshChat();
ShowFrogHappy();
});
}
private void OnServiceError(string error)
{
_lastStreamingLength = 0;
_messages.Add(new ChatMessage
{
Role = "assistant",
Content = $"[Error: {error}]",
});
this.AsyncUI(() => RefreshChat());
}
private void OnServiceExited()
{
ResetInputState();
}
// ── Tool Use Auto-Approval ──────────────────────────
private void OnToolUseRequested(ToolUseInfo info)
{
string responseJson = BuildToolResultJson(info.ToolUseId, "approved", false);
_claudeService.SendResponse(responseJson);
}
// ── AskUserQuestion ─────────────────────────────────
private void OnAskUserQuestionReceived(AskUserQuestionInfo info)
{
this.AsyncUI(() => ShowQuestionPanel(info));
}
private void ShowQuestionPanel(AskUserQuestionInfo info)
{
_currentQuestionInfo = info;
_selectedOptionIndex = 0;
_optionLabels.Clear();
pnlOptions.Controls.Clear();
if (info?.Questions == null || info.Questions.Count == 0)
return;
var firstQ = info.Questions[0];
lblQuestionText.Text = firstQ.Question;
int y = 4;
for (int i = 0; i < firstQ.Options.Count; i++)
{
var opt = firstQ.Options[i];
var lbl = new Label
{
Text = $"{opt.Label} {opt.Description}",
Font = new System.Drawing.Font("微软雅黑", 9F),
AutoSize = false,
Size = new System.Drawing.Size(pnlOptions.Width - 20, 26),
Location = new System.Drawing.Point(8, y),
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
BackColor = System.Drawing.Color.White,
Padding = new Padding(4, 0, 4, 0),
Tag = i,
};
lbl.Click += (s, e) =>
{
if (s is Label clicked)
{
_selectedOptionIndex = (int)clicked.Tag;
UpdateOptionHighlight();
}
};
lbl.DoubleClick += (s, e) => ConfirmSelection();
pnlOptions.Controls.Add(lbl);
_optionLabels.Add(lbl);
y += 28;
}
UpdateOptionHighlight();
panelQuestion.Visible = true;
pnlOptions.Focus();
}
private void HideQuestionPanel()
{
panelQuestion.Visible = false;
_currentQuestionInfo = null;
_selectedOptionIndex = 0;
_optionLabels.Clear();
pnlOptions.Controls.Clear();
}
private void UpdateOptionHighlight()
{
for (int i = 0; i < _optionLabels.Count; i++)
{
if (i == _selectedOptionIndex)
{
_optionLabels[i].BackColor = System.Drawing.Color.FromArgb(0, 120, 212);
_optionLabels[i].ForeColor = System.Drawing.Color.White;
}
else
{
_optionLabels[i].BackColor = System.Drawing.Color.White;
_optionLabels[i].ForeColor = System.Drawing.Color.Black;
}
}
}
private void OnOptionsKeyDown(object s, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
{
e.Handled = true;
NavigateOption(e.KeyCode == Keys.Up ? -1 : 1);
}
else if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
ConfirmSelection();
}
else if (e.KeyCode == Keys.Escape)
{
e.Handled = true;
HideQuestionPanel();
}
}
private void NavigateOption(int direction)
{
if (_optionLabels.Count == 0) return;
_selectedOptionIndex = (_selectedOptionIndex + direction + _optionLabels.Count) % _optionLabels.Count;
UpdateOptionHighlight();
}
private void ConfirmSelection()
{
if (_currentQuestionInfo == null || _optionLabels.Count == 0)
return;
var questionInfo = _currentQuestionInfo;
string selectedLabel = _optionLabels[_selectedOptionIndex].Text;
string answer = questionInfo.Questions[0].Options[_selectedOptionIndex].Label;
string questionText = questionInfo.Questions[0].Question;
_messages.Add(new ChatMessage
{
Role = "user",
Content = $"[已选择: {selectedLabel}]",
});
HideQuestionPanel();
RefreshChat();
string answerContent = $"{{\"answers\":{{\"{EscapeJson(questionText)}\":\"{EscapeJson(answer)}\"}}}}";
string responseJson = BuildToolResultJson(questionInfo.ToolUseId, answerContent, false);
_claudeService.SendResponse(responseJson);
}
// ── Tool Result Helpers ──────────────────────────────
private static string BuildToolResultJson(string toolUseId, string content, bool isError)
{
string escapedContent = EscapeJson(content);
string errorFlag = isError ? ",\"is_error\":true" : "";
return "{\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"" +
EscapeJson(toolUseId) + "\",\"content\":\"" + escapedContent + "\"" + errorFlag + "}]}}";
}
private static string EscapeJson(string text)
{
if (string.IsNullOrEmpty(text)) return "";
return text.Replace("\\", "\\\\").Replace("\"", "\\\"")
.Replace("\r", "").Replace("\n", "\\n")
.Replace("\t", "\\t");
}
private void ResetInputState()
{
this.AsyncUI(() =>
{
SetInputEnabled(true);
SetButtonState(false);
txtInput.Focus();
});
}
private void SetInputEnabled(bool enabled)
{
txtInput.Enabled = enabled;
}
// ── History ────────────────────────────────────────
private void SaveCurrentSession()
{
if (_messages.Count == 0) return;
_historyDir = HistoryManager.EnsureHistoryDir(_workingDirectory);
HistoryManager.SaveSession(_historyDir, _messages);
}
private void ShowHistoryPopup()
{
_historyDir = HistoryManager.EnsureHistoryDir(_workingDirectory);
var sessions = HistoryManager.ListSessions(_historyDir);
if (sessions.Count == 0)
{
Utils.Toast("暂无历史会话记录");
return;
}
using (var popup = new Form
{
Text = "历史会话",
Size = new System.Drawing.Size(480, 360),
StartPosition = FormStartPosition.CenterParent,
MinimizeBox = false,
MaximizeBox = false,
ShowIcon = false,
Font = new System.Drawing.Font("Microsoft YaHei", 9F),
})
{
var listBox = new ListBox
{
Dock = DockStyle.Fill,
Font = new System.Drawing.Font("Microsoft YaHei", 10F),
};
foreach (var s in sessions)
listBox.Items.Add(s);
listBox.DoubleClick += (s, e) =>
{
if (listBox.SelectedItem is SessionSummary summary)
{
LoadSession(summary.FilePath);
popup.Close();
}
};
var panelBottom = new Panel
{
Dock = DockStyle.Bottom,
Height = 40,
Padding = new Padding(4),
};
var btnDelete = new Button
{
Text = "删除选中",
Dock = DockStyle.Left,
Width = 80,
FlatStyle = FlatStyle.Flat,
Font = new System.Drawing.Font("Microsoft YaHei", 9F),
};
btnDelete.Click += (s, e) =>
{
if (listBox.SelectedItem is SessionSummary summary)
{
HistoryManager.DeleteSession(summary.FilePath);
listBox.Items.Remove(summary);
if (listBox.Items.Count == 0)
popup.Close();
}
};
var btnLoad = new Button
{
Text = "加载会话",
Dock = DockStyle.Right,
Width = 80,
FlatStyle = FlatStyle.Flat,
BackColor = System.Drawing.Color.FromArgb(0, 120, 212),
ForeColor = System.Drawing.Color.White,
Font = new System.Drawing.Font("Microsoft YaHei", 9F),
};
btnLoad.Click += (s, e) =>
{
if (listBox.SelectedItem is SessionSummary summary)
{
LoadSession(summary.FilePath);
popup.Close();
}
};
panelBottom.Controls.Add(btnLoad);
panelBottom.Controls.Add(btnDelete);
popup.Controls.Add(listBox);
popup.Controls.Add(panelBottom);
popup.ShowDialog(this);
}
}
private void LoadSession(string filePath)
{
if (_claudeService.IsRunning)
_claudeService.Cancel();
try
{
var session = HistoryManager.LoadSession(filePath);
if (session?.Messages == null || session.Messages.Count == 0)
{
Utils.Toast("会话内容为空");
return;
}
_messages.Clear();
_messages.AddRange(session.Messages);
_isFirstMessage = false;
_lastStreamingLength = 0;
RefreshChat();
}
catch (Exception ex)
{
Utils.Toast($"加载失败: {ex.Message}");
}
}
// ── Config Model ────────────────────────────────────
private class ChatConfig
{
public string WorkingDirectory { get; set; }
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="tmrFrog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
\ No newline at end of file
using System.Windows.Forms;
using Tools.UI;
namespace Tools
{
partial class wMain
{
private wClaudeChat _claudeChat;
private void InitClaudeChat()
{
_claudeChat = new wClaudeChat
{
TopLevel = false,
FormBorderStyle = FormBorderStyle.None,
Dock = DockStyle.Fill,
};
HomePage.Controls.Clear();
HomePage.Controls.Add(_claudeChat);
_claudeChat.Show();
}
}
}
......@@ -66,11 +66,6 @@
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.tabControl1 = new Tools.MyCommon.controls.TabControlEx();
this.HomePage = new System.Windows.Forms.TabPage();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.lbTellUS = new System.Windows.Forms.Label();
this.lbUserAck = new System.Windows.Forms.Label();
this.lbBrief = new System.Windows.Forms.Label();
this.label9 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
......@@ -80,8 +75,6 @@
this.Header.SuspendLayout();
this.Footer.SuspendLayout();
this.tabControl1.SuspendLayout();
this.HomePage.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// panel1
......@@ -471,7 +464,6 @@
//
// HomePage
//
this.HomePage.Controls.Add(this.tableLayoutPanel1);
this.HomePage.Location = new System.Drawing.Point(4, 26);
this.HomePage.Name = "HomePage";
this.HomePage.Padding = new System.Windows.Forms.Padding(3);
......@@ -480,85 +472,6 @@
this.HomePage.Text = "首页";
this.HomePage.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 5;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.Controls.Add(this.lbTellUS, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.lbUserAck, 3, 1);
this.tableLayoutPanel1.Controls.Add(this.lbBrief, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.label9, 3, 3);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 5;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(480, 333);
this.tableLayoutPanel1.TabIndex = 0;
//
// lbTellUS
//
this.lbTellUS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(192)))));
this.lbTellUS.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbTellUS.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbTellUS.ForeColor = System.Drawing.Color.Black;
this.lbTellUS.Location = new System.Drawing.Point(115, 93);
this.lbTellUS.Margin = new System.Windows.Forms.Padding(0);
this.lbTellUS.Name = "lbTellUS";
this.lbTellUS.Size = new System.Drawing.Size(115, 62);
this.lbTellUS.TabIndex = 0;
this.lbTellUS.Text = "需求反馈";
this.lbTellUS.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lbUserAck
//
this.lbUserAck.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(192)))), ((int)(((byte)(255)))));
this.lbUserAck.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbUserAck.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbUserAck.ForeColor = System.Drawing.Color.Black;
this.lbUserAck.Location = new System.Drawing.Point(250, 93);
this.lbUserAck.Margin = new System.Windows.Forms.Padding(0);
this.lbUserAck.Name = "lbUserAck";
this.lbUserAck.Size = new System.Drawing.Size(115, 62);
this.lbUserAck.TabIndex = 1;
this.lbUserAck.Text = "需求受理";
this.lbUserAck.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lbBrief
//
this.lbBrief.BackColor = System.Drawing.Color.PaleTurquoise;
this.lbBrief.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbBrief.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbBrief.ForeColor = System.Drawing.Color.Black;
this.lbBrief.Location = new System.Drawing.Point(115, 175);
this.lbBrief.Margin = new System.Windows.Forms.Padding(0);
this.lbBrief.Name = "lbBrief";
this.lbBrief.Size = new System.Drawing.Size(115, 62);
this.lbBrief.TabIndex = 2;
this.lbBrief.Text = "平台简介";
this.lbBrief.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// label9
//
this.label9.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(128)))));
this.label9.Dock = System.Windows.Forms.DockStyle.Fill;
this.label9.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label9.ForeColor = System.Drawing.Color.White;
this.label9.Location = new System.Drawing.Point(250, 175);
this.label9.Margin = new System.Windows.Forms.Padding(0);
this.label9.Name = "label9";
this.label9.Size = new System.Drawing.Size(115, 62);
this.label9.TabIndex = 3;
this.label9.Text = "敬请期待";
this.label9.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// wMain
//
......@@ -583,8 +496,6 @@
this.Header.ResumeLayout(false);
this.Footer.ResumeLayout(false);
this.tabControl1.ResumeLayout(false);
this.HomePage.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
......@@ -625,11 +536,6 @@
private System.Windows.Forms.ToolStripMenuItem tsmiSysUpdateLog;
private Tools.MyCommon.controls.TabControlEx tabControl1;
private System.Windows.Forms.TabPage HomePage;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label lbTellUS;
private System.Windows.Forms.Label lbUserAck;
private System.Windows.Forms.Label lbBrief;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.ToolStripMenuItem tsmiShowFetetoWork;
private System.Windows.Forms.Button btnToggleMenu;
}
......
......@@ -86,10 +86,8 @@ namespace Tools
this.tabControl1.MouseDoubleClick += TabControl1_MouseDoubleClick;
//用户登录
this.lbSysLoginUser.Click += LbSysLoginUser_Click;
//扩展:首页功能
this.lbTellUS.Click += LbTellUS_Click;
this.lbUserAck.Click += LbUserAck_Click;
this.lbBrief.Click += LbBrief_Click;
//初始化Claude Chat
this.InitClaudeChat();
//检查更新
Task.Run(() => this.CheckUpdate(true));
//加载首页
......
namespace Tools
{
partial class wMain_Old
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(wMain));
this.panel1 = new System.Windows.Forms.Panel();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.listView1 = new System.Windows.Forms.ListView();
this.imageList1 = new System.Windows.Forms.ImageList(this.components);
this.btnToggleMenu = new System.Windows.Forms.Button();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiSysUpload = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiSysUpdate = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiSysUpdateLog = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiSysExit = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiAccPasswordMgr = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiAccPersonalSetting = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiHelpDocument = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiHelpAboutUs = new System.Windows.Forms.ToolStripMenuItem();
this.tsmiShowFetetoWork = new System.Windows.Forms.ToolStripMenuItem();
this.Header = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.panel4 = new System.Windows.Forms.Panel();
this.btnMinimized = new System.Windows.Forms.Button();
this.btnMaximized = new System.Windows.Forms.Button();
this.btnClosed = new System.Windows.Forms.Button();
this.Footer = new System.Windows.Forms.Panel();
this.lbSysMemery = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.lbSysVersion = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.lbSysLoginUser = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.tabControl1 = new Tools.MyCommon.controls.TabControlEx();
this.HomePage = new System.Windows.Forms.TabPage();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.lbTellUS = new System.Windows.Forms.Label();
this.lbUserAck = new System.Windows.Forms.Label();
this.lbBrief = new System.Windows.Forms.Label();
this.label9 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.menuStrip1.SuspendLayout();
this.Header.SuspendLayout();
this.Footer.SuspendLayout();
this.tabControl1.SuspendLayout();
this.HomePage.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// panel1
//
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.splitContainer1);
this.panel1.Controls.Add(this.menuStrip1);
this.panel1.Controls.Add(this.Header);
this.panel1.Controls.Add(this.Footer);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(700, 450);
this.panel1.TabIndex = 0;
//
// splitContainer1
//
this.splitContainer1.BackColor = System.Drawing.SystemColors.Window;
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
this.splitContainer1.Location = new System.Drawing.Point(0, 52);
this.splitContainer1.Name = "splitContainer1";
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.listView1);
this.splitContainer1.Panel1.Controls.Add(this.btnToggleMenu);
this.splitContainer1.Panel1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.tabControl1);
this.splitContainer1.Size = new System.Drawing.Size(698, 369);
this.splitContainer1.SplitterDistance = 200;
this.splitContainer1.TabIndex = 0;
//
// listView1
//
this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.listView1.HideSelection = false;
this.listView1.LargeImageList = this.imageList1;
this.listView1.Location = new System.Drawing.Point(0, 0);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(200, 349);
this.listView1.SmallImageList = this.imageList1;
this.listView1.StateImageList = this.imageList1;
this.listView1.TabIndex = 0;
this.listView1.UseCompatibleStateImageBehavior = false;
//
// imageList1
//
this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream")));
this.imageList1.TransparentColor = System.Drawing.Color.Transparent;
this.imageList1.Images.SetKeyName(0, "29_流程.png");
this.imageList1.Images.SetKeyName(1, "25_密码.png");
this.imageList1.Images.SetKeyName(2, "27_关联.png");
this.imageList1.Images.SetKeyName(3, "51_工作群组.png");
this.imageList1.Images.SetKeyName(4, "60_空间.png");
//
// btnToggleMenu
//
this.btnToggleMenu.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(41)))), ((int)(((byte)(101)))), ((int)(((byte)(204)))));
this.btnToggleMenu.Dock = System.Windows.Forms.DockStyle.Bottom;
this.btnToggleMenu.FlatAppearance.BorderSize = 0;
this.btnToggleMenu.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnToggleMenu.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnToggleMenu.ForeColor = System.Drawing.Color.White;
this.btnToggleMenu.Location = new System.Drawing.Point(0, 349);
this.btnToggleMenu.Margin = new System.Windows.Forms.Padding(0);
this.btnToggleMenu.Name = "btnToggleMenu";
this.btnToggleMenu.Size = new System.Drawing.Size(200, 20);
this.btnToggleMenu.TabIndex = 1;
this.btnToggleMenu.Text = "<";
this.btnToggleMenu.UseVisualStyleBackColor = false;
this.btnToggleMenu.Click += new System.EventHandler(this.btnToggleMenu_Click);
//
// menuStrip1
//
this.menuStrip1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(245)))), ((int)(((byte)(255)))));
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripMenuItem1,
this.toolStripMenuItem2,
this.toolStripMenuItem3,
this.tsmiShowFetetoWork});
this.menuStrip1.Location = new System.Drawing.Point(0, 27);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(698, 25);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
//
// toolStripMenuItem1
//
this.toolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsmiSysUpload,
this.tsmiSysUpdate,
this.tsmiSysUpdateLog,
this.tsmiSysExit});
this.toolStripMenuItem1.Name = "toolStripMenuItem1";
this.toolStripMenuItem1.Size = new System.Drawing.Size(44, 21);
this.toolStripMenuItem1.Text = "系统";
//
// tsmiSysUpload
//
this.tsmiSysUpload.Name = "tsmiSysUpload";
this.tsmiSysUpload.Size = new System.Drawing.Size(124, 22);
this.tsmiSysUpload.Text = "上传更新";
//
// tsmiSysUpdate
//
this.tsmiSysUpdate.Name = "tsmiSysUpdate";
this.tsmiSysUpdate.Size = new System.Drawing.Size(124, 22);
this.tsmiSysUpdate.Text = "检查更新";
//
// tsmiSysUpdateLog
//
this.tsmiSysUpdateLog.Name = "tsmiSysUpdateLog";
this.tsmiSysUpdateLog.Size = new System.Drawing.Size(124, 22);
this.tsmiSysUpdateLog.Text = "更新记录";
//
// tsmiSysExit
//
this.tsmiSysExit.Name = "tsmiSysExit";
this.tsmiSysExit.Size = new System.Drawing.Size(124, 22);
this.tsmiSysExit.Text = "退出系统";
//
// toolStripMenuItem2
//
this.toolStripMenuItem2.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsmiAccPasswordMgr,
this.tsmiAccPersonalSetting});
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
this.toolStripMenuItem2.Size = new System.Drawing.Size(44, 21);
this.toolStripMenuItem2.Text = "账户";
//
// tsmiAccPasswordMgr
//
this.tsmiAccPasswordMgr.Name = "tsmiAccPasswordMgr";
this.tsmiAccPasswordMgr.Size = new System.Drawing.Size(124, 22);
this.tsmiAccPasswordMgr.Text = "密码管理";
//
// tsmiAccPersonalSetting
//
this.tsmiAccPersonalSetting.Name = "tsmiAccPersonalSetting";
this.tsmiAccPersonalSetting.Size = new System.Drawing.Size(124, 22);
this.tsmiAccPersonalSetting.Text = "个人设置";
//
// toolStripMenuItem3
//
this.toolStripMenuItem3.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsmiHelpDocument,
this.tsmiHelpAboutUs});
this.toolStripMenuItem3.Name = "toolStripMenuItem3";
this.toolStripMenuItem3.Size = new System.Drawing.Size(44, 21);
this.toolStripMenuItem3.Text = "帮助";
//
// tsmiHelpDocument
//
this.tsmiHelpDocument.Name = "tsmiHelpDocument";
this.tsmiHelpDocument.Size = new System.Drawing.Size(124, 22);
this.tsmiHelpDocument.Text = "帮助文档";
//
// tsmiHelpAboutUs
//
this.tsmiHelpAboutUs.Name = "tsmiHelpAboutUs";
this.tsmiHelpAboutUs.Size = new System.Drawing.Size(124, 22);
this.tsmiHelpAboutUs.Text = "关于我们";
//
// tsmiShowFetetoWork
//
this.tsmiShowFetetoWork.Name = "tsmiShowFetetoWork";
this.tsmiShowFetetoWork.Size = new System.Drawing.Size(68, 21);
this.tsmiShowFetetoWork.Text = "任务系统";
this.tsmiShowFetetoWork.Click += new System.EventHandler(this.MenuItem_Click);
//
// Header
//
this.Header.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(41)))), ((int)(((byte)(101)))), ((int)(((byte)(204)))));
this.Header.Controls.Add(this.label1);
this.Header.Controls.Add(this.panel4);
this.Header.Controls.Add(this.btnMinimized);
this.Header.Controls.Add(this.btnMaximized);
this.Header.Controls.Add(this.btnClosed);
this.Header.Cursor = System.Windows.Forms.Cursors.Hand;
this.Header.Dock = System.Windows.Forms.DockStyle.Top;
this.Header.Location = new System.Drawing.Point(0, 0);
this.Header.Name = "Header";
this.Header.Size = new System.Drawing.Size(698, 27);
this.Header.TabIndex = 1;
//
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.label1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(30, 3);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(168, 20);
this.label1.TabIndex = 4;
this.label1.Text = "飞天兔工具平台";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// panel4
//
this.panel4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.panel4.BackgroundImage = global::Tools.Properties.Resources.Logo;
this.panel4.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
this.panel4.Location = new System.Drawing.Point(3, 1);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(25, 25);
this.panel4.TabIndex = 3;
//
// btnMinimized
//
this.btnMinimized.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.btnMinimized.BackgroundImage = global::Tools.Properties.Resources.最小化;
this.btnMinimized.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
this.btnMinimized.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
this.btnMinimized.Location = new System.Drawing.Point(611, 2);
this.btnMinimized.Name = "btnMinimized";
this.btnMinimized.Size = new System.Drawing.Size(22, 22);
this.btnMinimized.TabIndex = 3;
this.btnMinimized.UseVisualStyleBackColor = true;
this.btnMinimized.Click += new System.EventHandler(this.btnMinimized_Click);
//
// btnMaximized
//
this.btnMaximized.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.btnMaximized.BackgroundImage = global::Tools.Properties.Resources.最大化;
this.btnMaximized.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
this.btnMaximized.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
this.btnMaximized.Location = new System.Drawing.Point(638, 2);
this.btnMaximized.Name = "btnMaximized";
this.btnMaximized.Size = new System.Drawing.Size(22, 22);
this.btnMaximized.TabIndex = 2;
this.btnMaximized.UseVisualStyleBackColor = true;
this.btnMaximized.Click += new System.EventHandler(this.btnMaximized_Click);
//
// btnClosed
//
this.btnClosed.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.btnClosed.BackgroundImage = global::Tools.Properties.Resources.关闭;
this.btnClosed.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
this.btnClosed.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
this.btnClosed.Location = new System.Drawing.Point(665, 2);
this.btnClosed.Name = "btnClosed";
this.btnClosed.Size = new System.Drawing.Size(22, 22);
this.btnClosed.TabIndex = 4;
this.btnClosed.UseVisualStyleBackColor = true;
this.btnClosed.Click += new System.EventHandler(this.btnClosed_Click);
//
// Footer
//
this.Footer.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(41)))), ((int)(((byte)(101)))), ((int)(((byte)(204)))));
this.Footer.Controls.Add(this.lbSysMemery);
this.Footer.Controls.Add(this.label3);
this.Footer.Controls.Add(this.lbSysVersion);
this.Footer.Controls.Add(this.label6);
this.Footer.Controls.Add(this.label4);
this.Footer.Controls.Add(this.lbSysLoginUser);
this.Footer.Controls.Add(this.label2);
this.Footer.Dock = System.Windows.Forms.DockStyle.Bottom;
this.Footer.Location = new System.Drawing.Point(0, 421);
this.Footer.Name = "Footer";
this.Footer.Size = new System.Drawing.Size(698, 27);
this.Footer.TabIndex = 0;
//
// lbSysMemery
//
this.lbSysMemery.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.lbSysMemery.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbSysMemery.ForeColor = System.Drawing.Color.White;
this.lbSysMemery.Location = new System.Drawing.Point(209, 4);
this.lbSysMemery.Name = "lbSysMemery";
this.lbSysMemery.Size = new System.Drawing.Size(75, 20);
this.lbSysMemery.TabIndex = 12;
this.lbSysMemery.Text = "00M";
this.lbSysMemery.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label3
//
this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.label3.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label3.ForeColor = System.Drawing.Color.White;
this.label3.Location = new System.Drawing.Point(177, 4);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(44, 20);
this.label3.TabIndex = 11;
this.label3.Text = "内存:";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lbSysVersion
//
this.lbSysVersion.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.lbSysVersion.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbSysVersion.ForeColor = System.Drawing.Color.White;
this.lbSysVersion.Location = new System.Drawing.Point(625, 4);
this.lbSysVersion.Name = "lbSysVersion";
this.lbSysVersion.Size = new System.Drawing.Size(70, 20);
this.lbSysVersion.TabIndex = 10;
this.lbSysVersion.Text = "2021.00.00";
this.lbSysVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label6
//
this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.label6.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label6.ForeColor = System.Drawing.Color.White;
this.label6.Location = new System.Drawing.Point(569, 4);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(69, 20);
this.label6.TabIndex = 9;
this.label6.Text = "软件版本:";
this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label4
//
this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Right)));
this.label4.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label4.ForeColor = System.Drawing.Color.White;
this.label4.Location = new System.Drawing.Point(346, 4);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(217, 20);
this.label4.TabIndex = 7;
this.label4.Text = "飞天兔技术服务热线:4008-677-568";
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lbSysLoginUser
//
this.lbSysLoginUser.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.lbSysLoginUser.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbSysLoginUser.ForeColor = System.Drawing.Color.White;
this.lbSysLoginUser.Location = new System.Drawing.Point(61, 4);
this.lbSysLoginUser.Name = "lbSysLoginUser";
this.lbSysLoginUser.Size = new System.Drawing.Size(76, 20);
this.lbSysLoginUser.TabIndex = 6;
this.lbSysLoginUser.Text = "临时用户";
this.lbSysLoginUser.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.toolTip1.SetToolTip(this.lbSysLoginUser, "点击登录");
//
// label2
//
this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.label2.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(6, 4);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(68, 20);
this.label2.TabIndex = 5;
this.label2.Text = "登录人员:";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// tabControl1
//
this.tabControl1.BackgroundColor = System.Drawing.Color.White;
this.tabControl1.Controls.Add(this.HomePage);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Font = new System.Drawing.Font("微软雅黑", 9F);
this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(494, 369);
this.tabControl1.TabActivedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(41)))), ((int)(((byte)(101)))), ((int)(((byte)(204)))));
this.tabControl1.TabActivedFontColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250)))));
this.tabControl1.TabDeactivedBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(246)))), ((int)(((byte)(247)))), ((int)(((byte)(254)))));
this.tabControl1.TabDeactivedBackColorHover = System.Drawing.Color.LightGreen;
this.tabControl1.TabDeactivedFontColor = System.Drawing.Color.FromArgb(((int)(((byte)(55)))), ((int)(((byte)(55)))), ((int)(((byte)(55)))));
this.tabControl1.TabDeactivedFontColorHover = System.Drawing.Color.FromArgb(((int)(((byte)(55)))), ((int)(((byte)(55)))), ((int)(((byte)(55)))));
this.tabControl1.TabIndex = 0;
//
// HomePage
//
this.HomePage.Controls.Add(this.tableLayoutPanel1);
this.HomePage.Location = new System.Drawing.Point(4, 26);
this.HomePage.Name = "HomePage";
this.HomePage.Padding = new System.Windows.Forms.Padding(3);
this.HomePage.Size = new System.Drawing.Size(486, 339);
this.HomePage.TabIndex = 0;
this.HomePage.Text = "首页";
this.HomePage.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 5;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.Controls.Add(this.lbTellUS, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.lbUserAck, 3, 1);
this.tableLayoutPanel1.Controls.Add(this.lbBrief, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.label9, 3, 3);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 5;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(480, 333);
this.tableLayoutPanel1.TabIndex = 0;
//
// lbTellUS
//
this.lbTellUS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(192)))));
this.lbTellUS.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbTellUS.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbTellUS.ForeColor = System.Drawing.Color.Black;
this.lbTellUS.Location = new System.Drawing.Point(115, 93);
this.lbTellUS.Margin = new System.Windows.Forms.Padding(0);
this.lbTellUS.Name = "lbTellUS";
this.lbTellUS.Size = new System.Drawing.Size(115, 62);
this.lbTellUS.TabIndex = 0;
this.lbTellUS.Text = "需求反馈";
this.lbTellUS.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lbUserAck
//
this.lbUserAck.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(192)))), ((int)(((byte)(255)))));
this.lbUserAck.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbUserAck.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbUserAck.ForeColor = System.Drawing.Color.Black;
this.lbUserAck.Location = new System.Drawing.Point(250, 93);
this.lbUserAck.Margin = new System.Windows.Forms.Padding(0);
this.lbUserAck.Name = "lbUserAck";
this.lbUserAck.Size = new System.Drawing.Size(115, 62);
this.lbUserAck.TabIndex = 1;
this.lbUserAck.Text = "需求受理";
this.lbUserAck.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lbBrief
//
this.lbBrief.BackColor = System.Drawing.Color.PaleTurquoise;
this.lbBrief.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbBrief.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lbBrief.ForeColor = System.Drawing.Color.Black;
this.lbBrief.Location = new System.Drawing.Point(115, 175);
this.lbBrief.Margin = new System.Windows.Forms.Padding(0);
this.lbBrief.Name = "lbBrief";
this.lbBrief.Size = new System.Drawing.Size(115, 62);
this.lbBrief.TabIndex = 2;
this.lbBrief.Text = "平台简介";
this.lbBrief.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// label9
//
this.label9.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(128)))));
this.label9.Dock = System.Windows.Forms.DockStyle.Fill;
this.label9.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label9.ForeColor = System.Drawing.Color.White;
this.label9.Location = new System.Drawing.Point(250, 175);
this.label9.Margin = new System.Windows.Forms.Padding(0);
this.label9.Name = "label9";
this.label9.Size = new System.Drawing.Size(115, 62);
this.label9.TabIndex = 3;
this.label9.Text = "敬请期待";
this.label9.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// wMain
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(700, 450);
this.Controls.Add(this.panel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "wMain";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "工具平台";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.WMain_FormClosing);
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.splitContainer1.Panel1.ResumeLayout(false);
this.splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.Header.ResumeLayout(false);
this.Footer.ResumeLayout(false);
this.tabControl1.ResumeLayout(false);
this.HomePage.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem2;
private System.Windows.Forms.ToolStripMenuItem tsmiAccPersonalSetting;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem tsmiSysUpdate;
private System.Windows.Forms.ToolStripMenuItem tsmiSysExit;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem3;
private System.Windows.Forms.ToolStripMenuItem tsmiHelpDocument;
private System.Windows.Forms.Panel Header;
private System.Windows.Forms.Panel Footer;
private System.Windows.Forms.Button btnMinimized;
private System.Windows.Forms.Button btnMaximized;
private System.Windows.Forms.Button btnClosed;
private System.Windows.Forms.Panel panel4;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label lbSysVersion;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label lbSysLoginUser;
private System.Windows.Forms.ToolStripMenuItem tsmiHelpAboutUs;
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.ToolStripMenuItem tsmiAccPasswordMgr;
private System.Windows.Forms.ImageList imageList1;
private System.Windows.Forms.ToolTip toolTip1;
private System.Windows.Forms.ToolStripMenuItem tsmiSysUpload;
private System.Windows.Forms.Label lbSysMemery;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.ToolStripMenuItem tsmiSysUpdateLog;
private Tools.MyCommon.controls.TabControlEx tabControl1;
private System.Windows.Forms.TabPage HomePage;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label lbTellUS;
private System.Windows.Forms.Label lbUserAck;
private System.Windows.Forms.Label lbBrief;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.ToolStripMenuItem tsmiShowFetetoWork;
private System.Windows.Forms.Button btnToggleMenu;
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//
using Tools.MyCommon;
using Tools.MyData;
using Tools.MyUtils;
using Tools.UI.sys;
namespace Tools
{
public partial class wMain_Old : Form
{
/// <summary>
/// 窗体扩展类
/// </summary>
wExtend Extend = new wExtend();
/// <summary>
/// 已打开的窗口列表
/// </summary>
List<string> OpenedList = new List<string>();
/// <summary>
/// 数据库访问对象
/// </summary>
DBHelper _db = null;
/// <summary>
/// 左侧菜单原始宽度
/// </summary>
private int OriginalMenuWidth = 200;
/// <summary>
/// 左侧菜单是否已折叠
/// </summary>
private bool IsMenuCollapsed = false;
public wMain()
{
InitializeComponent();
//基础属性设置
this.IsMdiContainer = true;
//重置窗体大小
this.SetWinMinimized();
//窗体基本事件
this.Load += WMain_Load;
this.Shown += WMain_Shown;
}
private void WMain_Load(object sender, EventArgs e)
{
//生成标识
APP.TOKEN = Guid.NewGuid().ToString();
//初始化数据库链接
var link = Utils.GetDbLinkString204("FTT_Work");
this._db = new DBHelper(link);
//初始化登录信息
this.InitLoginInfo();
//渲染菜单
this.RenderMenu();
}
private void WMain_Shown(object sender, EventArgs e)
{
//保存原始宽度
this.OriginalMenuWidth = this.splitContainer1.SplitterDistance;
//窗体拖拽事件
this.Header.MouseDown += Header_MouseDown;
this.Header.MouseUp += Header_MouseUp;
this.Header.MouseMove += Header_MouseMove;
this.Header.DoubleClick += btnMaximized_Click; //顶部双击缩放窗口
//菜单栏事件
foreach (ToolStripMenuItem item in this.menuStrip1.Items)
{
foreach (ToolStripMenuItem menuItem in item.DropDownItems)
{
menuItem.Click += MenuItem_Click;
}
}
//用TabControl模拟MDI效果
this.tabControl1.ShowToolTips = true;
this.tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged;
this.tabControl1.MouseDoubleClick += TabControl1_MouseDoubleClick;
//用户登录
this.lbSysLoginUser.Click += LbSysLoginUser_Click;
//扩展:首页功能
this.lbTellUS.Click += LbTellUS_Click;
this.lbUserAck.Click += LbUserAck_Click;
this.lbBrief.Click += LbBrief_Click;
//检查更新
Task.Run(() => this.CheckUpdate(true));
//加载首页
//Task.Run(() => this.AsyncUI(() => {
// this.webBrowser1.Url = new Uri("http://www.feteto.cn/#/");
// this.webBrowser1.Show();
//}));
//版本号处理
Task.Run(() => this.AsyncUI(() => {
var dt = this._db.GetDataTable($@"
SELECT TOP 1 CAST(DATENAME(YEAR, UDate) AS VARCHAR(10)) + '.' + CAST(DATENAME(MONTH, UDate) AS VARCHAR(10)) + '.' + CAST(COUNT(*) AS VARCHAR(10)) AS SysVersion
FROM dbo.FTT_AutoUpdate
GROUP BY DATENAME(YEAR, UDate), DATENAME(MONTH, UDate)
ORDER BY DATENAME(YEAR, UDate) DESC, DATENAME(MONTH, UDate) DESC", "version", false);
this.lbSysVersion.Text = dt.DefaultView.Count == 0 ? $"2021.0.1" : $"{dt.DefaultView[0][0]}";
}));
//日志记录
UserLog.Write("打开系统");
}
private void WMain_FormClosing(object sender, FormClosingEventArgs e)
{
this._db = null;
//this.webBrowser1.Hide();
//this.webBrowser1.Dispose();
}
#region 基础:关闭、最大化、最小化
/// <summary>
/// 窗体最小宽度
/// </summary>
private readonly int MinWidth = 1000;
/// <summary>
/// 窗体最小高度
/// </summary>
private readonly int MinHeight = 650;
/// <summary>
/// 是否最大化
/// </summary>
private bool IsMaximized = true;
private void btnClosed_Click(object sender, EventArgs e)
{
this.Exit();
}
private void btnMaximized_Click(object sender, EventArgs e)
{
if (this.IsMaximized)
{
this.SetWinMinimized();
}
else
{
this.SetWinMaximized();
}
//刷新Tab显示
this.RefreshTab();
}
private void btnMinimized_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
/// <summary>
/// 设置窗体最大化
/// </summary>
private void SetWinMaximized()
{
//重新锚定左上角
this.Location = new Point(0, 0);
//重置大小
this.Size = Screen.PrimaryScreen.WorkingArea.Size;
//更新标志
this.IsMaximized = true;
}
/// <summary>
/// 设置窗体最小化
/// </summary>
private void SetWinMinimized()
{
//重置大小
this.Size = new Size(this.MinWidth, this.MinHeight);
//重新锚定居中
var x = Screen.PrimaryScreen.WorkingArea.Width / 2 - this.Width / 2;
var y = Screen.PrimaryScreen.WorkingArea.Height / 2 - this.Height / 2;
this.Location = new Point(x, y);
//更新标志
this.IsMaximized = false;
}
#endregion
#region 基础:主窗体拖拽功能
/// <summary>
/// 是否在移动
/// </summary>
private bool IsMoving = false;
/// <summary>
/// 移动起点(以Size形式记录,方便加减运算)
/// </summary>
private Size MoveStart;
private void Header_MouseDown(object sender, MouseEventArgs e)
{
this.MoveStart = new Size(e.Location);
this.IsMoving = true;
}
private void Header_MouseUp(object sender, MouseEventArgs e)
{
this.IsMoving = false;
}
private void Header_MouseMove(object sender, MouseEventArgs e)
{
if (this.IsMoving)
{
Size relativeOffset = new Size(e.Location - this.MoveStart);
this.Location += relativeOffset;
}
}
#endregion
#region 基础:任务栏点击最小化与还原
const int WS_MINIMIZEBOX = 0x00020000;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style = cp.Style | WS_MINIMIZEBOX;
return cp;
}
}
#endregion
#region 本地:系统菜单功能
/// <summary>
/// 事件:菜单栏事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MenuItem_Click(object sender, EventArgs e)
{
var tsmi = sender as ToolStripMenuItem;
//MessageBox.Show($"[{tsmi.Name.Substring(4)}]{tsmi.Text}");
switch (tsmi.Name.Substring(4))
{
case "SysUpload": this.UploadUpdate(); break;
case "SysUpdate": this.CheckUpdate(); break;
case "SysUpdateLog": this.ViewUpdateLog(); break;
case "SysExit": this.Exit(); break;
case "AccPasswordMgr": this.PasswordMgr(); break;
case "AccPersonalSetting": this.PersonalSetting(); break;
case "HelpDocument": this.Document(); break;
case "HelpAboutUs": this.AboutUs(); break;
case "ShowFetetoWork": ShowFetetoWork(); break;
default: break;
}
}
#region 本地:系统菜单功能
/// <summary>
/// 上传更新
/// </summary>
private void UploadUpdate()
{
new UI.sys.wUploadUpdate().ShowDialog();
}
/// <summary>
/// 系统:检查更新
/// </summary>
private void CheckUpdate(bool IsSilentWhenNotFound = false)
{
var m = Utils.GetJsonConfig<LoginMemory>();
//检查更新
var updateTime = m.UpdateTime;
var dt =this._db.GetDataTable($"SELECT TOP 1 UDate, URemark FROM dbo.FTT_AutoUpdate WHERE UDate > '{updateTime}' ORDER BY UDate DESC", "FTT_AutoUpdate");
if (dt.DefaultView.Count > 0)
{
var drv = dt.DefaultView[0];
if (!Utils.TipYN($"发现新版本,是否立即更新?\r\n\r\n{drv["URemark"]}"))
return;
//启动自动更新
var info = new ProcessStartInfo()
{
FileName = $@".\AutoUpdate\AutoUpdate.exe",
Arguments = $"{Directory.GetParent(Utils.StartupPath)} {Process.GetCurrentProcess().ProcessName}",
UseShellExecute = false,
RedirectStandardError = true,
};
//启动更新程序
Task.Run(() => new Process() { StartInfo = info }.Start());
//退出运行环境
Application.Exit();
}
else if(!IsSilentWhenNotFound)
{
Utils.Tip("当前已是最新版本!");
}
}
/// <summary>
/// 查看更新记录
/// </summary>
private void ViewUpdateLog()
{
StringBuilder sb = new StringBuilder("*注:仅展示最近20条更新记录\r\n");
var dt = this._db.GetDataTable("SELECT TOP 20 ROW_NUMBER() OVER(ORDER BY ID) AS SN, UDate, URemark FROM dbo.FTT_AutoUpdate ORDER BY ID DESC", "Log", false);
foreach (DataRowView r in dt.DefaultView)
{
var date = (r["UDate"] as DateTime?)?.ToString("yyyy/MM/dd HH:mm:ss.fff");
sb.Append($"{new string('-', 100)}\r\n版本序号:{r["SN"]}\r\n更新时间:{date}\r\n{r["URemark"].ToString().Trim()}\r\n");
}
//
Utils.Tip(sb.ToString(), 600, 400);
}
/// <summary>
/// 系统:退出系统
/// </summary>
private void Exit()
{
//温馨提示
if (this.OpenedList.Count > 0)
{
var result = MessageBox.Show("还有业务模块未关闭,是否直接退出系统!", "温馨提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.No)
return;
}
//日志记录
UserLog.Write("退出系统");
//关闭窗体
this.Close();
}
/// <summary>
/// 账户:密码管理
/// </summary>
private void PasswordMgr()
{
this.ShowMessage("密码管理");
}
/// <summary>
/// 账户:个人设置
/// </summary>
private void PersonalSetting()
{
this.ShowMessage("个人设置");
}
/// <summary>
/// 帮助:帮助文档
/// </summary>
private void Document()
{
new UI.sys.wHelpDocument().ShowDialog();
}
/// <summary>
/// 帮助:关于我们
/// </summary>
private void AboutUs()
{
new UI.sys.wAboutUS().ShowDialog();
}
/// <summary>
/// 打开任务系统
/// </summary>
private void ShowFetetoWork()
{
var assembly = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebKit", "WebKit.exe");
if (File.Exists(assembly))
{
// url;title
Process.Start(assembly, "http://ftt.feteto.cn:21802/;Feteto任务系统");
}
else
{
Process.Start("http://ftt.feteto.cn:21802/");
}
}
#endregion
#endregion
#region 本地:模块打开与关闭
/// <summary>
/// 打开模块
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
private void OpenModule(ListViewItem item)
{
TabPage page = null;
//已经打开的,直接切换到窗体
var name = item.Text;
if (this.OpenedList.Contains(name))
{
page = this.tabControl1.TabPages.Cast<TabPage>().Where(p => p.Text.Equals(name)).FirstOrDefault();
}
else
{
//添加到已打开列表
this.OpenedList.Add(name);
//创建标签页
page = new TabPage(name)
{
Parent = this.tabControl1,
BackColor = Color.White,
ToolTipText = "双击关闭",
};
//挂载窗体
var frm = Activator.CreateInstance(item.Tag as Type) as Form;
frm.Text = name;
frm.TopLevel = false;
frm.FormBorderStyle = FormBorderStyle.None;
frm.BackColor = Color.White;
frm.Parent = page;
frm.Dock = DockStyle.Fill;
frm.Show();
}
//日志记录
UserLog.Write($"打开模块:{page.Text}");
//选中标签页
this.tabControl1.SelectedTab = page;
page.Refresh();
}
/// <summary>
/// 关闭模块
/// </summary>
/// <param name="page"></param>
private void CloseModule(TabPage page)
{
if (page.Name != nameof(HomePage))
{
UserLog.Write($"关闭模块:{page.Text}"); //日志记录
this.OpenedList.Remove(page.Text); //移除列表
this.tabControl1.TabPages.Remove(page); //移除页面
//先关闭并释放窗体
if (page.Controls.Count > 0)
{
var frm = page.Controls[0] as Form;
if (frm != null)
{
frm.Close(); //触发FormClosing事件
frm.Dispose(); //释放窗体资源
}
}
page.Dispose(); //释放页面
}
}
/// <summary>
/// 事件:Tab双击(双击移除Page,即关闭窗口)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TabControl1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
this.CloseModule(this.tabControl1.SelectedTab);
}
/// <summary>
/// 事件:Tab切换(刷新Tab,防止Tab尺寸和窗体尺寸不一致)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
this.RefreshTab();
}
#endregion
#region 扩展:折叠/展开左侧菜单
/// <summary>
/// 折叠/展开按钮点击事件
/// </summary>
private void btnToggleMenu_Click(object sender, EventArgs e)
{
this.ToggleMenu();
}
/// <summary>
/// 切换菜单折叠/展开状态
/// </summary>
private void ToggleMenu()
{
// 切换状态
this.IsMenuCollapsed = !this.IsMenuCollapsed;
// 使用三元运算符简化状态切换
this.splitContainer1.SplitterDistance = this.IsMenuCollapsed ? 20 : this.OriginalMenuWidth;
this.btnToggleMenu.Text = this.IsMenuCollapsed ? ">" : "<";
// 强制刷新按钮显示
this.btnToggleMenu.PerformLayout();
this.btnToggleMenu.Refresh();
}
#endregion
#region 扩展:使用内存展示
//定时器:默认开启,间隔1秒
private void timer1_Tick(object sender, EventArgs e)
{
var p = Process.GetCurrentProcess();
// 使用PrivateMemorySize64获取进程独占的内存,与任务管理器显示一致
// WorkingSet64 包含共享内存,通常比实际占用大
var MB = (p.PrivateMemorySize64 / 1024 / 1024);
this.AsyncUI(() => {
this.lbSysMemery.Text = $"{MB}M";
});
}
#endregion
#region 扩展:登录及权限
/// <summary>
/// 登录人员点击
/// </summary>
private void LbSysLoginUser_Click(object sender, EventArgs e)
{
if (this.ShowLoginWindow())
{
//关闭已打开窗口
while (this.tabControl1.TabPages.Count > 1)
{
this.CloseModule(this.tabControl1.TabPages[1]);
}
//新渲染菜单栏
this.RenderMenu();
}
}
/// <summary>
/// 初始化登录信息
/// </summary>
private void InitLoginInfo()
{
var m = Utils.GetJsonConfig<LoginMemory>();
//获取登录信息
var loginInfo = m.LoginInfo;
if (!string.IsNullOrEmpty(loginInfo))
{
var sql = $@"
SELECT Name as UserName,
LoginPassword as Password,
ISNULL(IsPrincipal, 0) AS IsPrincipal,
CAST(CASE WHEN UserType = 5311 THEN 1 ELSE 0 END AS BIT) AS IsStaff
FROM dbo.AC_User
WHERE ISNULL(IsStop, 0) = 0 and ID = {Utils.Decryptor(loginInfo)}";
var dt = this._db.GetDataTable(sql, "user", false);
if (dt.DefaultView.Count > 0)
{
var userName = dt.DefaultView[0]["UserName"].ToString();
var password = dt.DefaultView[0]["Password"].ToString();
//验证密码
var loginVerify = m.LoginVerify;
if (loginVerify != password)
{
if (Utils.TipYN("密码已过期,请重新登录!"))
{
if (this.ShowLoginWindow(userName))
{
return;
}
}
}
else
{
//初始化登录人员信息
LoginInfo.UserName = userName;
LoginInfo.Password = password;
LoginInfo.IsPrincipal = Convert.ToBoolean(dt.DefaultView[0]["IsPrincipal"].ToString());
LoginInfo.IsStaff = Convert.ToBoolean(dt.DefaultView[0]["IsStaff"].ToString());
}
}
}
//设置登录用户名
this.SetLoginUserName();
}
/// <summary>
/// 显示登录窗口
/// </summary>
/// <param name="tempName">临时用户名</param>
/// <returns>bool</returns>
private bool ShowLoginWindow(string tempName = "")
{
wLogin login = new wLogin(tempName);
var isOK = login.ShowDialog() == DialogResult.OK;
if (isOK)
{
this.SetLoginUserName();
}
return isOK;
}
/// <summary>
/// 设置登录人员名称
/// </summary>
/// <param name="userName"></param>
private void SetLoginUserName()
{
//设置登录用户名
this.lbSysLoginUser.Text = LoginInfo.UserName;
//非部门管理员,禁用上传更新
this.tsmiSysUpload.Enabled = LoginInfo.IsPrincipal;
}
#endregion
#region 扩展:首页功能
//需求反馈和受理之间应该加上并发控制,后续再说
//事件:需求反馈
private void LbTellUS_Click(object sender, EventArgs e)
{
//权限校验
if (LoginInfo.UserName.Equals(APP.TEMP_USER_NAME) && string.IsNullOrEmpty(LoginInfo.Password))
{
this.ShowMessage("使用此功能,需要先登录!");
return;
}
//弹出需求反馈窗口
new wTellUS().ShowDialog();
}
//事件:需求受理
private void LbUserAck_Click(object sender, EventArgs e)
{
//权限校验
if (LoginInfo.UserName.Equals(APP.TEMP_USER_NAME) && string.IsNullOrEmpty(LoginInfo.Password))
{
this.ShowMessage("使用此功能,需要先登录!");
return;
}
if (!LoginInfo.IsPrincipal)
{
this.ShowMessage("只有部门负责人才能使用该权限!");
return;
}
//弹出需求受理窗口
new wTellUSAck().ShowDialog();
}
//事件:平台简介
private void LbBrief_Click(object sender, EventArgs e)
{
new wToolsPrief().ShowDialog();
}
#endregion
#region 本地:菜单渲染
/// <summary>
/// 菜单渲染
/// </summary>
private void RenderMenu()
{
//listView初始化
this.listView1.Items.Clear(); //清空原有项
this.listView1.MultiSelect = false;
this.listView1.ShowItemToolTips = true;
this.listView1.View = View.SmallIcon;
this.listView1.DoubleClick += ListView1_DoubleClick;
//装载目录
var limitGroups = new List<string>();
//通用工具,只有公司内部人员可以使用
if (!LoginInfo.IsStaff)
limitGroups.Add(this.Extend.Tools_uni);
//行政工具,只有部门负责人可以使用
if (!LoginInfo.IsPrincipal)
limitGroups.Add(this.Extend.Tools_human);
var mList = this.Extend.GetModuleList().Where(m => limitGroups.IndexOf(m.GroupName) < 0);
foreach (ModuleInfo m in mList)
{
//获取分组
var grp = this.listView1.Groups[m.GroupName];
if (grp == null)
{
grp = new ListViewGroup() {
Name = m.GroupName,
Header = m.GroupName,
};
this.listView1.Groups.Add(grp);
}
//添加项
this.listView1.Items.Add(new ListViewItem()
{
Text = m.Text,
ToolTipText = m.ToolTipText,
ImageIndex = m.ImageIndex % this.imageList1.Images.Count,
Group = grp,
Tag = m.ViewFormType,
});
}
}
/// <summary>
/// 事件:双击打开模块
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView1_DoubleClick(object sender, EventArgs e)
{
if (this.listView1.Items.Count == 0)
return;
//打开窗口
try
{
var item = this.listView1.SelectedItems[0];
this.OpenModule(item);
}
catch (Exception ex)
{
var msg = $"错误消息:{ex.Message}\r\n堆栈信息:{ex.StackTrace}";
MyUtils.Utils.Tip(msg, 1200, 800);
}
}
#endregion
#region 自定义方法
/// <summary>
/// 消息提示
/// </summary>
/// <param name="msg"></param>
private void ShowMessage(string msg)
{
MessageBox.Show(msg, "提示");
}
/// <summary>
/// 刷新Tab:修复窗体尺寸与Tab页尺寸不一致问题
/// </summary>
private void RefreshTab()
{
if (this.tabControl1.SelectedTab == null || this.tabControl1.SelectedTab.Controls.Count == 0)
return;
var frm = this.tabControl1.SelectedTab.Controls[0] as Form;
if (frm != null && frm.Size != this.tabControl1.SelectedTab.Size)
{
this.tabControl1.SelectedTab.Controls.Clear();
this.tabControl1.SelectedTab.Controls.Add(frm);
}
}
#endregion
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment