diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..80327f6
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,201 @@
+name: Build
+
+on:
+ push:
+ tags:
+ - 'v*'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ build-windows:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23'
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Get version
+ id: version
+ shell: bash
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
+ else
+ echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Install Wails
+ run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
+
+ - name: Build
+ run: |
+ wails build `
+ -platform windows/amd64 `
+ -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" `
+ -o webhelper-windows-amd64.exe
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: webhelper-windows-amd64
+ path: build/bin/webhelper-windows-amd64.exe
+
+ build-linux:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23'
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Get version
+ id: version
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
+ else
+ echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Install Linux dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libgtk-3-dev \
+ libwebkit2gtk-4.0-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev \
+ patchelf
+
+ - name: Install Wails
+ run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
+
+ - name: Build amd64
+ env:
+ CGO_ENABLED: 1
+ GOOS: linux
+ GOARCH: amd64
+ run: |
+ wails build \
+ -platform linux/amd64 \
+ -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \
+ -o webhelper-linux-amd64
+
+ - name: Build arm64
+ env:
+ CGO_ENABLED: 0
+ GOOS: linux
+ GOARCH: arm64
+ run: |
+ wails build \
+ -platform linux/arm64 \
+ -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \
+ -o webhelper-linux-arm64
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ path: |
+ build/bin/webhelper-linux-amd64
+ build/bin/webhelper-linux-arm64
+
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23'
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Get version
+ id: version
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
+ else
+ echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Install Wails
+ run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
+
+ - name: Build amd64
+ env:
+ CGO_ENABLED: 1
+ GOOS: darwin
+ GOARCH: amd64
+ run: |
+ wails build \
+ -platform darwin/amd64 \
+ -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \
+ -o webhelper-darwin-amd64
+
+ - name: Build arm64
+ env:
+ CGO_ENABLED: 1
+ GOOS: darwin
+ GOARCH: arm64
+ run: |
+ wails build \
+ -platform darwin/arm64 \
+ -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \
+ -o webhelper-darwin-arm64
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ path: |
+ build/bin/webhelper-darwin-amd64
+ build/bin/webhelper-darwin-arm64
+
+ release:
+ needs: [build-windows, build-linux, build-macos]
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+ merge-multiple: true
+
+ - name: Create release
+ uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
+ files: artifacts/*
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5e04fcd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,63 @@
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Code coverage profiles and other test artifacts
+*.out
+coverage.*
+*.coverprofile
+profile.cov
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+go.work.sum
+
+# env file
+.env
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo
+
+.workbuddy/
+.codebuddy/
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e7e666d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,101 @@
+# WebHelper
+
+基于 Wails v2 + Vue 3 + Element Plus 的网页调试助手桌面应用。
+
+## 功能
+
+- **网页调试** — 发送 HTTP 请求,支持所有方法、请求头、Cookie、代理、文件上传、请求日志
+- **JS 调试** — 内置 JavaScript 执行引擎,支持 console.log 捕获和返回值
+- **JSON 解析** — 树形/表格/原始三种视图,支持从网页调试一键发送
+- **Socket 调试** — WebSocket/TCP 连接、收发消息、实时日志
+- **多语言** — 内置简体中文、English、日本語、한국어,运行时切换
+
+## 技术栈
+
+| 层级 | 技术 |
+|------|------|
+| 后端 | Go + Wails v2 + goja (JS引擎) + gorilla/websocket |
+| 前端 | Vue 3 + Element Plus + Vite |
+| 国际化 | 自定义 i18n,Go 后端加载,`go:embed` 打包 |
+
+## 环境要求
+
+- [Go](https://go.dev/dl/) >= 1.23
+- [Node.js](https://nodejs.org/) >= 18
+- [Wails CLI](https://wails.io/docs/gettingstarted/installation) v2
+
+```bash
+go install github.com/wailsapp/wails/v2/cmd/wails@latest
+```
+
+## 运行
+
+```bash
+# 安装依赖
+cd frontend && npm install && cd ..
+
+# 开发模式
+wails dev -tags debug
+
+# 构建生产版本
+wails build
+```
+
+## 构建指定版本
+
+```bash
+wails build -ldflags "-X webhelper/global.Version=v1.0.0"
+```
+
+## 项目结构
+
+```
+├── api/ # API 层 (HTTP/JS/Socket/Log)
+├── global/ # 全局配置、日志、语言包、版本号
+├── lang/ # 语言包 (go:embed 打包)
+│ ├── default/ # 简体中文
+│ ├── en/ # English
+│ ├── ja/ # 日本語
+│ └── ko/ # 한국어
+├── logger/ # 日志系统
+├── service/ # Wails 绑定方法
+├── frontend/ # Vue 3 前端
+│ └── src/components/ # 5 个功能页面
+├── .github/workflows/ # CI/CD 自动构建
+├── main.go # 程序入口
+└── wails.json
+```
+
+## 添加语言
+
+在 `lang/` 下新建目录,包含 `info.json` 和 `textmap.json`:
+
+```json
+// lang/ja/info.json
+{
+ "language_name": "日本語",
+ "language_code": "ja-JP",
+ "textmap_path": "textmap.json",
+ "translation_progress": "100%",
+ "translator": "",
+ "last_updated": "",
+ "version": "1.0.0"
+}
+```
+
+重启后自动出现在语言切换列表中。
+
+## CI/CD
+
+推送 `v*` 标签自动构建多平台版本并发布 Release:
+
+```bash
+git tag v1.0.0
+git push origin v1.0.0
+```
+
+构建目标:Windows (amd64)、Linux (amd64/arm64)、macOS (amd64/arm64)
+
+## License
+
+GPL-3.0
diff --git a/api/http.go b/api/http.go
new file mode 100644
index 0000000..ee8ca6c
--- /dev/null
+++ b/api/http.go
@@ -0,0 +1,495 @@
+package api
+
+import (
+ "bytes"
+ "crypto/tls"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/http/cookiejar"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+)
+
+// HttpRequest 请求结构体
+type HttpRequest struct {
+ Method string `json:"method"` // HTTP 方法
+ URL string `json:"url"` // 请求地址
+ Headers map[string]string `json:"headers"` // 请求头
+ Cookies map[string]string `json:"cookies"` // Cookie
+ Proxy string `json:"proxy"` // 代理地址
+ BodyType string `json:"bodyType"` // 数据类型: text, file, form, json
+ Body string `json:"body"` // 文本数据
+ FilePath string `json:"filePath"` // 文件路径
+ ContentType string `json:"contentType"` // Content-Type
+ Timeout int `json:"timeout"` // 超时时间(秒)
+ Insecure bool `json:"insecure"` // 忽略证书验证
+}
+
+// HttpResponse 响应结构体
+type HttpResponse struct {
+ StatusCode int `json:"statusCode"` // 状态码
+ Status string `json:"status"` // 状态描述
+ Headers map[string]string `json:"headers"` // 响应头
+ Cookies []CookieInfo `json:"cookies"` // 响应 Cookie
+ Body string `json:"body"` // 响应正文
+ ContentType string `json:"contentType"` // Content-Type
+ ContentLength int64 `json:"contentLength"` // 内容长度
+ Time string `json:"time"` // 请求耗时
+ Size int64 `json:"size"` // 响应大小
+ Error string `json:"error"` // 错误信息
+}
+
+// CookieInfo Cookie 信息
+type CookieInfo struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+ Path string `json:"path"`
+ Domain string `json:"domain"`
+ Expires string `json:"expires"`
+ MaxAge int `json:"maxAge"`
+ Secure bool `json:"secure"`
+ HttpOnly bool `json:"httpOnly"`
+ SameSite string `json:"sameSite"`
+}
+
+// FileData 文件数据
+type FileData struct {
+ FieldName string `json:"fieldName"` // 字段名
+ FileName string `json:"fileName"` // 文件名
+ ContentType string `json:"contentType"` // 文件类型
+ Path string `json:"path"` // 文件路径
+}
+
+// SendRequest 发送 HTTP 请求
+func SendRequest(req *HttpRequest) *HttpResponse {
+ resp := &HttpResponse{
+ Headers: make(map[string]string),
+ }
+
+ if req.URL == "" {
+ resp.Error = "请求地址不能为空"
+ return resp
+ }
+
+ // 补全协议
+ if !strings.HasPrefix(req.URL, "http://") && !strings.HasPrefix(req.URL, "https://") {
+ req.URL = "https://" + req.URL
+ }
+
+ // 设置超时
+ timeout := time.Duration(req.Timeout) * time.Second
+ if timeout == 0 {
+ timeout = 30 * time.Second
+ }
+
+ // 创建 HTTP 客户端
+ client := &http.Client{
+ Timeout: timeout,
+ }
+
+ // 配置代理
+ if req.Proxy != "" {
+ proxyURL, err := url.Parse(req.Proxy)
+ if err != nil {
+ resp.Error = fmt.Sprintf("代理地址格式错误: %v", err)
+ return resp
+ }
+ client.Transport = &http.Transport{
+ Proxy: http.ProxyURL(proxyURL),
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: req.Insecure,
+ },
+ }
+ } else if req.Insecure {
+ client.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+ }
+
+ // 创建请求体
+ var bodyReader io.Reader
+ contentType := req.ContentType
+
+ if req.Method != http.MethodGet && req.Method != http.MethodHead {
+ switch req.BodyType {
+ case "file":
+ // 文件上传
+ if req.FilePath != "" {
+ file, err := os.Open(req.FilePath)
+ if err != nil {
+ resp.Error = fmt.Sprintf("打开文件失败: %v", err)
+ return resp
+ }
+ defer file.Close()
+
+ // 获取文件信息
+ fileInfo, err := file.Stat()
+ if err != nil {
+ resp.Error = fmt.Sprintf("获取文件信息失败: %v", err)
+ return resp
+ }
+
+ // 使用 multipart 上传
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+ part, err := writer.CreateFormFile("file", fileInfo.Name())
+ if err != nil {
+ resp.Error = fmt.Sprintf("创建表单文件失败: %v", err)
+ return resp
+ }
+ if _, err := io.Copy(part, file); err != nil {
+ resp.Error = fmt.Sprintf("复制文件内容失败: %v", err)
+ return resp
+ }
+ writer.Close()
+ bodyReader = body
+ contentType = writer.FormDataContentType()
+ }
+
+ case "form":
+ // 表单数据
+ if req.Body != "" {
+ bodyReader = strings.NewReader(req.Body)
+ if contentType == "" {
+ contentType = "application/x-www-form-urlencoded"
+ }
+ }
+
+ case "json":
+ // JSON 数据
+ if req.Body != "" {
+ bodyReader = strings.NewReader(req.Body)
+ if contentType == "" {
+ contentType = "application/json"
+ }
+ }
+
+ default:
+ // 文本数据
+ if req.Body != "" {
+ bodyReader = strings.NewReader(req.Body)
+ if contentType == "" {
+ contentType = "text/plain"
+ }
+ }
+ }
+ }
+
+ // 创建请求
+ httpReq, err := http.NewRequest(req.Method, req.URL, bodyReader)
+ if err != nil {
+ resp.Error = fmt.Sprintf("创建请求失败: %v", err)
+ return resp
+ }
+
+ // 设置 Content-Type
+ if contentType != "" {
+ httpReq.Header.Set("Content-Type", contentType)
+ }
+
+ // 设置自定义请求头(需要单独处理 host)
+ for key, value := range req.Headers {
+ if strings.ToLower(key) == "host" {
+ httpReq.Host = value
+ } else {
+ httpReq.Header.Set(key, value)
+ }
+ }
+
+ // 设置 Cookie
+ if len(req.Cookies) > 0 {
+ cookieJar, _ := cookiejar.New(nil)
+ var cookieList []*http.Cookie
+ for name, value := range req.Cookies {
+ cookieList = append(cookieList, &http.Cookie{
+ Name: name,
+ Value: value,
+ })
+ }
+ parsedURL, _ := url.Parse(req.URL)
+ cookieJar.SetCookies(parsedURL, cookieList)
+ client.Jar = cookieJar
+ }
+
+ // 记录开始时间
+ start := time.Now()
+
+ // 发送请求
+ httpResp, err := client.Do(httpReq)
+ if err != nil {
+ resp.Error = fmt.Sprintf("请求失败: %v", err)
+ return resp
+ }
+ defer httpResp.Body.Close()
+
+ // 记录耗时
+ resp.Time = time.Since(start).String()
+
+ // 读取响应体
+ body, err := io.ReadAll(httpResp.Body)
+ if err != nil {
+ resp.Error = fmt.Sprintf("读取响应失败: %v", err)
+ return resp
+ }
+
+ // 设置响应
+ resp.StatusCode = httpResp.StatusCode
+ resp.Status = httpResp.Status
+ resp.Body = string(body)
+ resp.ContentLength = httpResp.ContentLength
+ resp.Size = int64(len(body))
+
+ // 获取 Content-Type
+ resp.ContentType = httpResp.Header.Get("Content-Type")
+
+ // 获取响应头
+ for key, values := range httpResp.Header {
+ resp.Headers[key] = strings.Join(values, "; ")
+ }
+
+ // 获取响应 Cookie
+ resp.Cookies = convertCookies(httpResp.Cookies())
+
+ return resp
+}
+
+// SendRequestWithFiles 带文件的请求
+func SendRequestWithFiles(req *HttpRequest, files []FileData) *HttpResponse {
+ resp := &HttpResponse{
+ Headers: make(map[string]string),
+ }
+
+ if req.URL == "" {
+ resp.Error = "请求地址不能为空"
+ return resp
+ }
+
+ // 补全协议
+ if !strings.HasPrefix(req.URL, "http://") && !strings.HasPrefix(req.URL, "https://") {
+ req.URL = "https://" + req.URL
+ }
+
+ // 设置超时
+ timeout := time.Duration(req.Timeout) * time.Second
+ if timeout == 0 {
+ timeout = 30 * time.Second
+ }
+
+ // 创建 HTTP 客户端
+ client := &http.Client{
+ Timeout: timeout,
+ }
+
+ // 配置代理
+ if req.Proxy != "" {
+ proxyURL, err := url.Parse(req.Proxy)
+ if err != nil {
+ resp.Error = fmt.Sprintf("代理地址格式错误: %v", err)
+ return resp
+ }
+ client.Transport = &http.Transport{
+ Proxy: http.ProxyURL(proxyURL),
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: req.Insecure,
+ },
+ }
+ } else if req.Insecure {
+ client.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+ }
+
+ // 创建 multipart 请求
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ // 添加文本字段
+ if req.Body != "" {
+ _ = writer.WriteField("text", req.Body)
+ }
+
+ // 添加文件
+ for _, file := range files {
+ f, err := os.Open(file.Path)
+ if err != nil {
+ resp.Error = fmt.Sprintf("打开文件 %s 失败: %v", file.Path, err)
+ return resp
+ }
+ defer f.Close()
+
+ part, err := writer.CreateFormFile(file.FieldName, file.FileName)
+ if err != nil {
+ resp.Error = fmt.Sprintf("创建文件字段失败: %v", err)
+ return resp
+ }
+
+ if _, err := io.Copy(part, f); err != nil {
+ resp.Error = fmt.Sprintf("复制文件内容失败: %v", err)
+ return resp
+ }
+ }
+
+ writer.Close()
+
+ // 创建请求
+ httpReq, err := http.NewRequest(req.Method, req.URL, body)
+ if err != nil {
+ resp.Error = fmt.Sprintf("创建请求失败: %v", err)
+ return resp
+ }
+
+ // 设置 Content-Type
+ httpReq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ // 设置自定义请求头(需要单独处理 host)
+ for key, value := range req.Headers {
+ if strings.ToLower(key) == "host" {
+ httpReq.Host = value
+ } else {
+ httpReq.Header.Set(key, value)
+ }
+ }
+
+ // 设置 Cookie
+ if len(req.Cookies) > 0 {
+ cookieJar, _ := cookiejar.New(nil)
+ var cookieList []*http.Cookie
+ for name, value := range req.Cookies {
+ cookieList = append(cookieList, &http.Cookie{
+ Name: name,
+ Value: value,
+ })
+ }
+ parsedURL, _ := url.Parse(req.URL)
+ cookieJar.SetCookies(parsedURL, cookieList)
+ client.Jar = cookieJar
+ }
+
+ // 记录开始时间
+ start := time.Now()
+
+ // 发送请求
+ httpResp, err := client.Do(httpReq)
+ if err != nil {
+ resp.Error = fmt.Sprintf("请求失败: %v", err)
+ return resp
+ }
+ defer httpResp.Body.Close()
+
+ // 记录耗时
+ resp.Time = time.Since(start).String()
+
+ // 读取响应体
+ respBody, err := io.ReadAll(httpResp.Body)
+ if err != nil {
+ resp.Error = fmt.Sprintf("读取响应失败: %v", err)
+ return resp
+ }
+
+ // 设置响应
+ resp.StatusCode = httpResp.StatusCode
+ resp.Status = httpResp.Status
+ resp.Body = string(respBody)
+ resp.ContentLength = httpResp.ContentLength
+ resp.Size = int64(len(respBody))
+
+ // 获取 Content-Type
+ resp.ContentType = httpResp.Header.Get("Content-Type")
+
+ // 获取响应头
+ for key, values := range httpResp.Header {
+ resp.Headers[key] = strings.Join(values, "; ")
+ }
+
+ // 获取响应 Cookie
+ resp.Cookies = convertCookies(httpResp.Cookies())
+
+ return resp
+}
+
+// ParseHeaders 解析请求头字符串
+func ParseHeaders(headerStr string) map[string]string {
+ headers := make(map[string]string)
+ if headerStr == "" {
+ return headers
+ }
+
+ lines := strings.Split(headerStr, "\n")
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+
+ parts := strings.SplitN(line, ":", 2)
+ if len(parts) == 2 {
+ key := strings.TrimSpace(parts[0])
+ value := strings.TrimSpace(parts[1])
+ headers[key] = value
+ }
+ }
+
+ return headers
+}
+
+// ParseCookies 解析 Cookie 字符串
+func ParseCookies(cookieStr string) map[string]string {
+ cookies := make(map[string]string)
+ if cookieStr == "" {
+ return cookies
+ }
+
+ pairs := strings.Split(cookieStr, ";")
+ for _, pair := range pairs {
+ pair = strings.TrimSpace(pair)
+ if pair == "" {
+ continue
+ }
+
+ parts := strings.SplitN(pair, "=", 2)
+ if len(parts) == 2 {
+ name := strings.TrimSpace(parts[0])
+ value := strings.TrimSpace(parts[1])
+ cookies[name] = value
+ }
+ }
+
+ return cookies
+}
+
+// convertCookies 将 http.Cookie 转换为 CookieInfo
+func convertCookies(httpCookies []*http.Cookie) []CookieInfo {
+ var cookies []CookieInfo
+ for _, c := range httpCookies {
+ sameSite := ""
+ switch c.SameSite {
+ case http.SameSiteStrictMode:
+ sameSite = "Strict"
+ case http.SameSiteLaxMode:
+ sameSite = "Lax"
+ case http.SameSiteNoneMode:
+ sameSite = "None"
+ }
+
+ cookies = append(cookies, CookieInfo{
+ Name: c.Name,
+ Value: c.Value,
+ Path: c.Path,
+ Domain: c.Domain,
+ Expires: c.Expires.Format(time.RFC1123),
+ MaxAge: c.MaxAge,
+ Secure: c.Secure,
+ HttpOnly: c.HttpOnly,
+ SameSite: sameSite,
+ })
+ }
+ return cookies
+}
diff --git a/api/js.go b/api/js.go
new file mode 100644
index 0000000..b91834a
--- /dev/null
+++ b/api/js.go
@@ -0,0 +1,176 @@
+package api
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/dop251/goja"
+)
+
+// JsExecResult JS 执行结果
+type JsExecResult struct {
+ Output string `json:"output"` // 控制台输出
+ Result string `json:"result"` // 返回值
+ Error string `json:"error"` // 错误信息
+ Duration string `json:"duration"` // 执行耗时
+ Success bool `json:"success"` // 是否成功
+}
+
+// consoleLog 存储 console.log 输出
+type consoleLogger struct {
+ logs []string
+}
+
+func (c *consoleLogger) log(args ...interface{}) {
+ c.logs = append(c.logs, fmt.Sprint(args...))
+}
+
+func (c *consoleLogger) logFn(call goja.FunctionCall) goja.Value {
+ var args []interface{}
+ for _, arg := range call.Arguments {
+ args = append(args, arg.Export())
+ }
+ c.logs = append(c.logs, fmt.Sprint(args...))
+ return goja.Undefined()
+}
+
+// ExecuteJs 执行 JavaScript 代码
+func ExecuteJs(code string) *JsExecResult {
+ result := &JsExecResult{
+ Success: false,
+ }
+
+ if strings.TrimSpace(code) == "" {
+ result.Error = "代码不能为空"
+ return result
+ }
+
+ start := time.Now()
+
+ // 创建 JS 虚拟机
+ vm := goja.New()
+
+ // 创建日志记录器
+ logger := &consoleLogger{
+ logs: make([]string, 0),
+ }
+
+ // 注入 console 对象
+ consoleObj := vm.NewObject()
+ consoleObj.Set("log", logger.logFn)
+ consoleObj.Set("info", logger.logFn)
+ consoleObj.Set("warn", logger.logFn)
+ consoleObj.Set("error", logger.logFn)
+ vm.Set("console", consoleObj)
+
+ // 注入常用全局函数
+ vm.Set("parseInt", parseInt)
+ vm.Set("parseFloat", parseFloat)
+ vm.Set("isNaN", isNaN)
+ vm.Set("isFinite", isFinite)
+ vm.Set("String", vm.Get("String"))
+ vm.Set("Number", vm.Get("Number"))
+ vm.Set("Boolean", vm.Get("Boolean"))
+ vm.Set("Array", vm.Get("Array"))
+ vm.Set("Object", vm.Get("Object"))
+ vm.Set("JSON", vm.Get("JSON"))
+ vm.Set("Math", vm.Get("Math"))
+ vm.Set("Date", vm.Get("Date"))
+ vm.Set("RegExp", vm.Get("RegExp"))
+
+ // 执行代码
+ val, err := vm.RunString(code)
+ if err != nil {
+ result.Error = err.Error()
+ result.Duration = time.Since(start).String()
+ result.Output = strings.Join(logger.logs, "\n")
+ return result
+ }
+
+ // 获取返回值
+ resultVal := ""
+ if val != nil && !goja.IsUndefined(val) && !goja.IsNull(val) {
+ resultVal = fmt.Sprintf("%v", val.Export())
+ }
+
+ result.Output = strings.Join(logger.logs, "\n")
+ result.Result = resultVal
+ result.Duration = time.Since(start).String()
+ result.Success = true
+
+ return result
+}
+
+// parseInt 解析整数
+func parseInt(call goja.FunctionCall) goja.Value {
+ if len(call.Arguments) < 1 {
+ return goja.NaN()
+ }
+
+ arg := call.Arguments[0].String()
+ var radix int64 = 10
+ if len(call.Arguments) > 1 {
+ radix = call.Arguments[1].ToInteger()
+ }
+
+ var result int64
+ var err error
+
+ switch radix {
+ case 16:
+ _, err = fmt.Sscanf(arg, "%x", &result)
+ case 8:
+ _, err = fmt.Sscanf(arg, "%o", &result)
+ default:
+ _, err = fmt.Sscanf(arg, "%d", &result)
+ }
+
+ if err != nil {
+ return goja.NaN()
+ }
+
+ return goja.New().ToValue(result)
+}
+
+// parseFloat 解析浮点数
+func parseFloat(call goja.FunctionCall) goja.Value {
+ if len(call.Arguments) < 1 {
+ return goja.NaN()
+ }
+
+ arg := call.Arguments[0].String()
+ var result float64
+ _, err := fmt.Sscanf(arg, "%f", &result)
+ if err != nil {
+ return goja.NaN()
+ }
+
+ return goja.New().ToValue(result)
+}
+
+// isNaN 检查是否为 NaN
+func isNaN(call goja.FunctionCall) goja.Value {
+ if len(call.Arguments) < 1 {
+ return goja.New().ToValue(true)
+ }
+
+ val := call.Arguments[0].ToNumber()
+ if goja.IsNaN(val) {
+ return goja.New().ToValue(true)
+ }
+ return goja.New().ToValue(false)
+}
+
+// isFinite 检查是否为有限数
+func isFinite(call goja.FunctionCall) goja.Value {
+ if len(call.Arguments) < 1 {
+ return goja.New().ToValue(false)
+ }
+
+ val := call.Arguments[0].ToNumber()
+ if goja.IsNaN(val) || goja.IsInfinity(val) {
+ return goja.New().ToValue(false)
+ }
+ return goja.New().ToValue(true)
+}
diff --git a/api/lang.go b/api/lang.go
new file mode 100644
index 0000000..143ce77
--- /dev/null
+++ b/api/lang.go
@@ -0,0 +1,13 @@
+package api
+
+import (
+ "webhelper/global"
+)
+
+func GetLang() (*global.LanguagePack, error) {
+ return global.GetLangPack()
+}
+
+func GetALLLang() []global.LanguageInfo {
+ return global.GetLangInfoList()
+}
\ No newline at end of file
diff --git a/api/log.go b/api/log.go
new file mode 100644
index 0000000..04927c9
--- /dev/null
+++ b/api/log.go
@@ -0,0 +1,233 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+)
+
+// RequestLog 请求日志
+type RequestLog struct {
+ ID int `json:"id"` // 序号
+ Method string `json:"method"` // 请求方法
+ URL string `json:"url"` // 请求地址
+ Host string `json:"host"` // 主机地址
+ Path string `json:"path"` // 请求路径
+ Headers map[string]string `json:"headers"` // 请求头
+ Cookies map[string]string `json:"cookies"` // Cookie
+ BodyType string `json:"bodyType"` // 数据类型
+ Body string `json:"body"` // 请求体
+ StatusCode int `json:"statusCode"` // 状态码
+ Status string `json:"status"` // 状态描述
+ RespHeaders map[string]string `json:"respHeaders"` // 响应头
+ RespBody string `json:"respBody"` // 响应正文
+ RespSize int64 `json:"respSize"` // 响应大小
+ Time string `json:"time"` // 请求耗时
+ CreatedAt string `json:"createdAt"` // 创建时间
+}
+
+// LogManager 日志管理器
+type LogManager struct {
+ logDir string
+ logs []*RequestLog
+ maxLogs int
+ fileName string
+}
+
+var logManager *LogManager
+
+// InitLogManager 初始化日志管理器
+func InitLogManager(logDir string) {
+ logManager = &LogManager{
+ logDir: logDir,
+ logs: make([]*RequestLog, 0),
+ maxLogs: 1000,
+ fileName: "http_requests.json",
+ }
+ logManager.loadLogs()
+}
+
+// GetLogManager 获取日志管理器
+func GetLogManager() *LogManager {
+ if logManager == nil {
+ InitLogManager("logs")
+ }
+ return logManager
+}
+
+// loadLogs 从文件加载日志
+func (m *LogManager) loadLogs() {
+ filePath := filepath.Join(m.logDir, m.fileName)
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ m.logs = make([]*RequestLog, 0)
+ return
+ }
+ return
+ }
+
+ var logs []*RequestLog
+ if err := json.Unmarshal(data, &logs); err != nil {
+ m.logs = make([]*RequestLog, 0)
+ return
+ }
+
+ m.logs = logs
+}
+
+// saveLogs 保存日志到文件
+func (m *LogManager) saveLogs() error {
+ if err := os.MkdirAll(m.logDir, 0755); err != nil {
+ return err
+ }
+
+ filePath := filepath.Join(m.logDir, m.fileName)
+ data, err := json.MarshalIndent(m.logs, "", " ")
+ if err != nil {
+ return err
+ }
+
+ return os.WriteFile(filePath, data, 0644)
+}
+
+// AddLog 添加日志
+func (m *LogManager) AddLog(log *RequestLog) error {
+ // 生成序号
+ if len(m.logs) > 0 {
+ log.ID = m.logs[0].ID + 1
+ } else {
+ log.ID = 1
+ }
+
+ log.CreatedAt = time.Now().Format(time.DateTime)
+
+ // 插入到开头(最新的在前面)
+ m.logs = append([]*RequestLog{log}, m.logs...)
+
+ // 限制最大日志数量
+ if len(m.logs) > m.maxLogs {
+ m.logs = m.logs[:m.maxLogs]
+ }
+
+ return m.saveLogs()
+}
+
+// GetLogs 获取所有日志
+func (m *LogManager) GetLogs() []*RequestLog {
+ return m.logs
+}
+
+// GetLog 获取单条日志
+func (m *LogManager) GetLog(id int) *RequestLog {
+ for _, log := range m.logs {
+ if log.ID == id {
+ return log
+ }
+ }
+ return nil
+}
+
+// DeleteLog 删除日志
+func (m *LogManager) DeleteLog(id int) error {
+ for i, log := range m.logs {
+ if log.ID == id {
+ m.logs = append(m.logs[:i], m.logs[i+1:]...)
+ return m.saveLogs()
+ }
+ }
+ return nil
+}
+
+// ClearLogs 清空日志
+func (m *LogManager) ClearLogs() error {
+ m.logs = make([]*RequestLog, 0)
+ return m.saveLogs()
+}
+
+// SearchLogs 搜索日志
+func (m *LogManager) SearchLogs(keyword string) []*RequestLog {
+ var result []*RequestLog
+ keyword = strings.ToLower(keyword)
+
+ for _, log := range m.logs {
+ if strings.Contains(strings.ToLower(log.URL), keyword) ||
+ strings.Contains(strings.ToLower(log.Method), keyword) ||
+ strings.Contains(strings.ToLower(log.Host), keyword) ||
+ strings.Contains(strings.ToLower(log.Path), keyword) {
+ result = append(result, log)
+ }
+ }
+
+ return result
+}
+
+// CreateLogFromResponse 从响应创建日志
+func CreateLogFromResponse(req *HttpRequest, resp *HttpResponse) *RequestLog {
+ // 解析 URL
+ host := ""
+ path := req.URL
+ if strings.HasPrefix(req.URL, "http://") {
+ parts := strings.SplitN(strings.TrimPrefix(req.URL, "http://"), "/", 2)
+ host = parts[0]
+ if len(parts) > 1 {
+ path = "/" + parts[1]
+ }
+ } else if strings.HasPrefix(req.URL, "https://") {
+ parts := strings.SplitN(strings.TrimPrefix(req.URL, "https://"), "/", 2)
+ host = parts[0]
+ if len(parts) > 1 {
+ path = "/" + parts[1]
+ }
+ }
+
+ return &RequestLog{
+ Method: req.Method,
+ URL: req.URL,
+ Host: host,
+ Path: path,
+ Headers: req.Headers,
+ Cookies: req.Cookies,
+ BodyType: req.BodyType,
+ Body: req.Body,
+ StatusCode: resp.StatusCode,
+ Status: resp.Status,
+ RespHeaders: resp.Headers,
+ RespBody: resp.Body,
+ RespSize: resp.Size,
+ Time: resp.Time,
+ }
+}
+
+// FormatFileSize 格式化文件大小
+func FormatFileSize(size int64) string {
+ if size < 1024 {
+ return fmt.Sprintf("%d B", size)
+ } else if size < 1024*1024 {
+ return fmt.Sprintf("%.2f KB", float64(size)/1024)
+ } else if size < 1024*1024*1024 {
+ return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
+ }
+ return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024))
+}
+
+// SortLogsByTime 按时间排序日志
+func SortLogsByTime(logs []*RequestLog, desc bool) []*RequestLog {
+ sorted := make([]*RequestLog, len(logs))
+ copy(sorted, logs)
+
+ sort.Slice(sorted, func(i, j int) bool {
+ timeI, _ := time.Parse(time.DateTime, sorted[i].CreatedAt)
+ timeJ, _ := time.Parse(time.DateTime, sorted[j].CreatedAt)
+ if desc {
+ return timeI.After(timeJ)
+ }
+ return timeI.Before(timeJ)
+ })
+
+ return sorted
+}
diff --git a/api/logger.go b/api/logger.go
new file mode 100644
index 0000000..97acdaf
--- /dev/null
+++ b/api/logger.go
@@ -0,0 +1,21 @@
+package api
+
+import (
+ "webhelper/global"
+)
+
+func LogInfo(msg string) {
+ global.Log.Info(msg)
+}
+
+func LogWarn(msg string) {
+ global.Log.Warn(msg)
+}
+
+func LogError(msg string) {
+ global.Log.Error(msg)
+}
+
+func LogDebug(msg string) {
+ global.Log.Debug(msg)
+}
\ No newline at end of file
diff --git a/api/socket.go b/api/socket.go
new file mode 100644
index 0000000..6d5c991
--- /dev/null
+++ b/api/socket.go
@@ -0,0 +1,333 @@
+package api
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "sync"
+ "time"
+
+ "webhelper/global"
+
+ "github.com/gorilla/websocket"
+)
+
+// WebSocketClient WebSocket 客户端
+type WebSocketClient struct {
+ conn *websocket.Conn
+ isConnected bool
+ mu sync.RWMutex
+ onMessage func(message string)
+ onError func(err error)
+ onClose func()
+}
+
+// WebSocketConfig WebSocket 配置
+type WebSocketConfig struct {
+ URL string `json:"url"`
+ Headers map[string]string `json:"headers"`
+ Insecure bool `json:"insecure"`
+}
+
+// WebSocketMessage WebSocket 消息
+type WebSocketMessage struct {
+ Type string `json:"type"` // text, binary, ping, pong, error, system
+ Content string `json:"content"`
+ Time string `json:"time"`
+}
+
+var wsClient *WebSocketClient
+
+// NewWebSocketClient 创建 WebSocket 客户端
+func NewWebSocketClient() *WebSocketClient {
+ return &WebSocketClient{
+ isConnected: false,
+ }
+}
+
+// Connect 连接 WebSocket
+func (c *WebSocketClient) Connect(config *WebSocketConfig, onMessage func(string), onError func(error), onClose func()) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.isConnected {
+ return fmt.Errorf("已连接")
+ }
+
+ // 解析 URL
+ u, err := url.Parse(config.URL)
+ if err != nil {
+ return fmt.Errorf("URL 格式错误: %v", err)
+ }
+
+ // 创建 HTTP Header(只添加用户自定义头)
+ header := http.Header{}
+ for key, value := range config.Headers {
+ header.Set(key, value)
+ }
+
+ // 创建 Dialer(最简配置)
+ dialer := &websocket.Dialer{
+ HandshakeTimeout: 10 * time.Second,
+ }
+
+ // 连接
+ global.Log.Infof("正在连接 WebSocket: %s", u.String())
+ conn, _, err := dialer.Dial(u.String(), header)
+ if err != nil {
+ return fmt.Errorf("连接失败: %v", err)
+ }
+
+ c.conn = conn
+ c.isConnected = true
+ c.onMessage = onMessage
+ c.onError = onError
+ c.onClose = onClose
+
+ global.Log.Infof("WebSocket 连接成功: %s", u.String())
+
+ // 启动消息监听
+ go c.readMessages()
+
+ return nil
+}
+
+// readMessages 读取消息
+func (c *WebSocketClient) readMessages() {
+ defer func() {
+ c.mu.Lock()
+ c.isConnected = false
+ c.mu.Unlock()
+ if c.onClose != nil {
+ c.onClose()
+ }
+ }()
+
+ for {
+ _, message, err := c.conn.ReadMessage()
+ if err != nil {
+ if c.onError != nil {
+ c.onError(err)
+ }
+ return
+ }
+ if c.onMessage != nil {
+ c.onMessage(string(message))
+ }
+ }
+}
+
+// Send 发送消息
+func (c *WebSocketClient) Send(message string) error {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ if !c.isConnected || c.conn == nil {
+ return fmt.Errorf("未连接")
+ }
+
+ return c.conn.WriteMessage(websocket.TextMessage, []byte(message))
+}
+
+// Close 关闭连接
+func (c *WebSocketClient) Close() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.conn != nil {
+ err := c.conn.Close()
+ c.isConnected = false
+ return err
+ }
+ return nil
+}
+
+// IsConnected 是否已连接
+func (c *WebSocketClient) IsConnected() bool {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.isConnected
+}
+
+// GetWebSocketClient 获取 WebSocket 客户端
+func GetWebSocketClient() *WebSocketClient {
+ if wsClient == nil {
+ wsClient = NewWebSocketClient()
+ }
+ return wsClient
+}
+
+// ConnectWebSocket 连接 WebSocket
+func ConnectWebSocket(config *WebSocketConfig, onMessage func(string), onError func(error), onClose func()) error {
+ client := GetWebSocketClient()
+ return client.Connect(config, onMessage, onError, onClose)
+}
+
+// SendWebSocketMessage 发送 WebSocket 消息
+func SendWebSocketMessage(message string) error {
+ client := GetWebSocketClient()
+ return client.Send(message)
+}
+
+// CloseWebSocket 关闭 WebSocket 连接
+func CloseWebSocket() error {
+ client := GetWebSocketClient()
+ return client.Close()
+}
+
+// IsWebSocketConnected WebSocket 是否已连接
+func IsWebSocketConnected() bool {
+ client := GetWebSocketClient()
+ return client.IsConnected()
+}
+
+// TCPClient TCP 客户端
+type TCPClient struct {
+ conn net.Conn
+ isConnected bool
+ mu sync.RWMutex
+ onMessage func(message string)
+ onError func(err error)
+ onClose func()
+}
+
+// TCPConfig TCP 配置
+type TCPConfig struct {
+ Host string `json:"host"`
+ Port int `json:"port"`
+}
+
+// TCPMessage TCP 消息
+type TCPMessage struct {
+ Type string `json:"type"`
+ Content string `json:"content"`
+ Time string `json:"time"`
+}
+
+var tcpClient *TCPClient
+
+// NewTCPClient 创建 TCP 客户端
+func NewTCPClient() *TCPClient {
+ return &TCPClient{
+ isConnected: false,
+ }
+}
+
+// Connect 连接 TCP
+func (c *TCPClient) Connect(config *TCPConfig, onMessage func(string), onError func(error), onClose func()) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.isConnected {
+ return fmt.Errorf("已连接")
+ }
+
+ addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
+ conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
+ if err != nil {
+ return fmt.Errorf("连接失败: %v", err)
+ }
+
+ c.conn = conn
+ c.isConnected = true
+ c.onMessage = onMessage
+ c.onError = onError
+ c.onClose = onClose
+
+ // 启动消息监听
+ go c.readMessages()
+
+ return nil
+}
+
+// readMessages 读取消息
+func (c *TCPClient) readMessages() {
+ defer func() {
+ c.mu.Lock()
+ c.isConnected = false
+ c.mu.Unlock()
+ if c.onClose != nil {
+ c.onClose()
+ }
+ }()
+
+ buf := make([]byte, 4096)
+ for {
+ n, err := c.conn.Read(buf)
+ if err != nil {
+ if c.onError != nil {
+ c.onError(err)
+ }
+ return
+ }
+ if c.onMessage != nil {
+ c.onMessage(string(buf[:n]))
+ }
+ }
+}
+
+// Send 发送消息
+func (c *TCPClient) Send(message string) error {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ if !c.isConnected || c.conn == nil {
+ return fmt.Errorf("未连接")
+ }
+
+ _, err := c.conn.Write([]byte(message))
+ return err
+}
+
+// Close 关闭连接
+func (c *TCPClient) Close() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.conn != nil {
+ err := c.conn.Close()
+ c.isConnected = false
+ return err
+ }
+ return nil
+}
+
+// IsConnected 是否已连接
+func (c *TCPClient) IsConnected() bool {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.isConnected
+}
+
+// GetTCPClient 获取 TCP 客户端
+func GetTCPClient() *TCPClient {
+ if tcpClient == nil {
+ tcpClient = NewTCPClient()
+ }
+ return tcpClient
+}
+
+// ConnectTCP 连接 TCP
+func ConnectTCP(config *TCPConfig, onMessage func(string), onError func(error), onClose func()) error {
+ client := GetTCPClient()
+ return client.Connect(config, onMessage, onError, onClose)
+}
+
+// SendTCPMessage 发送 TCP 消息
+func SendTCPMessage(message string) error {
+ client := GetTCPClient()
+ return client.Send(message)
+}
+
+// CloseTCP 关闭 TCP 连接
+func CloseTCP() error {
+ client := GetTCPClient()
+ return client.Close()
+}
+
+// IsTCPConnected TCP 是否已连接
+func IsTCPConnected() bool {
+ client := GetTCPClient()
+ return client.isConnected
+}
diff --git a/build/appicon.png b/build/appicon.png
new file mode 100644
index 0000000..6a76e08
Binary files /dev/null and b/build/appicon.png differ
diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist
new file mode 100644
index 0000000..14121ef
--- /dev/null
+++ b/build/darwin/Info.dev.plist
@@ -0,0 +1,68 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ {{.Info.ProductName}}
+ CFBundleExecutable
+ {{.OutputFilename}}
+ CFBundleIdentifier
+ com.wails.{{.Name}}
+ CFBundleVersion
+ {{.Info.ProductVersion}}
+ CFBundleGetInfoString
+ {{.Info.Comments}}
+ CFBundleShortVersionString
+ {{.Info.ProductVersion}}
+ CFBundleIconFile
+ iconfile
+ LSMinimumSystemVersion
+ 10.13.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ {{.Info.Copyright}}
+ {{if .Info.FileAssociations}}
+ CFBundleDocumentTypes
+
+ {{range .Info.FileAssociations}}
+
+ CFBundleTypeExtensions
+
+ {{.Ext}}
+
+ CFBundleTypeName
+ {{.Name}}
+ CFBundleTypeRole
+ {{.Role}}
+ CFBundleTypeIconFile
+ {{.IconName}}
+
+ {{end}}
+
+ {{end}}
+ {{if .Info.Protocols}}
+ CFBundleURLTypes
+
+ {{range .Info.Protocols}}
+
+ CFBundleURLName
+ com.wails.{{.Scheme}}
+ CFBundleURLSchemes
+
+ {{.Scheme}}
+
+ CFBundleTypeRole
+ {{.Role}}
+
+ {{end}}
+
+ {{end}}
+ NSAppTransportSecurity
+
+ NSAllowsLocalNetworking
+
+
+
+
diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist
new file mode 100644
index 0000000..d17a747
--- /dev/null
+++ b/build/darwin/Info.plist
@@ -0,0 +1,63 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ {{.Info.ProductName}}
+ CFBundleExecutable
+ {{.OutputFilename}}
+ CFBundleIdentifier
+ com.wails.{{.Name}}
+ CFBundleVersion
+ {{.Info.ProductVersion}}
+ CFBundleGetInfoString
+ {{.Info.Comments}}
+ CFBundleShortVersionString
+ {{.Info.ProductVersion}}
+ CFBundleIconFile
+ iconfile
+ LSMinimumSystemVersion
+ 10.13.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ {{.Info.Copyright}}
+ {{if .Info.FileAssociations}}
+ CFBundleDocumentTypes
+
+ {{range .Info.FileAssociations}}
+
+ CFBundleTypeExtensions
+
+ {{.Ext}}
+
+ CFBundleTypeName
+ {{.Name}}
+ CFBundleTypeRole
+ {{.Role}}
+ CFBundleTypeIconFile
+ {{.IconName}}
+
+ {{end}}
+
+ {{end}}
+ {{if .Info.Protocols}}
+ CFBundleURLTypes
+
+ {{range .Info.Protocols}}
+
+ CFBundleURLName
+ com.wails.{{.Scheme}}
+ CFBundleURLSchemes
+
+ {{.Scheme}}
+
+ CFBundleTypeRole
+ {{.Role}}
+
+ {{end}}
+
+ {{end}}
+
+
diff --git a/build/windows/icon.ico b/build/windows/icon.ico
new file mode 100644
index 0000000..cf46c2c
Binary files /dev/null and b/build/windows/icon.ico differ
diff --git a/build/windows/info.json b/build/windows/info.json
new file mode 100644
index 0000000..9727946
--- /dev/null
+++ b/build/windows/info.json
@@ -0,0 +1,15 @@
+{
+ "fixed": {
+ "file_version": "{{.Info.ProductVersion}}"
+ },
+ "info": {
+ "0000": {
+ "ProductVersion": "{{.Info.ProductVersion}}",
+ "CompanyName": "{{.Info.CompanyName}}",
+ "FileDescription": "{{.Info.ProductName}}",
+ "LegalCopyright": "{{.Info.Copyright}}",
+ "ProductName": "{{.Info.ProductName}}",
+ "Comments": "{{.Info.Comments}}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi
new file mode 100644
index 0000000..654ae2e
--- /dev/null
+++ b/build/windows/installer/project.nsi
@@ -0,0 +1,114 @@
+Unicode true
+
+####
+## Please note: Template replacements don't work in this file. They are provided with default defines like
+## mentioned underneath.
+## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
+## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
+## from outside of Wails for debugging and development of the installer.
+##
+## For development first make a wails nsis build to populate the "wails_tools.nsh":
+## > wails build --target windows/amd64 --nsis
+## Then you can call makensis on this file with specifying the path to your binary:
+## For a AMD64 only installer:
+## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
+## For a ARM64 only installer:
+## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
+## For a installer with both architectures:
+## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
+####
+## The following information is taken from the ProjectInfo file, but they can be overwritten here.
+####
+## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
+## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
+## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
+## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
+## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
+###
+## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
+## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
+####
+## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
+####
+## Include the wails tools
+####
+!include "wails_tools.nsh"
+
+# The version information for this two must consist of 4 parts
+VIProductVersion "${INFO_PRODUCTVERSION}.0"
+VIFileVersion "${INFO_PRODUCTVERSION}.0"
+
+VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
+VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
+VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
+VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
+VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
+VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
+
+# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
+ManifestDPIAware true
+
+!include "MUI.nsh"
+
+!define MUI_ICON "..\icon.ico"
+!define MUI_UNICON "..\icon.ico"
+# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
+!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
+!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
+
+!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
+# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
+!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
+!insertmacro MUI_PAGE_INSTFILES # Installing page.
+!insertmacro MUI_PAGE_FINISH # Finished installation page.
+
+!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
+
+!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
+
+## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
+#!uninstfinalize 'signtool --file "%1"'
+#!finalize 'signtool --file "%1"'
+
+Name "${INFO_PRODUCTNAME}"
+OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
+InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
+ShowInstDetails show # This will always show the installation details.
+
+Function .onInit
+ !insertmacro wails.checkArchitecture
+FunctionEnd
+
+Section
+ !insertmacro wails.setShellContext
+
+ !insertmacro wails.webview2runtime
+
+ SetOutPath $INSTDIR
+
+ !insertmacro wails.files
+
+ CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+ CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+
+ !insertmacro wails.associateFiles
+ !insertmacro wails.associateCustomProtocols
+
+ !insertmacro wails.writeUninstaller
+SectionEnd
+
+Section "uninstall"
+ !insertmacro wails.setShellContext
+
+ RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
+
+ RMDir /r $INSTDIR
+
+ Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
+ Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
+
+ !insertmacro wails.unassociateFiles
+ !insertmacro wails.unassociateCustomProtocols
+
+ !insertmacro wails.deleteUninstaller
+SectionEnd
diff --git a/build/windows/installer/wails_tools.nsh b/build/windows/installer/wails_tools.nsh
new file mode 100644
index 0000000..f9c0f88
--- /dev/null
+++ b/build/windows/installer/wails_tools.nsh
@@ -0,0 +1,249 @@
+# DO NOT EDIT - Generated automatically by `wails build`
+
+!include "x64.nsh"
+!include "WinVer.nsh"
+!include "FileFunc.nsh"
+
+!ifndef INFO_PROJECTNAME
+ !define INFO_PROJECTNAME "{{.Name}}"
+!endif
+!ifndef INFO_COMPANYNAME
+ !define INFO_COMPANYNAME "{{.Info.CompanyName}}"
+!endif
+!ifndef INFO_PRODUCTNAME
+ !define INFO_PRODUCTNAME "{{.Info.ProductName}}"
+!endif
+!ifndef INFO_PRODUCTVERSION
+ !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
+!endif
+!ifndef INFO_COPYRIGHT
+ !define INFO_COPYRIGHT "{{.Info.Copyright}}"
+!endif
+!ifndef PRODUCT_EXECUTABLE
+ !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
+!endif
+!ifndef UNINST_KEY_NAME
+ !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
+!endif
+!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
+
+!ifndef REQUEST_EXECUTION_LEVEL
+ !define REQUEST_EXECUTION_LEVEL "admin"
+!endif
+
+RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
+
+!ifdef ARG_WAILS_AMD64_BINARY
+ !define SUPPORTS_AMD64
+!endif
+
+!ifdef ARG_WAILS_ARM64_BINARY
+ !define SUPPORTS_ARM64
+!endif
+
+!ifdef SUPPORTS_AMD64
+ !ifdef SUPPORTS_ARM64
+ !define ARCH "amd64_arm64"
+ !else
+ !define ARCH "amd64"
+ !endif
+!else
+ !ifdef SUPPORTS_ARM64
+ !define ARCH "arm64"
+ !else
+ !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
+ !endif
+!endif
+
+!macro wails.checkArchitecture
+ !ifndef WAILS_WIN10_REQUIRED
+ !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
+ !endif
+
+ !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
+ !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
+ !endif
+
+ ${If} ${AtLeastWin10}
+ !ifdef SUPPORTS_AMD64
+ ${if} ${IsNativeAMD64}
+ Goto ok
+ ${EndIf}
+ !endif
+
+ !ifdef SUPPORTS_ARM64
+ ${if} ${IsNativeARM64}
+ Goto ok
+ ${EndIf}
+ !endif
+
+ IfSilent silentArch notSilentArch
+ silentArch:
+ SetErrorLevel 65
+ Abort
+ notSilentArch:
+ MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
+ Quit
+ ${else}
+ IfSilent silentWin notSilentWin
+ silentWin:
+ SetErrorLevel 64
+ Abort
+ notSilentWin:
+ MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
+ Quit
+ ${EndIf}
+
+ ok:
+!macroend
+
+!macro wails.files
+ !ifdef SUPPORTS_AMD64
+ ${if} ${IsNativeAMD64}
+ File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
+ ${EndIf}
+ !endif
+
+ !ifdef SUPPORTS_ARM64
+ ${if} ${IsNativeARM64}
+ File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
+ ${EndIf}
+ !endif
+!macroend
+
+!macro wails.writeUninstaller
+ WriteUninstaller "$INSTDIR\uninstall.exe"
+
+ SetRegView 64
+ WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+ WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
+ WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
+
+ ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
+ IntFmt $0 "0x%08X" $0
+ WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
+!macroend
+
+!macro wails.deleteUninstaller
+ Delete "$INSTDIR\uninstall.exe"
+
+ SetRegView 64
+ DeleteRegKey HKLM "${UNINST_KEY}"
+!macroend
+
+!macro wails.setShellContext
+ ${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
+ SetShellVarContext all
+ ${else}
+ SetShellVarContext current
+ ${EndIf}
+!macroend
+
+# Install webview2 by launching the bootstrapper
+# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
+!macro wails.webview2runtime
+ !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
+ !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
+ !endif
+
+ SetRegView 64
+ # If the admin key exists and is not empty then webview2 is already installed
+ ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+ ${If} $0 != ""
+ Goto ok
+ ${EndIf}
+
+ ${If} ${REQUEST_EXECUTION_LEVEL} == "user"
+ # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
+ ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+ ${If} $0 != ""
+ Goto ok
+ ${EndIf}
+ ${EndIf}
+
+ SetDetailsPrint both
+ DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
+ SetDetailsPrint listonly
+
+ InitPluginsDir
+ CreateDirectory "$pluginsdir\webview2bootstrapper"
+ SetOutPath "$pluginsdir\webview2bootstrapper"
+ File "tmp\MicrosoftEdgeWebview2Setup.exe"
+ ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
+
+ SetDetailsPrint both
+ ok:
+!macroend
+
+# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
+!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
+ ; Backup the previously associated file class
+ ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
+
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
+
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
+!macroend
+
+!macro APP_UNASSOCIATE EXT FILECLASS
+ ; Backup the previously associated file class
+ ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
+
+ DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
+!macroend
+
+!macro wails.associateFiles
+ ; Create file associations
+ {{range .Info.FileAssociations}}
+ !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
+
+ File "..\{{.IconName}}.ico"
+ {{end}}
+!macroend
+
+!macro wails.unassociateFiles
+ ; Delete app associations
+ {{range .Info.FileAssociations}}
+ !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
+
+ Delete "$INSTDIR\{{.IconName}}.ico"
+ {{end}}
+!macroend
+
+!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
+ DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
+!macroend
+
+!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
+ DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
+!macroend
+
+!macro wails.associateCustomProtocols
+ ; Create custom protocols associations
+ {{range .Info.Protocols}}
+ !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
+
+ {{end}}
+!macroend
+
+!macro wails.unassociateCustomProtocols
+ ; Delete app custom protocol associations
+ {{range .Info.Protocols}}
+ !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}"
+ {{end}}
+!macroend
diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest
new file mode 100644
index 0000000..17e1a23
--- /dev/null
+++ b/build/windows/wails.exe.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ true/pm
+ permonitorv2,permonitor
+
+
+
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..614ed27
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ App
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..bcc5ee6
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,1705 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "@wailsio/runtime": "^3.0.0-alpha.66",
+ "element-plus": "^2.9.10",
+ "vue": "^3.2.37"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^3.0.3",
+ "unplugin-auto-import": "^19.2.0",
+ "unplugin-vue-components": "^28.5.0",
+ "vite": "^3.0.7"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz",
+ "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz",
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@ctrl/tinycolor": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+ "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@element-plus/icons-vue": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+ "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.0.tgz",
+ "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.0.tgz",
+ "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.0",
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz",
+ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@popperjs/core": {
+ "name": "@sxzz/popperjs-es",
+ "version": "2.11.7",
+ "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+ "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.17",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.17.tgz",
+ "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.16",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+ "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz",
+ "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^3.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
+ "integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.27.2",
+ "@vue/shared": "3.5.14",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
+ "integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.14",
+ "@vue/shared": "3.5.14"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
+ "integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.27.2",
+ "@vue/compiler-core": "3.5.14",
+ "@vue/compiler-dom": "3.5.14",
+ "@vue/compiler-ssr": "3.5.14",
+ "@vue/shared": "3.5.14",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.3",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
+ "integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.14",
+ "@vue/shared": "3.5.14"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.14.tgz",
+ "integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.14"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
+ "integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.14",
+ "@vue/shared": "3.5.14"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
+ "integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.14",
+ "@vue/runtime-core": "3.5.14",
+ "@vue/shared": "3.5.14",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
+ "integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.14",
+ "@vue/shared": "3.5.14"
+ },
+ "peerDependencies": {
+ "vue": "3.5.14"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.14.tgz",
+ "integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==",
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+ "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.16",
+ "@vueuse/metadata": "9.13.0",
+ "@vueuse/shared": "9.13.0",
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/core/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+ "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+ "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@wailsio/runtime": {
+ "version": "3.0.0-alpha.66",
+ "resolved": "https://registry.npmmirror.com/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",
+ "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==",
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/async-validator": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+ "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz",
+ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/element-plus": {
+ "version": "2.9.10",
+ "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz",
+ "integrity": "sha512-W2v9jWnm1kl/zm4bSvCh8aFCVlxvhG3fmqiDZwyd6WQiWGE595J/mpjcCggEr+49QDgIymhXrpPMOPPSARUbng==",
+ "license": "MIT",
+ "dependencies": {
+ "@ctrl/tinycolor": "^3.4.1",
+ "@element-plus/icons-vue": "^2.3.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+ "@types/lodash": "^4.14.182",
+ "@types/lodash-es": "^4.17.6",
+ "@vueuse/core": "^9.1.0",
+ "async-validator": "^4.2.5",
+ "dayjs": "^1.11.13",
+ "escape-html": "^1.0.3",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "lodash-unified": "^1.0.2",
+ "memoize-one": "^6.0.0",
+ "normalize-wheel-es": "^1.2.0"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.5.tgz",
+ "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/local-pkg": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz",
+ "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.0.1",
+ "quansync": "^0.2.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-unified": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+ "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/lodash-es": "*",
+ "lodash": "*",
+ "lodash-es": "*"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
+ "node_modules/mlly": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz",
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "pathe": "^2.0.1",
+ "pkg-types": "^1.3.0",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/mlly/node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mlly/node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-wheel-es": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+ "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz",
+ "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.1",
+ "exsolve": "^1.0.1",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/quansync": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz",
+ "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/readdirp/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.79.2",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.79.2.tgz",
+ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unimport": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.2.0.tgz",
+ "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.1",
+ "escape-string-regexp": "^5.0.0",
+ "estree-walker": "^3.0.3",
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^2.1.0",
+ "scule": "^1.3.0",
+ "strip-literal": "^3.0.0",
+ "tinyglobby": "^0.2.12",
+ "unplugin": "^2.2.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unimport/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.4.tgz",
+ "integrity": "sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.1",
+ "picomatch": "^4.0.2",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unplugin-auto-import": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.2.0.tgz",
+ "integrity": "sha512-DGRHg86nUDKEYpny1p2kFZjeLg7kHQmknsPQ8krAshvpeypps7dFxNBsAqhBaxYINjetbgQilF8wbjuZxpdomg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "picomatch": "^4.0.2",
+ "unimport": "^4.2.0",
+ "unplugin": "^2.3.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2",
+ "@vueuse/core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@vueuse/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-utils": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
+ "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/unplugin-vue-components": {
+ "version": "28.5.0",
+ "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz",
+ "integrity": "sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.6.0",
+ "debug": "^4.4.0",
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "tinyglobby": "^0.2.12",
+ "unplugin": "^2.3.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@babel/parser": "^7.15.8",
+ "@nuxt/kit": "^3.2.2",
+ "vue": "2 || 3"
+ },
+ "peerDependenciesMeta": {
+ "@babel/parser": {
+ "optional": true
+ },
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.15.9",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.14",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.14.tgz",
+ "integrity": "sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.14",
+ "@vue/compiler-sfc": "3.5.14",
+ "@vue/runtime-dom": "3.5.14",
+ "@vue/server-renderer": "3.5.14",
+ "@vue/shared": "3.5.14"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..f194bd7
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@wailsio/runtime": "^3.0.0-alpha.66",
+ "element-plus": "^2.9.10",
+ "vue": "^3.2.37"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^3.0.3",
+ "unplugin-auto-import": "^19.2.0",
+ "unplugin-vue-components": "^28.5.0",
+ "vite": "^3.0.7"
+ }
+}
diff --git a/frontend/package.json.md5 b/frontend/package.json.md5
new file mode 100644
index 0000000..0c1b4c1
--- /dev/null
+++ b/frontend/package.json.md5
@@ -0,0 +1 @@
+4e89b15208d82473f2aacb5ee7a39659
\ No newline at end of file
diff --git a/frontend/public/appicon.png b/frontend/public/appicon.png
new file mode 100644
index 0000000..6a76e08
Binary files /dev/null and b/frontend/public/appicon.png differ
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
new file mode 100644
index 0000000..2d67793
--- /dev/null
+++ b/frontend/src/App.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/assets/fonts/OFL.txt b/frontend/src/assets/fonts/OFL.txt
new file mode 100644
index 0000000..9cac04c
--- /dev/null
+++ b/frontend/src/assets/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
new file mode 100644
index 0000000..2f9cc59
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ
diff --git a/frontend/src/components/HeaderBar.vue b/frontend/src/components/HeaderBar.vue
new file mode 100644
index 0000000..43fb28d
--- /dev/null
+++ b/frontend/src/components/HeaderBar.vue
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
{{ t('header.app_name') }}
+
{{ version }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/JsDebug.vue b/frontend/src/components/JsDebug.vue
new file mode 100644
index 0000000..fcdf57f
--- /dev/null
+++ b/frontend/src/components/JsDebug.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
{{ t('jsdebug.section.code') }}
+
+
+
+
+
+ {{ t('jsdebug.section.output') }}
+ {{ t('jsdebug.btn.copy') }}
+
+
+
+
+
+
{{ t('jsdebug.section.return_value') }}
+
+
+
+
+
+
diff --git a/frontend/src/components/JsonParser.vue b/frontend/src/components/JsonParser.vue
new file mode 100644
index 0000000..c8a7fa0
--- /dev/null
+++ b/frontend/src/components/JsonParser.vue
@@ -0,0 +1,526 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getValueType(row._value) }}
+
+ {{ typeof row._value === 'object' ? JSON.stringify(row._value) : formatValue(row._value) }}
+
+
+
+
+
+ {{ t('jsonparser.btn.copy') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/LangSelector.vue b/frontend/src/components/LangSelector.vue
new file mode 100644
index 0000000..3f25b25
--- /dev/null
+++ b/frontend/src/components/LangSelector.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Settings.vue b/frontend/src/components/Settings.vue
new file mode 100644
index 0000000..9a14111
--- /dev/null
+++ b/frontend/src/components/Settings.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('settings.form.proxy_divider') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('settings.form.btn.save') }}
+ {{ t('settings.form.btn.reset') }}
+
+
+
+
+
+
diff --git a/frontend/src/components/SocketDebug.vue b/frontend/src/components/SocketDebug.vue
new file mode 100644
index 0000000..79ee05e
--- /dev/null
+++ b/frontend/src/components/SocketDebug.vue
@@ -0,0 +1,462 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ msg.time }}
+ {{ t('socket.message.tag.sent') }}
+ {{ t('socket.message.tag.received') }}
+ {{ t('socket.message.tag.error') }}
+ {{ t('socket.message.tag.system') }}
+
+
{{ msg.content }}
+
+
+
+
+
+
+
+
+
+ {{ t('socket.message.btn.send') }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Test.vue b/frontend/src/components/Test.vue
new file mode 100644
index 0000000..1b3ec47
--- /dev/null
+++ b/frontend/src/components/Test.vue
@@ -0,0 +1,751 @@
+
+
+
+
+
+
+
+
+ {{ t('feature_overview_desc', '本模板集成了以下核心功能,可作为 Wails 项目的开发起点。') }}
+
+
+
+ {{ feature.icon }}
+
+
+
{{ t(feature.key, feature.key) }}
+
{{ t(feature.desc, feature.desc) }}
+
+
+
+
+
+
+
+
+
+ {{ t('demo_data_interaction_desc', '通过 Wails 绑定调用 Go 后端函数,获取返回值并展示。') }}
+
+
+
+ {{ t('send_greet', '发送问候') }}
+
+
+
+
+
+
+
+
+
+ {{ t('demo_event_desc', 'Go 后端通过 EventsEmit 推送数据,前端通过 EventsOn 监听接收。') }}
+
+ {{ t('time_label', '当前时间') }}
+ {{ systime }}
+
+
+
+
+
+
+
+ {{ t('demo_json_desc', '后端返回 JSON 字符串,前端解析后以表格形式展示。') }}
+
+
+ {{ t('fetch_data', '获取数据') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('demo_lang_switch_desc', '切换应用语言,支持嵌入语言包和本地语言文件。') }}
+
+ {{ t('current_lang', '当前语言') }}:
+
+
+
+
+
+
+
+
+
+
+ {{ t('demo_log_level_desc', '动态调整日志输出等级。') }}
+
+ {{ t('log_level', '日志等级') }}:
+
+
+
+
+
+
+
+
+
+
+ {{ t('demo_log_viewer_desc', '获取日志文件列表并查看内容,展示前后端数据交互的实际应用。') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('demo_system_info_desc', '获取当前操作系统、架构、CPU 核心数等系统信息。') }}
+
+
+ {{ t('fetch_data', '获取数据') }}
+
+
+
+ {{ sysInfo.os }}
+ {{ sysInfo.arch }}
+ {{ sysInfo.num_cpu }}
+ {{ sysInfo.hostname }}
+ {{ sysInfo.go_ver }}
+ {{ sysInfo.time }}
+ {{ sysInfo.process_name }}
+
+
+
+
+
+
+
+
+ {{ t('demo_file_dialog_desc', '调用系统原生文件对话框,支持打开文件、选择目录和保存文件。') }}
+
+ {{ t('open_file', '打开文件') }}
+ {{ t('open_folder', '选择目录') }}
+ {{ t('save_file', '保存文件') }}
+
+
+
+
+
+
+
+
+
+ {{ t('demo_file_read_write_desc', '读取和写入本地文件,展示文件系统交互能力。') }}
+
+
+
+ {{ t('read_file', '读取文件') }}
+
+
+ {{ t('write_file', '写入文件') }}
+
+
+
+
+
+
+
+
+
+ {{ t('demo_notification_desc', '后端通过 EventsEmit 向前端发送自定义通知事件。') }}
+
+
+
+
+ {{ t('send_notify', '发送通知') }}
+
+
+
+
+ [{{ item.time }}]
+ {{ item.title }}
+ - {{ item.message }}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/WebDebug.vue b/frontend/src/components/WebDebug.vue
new file mode 100644
index 0000000..6a887e0
--- /dev/null
+++ b/frontend/src/components/WebDebug.vue
@@ -0,0 +1,771 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('webdebug.btn_send') }}
+
+
+
+
+
+
+
+
+
+
+ {{ t('webdebug.body.type_text') }}
+ {{ t('webdebug.body.type_json') }}
+ {{ t('webdebug.body.type_form') }}
+ {{ t('webdebug.body.type_file') }}
+
+
+
+
+
+
+ {{ t('webdebug.body.file_btn') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('webdebug.advanced.timeout_unit') }}
+
+
+
+
+
+
+
+ {{ t('webdebug.advanced.log_divider') }}
+
+
+
+
+
+ {{ t('webdebug.advanced.view_logs_btn') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('webdebug.response.send_to_json') }}
+
+
+
+
+
+
+
+
+ {{ response.statusCode || '-' }}
+ {{ response.status || '-' }}
+ {{ response.contentType || '-' }}
+ {{ formatContentSize }}
+ {{ response.time || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ log.host }}
+
{{ log.path }}
+
+
+
+ {{ log.statusCode }}
+
+ {{ log.createdAt }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedLog.method }}
+ {{ selectedLog.url }}
+ {{ selectedLog.host }}
+ {{ selectedLog.path }}
+
+
+
+
+
+
+
+ {{ selectedLog.statusCode }}
+ {{ selectedLog.status }}
+ {{ selectedLog.respSize ? (selectedLog.respSize / 1024).toFixed(2) + ' KB' : '-' }}
+ {{ selectedLog.time }}
+
+
+
+
+
+
+
+
+ {{ t('webdebug.log.load_to_debug') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/composables/useI18n.js b/frontend/src/composables/useI18n.js
new file mode 100644
index 0000000..2aee6ae
--- /dev/null
+++ b/frontend/src/composables/useI18n.js
@@ -0,0 +1,37 @@
+import { ref, onMounted } from 'vue'
+import { GetLangTextMap } from '../../wailsjs/go/service/App'
+
+// 全局单例状态
+const textMap = ref({})
+const isLoaded = ref(false)
+
+export function useI18n() {
+ const loadTextMap = async () => {
+ try {
+ const map = await GetLangTextMap()
+ textMap.value = map || {}
+ isLoaded.value = true
+ } catch (error) {
+ console.error('加载语言包失败:', error)
+ textMap.value = {}
+ isLoaded.value = true
+ }
+ }
+
+ const t = (key, defaultValue = '') => {
+ return textMap.value[key] || defaultValue || key
+ }
+
+ onMounted(() => {
+ if (!isLoaded.value) {
+ loadTextMap()
+ }
+ })
+
+ return {
+ t,
+ textMap,
+ isLoaded,
+ loadTextMap
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/main.js b/frontend/src/main.js
new file mode 100644
index 0000000..dd70ff8
--- /dev/null
+++ b/frontend/src/main.js
@@ -0,0 +1,11 @@
+import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import App from './App.vue'
+
+const app = createApp(App)
+
+app.use(ElementPlus)
+app.mount('#app')
+
+document.body.style.overflow = 'hidden';
\ No newline at end of file
diff --git a/frontend/src/style.css b/frontend/src/style.css
new file mode 100644
index 0000000..7225f6e
--- /dev/null
+++ b/frontend/src/style.css
@@ -0,0 +1,31 @@
+/* 基础样式 */
+html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+#app {
+ height: 100vh;
+}
+
+/* 隐藏滚动条 - 通用方法 */
+.hide-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+
+.hide-scrollbar::-webkit-scrollbar {
+ display: none;
+}
+
+/* Element Plus textarea 内部滚动条隐藏 */
+.el-textarea__inner {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+
+.el-textarea__inner::-webkit-scrollbar {
+ display: none;
+}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..58d3450
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,20 @@
+import {defineConfig} from 'vite'
+import vue from '@vitejs/plugin-vue'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+ vue(),
+ AutoImport({
+ resolvers: [ElementPlusResolver()],
+ }),
+ Components({
+ resolvers: [ElementPlusResolver()],
+ }),
+ ],
+})
+
+
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts
new file mode 100644
index 0000000..f4bc077
--- /dev/null
+++ b/frontend/wailsjs/go/models.ts
@@ -0,0 +1,227 @@
+export namespace api {
+
+ export class CookieInfo {
+ name: string;
+ value: string;
+ path: string;
+ domain: string;
+ expires: string;
+ maxAge: number;
+ secure: boolean;
+ httpOnly: boolean;
+ sameSite: string;
+
+ static createFrom(source: any = {}) {
+ return new CookieInfo(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.name = source["name"];
+ this.value = source["value"];
+ this.path = source["path"];
+ this.domain = source["domain"];
+ this.expires = source["expires"];
+ this.maxAge = source["maxAge"];
+ this.secure = source["secure"];
+ this.httpOnly = source["httpOnly"];
+ this.sameSite = source["sameSite"];
+ }
+ }
+ export class HttpResponse {
+ statusCode: number;
+ status: string;
+ headers: Record;
+ cookies: CookieInfo[];
+ body: string;
+ contentType: string;
+ contentLength: number;
+ time: string;
+ size: number;
+ error: string;
+
+ static createFrom(source: any = {}) {
+ return new HttpResponse(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.statusCode = source["statusCode"];
+ this.status = source["status"];
+ this.headers = source["headers"];
+ this.cookies = this.convertValues(source["cookies"], CookieInfo);
+ this.body = source["body"];
+ this.contentType = source["contentType"];
+ this.contentLength = source["contentLength"];
+ this.time = source["time"];
+ this.size = source["size"];
+ this.error = source["error"];
+ }
+
+ convertValues(a: any, classs: any, asMap: boolean = false): any {
+ if (!a) {
+ return a;
+ }
+ if (a.slice && a.map) {
+ return (a as any[]).map(elem => this.convertValues(elem, classs));
+ } else if ("object" === typeof a) {
+ if (asMap) {
+ for (const key of Object.keys(a)) {
+ a[key] = new classs(a[key]);
+ }
+ return a;
+ }
+ return new classs(a);
+ }
+ return a;
+ }
+ }
+ export class JsExecResult {
+ output: string;
+ result: string;
+ error: string;
+ duration: string;
+ success: boolean;
+
+ static createFrom(source: any = {}) {
+ return new JsExecResult(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.output = source["output"];
+ this.result = source["result"];
+ this.error = source["error"];
+ this.duration = source["duration"];
+ this.success = source["success"];
+ }
+ }
+ export class RequestLog {
+ id: number;
+ method: string;
+ url: string;
+ host: string;
+ path: string;
+ headers: Record;
+ cookies: Record;
+ bodyType: string;
+ body: string;
+ statusCode: number;
+ status: string;
+ respHeaders: Record;
+ respBody: string;
+ respSize: number;
+ time: string;
+ createdAt: string;
+
+ static createFrom(source: any = {}) {
+ return new RequestLog(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.id = source["id"];
+ this.method = source["method"];
+ this.url = source["url"];
+ this.host = source["host"];
+ this.path = source["path"];
+ this.headers = source["headers"];
+ this.cookies = source["cookies"];
+ this.bodyType = source["bodyType"];
+ this.body = source["body"];
+ this.statusCode = source["statusCode"];
+ this.status = source["status"];
+ this.respHeaders = source["respHeaders"];
+ this.respBody = source["respBody"];
+ this.respSize = source["respSize"];
+ this.time = source["time"];
+ this.createdAt = source["createdAt"];
+ }
+ }
+
+}
+
+export namespace global {
+
+ export class LanguageInfo {
+ language_name: string;
+ language_code: string;
+ textmap_path: string;
+ translation_progress: string;
+ translator: string;
+ last_updated: string;
+ version: string;
+
+ static createFrom(source: any = {}) {
+ return new LanguageInfo(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.language_name = source["language_name"];
+ this.language_code = source["language_code"];
+ this.textmap_path = source["textmap_path"];
+ this.translation_progress = source["translation_progress"];
+ this.translator = source["translator"];
+ this.last_updated = source["last_updated"];
+ this.version = source["version"];
+ }
+ }
+ export class LanguagePack {
+ language_name: string;
+ language_code: string;
+ textmap_path: string;
+ translation_progress: string;
+ translator: string;
+ last_updated: string;
+ version: string;
+ textmap: Record;
+
+ static createFrom(source: any = {}) {
+ return new LanguagePack(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.language_name = source["language_name"];
+ this.language_code = source["language_code"];
+ this.textmap_path = source["textmap_path"];
+ this.translation_progress = source["translation_progress"];
+ this.translator = source["translator"];
+ this.last_updated = source["last_updated"];
+ this.version = source["version"];
+ this.textmap = source["textmap"];
+ }
+ }
+
+}
+
+export namespace service {
+
+ export class SystemInfo {
+ os: string;
+ arch: string;
+ num_cpu: number;
+ hostname: string;
+ go_ver: string;
+ time: string;
+ process_name: string;
+
+ static createFrom(source: any = {}) {
+ return new SystemInfo(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.os = source["os"];
+ this.arch = source["arch"];
+ this.num_cpu = source["num_cpu"];
+ this.hostname = source["hostname"];
+ this.go_ver = source["go_ver"];
+ this.time = source["time"];
+ this.process_name = source["process_name"];
+ }
+ }
+
+}
+
diff --git a/frontend/wailsjs/go/service/App.d.ts b/frontend/wailsjs/go/service/App.d.ts
new file mode 100644
index 0000000..60835d6
--- /dev/null
+++ b/frontend/wailsjs/go/service/App.d.ts
@@ -0,0 +1,73 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+import {api} from '../models';
+import {global} from '../models';
+import {service} from '../models';
+
+export function ClearRequestLogs():Promise;
+
+export function ConnectSocket(arg1:string,arg2:string,arg3:number,arg4:string,arg5:string):Promise;
+
+export function DeleteRequestLog(arg1:number):Promise;
+
+export function DisconnectSocket(arg1:string):Promise;
+
+export function ExecuteJs(arg1:string):Promise;
+
+export function Flashtime():Promise;
+
+export function GetALLLang():Promise>;
+
+export function GetCurrentLang():Promise;
+
+export function GetLangPack():Promise;
+
+export function GetLangTextMap():Promise>;
+
+export function GetLogFileContent(arg1:string):Promise;
+
+export function GetLogFiles():Promise>;
+
+export function GetLogLevel():Promise;
+
+export function GetProcessName():Promise;
+
+export function GetRequestLog(arg1:number):Promise;
+
+export function GetRequestLogs():Promise>;
+
+export function GetSystemInfo():Promise;
+
+export function Gettestjson():Promise;
+
+export function Greet(arg1:string):Promise;
+
+export function IsSocketConnected(arg1:string):Promise;
+
+export function Notify(arg1:string,arg2:string):Promise;
+
+export function OpenFileSelect():Promise;
+
+export function OpenFolderSelect():Promise;
+
+export function ReadFileContent(arg1:string):Promise;
+
+export function SaveFileSelect():Promise;
+
+export function SearchRequestLogs(arg1:string):Promise>;
+
+export function SendHttpRequest(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string,arg7:string,arg8:string,arg9:string,arg10:number,arg11:boolean,arg12:boolean):Promise;
+
+export function SendSocketMessage(arg1:string,arg2:string):Promise;
+
+export function SetLanguage(arg1:string):Promise;
+
+export function SetLogLevel(arg1:string):Promise;
+
+export function WindowClose():Promise;
+
+export function WindowMinimise():Promise;
+
+export function WindowToggleMaximise():Promise;
+
+export function WriteFileContent(arg1:string,arg2:string):Promise;
diff --git a/frontend/wailsjs/go/service/App.js b/frontend/wailsjs/go/service/App.js
new file mode 100644
index 0000000..bec1632
--- /dev/null
+++ b/frontend/wailsjs/go/service/App.js
@@ -0,0 +1,139 @@
+// @ts-check
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+export function ClearRequestLogs() {
+ return window['go']['service']['App']['ClearRequestLogs']();
+}
+
+export function ConnectSocket(arg1, arg2, arg3, arg4, arg5) {
+ return window['go']['service']['App']['ConnectSocket'](arg1, arg2, arg3, arg4, arg5);
+}
+
+export function DeleteRequestLog(arg1) {
+ return window['go']['service']['App']['DeleteRequestLog'](arg1);
+}
+
+export function DisconnectSocket(arg1) {
+ return window['go']['service']['App']['DisconnectSocket'](arg1);
+}
+
+export function ExecuteJs(arg1) {
+ return window['go']['service']['App']['ExecuteJs'](arg1);
+}
+
+export function Flashtime() {
+ return window['go']['service']['App']['Flashtime']();
+}
+
+export function GetALLLang() {
+ return window['go']['service']['App']['GetALLLang']();
+}
+
+export function GetCurrentLang() {
+ return window['go']['service']['App']['GetCurrentLang']();
+}
+
+export function GetLangPack() {
+ return window['go']['service']['App']['GetLangPack']();
+}
+
+export function GetLangTextMap() {
+ return window['go']['service']['App']['GetLangTextMap']();
+}
+
+export function GetLogFileContent(arg1) {
+ return window['go']['service']['App']['GetLogFileContent'](arg1);
+}
+
+export function GetLogFiles() {
+ return window['go']['service']['App']['GetLogFiles']();
+}
+
+export function GetLogLevel() {
+ return window['go']['service']['App']['GetLogLevel']();
+}
+
+export function GetProcessName() {
+ return window['go']['service']['App']['GetProcessName']();
+}
+
+export function GetRequestLog(arg1) {
+ return window['go']['service']['App']['GetRequestLog'](arg1);
+}
+
+export function GetRequestLogs() {
+ return window['go']['service']['App']['GetRequestLogs']();
+}
+
+export function GetSystemInfo() {
+ return window['go']['service']['App']['GetSystemInfo']();
+}
+
+export function Gettestjson() {
+ return window['go']['service']['App']['Gettestjson']();
+}
+
+export function Greet(arg1) {
+ return window['go']['service']['App']['Greet'](arg1);
+}
+
+export function IsSocketConnected(arg1) {
+ return window['go']['service']['App']['IsSocketConnected'](arg1);
+}
+
+export function Notify(arg1, arg2) {
+ return window['go']['service']['App']['Notify'](arg1, arg2);
+}
+
+export function OpenFileSelect() {
+ return window['go']['service']['App']['OpenFileSelect']();
+}
+
+export function OpenFolderSelect() {
+ return window['go']['service']['App']['OpenFolderSelect']();
+}
+
+export function ReadFileContent(arg1) {
+ return window['go']['service']['App']['ReadFileContent'](arg1);
+}
+
+export function SaveFileSelect() {
+ return window['go']['service']['App']['SaveFileSelect']();
+}
+
+export function SearchRequestLogs(arg1) {
+ return window['go']['service']['App']['SearchRequestLogs'](arg1);
+}
+
+export function SendHttpRequest(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) {
+ return window['go']['service']['App']['SendHttpRequest'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12);
+}
+
+export function SendSocketMessage(arg1, arg2) {
+ return window['go']['service']['App']['SendSocketMessage'](arg1, arg2);
+}
+
+export function SetLanguage(arg1) {
+ return window['go']['service']['App']['SetLanguage'](arg1);
+}
+
+export function SetLogLevel(arg1) {
+ return window['go']['service']['App']['SetLogLevel'](arg1);
+}
+
+export function WindowClose() {
+ return window['go']['service']['App']['WindowClose']();
+}
+
+export function WindowMinimise() {
+ return window['go']['service']['App']['WindowMinimise']();
+}
+
+export function WindowToggleMaximise() {
+ return window['go']['service']['App']['WindowToggleMaximise']();
+}
+
+export function WriteFileContent(arg1, arg2) {
+ return window['go']['service']['App']['WriteFileContent'](arg1, arg2);
+}
diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json
new file mode 100644
index 0000000..1e7c8a5
--- /dev/null
+++ b/frontend/wailsjs/runtime/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@wailsapp/runtime",
+ "version": "2.0.0",
+ "description": "Wails Javascript runtime library",
+ "main": "runtime.js",
+ "types": "runtime.d.ts",
+ "scripts": {
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/wailsapp/wails.git"
+ },
+ "keywords": [
+ "Wails",
+ "Javascript",
+ "Go"
+ ],
+ "author": "Lea Anthony ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/wailsapp/wails/issues"
+ },
+ "homepage": "https://github.com/wailsapp/wails#readme"
+}
diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts
new file mode 100644
index 0000000..3bbea84
--- /dev/null
+++ b/frontend/wailsjs/runtime/runtime.d.ts
@@ -0,0 +1,330 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export interface Position {
+ x: number;
+ y: number;
+}
+
+export interface Size {
+ w: number;
+ h: number;
+}
+
+export interface Screen {
+ isCurrent: boolean;
+ isPrimary: boolean;
+ width : number
+ height : number
+}
+
+// Environment information such as platform, buildtype, ...
+export interface EnvironmentInfo {
+ buildType: string;
+ platform: string;
+ arch: string;
+}
+
+// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
+// emits the given event. Optional data may be passed with the event.
+// This will trigger any event listeners.
+export function EventsEmit(eventName: string, ...data: any): void;
+
+// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
+export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
+// sets up a listener for the given event name, but will only trigger a given number times.
+export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
+
+// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
+// sets up a listener for the given event name, but will only trigger once.
+export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
+// unregisters the listener for the given event name.
+export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
+
+// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
+// unregisters all listeners.
+export function EventsOffAll(): void;
+
+// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
+// logs the given message as a raw message
+export function LogPrint(message: string): void;
+
+// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
+// logs the given message at the `trace` log level.
+export function LogTrace(message: string): void;
+
+// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
+// logs the given message at the `debug` log level.
+export function LogDebug(message: string): void;
+
+// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
+// logs the given message at the `error` log level.
+export function LogError(message: string): void;
+
+// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
+// logs the given message at the `fatal` log level.
+// The application will quit after calling this method.
+export function LogFatal(message: string): void;
+
+// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
+// logs the given message at the `info` log level.
+export function LogInfo(message: string): void;
+
+// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
+// logs the given message at the `warning` log level.
+export function LogWarning(message: string): void;
+
+// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
+// Forces a reload by the main application as well as connected browsers.
+export function WindowReload(): void;
+
+// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
+// Reloads the application frontend.
+export function WindowReloadApp(): void;
+
+// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
+// Sets the window AlwaysOnTop or not on top.
+export function WindowSetAlwaysOnTop(b: boolean): void;
+
+// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
+// *Windows only*
+// Sets window theme to system default (dark/light).
+export function WindowSetSystemDefaultTheme(): void;
+
+// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
+// *Windows only*
+// Sets window to light theme.
+export function WindowSetLightTheme(): void;
+
+// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
+// *Windows only*
+// Sets window to dark theme.
+export function WindowSetDarkTheme(): void;
+
+// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
+// Centers the window on the monitor the window is currently on.
+export function WindowCenter(): void;
+
+// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
+// Sets the text in the window title bar.
+export function WindowSetTitle(title: string): void;
+
+// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
+// Makes the window full screen.
+export function WindowFullscreen(): void;
+
+// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
+// Restores the previous window dimensions and position prior to full screen.
+export function WindowUnfullscreen(): void;
+
+// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
+// Returns the state of the window, i.e. whether the window is in full screen mode or not.
+export function WindowIsFullscreen(): Promise;
+
+// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
+// Sets the width and height of the window.
+export function WindowSetSize(width: number, height: number): void;
+
+// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
+// Gets the width and height of the window.
+export function WindowGetSize(): Promise;
+
+// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
+// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMaxSize(width: number, height: number): void;
+
+// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
+// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMinSize(width: number, height: number): void;
+
+// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
+// Sets the window position relative to the monitor the window is currently on.
+export function WindowSetPosition(x: number, y: number): void;
+
+// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
+// Gets the window position relative to the monitor the window is currently on.
+export function WindowGetPosition(): Promise;
+
+// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
+// Hides the window.
+export function WindowHide(): void;
+
+// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
+// Shows the window, if it is currently hidden.
+export function WindowShow(): void;
+
+// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
+// Maximises the window to fill the screen.
+export function WindowMaximise(): void;
+
+// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
+// Toggles between Maximised and UnMaximised.
+export function WindowToggleMaximise(): void;
+
+// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
+// Restores the window to the dimensions and position prior to maximising.
+export function WindowUnmaximise(): void;
+
+// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
+// Returns the state of the window, i.e. whether the window is maximised or not.
+export function WindowIsMaximised(): Promise;
+
+// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
+// Minimises the window.
+export function WindowMinimise(): void;
+
+// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
+// Restores the window to the dimensions and position prior to minimising.
+export function WindowUnminimise(): void;
+
+// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
+// Returns the state of the window, i.e. whether the window is minimised or not.
+export function WindowIsMinimised(): Promise;
+
+// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
+// Returns the state of the window, i.e. whether the window is normal or not.
+export function WindowIsNormal(): Promise;
+
+// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
+// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
+export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
+
+// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
+// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
+export function ScreenGetAll(): Promise;
+
+// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
+// Opens the given URL in the system browser.
+export function BrowserOpenURL(url: string): void;
+
+// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
+// Returns information about the environment
+export function Environment(): Promise;
+
+// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
+// Quits the application.
+export function Quit(): void;
+
+// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
+// Hides the application.
+export function Hide(): void;
+
+// [Show](https://wails.io/docs/reference/runtime/intro#show)
+// Shows the application.
+export function Show(): void;
+
+// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
+// Returns the current text stored on clipboard
+export function ClipboardGetText(): Promise;
+
+// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
+// Sets a text on the clipboard
+export function ClipboardSetText(text: string): Promise;
+
+// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
+// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
+
+// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
+// OnFileDropOff removes the drag and drop listeners and handlers.
+export function OnFileDropOff() :void
+
+// Check if the file path resolver is available
+export function CanResolveFilePaths(): boolean;
+
+// Resolves file paths for an array of files
+export function ResolveFilePaths(files: File[]): void
+
+// Notification types
+export interface NotificationOptions {
+ id: string;
+ title: string;
+ subtitle?: string; // macOS and Linux only
+ body?: string;
+ categoryId?: string;
+ data?: { [key: string]: any };
+}
+
+export interface NotificationAction {
+ id?: string;
+ title?: string;
+ destructive?: boolean; // macOS-specific
+}
+
+export interface NotificationCategory {
+ id?: string;
+ actions?: NotificationAction[];
+ hasReplyField?: boolean;
+ replyPlaceholder?: string;
+ replyButtonTitle?: string;
+}
+
+// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
+// Initializes the notification service for the application.
+// This must be called before sending any notifications.
+export function InitializeNotifications(): Promise;
+
+// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
+// Cleans up notification resources and releases any held connections.
+export function CleanupNotifications(): Promise;
+
+// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
+// Checks if notifications are available on the current platform.
+export function IsNotificationAvailable(): Promise;
+
+// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
+// Requests notification authorization from the user (macOS only).
+export function RequestNotificationAuthorization(): Promise;
+
+// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
+// Checks the current notification authorization status (macOS only).
+export function CheckNotificationAuthorization(): Promise;
+
+// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
+// Sends a basic notification with the given options.
+export function SendNotification(options: NotificationOptions): Promise;
+
+// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
+// Sends a notification with action buttons. Requires a registered category.
+export function SendNotificationWithActions(options: NotificationOptions): Promise;
+
+// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
+// Registers a notification category that can be used with SendNotificationWithActions.
+export function RegisterNotificationCategory(category: NotificationCategory): Promise;
+
+// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
+// Removes a previously registered notification category.
+export function RemoveNotificationCategory(categoryId: string): Promise;
+
+// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
+// Removes all pending notifications from the notification center.
+export function RemoveAllPendingNotifications(): Promise;
+
+// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
+// Removes a specific pending notification by its identifier.
+export function RemovePendingNotification(identifier: string): Promise;
+
+// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
+// Removes all delivered notifications from the notification center.
+export function RemoveAllDeliveredNotifications(): Promise;
+
+// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
+// Removes a specific delivered notification by its identifier.
+export function RemoveDeliveredNotification(identifier: string): Promise;
+
+// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
+// Removes a notification by its identifier (cross-platform convenience function).
+export function RemoveNotification(identifier: string): Promise;
\ No newline at end of file
diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js
new file mode 100644
index 0000000..556621e
--- /dev/null
+++ b/frontend/wailsjs/runtime/runtime.js
@@ -0,0 +1,298 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export function LogPrint(message) {
+ window.runtime.LogPrint(message);
+}
+
+export function LogTrace(message) {
+ window.runtime.LogTrace(message);
+}
+
+export function LogDebug(message) {
+ window.runtime.LogDebug(message);
+}
+
+export function LogInfo(message) {
+ window.runtime.LogInfo(message);
+}
+
+export function LogWarning(message) {
+ window.runtime.LogWarning(message);
+}
+
+export function LogError(message) {
+ window.runtime.LogError(message);
+}
+
+export function LogFatal(message) {
+ window.runtime.LogFatal(message);
+}
+
+export function EventsOnMultiple(eventName, callback, maxCallbacks) {
+ return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
+}
+
+export function EventsOn(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, -1);
+}
+
+export function EventsOff(eventName, ...additionalEventNames) {
+ return window.runtime.EventsOff(eventName, ...additionalEventNames);
+}
+
+export function EventsOffAll() {
+ return window.runtime.EventsOffAll();
+}
+
+export function EventsOnce(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, 1);
+}
+
+export function EventsEmit(eventName) {
+ let args = [eventName].slice.call(arguments);
+ return window.runtime.EventsEmit.apply(null, args);
+}
+
+export function WindowReload() {
+ window.runtime.WindowReload();
+}
+
+export function WindowReloadApp() {
+ window.runtime.WindowReloadApp();
+}
+
+export function WindowSetAlwaysOnTop(b) {
+ window.runtime.WindowSetAlwaysOnTop(b);
+}
+
+export function WindowSetSystemDefaultTheme() {
+ window.runtime.WindowSetSystemDefaultTheme();
+}
+
+export function WindowSetLightTheme() {
+ window.runtime.WindowSetLightTheme();
+}
+
+export function WindowSetDarkTheme() {
+ window.runtime.WindowSetDarkTheme();
+}
+
+export function WindowCenter() {
+ window.runtime.WindowCenter();
+}
+
+export function WindowSetTitle(title) {
+ window.runtime.WindowSetTitle(title);
+}
+
+export function WindowFullscreen() {
+ window.runtime.WindowFullscreen();
+}
+
+export function WindowUnfullscreen() {
+ window.runtime.WindowUnfullscreen();
+}
+
+export function WindowIsFullscreen() {
+ return window.runtime.WindowIsFullscreen();
+}
+
+export function WindowGetSize() {
+ return window.runtime.WindowGetSize();
+}
+
+export function WindowSetSize(width, height) {
+ window.runtime.WindowSetSize(width, height);
+}
+
+export function WindowSetMaxSize(width, height) {
+ window.runtime.WindowSetMaxSize(width, height);
+}
+
+export function WindowSetMinSize(width, height) {
+ window.runtime.WindowSetMinSize(width, height);
+}
+
+export function WindowSetPosition(x, y) {
+ window.runtime.WindowSetPosition(x, y);
+}
+
+export function WindowGetPosition() {
+ return window.runtime.WindowGetPosition();
+}
+
+export function WindowHide() {
+ window.runtime.WindowHide();
+}
+
+export function WindowShow() {
+ window.runtime.WindowShow();
+}
+
+export function WindowMaximise() {
+ window.runtime.WindowMaximise();
+}
+
+export function WindowToggleMaximise() {
+ window.runtime.WindowToggleMaximise();
+}
+
+export function WindowUnmaximise() {
+ window.runtime.WindowUnmaximise();
+}
+
+export function WindowIsMaximised() {
+ return window.runtime.WindowIsMaximised();
+}
+
+export function WindowMinimise() {
+ window.runtime.WindowMinimise();
+}
+
+export function WindowUnminimise() {
+ window.runtime.WindowUnminimise();
+}
+
+export function WindowSetBackgroundColour(R, G, B, A) {
+ window.runtime.WindowSetBackgroundColour(R, G, B, A);
+}
+
+export function ScreenGetAll() {
+ return window.runtime.ScreenGetAll();
+}
+
+export function WindowIsMinimised() {
+ return window.runtime.WindowIsMinimised();
+}
+
+export function WindowIsNormal() {
+ return window.runtime.WindowIsNormal();
+}
+
+export function BrowserOpenURL(url) {
+ window.runtime.BrowserOpenURL(url);
+}
+
+export function Environment() {
+ return window.runtime.Environment();
+}
+
+export function Quit() {
+ window.runtime.Quit();
+}
+
+export function Hide() {
+ window.runtime.Hide();
+}
+
+export function Show() {
+ window.runtime.Show();
+}
+
+export function ClipboardGetText() {
+ return window.runtime.ClipboardGetText();
+}
+
+export function ClipboardSetText(text) {
+ return window.runtime.ClipboardSetText(text);
+}
+
+/**
+ * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ *
+ * @export
+ * @callback OnFileDropCallback
+ * @param {number} x - x coordinate of the drop
+ * @param {number} y - y coordinate of the drop
+ * @param {string[]} paths - A list of file paths.
+ */
+
+/**
+ * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+ *
+ * @export
+ * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
+ */
+export function OnFileDrop(callback, useDropTarget) {
+ return window.runtime.OnFileDrop(callback, useDropTarget);
+}
+
+/**
+ * OnFileDropOff removes the drag and drop listeners and handlers.
+ */
+export function OnFileDropOff() {
+ return window.runtime.OnFileDropOff();
+}
+
+export function CanResolveFilePaths() {
+ return window.runtime.CanResolveFilePaths();
+}
+
+export function ResolveFilePaths(files) {
+ return window.runtime.ResolveFilePaths(files);
+}
+
+export function InitializeNotifications() {
+ return window.runtime.InitializeNotifications();
+}
+
+export function CleanupNotifications() {
+ return window.runtime.CleanupNotifications();
+}
+
+export function IsNotificationAvailable() {
+ return window.runtime.IsNotificationAvailable();
+}
+
+export function RequestNotificationAuthorization() {
+ return window.runtime.RequestNotificationAuthorization();
+}
+
+export function CheckNotificationAuthorization() {
+ return window.runtime.CheckNotificationAuthorization();
+}
+
+export function SendNotification(options) {
+ return window.runtime.SendNotification(options);
+}
+
+export function SendNotificationWithActions(options) {
+ return window.runtime.SendNotificationWithActions(options);
+}
+
+export function RegisterNotificationCategory(category) {
+ return window.runtime.RegisterNotificationCategory(category);
+}
+
+export function RemoveNotificationCategory(categoryId) {
+ return window.runtime.RemoveNotificationCategory(categoryId);
+}
+
+export function RemoveAllPendingNotifications() {
+ return window.runtime.RemoveAllPendingNotifications();
+}
+
+export function RemovePendingNotification(identifier) {
+ return window.runtime.RemovePendingNotification(identifier);
+}
+
+export function RemoveAllDeliveredNotifications() {
+ return window.runtime.RemoveAllDeliveredNotifications();
+}
+
+export function RemoveDeliveredNotification(identifier) {
+ return window.runtime.RemoveDeliveredNotification(identifier);
+}
+
+export function RemoveNotification(identifier) {
+ return window.runtime.RemoveNotification(identifier);
+}
\ No newline at end of file
diff --git a/global/build_debug.go b/global/build_debug.go
new file mode 100644
index 0000000..67649e9
--- /dev/null
+++ b/global/build_debug.go
@@ -0,0 +1,7 @@
+//go:build debug
+
+package global
+
+// IsDebug 标记是否为调试模式
+// 仅在使用 -tags debug 编译时为 true
+var IsDebug = true
diff --git a/global/build_release.go b/global/build_release.go
new file mode 100644
index 0000000..db4336b
--- /dev/null
+++ b/global/build_release.go
@@ -0,0 +1,7 @@
+//go:build !debug
+
+package global
+
+// IsDebug 标记是否为调试模式
+// 编译后为 false,不允许使用 debug 模式
+var IsDebug = false
diff --git a/global/config.go b/global/config.go
new file mode 100644
index 0000000..441a8bd
--- /dev/null
+++ b/global/config.go
@@ -0,0 +1,11 @@
+package global
+
+type GConfig struct {
+ Language string `json:"language"`
+ LogDir string `json:"log_dir"`
+}
+
+var GlobalConfig = &GConfig{
+ Language: "zh-CN",
+ LogDir: "logs",
+}
\ No newline at end of file
diff --git a/global/init.go b/global/init.go
new file mode 100644
index 0000000..84958d5
--- /dev/null
+++ b/global/init.go
@@ -0,0 +1,124 @@
+package global
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/fs"
+ "os"
+ Pa "path"
+)
+
+func Init() {
+ // 1. 创建必要目录
+ dirs := []string{
+ "lang",
+ GlobalConfig.LogDir,
+ }
+ for _, dir := range dirs {
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ os.MkdirAll(dir, 0755)
+ }
+ }
+
+ InitLogger()
+ Log.Info("日志系统初始化完成")
+
+ InitLang()
+ Log.Infof("语言系统初始化完成,当前语言: %s", useLangPath)
+}
+
+func GetLangTextMap() map[string]string {
+ langPack, err := GetLangPack()
+ if err != nil {
+ Log.Warnf("获取语言包失败: %v", err)
+ return make(map[string]string)
+ }
+ return langPack.Textmap
+}
+
+func GetLangPack() (*LanguagePack, error) {
+ langPath := Pa.Join(pathLang, useLangPath)
+
+ if cached, ok := langPackCache[langPath]; ok {
+ return cached, nil
+ }
+
+ if _, err := os.Stat(langPath); err == nil {
+ pack, err := tryLoadLangPack(langPath)
+ if err == nil {
+ langPackCache[langPath] = pack
+ }
+ return pack, err
+ }
+
+ embedPath := Pa.Join("lang", useLangPath)
+ pack, err := tryLoadLangPackFromEmbed(embedPath)
+ if err == nil {
+ langPackCache[langPath] = pack
+ }
+ return pack, err
+}
+
+func tryLoadLangPack(langPath string) (*LanguagePack, error) {
+ infoPath := Pa.Join(langPath, "info.json")
+ infoData, err := os.ReadFile(infoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ var langInfo LanguageInfo
+ if err := json.Unmarshal(infoData, &langInfo); err != nil {
+ return nil, err
+ }
+
+ textmapPath := Pa.Join(langPath, "textmap.json")
+ textmapData, err := os.ReadFile(textmapPath)
+ if err != nil {
+ return nil, fmt.Errorf("读取 textmap.json 失败: %v", err)
+ }
+
+ var textmap map[string]string
+ if err := json.Unmarshal(textmapData, &textmap); err != nil {
+ return nil, fmt.Errorf("解析 textmap.json 失败: %v", err)
+ }
+
+ return &LanguagePack{
+ LanguageInfo: langInfo,
+ Textmap: textmap,
+ }, nil
+}
+
+func tryLoadLangPackFromEmbed(langPath string) (*LanguagePack, error) {
+ if LangFS == nil {
+ return nil, fmt.Errorf("嵌入的文件系统未初始化")
+ }
+
+ infoPath := Pa.Join(langPath, "info.json")
+ infoData, err := fs.ReadFile(LangFS, infoPath)
+ if err != nil {
+ Log.Warnf("读取嵌入 info.json 失败: %v", err)
+ return nil, err
+ }
+
+ var langInfo LanguageInfo
+ if err := json.Unmarshal(infoData, &langInfo); err != nil {
+ Log.Warnf("解析嵌入 info.json 失败: %v", err)
+ return nil, err
+ }
+
+ textmapPath := Pa.Join(langPath, "textmap.json")
+ textmapData, err := fs.ReadFile(LangFS, textmapPath)
+ if err != nil {
+ return nil, fmt.Errorf("读取嵌入 textmap.json 失败: %v", err)
+ }
+
+ var textmap map[string]string
+ if err := json.Unmarshal(textmapData, &textmap); err != nil {
+ return nil, fmt.Errorf("解析嵌入 textmap.json 失败: %v", err)
+ }
+
+ return &LanguagePack{
+ LanguageInfo: langInfo,
+ Textmap: textmap,
+ }, nil
+}
diff --git a/global/lang.go b/global/lang.go
new file mode 100644
index 0000000..29adf61
--- /dev/null
+++ b/global/lang.go
@@ -0,0 +1,154 @@
+package global
+
+import (
+ "encoding/json"
+ "io/fs"
+ "os"
+ Pa "path"
+ "path/filepath"
+)
+
+var (
+ pathLang = "lang/"
+ useLangPath = "default"
+ allLangInfo = []LanguageInfo{}
+ LangFS fs.FS
+ langPackCache = map[string]*LanguagePack{}
+ langCodeToDir = map[string]string{}
+)
+
+type LanguageInfo struct {
+ LanguageName string `json:"language_name"`
+ LanguageCode string `json:"language_code"`
+ TextmapPath string `json:"textmap_path"`
+ TranslationProgress string `json:"translation_progress"`
+ Translator string `json:"translator"`
+ LastUpdated string `json:"last_updated"`
+ Version string `json:"version"`
+}
+
+type LanguagePack struct {
+ LanguageInfo
+ Textmap map[string]string `json:"textmap"`
+}
+
+func InitLang() {
+ useLang := GlobalConfig.Language
+ allLangInfo = []LanguageInfo{}
+ langCodeToDir = map[string]string{}
+
+ // 扫描文件系统语言目录
+ scanFileSystemLangs(langCodeToDir)
+ // 扫描嵌入文件系统语言
+ scanEmbeddedLangs(langCodeToDir)
+
+ // 设置当前语言路径
+ if dir, ok := langCodeToDir[useLang]; ok {
+ Log.Infof("找到匹配语言: %s -> %s", useLang, dir)
+ useLangPath = dir
+ } else {
+ Log.Infof("未找到匹配语言 %s,使用默认语言", useLang)
+ useLangPath = "default"
+ }
+}
+
+func scanFileSystemLangs(langDirMap map[string]string) {
+ err := filepath.WalkDir(pathLang, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ Log.Warnf("读取语言文件夹失败: %v", err)
+ return nil
+ }
+ if !d.IsDir() || path == pathLang {
+ return nil
+ }
+ return loadLangInfoFromFile(path, langDirMap, "[文件系统]")
+ })
+ if err != nil {
+ Log.Warnf("遍历文件系统 lang 目录出错: %v", err)
+ }
+}
+
+func scanEmbeddedLangs(langDirMap map[string]string) {
+ if LangFS == nil {
+ Log.Warnf("读取语言文件夹失败: %v", "LangFS 为空")
+ return
+ }
+
+ entries, err := fs.ReadDir(LangFS, "lang")
+ if err != nil {
+ Log.Warnf("读取语言文件夹失败: %v", err)
+ return
+ }
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ continue
+ }
+ loadLangInfoFromEmbed(entry.Name(), langDirMap, "[嵌入FS]")
+ }
+}
+
+func loadLangInfoFromFile(path string, langDirMap map[string]string, source string) error {
+ infoPath := Pa.Join(path, "info.json")
+ data, err := os.ReadFile(infoPath)
+ if err != nil {
+ Log.Warnf("读取语言文件夹失败: %v", err)
+ return nil
+ }
+ return parseAndAddLangInfo(data, filepath.Base(path), langDirMap, source)
+}
+
+func loadLangInfoFromEmbed(path string, langDirMap map[string]string, source string) {
+ infoPath := Pa.Join("lang", path, "info.json")
+ data, err := fs.ReadFile(LangFS, infoPath)
+ if err != nil {
+ Log.Warnf("读取语言文件夹失败: %v", err)
+ return
+ }
+ parseAndAddLangInfo(data, filepath.Base(path), langDirMap, source)
+}
+
+func parseAndAddLangInfo(data []byte, dirName string, langDirMap map[string]string, source string) error {
+ var info LanguageInfo
+ if err := json.Unmarshal(data, &info); err != nil {
+ Log.Warnf("解析语言文件夹失败: %v", err)
+ return nil
+ }
+ if info.LanguageCode == "" || info.TextmapPath == "" {
+ Log.Warnf("语言信息不完整: %s", dirName)
+ return nil
+ }
+ if !containsLang(allLangInfo, info.LanguageCode) {
+ allLangInfo = append(allLangInfo, info)
+ langDirMap[info.LanguageCode] = dirName
+ Log.Debugf("%s 识别到语言: %s (%s) -> %s", source, info.LanguageName, info.LanguageCode, dirName)
+ }
+ return nil
+}
+
+func containsLang(slice []LanguageInfo, code string) bool {
+ for _, s := range slice {
+ if s.LanguageCode == code {
+ return true
+ }
+ }
+ return false
+}
+
+func ClearLangCache() {
+ langPackCache = map[string]*LanguagePack{}
+}
+
+func GetLangInfoList() []LanguageInfo {
+ return allLangInfo
+}
+
+func UpdateCurrentLangPath() {
+ useLang := GlobalConfig.Language
+ if dir, ok := langCodeToDir[useLang]; ok {
+ Log.Infof("更新语言路径: %s -> %s", useLang, dir)
+ useLangPath = dir
+ } else {
+ Log.Infof("未找到匹配语言 %s,使用默认语言", useLang)
+ useLangPath = "default"
+ }
+}
diff --git a/global/logger.go b/global/logger.go
new file mode 100644
index 0000000..cf7e3d3
--- /dev/null
+++ b/global/logger.go
@@ -0,0 +1,190 @@
+package global
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+var Log *logrus.Logger
+
+const (
+ red = 31
+ yellow = 33
+ blue = 36
+ gray = 37
+)
+
+// LogFormatter 控制台日志格式化器(带颜色)
+type LogFormatter struct{}
+
+func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var levelColor int
+ switch entry.Level {
+ case logrus.DebugLevel, logrus.TraceLevel:
+ levelColor = gray
+ case logrus.WarnLevel:
+ levelColor = yellow
+ case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
+ levelColor = red
+ default:
+ levelColor = blue
+ }
+
+ var b *bytes.Buffer
+ if entry.Buffer != nil {
+ b = entry.Buffer
+ } else {
+ b = &bytes.Buffer{}
+ }
+
+ timestamp := entry.Time.Format("2006/01/02 15:04:05")
+ levelStr := strings.ToUpper(entry.Level.String())
+
+ if IsDebug && entry.HasCaller() {
+ funcVal := entry.Caller.Function
+ fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
+ fmt.Fprintf(b, "\033[%dm[%s]\033[0m [%s] \033[%dm[%s]\033[0m %s %s: %s\n",
+ colorGreen, processName, timestamp, levelColor, levelStr, funcVal, fileVal, entry.Message)
+ } else {
+ fmt.Fprintf(b, "\033[%dm[%s]\033[0m [%s] \033[%dm[%s]\033[0m %s\n",
+ colorGreen, processName, timestamp, levelColor, levelStr, entry.Message)
+ }
+ return b.Bytes(), nil
+}
+
+// FileFormatter 文件日志格式化器(不包含 ANSI 颜色代码)
+type FileFormatter struct{}
+
+func (f *FileFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var b *bytes.Buffer
+ if entry.Buffer != nil {
+ b = entry.Buffer
+ } else {
+ b = &bytes.Buffer{}
+ }
+
+ timestamp := entry.Time.Format("2006/01/02 15:04:05")
+ levelStr := strings.ToUpper(entry.Level.String())
+
+ if IsDebug && entry.HasCaller() {
+ funcVal := entry.Caller.Function
+ fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
+ fmt.Fprintf(b, "[%s] [%s] [%s] %s %s: %s\n",
+ processName, timestamp, levelStr, funcVal, fileVal, entry.Message)
+ } else {
+ fmt.Fprintf(b, "[%s] [%s] [%s] %s\n",
+ processName, timestamp, levelStr, entry.Message)
+ }
+ return b.Bytes(), nil
+}
+
+var processName string
+
+func init() {
+ processName = filepath.Base(os.Args[0])
+ processName = strings.TrimSuffix(processName, filepath.Ext(processName))
+}
+
+func GetProcessName() string {
+ return processName
+}
+
+// SetLogLevel 动态设置日志等级
+func SetLogLevel(level logrus.Level) {
+ if Log != nil {
+ Log.SetLevel(level)
+ }
+}
+
+// InitLogger 初始化日志系统,每次运行时按启动时间保存日志文件,自动清理旧日志
+func InitLogger() {
+ Log = logrus.New()
+ Log.SetFormatter(&LogFormatter{})
+ Log.SetLevel(logrus.InfoLevel)
+
+ if IsDebug {
+ Log.SetReportCaller(true)
+ } else {
+ Log.SetReportCaller(false)
+ }
+
+ logDir := GlobalConfig.LogDir
+ if err := os.MkdirAll(logDir, 0755); err != nil {
+ Log.Warnf("无法创建日志目录: %v", err)
+ }
+
+ cleanOldLogs(logDir, 10)
+
+ startTime := time.Now().Format("2006-01-02_15-04-05")
+ logFile := filepath.Join(logDir, startTime+".log")
+ file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ Log.Warnf("无法打开日志文件: %v", err)
+ } else {
+ Log.SetOutput(os.Stdout)
+ Log.AddHook(&FileHook{file: file})
+ }
+}
+
+const colorGreen = 32
+
+// FileHook 将日志同时写入文件的 Hook
+type FileHook struct {
+ file *os.File
+}
+
+func (h *FileHook) Levels() []logrus.Level {
+ return logrus.AllLevels
+}
+
+func (h *FileHook) Fire(entry *logrus.Entry) error {
+ formatter := &FileFormatter{}
+ line, err := formatter.Format(entry)
+ if err != nil {
+ return err
+ }
+ _, err = h.file.Write(line)
+ return err
+}
+
+// cleanOldLogs 清理旧日志文件,保留最新的 maxKeep 个日志
+func cleanOldLogs(logDir string, maxKeep int) {
+ entries, err := os.ReadDir(logDir)
+ if err != nil {
+ return
+ }
+
+ var logFiles []os.DirEntry
+ for _, entry := range entries {
+ if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") {
+ logFiles = append(logFiles, entry)
+ }
+ }
+
+ if len(logFiles) <= maxKeep {
+ return
+ }
+
+ // 文件名包含时间戳,按文件名排序即按时间排序
+ sort.Slice(logFiles, func(i, j int) bool {
+ return logFiles[i].Name() < logFiles[j].Name()
+ })
+
+ filesToDelete := logFiles[:len(logFiles)-maxKeep]
+ for _, file := range filesToDelete {
+ os.Remove(filepath.Join(logDir, file.Name()))
+ }
+}
+
+func GetRuntimeInfo() string {
+ return fmt.Sprintf("Go %s, OS %s, Arch %s", runtime.Version(), runtime.GOOS, runtime.GOARCH)
+}
diff --git a/global/version.go b/global/version.go
new file mode 100644
index 0000000..f9c2603
--- /dev/null
+++ b/global/version.go
@@ -0,0 +1,6 @@
+package global
+
+// Version 版本号,通过 ldflags 注入
+// 编译时: wails build -ldflags "-X webhelper/global.Version=v1.2.3"
+// 开发时默认为 "dev"
+var Version = "dev"
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..a739697
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,45 @@
+module webhelper
+
+go 1.23
+
+require (
+ github.com/dop251/goja v0.0.0-20260311135729-065cd970411c
+ github.com/gorilla/websocket v1.5.3
+ github.com/sirupsen/logrus v1.9.3
+ github.com/wailsapp/wails/v2 v2.12.0
+)
+
+require (
+ git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
+ github.com/bep/debounce v1.2.1 // indirect
+ github.com/dlclark/regexp2 v1.11.4 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
+ github.com/labstack/echo/v4 v4.13.3 // indirect
+ github.com/labstack/gommon v0.4.2 // indirect
+ github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
+ github.com/leaanthony/gosod v1.0.4 // indirect
+ github.com/leaanthony/slicer v1.6.0 // indirect
+ github.com/leaanthony/u v1.1.1 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/samber/lo v1.49.1 // indirect
+ github.com/tkrajina/go-reflector v0.5.8 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v1.2.2 // indirect
+ github.com/wailsapp/go-webview2 v1.0.22 // indirect
+ github.com/wailsapp/mimetype v1.4.1 // indirect
+ golang.org/x/crypto v0.33.0 // indirect
+ golang.org/x/net v0.35.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
+)
+
+// replace github.com/wailsapp/wails/v2 v2.10.1 => C:\Users\mogum\go\pkg\mod
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..3e03409
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,104 @@
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
+github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
+github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
+github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20260311135729-065cd970411c h1:OcLmPfx1T1RmZVHHFwWMPaZDdRf0DBMZOFMVWJa7Pdk=
+github.com/dop251/goja v0.0.0-20260311135729-065cd970411c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
+github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
+github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
+github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
+github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
+github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
+github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
+github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
+github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
+github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
+github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
+github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
+github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
+github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
+github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
+github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
+github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
+github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
+github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
+github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
+github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
+github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
+github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c=
+github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/lang/default/info.json b/lang/default/info.json
new file mode 100644
index 0000000..6a87d07
--- /dev/null
+++ b/lang/default/info.json
@@ -0,0 +1,9 @@
+{
+ "language_name": "简体中文",
+ "language_code": "zh-CN",
+ "textmap_path": "textmap.json",
+ "translation_progress": "100%",
+ "translator": "MoGuQAQ",
+ "last_updated": "2025-07-26",
+ "version": "1.0.0"
+}
\ No newline at end of file
diff --git a/lang/default/textmap.json b/lang/default/textmap.json
new file mode 100644
index 0000000..6e70559
--- /dev/null
+++ b/lang/default/textmap.json
@@ -0,0 +1,160 @@
+{
+ "app.tab.webdebug": "网页调试",
+ "app.tab.jsdebug": "JS调试",
+ "app.tab.jsonparser": "JSON解析",
+ "app.tab.socketdebug": "Socket调试",
+ "app.tab.settings": "程序设置",
+
+ "header.app_name": "WebHelper",
+
+ "webdebug.url_placeholder": "输入请求地址",
+ "webdebug.btn_send": "发送",
+ "webdebug.tab.headers": "请求头",
+ "webdebug.tab.body": "请求体",
+ "webdebug.tab.cookie": "Cookie",
+ "webdebug.tab.advanced": "高级设置",
+ "webdebug.header.key_placeholder": "Key",
+ "webdebug.header.value_placeholder": "Value",
+ "webdebug.header.add": "添加请求头",
+ "webdebug.body.type_text": "文本",
+ "webdebug.body.type_json": "JSON",
+ "webdebug.body.type_form": "表单",
+ "webdebug.body.type_file": "文件",
+ "webdebug.body.file_placeholder": "选择文件",
+ "webdebug.body.file_btn": "选择文件",
+ "webdebug.body.json_placeholder": "输入 JSON 数据",
+ "webdebug.body.text_placeholder": "输入请求体内容",
+ "webdebug.body.cookie_placeholder": "Cookie 字符串 (格式: name1=value1; name2=value2)",
+ "webdebug.advanced.proxy_label": "代理地址",
+ "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890",
+ "webdebug.advanced.timeout_label": "超时时间",
+ "webdebug.advanced.timeout_unit": "秒",
+ "webdebug.advanced.content_type_label": "Content-Type",
+ "webdebug.advanced.content_type_placeholder": "自动检测",
+ "webdebug.advanced.insecure_label": "忽略证书",
+ "webdebug.advanced.log_divider": "日志设置",
+ "webdebug.advanced.save_log_label": "记录日志",
+ "webdebug.advanced.log_manage_label": "日志管理",
+ "webdebug.advanced.view_logs_btn": "查看日志记录",
+ "webdebug.response.title": "响应结果",
+ "webdebug.response.tab.body": "响应正文",
+ "webdebug.response.tab.headers": "响应头",
+ "webdebug.response.tab.more": "更多信息",
+ "webdebug.response.body_placeholder": "响应内容将显示在这里",
+ "webdebug.response.send_to_json": "发送到 JSON 解析器",
+ "webdebug.response.headers_placeholder": "响应头将显示在这里",
+ "webdebug.response.status_code_label": "状态码",
+ "webdebug.response.status_label": "状态",
+ "webdebug.response.content_type_label": "Content-Type",
+ "webdebug.response.content_length_label": "内容长度",
+ "webdebug.response.request_time_label": "请求耗时",
+ "webdebug.log.title": "请求日志",
+ "webdebug.log.search_placeholder": "搜索日志...",
+ "webdebug.log.clear": "清空",
+ "webdebug.log.empty": "暂无日志",
+ "webdebug.log.tab.request_info": "请求信息",
+ "webdebug.log.tab.response_info": "响应信息",
+ "webdebug.log.request.method_label": "请求方法",
+ "webdebug.log.request.url_label": "请求地址",
+ "webdebug.log.request.host_label": "主机",
+ "webdebug.log.request.path_label": "路径",
+ "webdebug.log.request.body_label": "请求体",
+ "webdebug.log.response.status_code_label": "状态码",
+ "webdebug.log.response.status_label": "状态",
+ "webdebug.log.response.size_label": "响应大小",
+ "webdebug.log.response.time_label": "耗时",
+ "webdebug.log.response.body_label": "响应正文",
+ "webdebug.log.load_to_debug": "加载到调试页面",
+ "webdebug.log.select_hint": "选择一条日志查看详情",
+ "webdebug.msg.url_required": "请输入请求地址",
+ "webdebug.msg.request_failed": "请求失败: ",
+ "webdebug.msg.load_logs_failed": "加载日志失败: ",
+ "webdebug.msg.delete_failed": "删除失败: ",
+ "webdebug.msg.clear_failed": "清空失败: ",
+
+ "jsdebug.title": "JavaScript 调试器",
+ "jsdebug.tag.duration": "耗时: ",
+ "jsdebug.tag.success": "成功",
+ "jsdebug.tag.failed": "失败",
+ "jsdebug.btn.run": "运行",
+ "jsdebug.btn.clear": "清空",
+ "jsdebug.section.code": "代码",
+ "jsdebug.section.output": "输出",
+ "jsdebug.btn.copy": "复制",
+ "jsdebug.section.return_value": "返回值",
+ "jsdebug.code_placeholder": "输入 JavaScript 代码",
+ "jsdebug.output_placeholder": "console.log 输出将显示在这里",
+ "jsdebug.return_placeholder": "函数返回值将显示在这里",
+ "jsdebug.msg.code_required": "请输入代码",
+ "jsdebug.msg.execute_failed": "执行失败: ",
+ "jsdebug.msg.copy_prefix": "返回值: ",
+ "jsdebug.msg.copied": "已复制到剪贴板",
+ "jsdebug.msg.copy_failed": "复制失败",
+
+ "jsonparser.title": "JSON 解析器",
+ "jsonparser.stats.objects": "对象: ",
+ "jsonparser.stats.arrays": "数组: ",
+ "jsonparser.stats.strings": "字符串: ",
+ "jsonparser.stats.numbers": "数字: ",
+ "jsonparser.view.tree": "树形",
+ "jsonparser.view.table": "表格",
+ "jsonparser.view.raw": "原始",
+ "jsonparser.btn.copy": "复制",
+ "jsonparser.btn.clear": "清空",
+ "jsonparser.section.input": "输入",
+ "jsonparser.section.result": "解析结果",
+ "jsonparser.input_placeholder": "粘贴或输入 JSON 内容",
+ "jsonparser.raw_placeholder": "格式化后的 JSON 将显示在这里",
+ "jsonparser.empty": "请输入 JSON 数据",
+ "jsonparser.table.column.key": "Key",
+ "jsonparser.table.column.index": "Index",
+ "jsonparser.table.column.value": "Value",
+ "jsonparser.table.column.action": "操作",
+ "jsonparser.msg.parse_error": "JSON 解析错误: ",
+ "jsonparser.msg.copied": "已复制到剪贴板",
+ "jsonparser.msg.copy_failed": "复制失败",
+ "jsonparser.msg.received": "已接收 JSON 数据",
+
+ "socket.connection.title": "连接配置",
+ "socket.connection.status.connected": "已连接",
+ "socket.connection.status.disconnected": "未连接",
+ "socket.connection.btn.connect": "连接",
+ "socket.connection.btn.disconnect": "断开",
+ "socket.connection.label.protocol": "协议",
+ "socket.connection.label.address": "地址",
+ "socket.connection.label.port": "端口",
+ "socket.connection.label.path": "路径",
+ "socket.connection.label.headers": "请求头",
+ "socket.connection.placeholder.host": "主机地址",
+ "socket.connection.placeholder.port": "8080",
+ "socket.connection.placeholder.path": "/",
+ "socket.connection.placeholder.headers": "可选,格式: Key: Value",
+ "socket.message.title": "消息记录",
+ "socket.message.auto_scroll": "自动滚动",
+ "socket.message.btn.clear": "清空",
+ "socket.message.tag.sent": "发送",
+ "socket.message.tag.received": "接收",
+ "socket.message.tag.error": "错误",
+ "socket.message.tag.system": "系统",
+ "socket.message.empty": "暂无消息",
+ "socket.message.placeholder": "输入消息内容",
+ "socket.message.btn.send": "发送",
+ "socket.msg.host_required": "请输入主机地址",
+ "socket.msg.port_invalid": "端口格式错误",
+ "socket.msg.connect_failed": "连接失败: ",
+ "socket.msg.disconnect_failed": "断开连接失败: ",
+ "socket.msg.not_connected": "请先连接",
+ "socket.msg.send_failed": "发送失败: ",
+
+ "settings.title": "程序设置",
+ "settings.form.language_label": "语言",
+ "settings.form.proxy_divider": "代理",
+ "settings.form.enable_proxy_label": "启用代理",
+ "settings.form.proxy_address_label": "代理地址",
+ "settings.form.proxy_address_placeholder": "127.0.0.1",
+ "settings.form.proxy_port_label": "代理端口",
+ "settings.form.btn.save": "保存设置",
+ "settings.form.btn.reset": "恢复默认",
+ "settings.msg.saved": "设置已保存",
+ "settings.msg.reset": "设置已重置"
+}
diff --git a/lang/en/info.json b/lang/en/info.json
new file mode 100644
index 0000000..94254d1
--- /dev/null
+++ b/lang/en/info.json
@@ -0,0 +1,9 @@
+{
+ "language_name": "English",
+ "language_code": "en-US",
+ "textmap_path": "textmap.json",
+ "translation_progress": "100%",
+ "translator": "MoGuQAQ",
+ "last_updated": "2025-07-26",
+ "version": "1.0.0"
+}
diff --git a/lang/en/textmap.json b/lang/en/textmap.json
new file mode 100644
index 0000000..b7fd43c
--- /dev/null
+++ b/lang/en/textmap.json
@@ -0,0 +1,160 @@
+{
+ "app.tab.webdebug": "Web Debug",
+ "app.tab.jsdebug": "JS Debug",
+ "app.tab.jsonparser": "JSON Parser",
+ "app.tab.socketdebug": "Socket Debug",
+ "app.tab.settings": "Settings",
+
+ "header.app_name": "WebHelper",
+
+ "webdebug.url_placeholder": "Enter request URL",
+ "webdebug.btn_send": "Send",
+ "webdebug.tab.headers": "Headers",
+ "webdebug.tab.body": "Body",
+ "webdebug.tab.cookie": "Cookie",
+ "webdebug.tab.advanced": "Advanced",
+ "webdebug.header.key_placeholder": "Key",
+ "webdebug.header.value_placeholder": "Value",
+ "webdebug.header.add": "Add Header",
+ "webdebug.body.type_text": "Text",
+ "webdebug.body.type_json": "JSON",
+ "webdebug.body.type_form": "Form",
+ "webdebug.body.type_file": "File",
+ "webdebug.body.file_placeholder": "Select file",
+ "webdebug.body.file_btn": "Select File",
+ "webdebug.body.json_placeholder": "Enter JSON data",
+ "webdebug.body.text_placeholder": "Enter request body",
+ "webdebug.body.cookie_placeholder": "Cookie string (format: name1=value1; name2=value2)",
+ "webdebug.advanced.proxy_label": "Proxy",
+ "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890",
+ "webdebug.advanced.timeout_label": "Timeout",
+ "webdebug.advanced.timeout_unit": "sec",
+ "webdebug.advanced.content_type_label": "Content-Type",
+ "webdebug.advanced.content_type_placeholder": "Auto detect",
+ "webdebug.advanced.insecure_label": "Ignore Certificate",
+ "webdebug.advanced.log_divider": "Log Settings",
+ "webdebug.advanced.save_log_label": "Save Log",
+ "webdebug.advanced.log_manage_label": "Log Management",
+ "webdebug.advanced.view_logs_btn": "View Logs",
+ "webdebug.response.title": "Response",
+ "webdebug.response.tab.body": "Body",
+ "webdebug.response.tab.headers": "Headers",
+ "webdebug.response.tab.more": "More Info",
+ "webdebug.response.body_placeholder": "Response body will appear here",
+ "webdebug.response.send_to_json": "Send to JSON Parser",
+ "webdebug.response.headers_placeholder": "Response headers will appear here",
+ "webdebug.response.status_code_label": "Status Code",
+ "webdebug.response.status_label": "Status",
+ "webdebug.response.content_type_label": "Content-Type",
+ "webdebug.response.content_length_label": "Content Length",
+ "webdebug.response.request_time_label": "Time",
+ "webdebug.log.title": "Request Logs",
+ "webdebug.log.search_placeholder": "Search logs...",
+ "webdebug.log.clear": "Clear",
+ "webdebug.log.empty": "No logs",
+ "webdebug.log.tab.request_info": "Request Info",
+ "webdebug.log.tab.response_info": "Response Info",
+ "webdebug.log.request.method_label": "Method",
+ "webdebug.log.request.url_label": "URL",
+ "webdebug.log.request.host_label": "Host",
+ "webdebug.log.request.path_label": "Path",
+ "webdebug.log.request.body_label": "Body",
+ "webdebug.log.response.status_code_label": "Status Code",
+ "webdebug.log.response.status_label": "Status",
+ "webdebug.log.response.size_label": "Size",
+ "webdebug.log.response.time_label": "Time",
+ "webdebug.log.response.body_label": "Response Body",
+ "webdebug.log.load_to_debug": "Load to Debugger",
+ "webdebug.log.select_hint": "Select a log to view details",
+ "webdebug.msg.url_required": "Please enter a URL",
+ "webdebug.msg.request_failed": "Request failed: ",
+ "webdebug.msg.load_logs_failed": "Failed to load logs: ",
+ "webdebug.msg.delete_failed": "Delete failed: ",
+ "webdebug.msg.clear_failed": "Clear failed: ",
+
+ "jsdebug.title": "JavaScript Debugger",
+ "jsdebug.tag.duration": "Duration: ",
+ "jsdebug.tag.success": "Success",
+ "jsdebug.tag.failed": "Failed",
+ "jsdebug.btn.run": "Run",
+ "jsdebug.btn.clear": "Clear",
+ "jsdebug.section.code": "Code",
+ "jsdebug.section.output": "Output",
+ "jsdebug.btn.copy": "Copy",
+ "jsdebug.section.return_value": "Return Value",
+ "jsdebug.code_placeholder": "Enter JavaScript code",
+ "jsdebug.output_placeholder": "console.log output will appear here",
+ "jsdebug.return_placeholder": "Function return value will appear here",
+ "jsdebug.msg.code_required": "Please enter code",
+ "jsdebug.msg.execute_failed": "Execution failed: ",
+ "jsdebug.msg.copy_prefix": "Return value: ",
+ "jsdebug.msg.copied": "Copied to clipboard",
+ "jsdebug.msg.copy_failed": "Copy failed",
+
+ "jsonparser.title": "JSON Parser",
+ "jsonparser.stats.objects": "Objects: ",
+ "jsonparser.stats.arrays": "Arrays: ",
+ "jsonparser.stats.strings": "Strings: ",
+ "jsonparser.stats.numbers": "Numbers: ",
+ "jsonparser.view.tree": "Tree",
+ "jsonparser.view.table": "Table",
+ "jsonparser.view.raw": "Raw",
+ "jsonparser.btn.copy": "Copy",
+ "jsonparser.btn.clear": "Clear",
+ "jsonparser.section.input": "Input",
+ "jsonparser.section.result": "Result",
+ "jsonparser.input_placeholder": "Paste or enter JSON content",
+ "jsonparser.raw_placeholder": "Formatted JSON will appear here",
+ "jsonparser.empty": "Enter JSON data to parse",
+ "jsonparser.table.column.key": "Key",
+ "jsonparser.table.column.index": "Index",
+ "jsonparser.table.column.value": "Value",
+ "jsonparser.table.column.action": "Action",
+ "jsonparser.msg.parse_error": "JSON parse error: ",
+ "jsonparser.msg.copied": "Copied to clipboard",
+ "jsonparser.msg.copy_failed": "Copy failed",
+ "jsonparser.msg.received": "JSON data received",
+
+ "socket.connection.title": "Connection",
+ "socket.connection.status.connected": "Connected",
+ "socket.connection.status.disconnected": "Disconnected",
+ "socket.connection.btn.connect": "Connect",
+ "socket.connection.btn.disconnect": "Disconnect",
+ "socket.connection.label.protocol": "Protocol",
+ "socket.connection.label.address": "Address",
+ "socket.connection.label.port": "Port",
+ "socket.connection.label.path": "Path",
+ "socket.connection.label.headers": "Headers",
+ "socket.connection.placeholder.host": "Host address",
+ "socket.connection.placeholder.port": "8080",
+ "socket.connection.placeholder.path": "/",
+ "socket.connection.placeholder.headers": "Optional, format: Key: Value",
+ "socket.message.title": "Messages",
+ "socket.message.auto_scroll": "Auto Scroll",
+ "socket.message.btn.clear": "Clear",
+ "socket.message.tag.sent": "Sent",
+ "socket.message.tag.received": "Received",
+ "socket.message.tag.error": "Error",
+ "socket.message.tag.system": "System",
+ "socket.message.empty": "No messages",
+ "socket.message.placeholder": "Enter message",
+ "socket.message.btn.send": "Send",
+ "socket.msg.host_required": "Please enter host address",
+ "socket.msg.port_invalid": "Invalid port",
+ "socket.msg.connect_failed": "Connection failed: ",
+ "socket.msg.disconnect_failed": "Disconnect failed: ",
+ "socket.msg.not_connected": "Not connected",
+ "socket.msg.send_failed": "Send failed: ",
+
+ "settings.title": "Settings",
+ "settings.form.language_label": "Language",
+ "settings.form.proxy_divider": "Proxy",
+ "settings.form.enable_proxy_label": "Enable Proxy",
+ "settings.form.proxy_address_label": "Proxy Address",
+ "settings.form.proxy_address_placeholder": "127.0.0.1",
+ "settings.form.proxy_port_label": "Proxy Port",
+ "settings.form.btn.save": "Save Settings",
+ "settings.form.btn.reset": "Reset to Default",
+ "settings.msg.saved": "Settings saved",
+ "settings.msg.reset": "Settings reset"
+}
diff --git a/lang/ja/info.json b/lang/ja/info.json
new file mode 100644
index 0000000..0fbd19f
--- /dev/null
+++ b/lang/ja/info.json
@@ -0,0 +1,9 @@
+{
+ "language_name": "日本語",
+ "language_code": "ja-JP",
+ "textmap_path": "textmap.json",
+ "translation_progress": "100%",
+ "translator": "MoGuQAQ",
+ "last_updated": "2025-07-26",
+ "version": "1.0.0"
+}
diff --git a/lang/ja/textmap.json b/lang/ja/textmap.json
new file mode 100644
index 0000000..9164821
--- /dev/null
+++ b/lang/ja/textmap.json
@@ -0,0 +1,160 @@
+{
+ "app.tab.webdebug": "ウェブデバッグ",
+ "app.tab.jsdebug": "JSデバッグ",
+ "app.tab.jsonparser": "JSONパーサー",
+ "app.tab.socketdebug": "Socketデバッグ",
+ "app.tab.settings": "設定",
+
+ "header.app_name": "WebHelper",
+
+ "webdebug.url_placeholder": "リクエストURLを入力",
+ "webdebug.btn_send": "送信",
+ "webdebug.tab.headers": "ヘッダー",
+ "webdebug.tab.body": "ボディ",
+ "webdebug.tab.cookie": "Cookie",
+ "webdebug.tab.advanced": "詳細設定",
+ "webdebug.header.key_placeholder": "キー",
+ "webdebug.header.value_placeholder": "値",
+ "webdebug.header.add": "ヘッダーを追加",
+ "webdebug.body.type_text": "テキスト",
+ "webdebug.body.type_json": "JSON",
+ "webdebug.body.type_form": "フォーム",
+ "webdebug.body.type_file": "ファイル",
+ "webdebug.body.file_placeholder": "ファイルを選択",
+ "webdebug.body.file_btn": "ファイルを選択",
+ "webdebug.body.json_placeholder": "JSONデータを入力",
+ "webdebug.body.text_placeholder": "リクエストボディを入力",
+ "webdebug.body.cookie_placeholder": "Cookie文字列(形式: name1=value1; name2=value2)",
+ "webdebug.advanced.proxy_label": "プロキシ",
+ "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890",
+ "webdebug.advanced.timeout_label": "タイムアウト",
+ "webdebug.advanced.timeout_unit": "秒",
+ "webdebug.advanced.content_type_label": "Content-Type",
+ "webdebug.advanced.content_type_placeholder": "自動検出",
+ "webdebug.advanced.insecure_label": "証明書を無視",
+ "webdebug.advanced.log_divider": "ログ設定",
+ "webdebug.advanced.save_log_label": "ログを保存",
+ "webdebug.advanced.log_manage_label": "ログ管理",
+ "webdebug.advanced.view_logs_btn": "ログを表示",
+ "webdebug.response.title": "レスポンス",
+ "webdebug.response.tab.body": "本文",
+ "webdebug.response.tab.headers": "ヘッダー",
+ "webdebug.response.tab.more": "詳細情報",
+ "webdebug.response.body_placeholder": "レスポンス本文がここに表示されます",
+ "webdebug.response.send_to_json": "JSONパーサーに送信",
+ "webdebug.response.headers_placeholder": "レスポンスヘッダーがここに表示されます",
+ "webdebug.response.status_code_label": "ステータスコード",
+ "webdebug.response.status_label": "ステータス",
+ "webdebug.response.content_type_label": "Content-Type",
+ "webdebug.response.content_length_label": "コンテンツ長",
+ "webdebug.response.request_time_label": "所要時間",
+ "webdebug.log.title": "リクエストログ",
+ "webdebug.log.search_placeholder": "ログを検索...",
+ "webdebug.log.clear": "クリア",
+ "webdebug.log.empty": "ログなし",
+ "webdebug.log.tab.request_info": "リクエスト情報",
+ "webdebug.log.tab.response_info": "レスポンス情報",
+ "webdebug.log.request.method_label": "メソッド",
+ "webdebug.log.request.url_label": "URL",
+ "webdebug.log.request.host_label": "ホスト",
+ "webdebug.log.request.path_label": "パス",
+ "webdebug.log.request.body_label": "ボディ",
+ "webdebug.log.response.status_code_label": "ステータスコード",
+ "webdebug.log.response.status_label": "ステータス",
+ "webdebug.log.response.size_label": "サイズ",
+ "webdebug.log.response.time_label": "所要時間",
+ "webdebug.log.response.body_label": "レスポンス本文",
+ "webdebug.log.load_to_debug": "デバッガーに読み込む",
+ "webdebug.log.select_hint": "ログを選択して詳細を表示",
+ "webdebug.msg.url_required": "URLを入力してください",
+ "webdebug.msg.request_failed": "リクエスト失敗: ",
+ "webdebug.msg.load_logs_failed": "ログの読み込みに失敗: ",
+ "webdebug.msg.delete_failed": "削除に失敗: ",
+ "webdebug.msg.clear_failed": "クリアに失敗: ",
+
+ "jsdebug.title": "JavaScriptデバッガー",
+ "jsdebug.tag.duration": "所要時間: ",
+ "jsdebug.tag.success": "成功",
+ "jsdebug.tag.failed": "失敗",
+ "jsdebug.btn.run": "実行",
+ "jsdebug.btn.clear": "クリア",
+ "jsdebug.section.code": "コード",
+ "jsdebug.section.output": "出力",
+ "jsdebug.btn.copy": "コピー",
+ "jsdebug.section.return_value": "戻り値",
+ "jsdebug.code_placeholder": "JavaScriptコードを入力",
+ "jsdebug.output_placeholder": "console.log出力がここに表示されます",
+ "jsdebug.return_placeholder": "関数の戻り値がここに表示されます",
+ "jsdebug.msg.code_required": "コードを入力してください",
+ "jsdebug.msg.execute_failed": "実行失敗: ",
+ "jsdebug.msg.copy_prefix": "戻り値: ",
+ "jsdebug.msg.copied": "クリップボードにコピーしました",
+ "jsdebug.msg.copy_failed": "コピーに失敗しました",
+
+ "jsonparser.title": "JSONパーサー",
+ "jsonparser.stats.objects": "オブジェクト: ",
+ "jsonparser.stats.arrays": "配列: ",
+ "jsonparser.stats.strings": "文字列: ",
+ "jsonparser.stats.numbers": "数値: ",
+ "jsonparser.view.tree": "ツリー",
+ "jsonparser.view.table": "テーブル",
+ "jsonparser.view.raw": "生データ",
+ "jsonparser.btn.copy": "コピー",
+ "jsonparser.btn.clear": "クリア",
+ "jsonparser.section.input": "入力",
+ "jsonparser.section.result": "解析結果",
+ "jsonparser.input_placeholder": "JSONコンテンツを貼り付けまたは入力",
+ "jsonparser.raw_placeholder": "フォーマットされたJSONがここに表示されます",
+ "jsonparser.empty": "JSONデータを入力してください",
+ "jsonparser.table.column.key": "キー",
+ "jsonparser.table.column.index": "インデックス",
+ "jsonparser.table.column.value": "値",
+ "jsonparser.table.column.action": "操作",
+ "jsonparser.msg.parse_error": "JSON解析エラー: ",
+ "jsonparser.msg.copied": "クリップボードにコピーしました",
+ "jsonparser.msg.copy_failed": "コピーに失敗しました",
+ "jsonparser.msg.received": "JSONデータを受信しました",
+
+ "socket.connection.title": "接続設定",
+ "socket.connection.status.connected": "接続済み",
+ "socket.connection.status.disconnected": "未接続",
+ "socket.connection.btn.connect": "接続",
+ "socket.connection.btn.disconnect": "切断",
+ "socket.connection.label.protocol": "プロトコル",
+ "socket.connection.label.address": "アドレス",
+ "socket.connection.label.port": "ポート",
+ "socket.connection.label.path": "パス",
+ "socket.connection.label.headers": "ヘッダー",
+ "socket.connection.placeholder.host": "ホストアドレス",
+ "socket.connection.placeholder.port": "8080",
+ "socket.connection.placeholder.path": "/",
+ "socket.connection.placeholder.headers": "オプション、形式: Key: Value",
+ "socket.message.title": "メッセージ履歴",
+ "socket.message.auto_scroll": "自動スクロール",
+ "socket.message.btn.clear": "クリア",
+ "socket.message.tag.sent": "送信",
+ "socket.message.tag.received": "受信",
+ "socket.message.tag.error": "エラー",
+ "socket.message.tag.system": "システム",
+ "socket.message.empty": "メッセージなし",
+ "socket.message.placeholder": "メッセージを入力",
+ "socket.message.btn.send": "送信",
+ "socket.msg.host_required": "ホストアドレスを入力してください",
+ "socket.msg.port_invalid": "ポート番号が無効です",
+ "socket.msg.connect_failed": "接続失敗: ",
+ "socket.msg.disconnect_failed": "切断失敗: ",
+ "socket.msg.not_connected": "未接続です",
+ "socket.msg.send_failed": "送信失敗: ",
+
+ "settings.title": "設定",
+ "settings.form.language_label": "言語",
+ "settings.form.proxy_divider": "プロキシ",
+ "settings.form.enable_proxy_label": "プロキシを有効にする",
+ "settings.form.proxy_address_label": "プロキシアドレス",
+ "settings.form.proxy_address_placeholder": "127.0.0.1",
+ "settings.form.proxy_port_label": "プロキシポート",
+ "settings.form.btn.save": "設定を保存",
+ "settings.form.btn.reset": "デフォルトに戻す",
+ "settings.msg.saved": "設定を保存しました",
+ "settings.msg.reset": "設定をリセットしました"
+}
diff --git a/lang/ko/info.json b/lang/ko/info.json
new file mode 100644
index 0000000..a70bc38
--- /dev/null
+++ b/lang/ko/info.json
@@ -0,0 +1,9 @@
+{
+ "language_name": "한국어",
+ "language_code": "ko-KR",
+ "textmap_path": "textmap.json",
+ "translation_progress": "100%",
+ "translator": "MoGuQAQ",
+ "last_updated": "2025-07-26",
+ "version": "1.0.0"
+}
diff --git a/lang/ko/textmap.json b/lang/ko/textmap.json
new file mode 100644
index 0000000..62734fa
--- /dev/null
+++ b/lang/ko/textmap.json
@@ -0,0 +1,160 @@
+{
+ "app.tab.webdebug": "웹 디버그",
+ "app.tab.jsdebug": "JS 디버그",
+ "app.tab.jsonparser": "JSON 파서",
+ "app.tab.socketdebug": "Socket 디버그",
+ "app.tab.settings": "설정",
+
+ "header.app_name": "WebHelper",
+
+ "webdebug.url_placeholder": "요청 URL 입력",
+ "webdebug.btn_send": "보내기",
+ "webdebug.tab.headers": "헤더",
+ "webdebug.tab.body": "본문",
+ "webdebug.tab.cookie": "Cookie",
+ "webdebug.tab.advanced": "고급 설정",
+ "webdebug.header.key_placeholder": "키",
+ "webdebug.header.value_placeholder": "값",
+ "webdebug.header.add": "헤더 추가",
+ "webdebug.body.type_text": "텍스트",
+ "webdebug.body.type_json": "JSON",
+ "webdebug.body.type_form": "폼",
+ "webdebug.body.type_file": "파일",
+ "webdebug.body.file_placeholder": "파일 선택",
+ "webdebug.body.file_btn": "파일 선택",
+ "webdebug.body.json_placeholder": "JSON 데이터 입력",
+ "webdebug.body.text_placeholder": "요청 본문 입력",
+ "webdebug.body.cookie_placeholder": "Cookie 문자열 (형식: name1=value1; name2=value2)",
+ "webdebug.advanced.proxy_label": "프록시",
+ "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890",
+ "webdebug.advanced.timeout_label": "타임아웃",
+ "webdebug.advanced.timeout_unit": "초",
+ "webdebug.advanced.content_type_label": "Content-Type",
+ "webdebug.advanced.content_type_placeholder": "자동 감지",
+ "webdebug.advanced.insecure_label": "인증서 무시",
+ "webdebug.advanced.log_divider": "로그 설정",
+ "webdebug.advanced.save_log_label": "로그 저장",
+ "webdebug.advanced.log_manage_label": "로그 관리",
+ "webdebug.advanced.view_logs_btn": "로그 보기",
+ "webdebug.response.title": "응답",
+ "webdebug.response.tab.body": "본문",
+ "webdebug.response.tab.headers": "헤더",
+ "webdebug.response.tab.more": "추가 정보",
+ "webdebug.response.body_placeholder": "응답 본문이 여기에 표시됩니다",
+ "webdebug.response.send_to_json": "JSON 파서로 보내기",
+ "webdebug.response.headers_placeholder": "응답 헤더가 여기에 표시됩니다",
+ "webdebug.response.status_code_label": "상태 코드",
+ "webdebug.response.status_label": "상태",
+ "webdebug.response.content_type_label": "Content-Type",
+ "webdebug.response.content_length_label": "콘텐츠 길이",
+ "webdebug.response.request_time_label": "소요 시간",
+ "webdebug.log.title": "요청 로그",
+ "webdebug.log.search_placeholder": "로그 검색...",
+ "webdebug.log.clear": "지우기",
+ "webdebug.log.empty": "로그 없음",
+ "webdebug.log.tab.request_info": "요청 정보",
+ "webdebug.log.tab.response_info": "응답 정보",
+ "webdebug.log.request.method_label": "메서드",
+ "webdebug.log.request.url_label": "URL",
+ "webdebug.log.request.host_label": "호스트",
+ "webdebug.log.request.path_label": "경로",
+ "webdebug.log.request.body_label": "본문",
+ "webdebug.log.response.status_code_label": "상태 코드",
+ "webdebug.log.response.status_label": "상태",
+ "webdebug.log.response.size_label": "크기",
+ "webdebug.log.response.time_label": "소요 시간",
+ "webdebug.log.response.body_label": "응답 본문",
+ "webdebug.log.load_to_debug": "디버거에 로드",
+ "webdebug.log.select_hint": "로그를 선택하여 상세 정보 보기",
+ "webdebug.msg.url_required": "URL을 입력하세요",
+ "webdebug.msg.request_failed": "요청 실패: ",
+ "webdebug.msg.load_logs_failed": "로그 로드 실패: ",
+ "webdebug.msg.delete_failed": "삭제 실패: ",
+ "webdebug.msg.clear_failed": "지우기 실패: ",
+
+ "jsdebug.title": "JavaScript 디버거",
+ "jsdebug.tag.duration": "소요 시간: ",
+ "jsdebug.tag.success": "성공",
+ "jsdebug.tag.failed": "실패",
+ "jsdebug.btn.run": "실행",
+ "jsdebug.btn.clear": "지우기",
+ "jsdebug.section.code": "코드",
+ "jsdebug.section.output": "출력",
+ "jsdebug.btn.copy": "복사",
+ "jsdebug.section.return_value": "반환값",
+ "jsdebug.code_placeholder": "JavaScript 코드 입력",
+ "jsdebug.output_placeholder": "console.log 출력이 여기에 표시됩니다",
+ "jsdebug.return_placeholder": "함수 반환값이 여기에 표시됩니다",
+ "jsdebug.msg.code_required": "코드를 입력하세요",
+ "jsdebug.msg.execute_failed": "실행 실패: ",
+ "jsdebug.msg.copy_prefix": "반환값: ",
+ "jsdebug.msg.copied": "클립보드에 복사됨",
+ "jsdebug.msg.copy_failed": "복사 실패",
+
+ "jsonparser.title": "JSON 파서",
+ "jsonparser.stats.objects": "객체: ",
+ "jsonparser.stats.arrays": "배열: ",
+ "jsonparser.stats.strings": "문자열: ",
+ "jsonparser.stats.numbers": "숫자: ",
+ "jsonparser.view.tree": "트리",
+ "jsonparser.view.table": "테이블",
+ "jsonparser.view.raw": "원본",
+ "jsonparser.btn.copy": "복사",
+ "jsonparser.btn.clear": "지우기",
+ "jsonparser.section.input": "입력",
+ "jsonparser.section.result": "파싱 결과",
+ "jsonparser.input_placeholder": "JSON 콘텐츠를 붙여넣거나 입력",
+ "jsonparser.raw_placeholder": "포맷된 JSON이 여기에 표시됩니다",
+ "jsonparser.empty": "JSON 데이터를 입력하세요",
+ "jsonparser.table.column.key": "키",
+ "jsonparser.table.column.index": "인덱스",
+ "jsonparser.table.column.value": "값",
+ "jsonparser.table.column.action": "작업",
+ "jsonparser.msg.parse_error": "JSON 파싱 오류: ",
+ "jsonparser.msg.copied": "클립보드에 복사됨",
+ "jsonparser.msg.copy_failed": "복사 실패",
+ "jsonparser.msg.received": "JSON 데이터 수신됨",
+
+ "socket.connection.title": "연결 설정",
+ "socket.connection.status.connected": "연결됨",
+ "socket.connection.status.disconnected": "미연결",
+ "socket.connection.btn.connect": "연결",
+ "socket.connection.btn.disconnect": "해제",
+ "socket.connection.label.protocol": "프로토콜",
+ "socket.connection.label.address": "주소",
+ "socket.connection.label.port": "포트",
+ "socket.connection.label.path": "경로",
+ "socket.connection.label.headers": "헤더",
+ "socket.connection.placeholder.host": "호스트 주소",
+ "socket.connection.placeholder.port": "8080",
+ "socket.connection.placeholder.path": "/",
+ "socket.connection.placeholder.headers": "선택, 형식: Key: Value",
+ "socket.message.title": "메시지 기록",
+ "socket.message.auto_scroll": "자동 스크롤",
+ "socket.message.btn.clear": "지우기",
+ "socket.message.tag.sent": "보냄",
+ "socket.message.tag.received": "수신",
+ "socket.message.tag.error": "오류",
+ "socket.message.tag.system": "시스템",
+ "socket.message.empty": "메시지 없음",
+ "socket.message.placeholder": "메시지 입력",
+ "socket.message.btn.send": "보내기",
+ "socket.msg.host_required": "호스트 주소를 입력하세요",
+ "socket.msg.port_invalid": "잘못된 포트",
+ "socket.msg.connect_failed": "연결 실패: ",
+ "socket.msg.disconnect_failed": "해제 실패: ",
+ "socket.msg.not_connected": "연결되지 않음",
+ "socket.msg.send_failed": "전송 실패: ",
+
+ "settings.title": "설정",
+ "settings.form.language_label": "언어",
+ "settings.form.proxy_divider": "프록시",
+ "settings.form.enable_proxy_label": "프록시 활성화",
+ "settings.form.proxy_address_label": "프록시 주소",
+ "settings.form.proxy_address_placeholder": "127.0.0.1",
+ "settings.form.proxy_port_label": "프록시 포트",
+ "settings.form.btn.save": "설정 저장",
+ "settings.form.btn.reset": "기본값으로 복원",
+ "settings.msg.saved": "설정이 저장되었습니다",
+ "settings.msg.reset": "설정이 초기화되었습니다"
+}
diff --git a/logger/logger.go b/logger/logger.go
new file mode 100644
index 0000000..5bc398e
--- /dev/null
+++ b/logger/logger.go
@@ -0,0 +1,168 @@
+package logger
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+var Log *logrus.Logger
+
+// ANSI 颜色代码
+const (
+ red = 31
+ yellow = 33
+ blue = 36
+ gray = 37
+)
+
+// LogFormatter 自定义日志格式化器
+type LogFormatter struct{}
+
+func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var levelColor int
+ switch entry.Level {
+ case logrus.DebugLevel, logrus.TraceLevel:
+ levelColor = gray
+ case logrus.WarnLevel:
+ levelColor = yellow
+ case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
+ levelColor = red
+ default:
+ levelColor = blue
+ }
+
+ var b *bytes.Buffer
+ if entry.Buffer != nil {
+ b = entry.Buffer
+ } else {
+ b = &bytes.Buffer{}
+ }
+
+ timestamp := entry.Time.Format("2006/01/02 15:04:05")
+ if entry.HasCaller() {
+ funcVal := entry.Caller.Function
+ fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
+ fmt.Fprintf(b, "%s[%s] \033[%dm[%s]\033[0m %s %s: %s\n",
+ "[Logger]", timestamp, levelColor, entry.Level, funcVal, fileVal, entry.Message)
+ } else {
+ fmt.Fprintf(b, "%s[%s] \033[%dm[%s]\033[0m %s\n",
+ "[Logger]", timestamp, levelColor, entry.Level, entry.Message)
+ }
+ return b.Bytes(), nil
+}
+
+// FileFormatter 文件日志格式化器(不包含 ANSI 颜色代码)
+type FileFormatter struct{}
+
+func (f *FileFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var b *bytes.Buffer
+ if entry.Buffer != nil {
+ b = entry.Buffer
+ } else {
+ b = &bytes.Buffer{}
+ }
+
+ timestamp := entry.Time.Format("2006/01/02 15:04:05")
+ if entry.HasCaller() {
+ funcVal := entry.Caller.Function
+ fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
+ fmt.Fprintf(b, "%s[%s] [%s] %s %s: %s\n",
+ "[Logger]", timestamp, entry.Level, funcVal, fileVal, entry.Message)
+ } else {
+ fmt.Fprintf(b, "%s[%s] [%s] %s\n",
+ "[Logger]", timestamp, entry.Level, entry.Message)
+ }
+ return b.Bytes(), nil
+}
+
+// InitLogger 初始化日志系统
+// 每次运行时按照启动时间保存日志文件,并自动删除最新十个日志之前的日志
+func InitLogger() {
+ Log = logrus.New()
+ Log.SetReportCaller(true)
+ Log.SetFormatter(&LogFormatter{})
+ Log.SetLevel(logrus.DebugLevel)
+
+ // 确保日志目录存在
+ logDir := "logs"
+ if err := os.MkdirAll(logDir, 0755); err != nil {
+ Log.Warnf("无法创建日志目录: %v", err)
+ }
+
+ // 清理旧日志(保留最新10个)
+ cleanOldLogs(logDir, 10)
+
+ // 创建按启动时间命名的日志文件
+ startTime := time.Now().Format("2006-01-02_15-04-05")
+ logFile := filepath.Join(logDir, startTime+".log")
+ file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ Log.Warnf("无法打开日志文件: %v", err)
+ } else {
+ // 控制台输出使用带颜色的格式
+ Log.SetOutput(os.Stdout)
+
+ // 文件输出使用无颜色的格式,通过 Hook 实现
+ Log.AddHook(&FileHook{file: file})
+ }
+}
+
+// FileHook 将日志同时写入文件的 Hook
+type FileHook struct {
+ file *os.File
+}
+
+func (h *FileHook) Levels() []logrus.Level {
+ return logrus.AllLevels
+}
+
+func (h *FileHook) Fire(entry *logrus.Entry) error {
+ formatter := &FileFormatter{}
+ line, err := formatter.Format(entry)
+ if err != nil {
+ return err
+ }
+ _, err = h.file.Write(line)
+ return err
+}
+
+// cleanOldLogs 清理旧日志文件,保留最新的 maxKeep 个日志
+func cleanOldLogs(logDir string, maxKeep int) {
+ entries, err := os.ReadDir(logDir)
+ if err != nil {
+ return
+ }
+
+ // 收集所有 .log 文件
+ var logFiles []os.DirEntry
+ for _, entry := range entries {
+ if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") {
+ logFiles = append(logFiles, entry)
+ }
+ }
+
+ // 如果日志文件数量未超过限制,无需清理
+ if len(logFiles) <= maxKeep {
+ return
+ }
+
+ // 按文件名排序(文件名包含时间戳,排序即按时间排序)
+ sort.Slice(logFiles, func(i, j int) bool {
+ return logFiles[i].Name() < logFiles[j].Name()
+ })
+
+ // 删除超出限制的旧日志
+ filesToDelete := logFiles[:len(logFiles)-maxKeep]
+ for _, file := range filesToDelete {
+ filePath := filepath.Join(logDir, file.Name())
+ os.Remove(filePath)
+ }
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..8a547f4
--- /dev/null
+++ b/main.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "embed"
+ "webhelper/global"
+ "webhelper/service"
+
+ "github.com/wailsapp/wails/v2"
+ "github.com/wailsapp/wails/v2/pkg/options"
+ "github.com/wailsapp/wails/v2/pkg/options/assetserver"
+)
+
+//go:embed all:frontend/dist
+var assets embed.FS
+
+//go:embed lang/*
+var langFS embed.FS
+
+func main() {
+ global.LangFS = langFS
+ global.Init()
+
+ appName := global.GetProcessName()
+ App := service.NewApp()
+
+ err := wails.Run(&options.App{
+ Title: appName,
+ Width: 1024,
+ Height: 768,
+ MinWidth: 800,
+ MinHeight: 600,
+ AssetServer: &assetserver.Options{
+ Assets: assets,
+ },
+ BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
+ OnStartup: App.Startup,
+ Frameless: true,
+ Bind: []interface{}{
+ App,
+ },
+ })
+
+ if err != nil {
+ println("Error:", err.Error())
+ }
+}
diff --git a/service/router.go b/service/router.go
new file mode 100644
index 0000000..f290db1
--- /dev/null
+++ b/service/router.go
@@ -0,0 +1,517 @@
+package service
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ goruntime "runtime"
+ "sort"
+ "strings"
+ "time"
+
+ "webhelper/api"
+ "webhelper/global"
+
+ "github.com/sirupsen/logrus"
+ "github.com/wailsapp/wails/v2/pkg/runtime"
+)
+
+type App struct {
+ ctx context.Context
+}
+
+func NewApp() *App {
+ return &App{}
+}
+
+func (a *App) Startup(ctx context.Context) {
+ a.ctx = ctx
+}
+
+func (a *App) Greet(name string) string {
+ return greet(name)
+}
+
+func (a *App) Flashtime() {
+ flashtime(a)
+}
+
+func (a *App) Gettestjson() string {
+ return getJSONString()
+}
+
+func (a *App) GetLangTextMap() map[string]string {
+ return global.GetLangTextMap()
+}
+
+func (a *App) GetLangPack() *global.LanguagePack {
+ langPack, err := global.GetLangPack()
+ if err != nil {
+ global.Log.Warnf("获取语言包失败: %v", err)
+ return nil
+ }
+ return langPack
+}
+
+func (a *App) GetALLLang() []global.LanguageInfo {
+ return global.GetLangInfoList()
+}
+
+func (a *App) SetLanguage(langCode string) bool {
+ global.GlobalConfig.Language = langCode
+ global.ClearLangCache()
+ global.UpdateCurrentLangPath()
+ return true
+}
+
+func (a *App) GetCurrentLang() string {
+ return global.GlobalConfig.Language
+}
+
+func (a *App) GetLogFiles() []string {
+ logDir := global.GlobalConfig.LogDir
+ entries, err := os.ReadDir(logDir)
+ if err != nil {
+ global.Log.Warnf("读取日志目录失败: %v", err)
+ return []string{}
+ }
+
+ var logFiles []string
+ for _, entry := range entries {
+ if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") {
+ logFiles = append(logFiles, entry.Name())
+ }
+ }
+
+ sort.Sort(sort.Reverse(sort.StringSlice(logFiles)))
+ return logFiles
+}
+
+func (a *App) GetLogFileContent(filename string) string {
+ // 安全检查:防止路径遍历
+ if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") {
+ global.Log.Warnf("非法的日志文件名: %s", filename)
+ return ""
+ }
+
+ logPath := filepath.Join(global.GlobalConfig.LogDir, filename)
+ data, err := os.ReadFile(logPath)
+ if err != nil {
+ global.Log.Warnf("读取日志文件失败: %v", err)
+ return ""
+ }
+ return string(data)
+}
+
+func (a *App) SetLogLevel(level string) bool {
+ switch strings.ToLower(level) {
+ case "debug":
+ global.SetLogLevel(logrus.DebugLevel)
+ global.Log.Debug("日志等级已切换为 Debug")
+ case "info":
+ global.SetLogLevel(logrus.InfoLevel)
+ global.Log.Info("日志等级已切换为 Info")
+ case "warn":
+ global.SetLogLevel(logrus.WarnLevel)
+ global.Log.Warn("日志等级已切换为 Warn")
+ case "error":
+ global.SetLogLevel(logrus.ErrorLevel)
+ global.Log.Error("日志等级已切换为 Error")
+ default:
+ global.Log.Warnf("未知的日志等级: %s", level)
+ return false
+ }
+ return true
+}
+
+func (a *App) GetLogLevel() string {
+ if global.Log == nil {
+ return "info"
+ }
+ return strings.ToUpper(global.Log.GetLevel().String())
+}
+
+func (a *App) WindowMinimise() {
+ runtime.WindowMinimise(a.ctx)
+}
+
+func (a *App) WindowToggleMaximise() {
+ runtime.WindowToggleMaximise(a.ctx)
+}
+
+func (a *App) WindowClose() {
+ runtime.Quit(a.ctx)
+}
+
+func (a *App) GetSystemInfo() SystemInfo {
+ hostname, _ := os.Hostname()
+
+ return SystemInfo{
+ OS: goruntime.GOOS,
+ Arch: goruntime.GOARCH,
+ NumCPU: goruntime.NumCPU(),
+ Hostname: hostname,
+ GoVer: goruntime.Version(),
+ Time: time.Now().Format(time.DateTime),
+ ProcessName: global.GetProcessName(),
+ }
+}
+
+func (a *App) GetProcessName() string {
+ return global.GetProcessName()
+}
+
+func (a *App) GetVersion() string {
+ return global.Version
+}
+
+func (a *App) OpenFileSelect() string {
+ file, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
+ Title: "选择文件",
+ Filters: []runtime.FileFilter{
+ {DisplayName: "所有文件", Pattern: "*.*"},
+ {DisplayName: "文本文件", Pattern: "*.txt"},
+ {DisplayName: "JSON 文件", Pattern: "*.json"},
+ },
+ })
+ if err != nil {
+ global.Log.Warnf("打开文件对话框失败: %v", err)
+ return ""
+ }
+ return file
+}
+
+func (a *App) OpenFolderSelect() string {
+ folder, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{
+ Title: "选择目录",
+ })
+ if err != nil {
+ global.Log.Warnf("打开目录对话框失败: %v", err)
+ return ""
+ }
+ return folder
+}
+
+func (a *App) SaveFileSelect() string {
+ file, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
+ Title: "保存文件",
+ Filters: []runtime.FileFilter{
+ {DisplayName: "文本文件", Pattern: "*.txt"},
+ {DisplayName: "JSON 文件", Pattern: "*.json"},
+ },
+ })
+ if err != nil {
+ global.Log.Warnf("打开保存对话框失败: %v", err)
+ return ""
+ }
+ return file
+}
+
+func (a *App) ReadFileContent(path string) string {
+ if path == "" {
+ return ""
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ global.Log.Warnf("读取文件失败: %v", err)
+ return fmt.Sprintf("读取失败: %v", err)
+ }
+ return string(data)
+}
+
+func (a *App) WriteFileContent(path string, content string) bool {
+ if path == "" {
+ return false
+ }
+ err := os.WriteFile(path, []byte(content), 0644)
+ if err != nil {
+ global.Log.Warnf("写入文件失败: %v", err)
+ return false
+ }
+ global.Log.Infof("文件写入成功: %s", path)
+ return true
+}
+
+func (a *App) Notify(title string, message string) {
+ runtime.EventsEmit(a.ctx, "notification", map[string]string{
+ "title": title,
+ "message": message,
+ })
+}
+
+// SendHttpRequest 发送 HTTP 请求
+func (a *App) SendHttpRequest(method, url, headers, cookies, proxy, bodyType, body, filePath, contentType string, timeout int, insecure bool, saveLog bool) *api.HttpResponse {
+ // 解析请求头
+ headerMap := api.ParseHeaders(headers)
+
+ // 解析 Cookie
+ cookieMap := api.ParseCookies(cookies)
+
+ req := &api.HttpRequest{
+ Method: method,
+ URL: url,
+ Headers: headerMap,
+ Cookies: cookieMap,
+ Proxy: proxy,
+ BodyType: bodyType,
+ Body: body,
+ FilePath: filePath,
+ ContentType: contentType,
+ Timeout: timeout,
+ Insecure: insecure,
+ }
+
+ global.Log.Infof("发送 HTTP 请求: %s %s", method, url)
+ resp := api.SendRequest(req)
+
+ if resp.Error != "" {
+ global.Log.Warnf("HTTP 请求失败: %s", resp.Error)
+ } else {
+ global.Log.Infof("HTTP 请求完成: %d %s (%v)", resp.StatusCode, resp.Status, resp.Time)
+ }
+
+ // 保存日志
+ if saveLog {
+ logManager := api.GetLogManager()
+ requestLog := api.CreateLogFromResponse(req, resp)
+ if err := logManager.AddLog(requestLog); err != nil {
+ global.Log.Warnf("保存请求日志失败: %v", err)
+ }
+ }
+
+ return resp
+}
+
+// GetRequestLogs 获取请求日志列表
+func (a *App) GetRequestLogs() []*api.RequestLog {
+ logManager := api.GetLogManager()
+ return logManager.GetLogs()
+}
+
+// GetRequestLog 获取单条请求日志
+func (a *App) GetRequestLog(id int) *api.RequestLog {
+ logManager := api.GetLogManager()
+ return logManager.GetLog(id)
+}
+
+// DeleteRequestLog 删除请求日志
+func (a *App) DeleteRequestLog(id int) bool {
+ logManager := api.GetLogManager()
+ if err := logManager.DeleteLog(id); err != nil {
+ global.Log.Warnf("删除请求日志失败: %v", err)
+ return false
+ }
+ return true
+}
+
+// ClearRequestLogs 清空请求日志
+func (a *App) ClearRequestLogs() bool {
+ logManager := api.GetLogManager()
+ if err := logManager.ClearLogs(); err != nil {
+ global.Log.Warnf("清空请求日志失败: %v", err)
+ return false
+ }
+ return true
+}
+
+// SearchRequestLogs 搜索请求日志
+func (a *App) SearchRequestLogs(keyword string) []*api.RequestLog {
+ logManager := api.GetLogManager()
+ return logManager.SearchLogs(keyword)
+}
+
+// ExecuteJs 执行 JavaScript 代码
+func (a *App) ExecuteJs(code string) *api.JsExecResult {
+ global.Log.Infof("执行 JavaScript 代码,长度: %d", len(code))
+ result := api.ExecuteJs(code)
+
+ if result.Success {
+ global.Log.Infof("JavaScript 执行成功,耗时: %s", result.Duration)
+ } else {
+ global.Log.Warnf("JavaScript 执行失败: %s", result.Error)
+ }
+
+ return result
+}
+
+// ConnectSocket 连接 WebSocket 或 TCP
+func (a *App) ConnectSocket(protocol, host string, port int, path string, headers string) bool {
+ // 确保 path 以 / 开头
+ if path == "" {
+ path = "/"
+ } else if path[0] != '/' {
+ path = "/" + path
+ }
+
+ var url string
+ if protocol == "tcp" {
+ url = fmt.Sprintf("%s:%d", host, port)
+ } else {
+ scheme := "ws"
+ if protocol == "wss" {
+ scheme = "wss"
+ }
+ url = fmt.Sprintf("%s://%s:%d%s", scheme, host, port, path)
+ }
+
+ global.Log.Infof("连接 %s: %s", protocol, url)
+
+ // 发送连接中事件
+ runtime.EventsEmit(a.ctx, "socket-status", map[string]string{
+ "type": "connecting",
+ "content": "正在连接...",
+ })
+
+ if protocol == "tcp" {
+ // TCP 连接
+ config := &api.TCPConfig{
+ Host: host,
+ Port: port,
+ }
+
+ err := api.ConnectTCP(config,
+ // onMessage
+ func(message string) {
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "received",
+ "content": message,
+ })
+ },
+ // onError
+ func(err error) {
+ // 连接失败时的错误回调
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "error",
+ "content": err.Error(),
+ })
+ },
+ // onClose
+ func() {
+ runtime.EventsEmit(a.ctx, "socket-status", map[string]string{
+ "type": "disconnected",
+ "content": "连接已断开",
+ })
+ },
+ )
+
+ if err != nil {
+ global.Log.Warnf("TCP 连接失败: %v", err)
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "error",
+ "content": "连接失败: " + err.Error(),
+ })
+ return false
+ }
+ } else {
+ // WebSocket 连接
+ headerMap := api.ParseHeaders(headers)
+ config := &api.WebSocketConfig{
+ URL: url,
+ Headers: headerMap,
+ }
+
+ err := api.ConnectWebSocket(config,
+ // onMessage
+ func(message string) {
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "received",
+ "content": message,
+ })
+ },
+ // onError
+ func(err error) {
+ // 连接失败时的错误回调
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "error",
+ "content": err.Error(),
+ })
+ },
+ // onClose
+ func() {
+ runtime.EventsEmit(a.ctx, "socket-status", map[string]string{
+ "type": "disconnected",
+ "content": "连接已断开",
+ })
+ },
+ )
+
+ if err != nil {
+ global.Log.Warnf("WebSocket 连接失败: %v", err)
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "error",
+ "content": "连接失败: " + err.Error(),
+ })
+ return false
+ }
+ }
+
+ global.Log.Infof("连接成功: %s", url)
+ runtime.EventsEmit(a.ctx, "socket-status", map[string]string{
+ "type": "connected",
+ "content": "连接成功",
+ })
+
+ return true
+}
+
+// SendSocketMessage 发送 Socket 消息
+func (a *App) SendSocketMessage(protocol, message string) bool {
+ global.Log.Infof("发送 %s 消息: %s", protocol, message)
+
+ var err error
+ if protocol == "tcp" {
+ err = api.SendTCPMessage(message)
+ } else {
+ err = api.SendWebSocketMessage(message)
+ }
+
+ if err != nil {
+ global.Log.Warnf("发送消息失败: %v", err)
+ runtime.EventsEmit(a.ctx, "socket-message", map[string]string{
+ "type": "error",
+ "content": "发送失败: " + err.Error(),
+ })
+ return false
+ }
+
+ // 发送成功事件
+ runtime.EventsEmit(a.ctx, "socket-sent", map[string]string{
+ "type": "sent",
+ "content": message,
+ })
+
+ return true
+}
+
+// DisconnectSocket 断开 Socket 连接
+func (a *App) DisconnectSocket(protocol string) bool {
+ global.Log.Infof("断开 %s 连接", protocol)
+
+ var err error
+ if protocol == "tcp" {
+ err = api.CloseTCP()
+ } else {
+ err = api.CloseWebSocket()
+ }
+
+ if err != nil {
+ global.Log.Warnf("断开连接失败: %v", err)
+ return false
+ }
+
+ runtime.EventsEmit(a.ctx, "socket-status", map[string]string{
+ "type": "disconnected",
+ "content": "已断开连接",
+ })
+
+ return true
+}
+
+// IsSocketConnected 检查 Socket 是否已连接
+func (a *App) IsSocketConnected(protocol string) bool {
+ if protocol == "tcp" {
+ return api.IsTCPConnected()
+ }
+ return api.IsWebSocketConnected()
+}
diff --git a/service/temp.go b/service/temp.go
new file mode 100644
index 0000000..723538c
--- /dev/null
+++ b/service/temp.go
@@ -0,0 +1,67 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "github.com/wailsapp/wails/v2/pkg/runtime"
+)
+
+func greet(name string) string {
+ return fmt.Sprintf("Hello %s, It's show time!", name)
+}
+
+func flashtime(a *App) {
+ go func() {
+ for {
+ now := time.Now().Format(time.DateTime)
+ runtime.EventsEmit(a.ctx, "time", now)
+ time.Sleep(time.Second)
+ }
+ }()
+}
+
+func getJSONString() string {
+ datas := map[string]interface{}{
+ "code": 200,
+ "message": "ok",
+ "data": []map[string]interface{}{
+ {
+ "id": 1,
+ "name": "apps",
+ "isdir": true,
+ "size": 0,
+ },
+ {
+ "id": 2,
+ "name": "docs",
+ "isdir": true,
+ "size": 0,
+ },
+ {
+ "id": 3,
+ "name": "readme.txt",
+ "isdir": false,
+ "size": 2048,
+ },
+ },
+ }
+
+ jsonBytes, err := json.Marshal(datas)
+ if err != nil {
+ return "{}"
+ }
+
+ return string(jsonBytes)
+}
+
+type SystemInfo struct {
+ OS string `json:"os"`
+ Arch string `json:"arch"`
+ NumCPU int `json:"num_cpu"`
+ Hostname string `json:"hostname"`
+ GoVer string `json:"go_ver"`
+ Time string `json:"time"`
+ ProcessName string `json:"process_name"`
+}
diff --git a/wails.json b/wails.json
new file mode 100644
index 0000000..664fecd
--- /dev/null
+++ b/wails.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://wails.io/schemas/config.v2.json",
+ "name": "webhelper",
+ "outputfilename": "webhelper",
+ "frontend:install": "npm install",
+ "frontend:build": "npm run build",
+ "frontend:dev:watcher": "npm run dev",
+ "frontend:dev:serverUrl": "auto",
+ "author": {
+ "name": "MoGuQAQ"
+ },
+ "info": {
+ "companyName": "MoGuQAQ",
+ "productName": "WebHelper",
+ "productVersion": "1.0.0",
+ "copyright": "Copyright © 2026 MoGuQAQ",
+ "comments": "MIT license"
+ }
+}