feat: 添加基于webview实现的包含GUI的版本,添加通过upx进行压缩的版本,优化分享功能。
This commit is contained in:
1 parent
7f26b46cde
commit
f9b93624e4
14 files changed
+497
-30
No files matched your search
+10
@@ -23,10 +23,20 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
miao-directory.exe
|
miao-directory.exe
|
||||||
miao-directory-amd64-linux
|
miao-directory-amd64-linux
|
||||||
miao-directory-amd64-win.exe
|
miao-directory-amd64-win.exe
|
||||||
|
miao-directory-amd64-win.gui.exe
|
||||||
miao-directory-amd64-win.upx.exe
|
miao-directory-amd64-win.upx.exe
|
||||||
|
miao-directory-amd64-win.gui.upx.exe
|
||||||
|
miao-directory.exe~
|
||||||
|
miao-directory-amd64-linux~
|
||||||
|
miao-directory-amd64-win.exe~
|
||||||
|
miao-directory-amd64-win.gui.exe~
|
||||||
|
miao-directory-amd64-win.upx.exe~
|
||||||
|
miao-directory-amd64-win.gui.upx.exe~
|
||||||
|
|
||||||
test
|
test
|
||||||
stats.html
|
stats.html
|
||||||
|
|
||||||
|
|||||||
+39
-2
@@ -1,5 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 检查UPX是否存在
|
||||||
|
check_upx() {
|
||||||
|
if command -v upx &> /dev/null; then
|
||||||
|
echo "UPX已找到,将执行可执行文件压缩"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "UPX未找到,跳过压缩步骤"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
pnpm run build
|
pnpm run build
|
||||||
|
|
||||||
rm -rf ./src-server/web/*
|
rm -rf ./src-server/web/*
|
||||||
@@ -8,16 +19,42 @@ cp -r ./dist/* ./src-server/web/
|
|||||||
|
|
||||||
cd ./src-server
|
cd ./src-server
|
||||||
|
|
||||||
echo "building amd64 win"
|
# 安装webview2依赖(如果尚未安装)
|
||||||
|
go get github.com/jchv/go-webview2
|
||||||
|
|
||||||
|
# 构建不带GUI版本(Windows)
|
||||||
|
echo "building amd64 win (no GUI)"
|
||||||
go env -w GOOS=windows GOARCH=amd64
|
go env -w GOOS=windows GOARCH=amd64
|
||||||
go build -ldflags="-s -w" -o ../miao-directory-amd64-win.exe
|
go build -ldflags="-s -w" -o ../miao-directory-amd64-win.exe
|
||||||
|
|
||||||
echo "building amd64 linux"
|
# 尝试UPX压缩Windows无GUI版本
|
||||||
|
if check_upx; then
|
||||||
|
echo "正在压缩Windows无GUI版本"
|
||||||
|
upx -9 -o ../miao-directory-amd64-win.upx.exe ../miao-directory-amd64-win.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建不带GUI版本(Linux)
|
||||||
|
echo "building amd64 linux (no GUI)"
|
||||||
go env -w GOOS=linux GOARCH=amd64
|
go env -w GOOS=linux GOARCH=amd64
|
||||||
go build -ldflags="-s -w" -o ../miao-directory-amd64-linux
|
go build -ldflags="-s -w" -o ../miao-directory-amd64-linux
|
||||||
|
|
||||||
|
# 构建带GUI版本(仅Windows,go-webview2主要针对Windows平台)
|
||||||
|
echo "building amd64 win with GUI"
|
||||||
|
go env -w GOOS=windows GOARCH=amd64
|
||||||
|
go build -tags webview -ldflags="-s -w -H=windowsgui" -o ../miao-directory-amd64-win.gui.exe
|
||||||
|
|
||||||
|
# 尝试UPX压缩Windows GUI版本
|
||||||
|
if check_upx; then
|
||||||
|
echo "正在压缩Windows GUI版本"
|
||||||
|
upx -9 -o ../miao-directory-amd64-win.gui.upx.exe ../miao-directory-amd64-win.gui.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果需要为Linux构建带GUI版本,需要系统安装相应的依赖
|
||||||
|
# echo "building amd64 linux with GUI"
|
||||||
|
# go env -w GOOS=linux GOARCH=amd64
|
||||||
|
# go build -tags webview -ldflags="-s -w" -o ../miao-directory-gui-amd64-linux
|
||||||
|
|
||||||
|
# 可选:ARM64 Linux版本
|
||||||
# go env -w GOOS=linux GOARCH=arm64
|
# go env -w GOOS=linux GOARCH=arm64
|
||||||
# go build -ldflags="-s -w" -o ../miao-directory-arm64-linux
|
# go build -ldflags="-s -w" -o ../miao-directory-arm64-linux
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddressesHandler 返回服务器所有可用的访问地址
|
||||||
|
func AddressesHandler(serverPort string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
addresses := []gin.H{}
|
||||||
|
|
||||||
|
addresses = append(addresses, gin.H{
|
||||||
|
"title": "localhost",
|
||||||
|
"url": "http://localhost" + serverPort,
|
||||||
|
})
|
||||||
|
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
|
||||||
|
addrs, _ := net.LookupHost(host)
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if strings.Contains(addr, "%") {
|
||||||
|
addr = strings.Split(addr, "%")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var url string
|
||||||
|
var title string
|
||||||
|
|
||||||
|
if strings.Contains(addr, ":") {
|
||||||
|
url = "http://[" + addr + "]" + serverPort
|
||||||
|
title = "IPv6 Address (" + addr + ")"
|
||||||
|
} else {
|
||||||
|
url = "http://" + addr + serverPort
|
||||||
|
title = "IPv4 Address (" + addr + ")"
|
||||||
|
}
|
||||||
|
addresses = append(addresses, gin.H{
|
||||||
|
"title": title,
|
||||||
|
"url": url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, addresses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是私有IP地址
|
||||||
|
func isPrivateIP(ip string) bool {
|
||||||
|
ipAddr := net.ParseIP(ip)
|
||||||
|
if ipAddr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查常见的私有IP范围
|
||||||
|
// 10.0.0.0/8
|
||||||
|
if ipAddr[0] == 10 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 172.16.0.0/12
|
||||||
|
if ipAddr[0] == 172 && ipAddr[1] >= 16 && ipAddr[1] <= 31 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 192.168.0.0/16
|
||||||
|
if ipAddr[0] == 192 && ipAddr[1] == 168 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 127.0.0.0/8 (本地回环)
|
||||||
|
if ipAddr[0] == 127 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
+4
-1
@@ -16,6 +16,8 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/jchv/go-webview2 v0.0.0-20221223143126-dc24628cff85 // indirect
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||||
github.com/josephspurrier/goversioninfo v1.4.0 // indirect
|
github.com/josephspurrier/goversioninfo v1.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
@@ -26,10 +28,11 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/webview/webview v0.0.0-20250319170551-f1f42f0f164e // indirect
|
||||||
golang.org/x/arch v0.9.0 // indirect
|
golang.org/x/arch v0.9.0 // indirect
|
||||||
golang.org/x/crypto v0.26.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/net v0.28.0 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC
|
|||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/jchv/go-webview2 v0.0.0-20221223143126-dc24628cff85 h1:t6lhRbwURcdWgp8OsJlq6sOfWMOXP21YuiCicjutHv4=
|
||||||
|
github.com/jchv/go-webview2 v0.0.0-20221223143126-dc24628cff85/go.mod h1:/BNVc0Sw3Wj6Sz9uSxPwhCEUhhWs92hPde75K2YV24A=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
|
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/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8=
|
github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8=
|
||||||
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@@ -64,16 +69,22 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/webview/webview v0.0.0-20250319170551-f1f42f0f164e h1:Kz8a4/xfC3vH5sXgPfNfwu2qwwTUrD79N0bFRS6E4tI=
|
||||||
|
github.com/webview/webview v0.0.0-20250319170551-f1f42f0f164e/go.mod h1:Zk81X+8/mp/MNoeJXXK4Noydn8pcea09e00dFfAwrxg=
|
||||||
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
||||||
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210218145245-beda7e5e158e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
|||||||
+19
-1
@@ -39,6 +39,9 @@ func main() {
|
|||||||
router.POST("/rename/*path", api.RenameHandler(fsOperateEventCenter))
|
router.POST("/rename/*path", api.RenameHandler(fsOperateEventCenter))
|
||||||
router.POST("/delete/*path", api.DeleteHandler(fsOperateEventCenter))
|
router.POST("/delete/*path", api.DeleteHandler(fsOperateEventCenter))
|
||||||
|
|
||||||
|
// 添加获取所有可用地址的API
|
||||||
|
router.GET("/addresses", api.AddressesHandler(port))
|
||||||
|
|
||||||
// Redirect from '/' to '/web/'
|
// Redirect from '/' to '/web/'
|
||||||
router.GET("/", func(c *gin.Context) {
|
router.GET("/", func(c *gin.Context) {
|
||||||
c.Redirect(http.StatusMovedPermanently, "/web/")
|
c.Redirect(http.StatusMovedPermanently, "/web/")
|
||||||
@@ -54,7 +57,22 @@ func main() {
|
|||||||
|
|
||||||
address := "[::]" + port
|
address := "[::]" + port
|
||||||
printListeningAddresses(port)
|
printListeningAddresses(port)
|
||||||
router.Run(address)
|
|
||||||
|
// 在非阻塞的goroutine中启动服务器
|
||||||
|
go func() {
|
||||||
|
if err := router.Run(address); err != nil {
|
||||||
|
fmt.Printf("服务器启动失败: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 如果启用了webview,则启动webview窗口
|
||||||
|
if useWebView {
|
||||||
|
startWebView(port)
|
||||||
|
} else {
|
||||||
|
// 如果不使用webview,则阻塞主线程,保持服务器运行
|
||||||
|
select {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func corsMiddleware() gin.HandlerFunc {
|
func corsMiddleware() gin.HandlerFunc {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build webview
|
||||||
|
// +build webview
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jchv/go-webview2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 启动WebView窗口
|
||||||
|
func startWebView(port string) {
|
||||||
|
// 构建本地服务URL
|
||||||
|
serverURL := fmt.Sprintf("http://localhost%s", port)
|
||||||
|
|
||||||
|
// 创建webview窗口
|
||||||
|
w := webview2.NewWithOptions(webview2.WebViewOptions{
|
||||||
|
Debug: false,
|
||||||
|
AutoFocus: true,
|
||||||
|
WindowOptions: webview2.WindowOptions{
|
||||||
|
Title: "MiaoDirectory Server",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
Center: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer w.Destroy()
|
||||||
|
|
||||||
|
// 导航到本地服务器
|
||||||
|
w.Navigate(serverURL)
|
||||||
|
|
||||||
|
// 启动webview窗口
|
||||||
|
w.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义是否使用WebView的变量
|
||||||
|
var useWebView = true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !webview
|
||||||
|
// +build !webview
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// 定义是否使用WebView的变量
|
||||||
|
var useWebView = false
|
||||||
|
|
||||||
|
// 空函数,在非webview版本中不执行任何操作
|
||||||
|
func startWebView(port string) {
|
||||||
|
// 在非webview版本中不做任何事情
|
||||||
|
}
|
||||||
+22
-24
@@ -12,7 +12,7 @@ import type VirtualDirectory from './class/VirtualDirectory'
|
|||||||
import type { VirtualFile } from './class/VirtualDirectory'
|
import type { VirtualFile } from './class/VirtualDirectory'
|
||||||
import MiaoMask from './components/miaoMask.vue'
|
import MiaoMask from './components/miaoMask.vue'
|
||||||
import MiaoMessageProvider from './components/miaoAlertTipProvider.vue'
|
import MiaoMessageProvider from './components/miaoAlertTipProvider.vue'
|
||||||
import config from './config'
|
// import config from './config'
|
||||||
import useVirtualPages from './hooks/useVirtualPages'
|
import useVirtualPages from './hooks/useVirtualPages'
|
||||||
import init from './hooks/init'
|
import init from './hooks/init'
|
||||||
import usePluginCenter from './hooks/usePluginCenter'
|
import usePluginCenter from './hooks/usePluginCenter'
|
||||||
@@ -22,8 +22,9 @@ import { DropdownMixedOption } from 'naive-ui/es/dropdown/src/interface'
|
|||||||
import useRootVDirectory from './hooks/useRootVDirectory'
|
import useRootVDirectory from './hooks/useRootVDirectory'
|
||||||
import ViewController from '@/components/APP/ViewController.vue'
|
import ViewController from '@/components/APP/ViewController.vue'
|
||||||
import ViewContainer from '@/components/APP/ViewContainer.vue'
|
import ViewContainer from '@/components/APP/ViewContainer.vue'
|
||||||
|
import ShareQrCodeCard from './components/APP/ShareQrCodeCard.vue'
|
||||||
|
|
||||||
const { baseUrl } = config
|
// const { baseUrl } = config
|
||||||
|
|
||||||
const showModal = ref<boolean>(false)
|
const showModal = ref<boolean>(false)
|
||||||
const viewRef = ref<HTMLDivElement>()
|
const viewRef = ref<HTMLDivElement>()
|
||||||
@@ -36,6 +37,7 @@ const views = useVirtualPages()
|
|||||||
let pluginCenter = ref<PluginCenter>()
|
let pluginCenter = ref<PluginCenter>()
|
||||||
const rootDirectory = useRootVDirectory()
|
const rootDirectory = useRootVDirectory()
|
||||||
|
|
||||||
|
|
||||||
// component就是一个vue组件,类似于miaoDirectory
|
// component就是一个vue组件,类似于miaoDirectory
|
||||||
const createView = (
|
const createView = (
|
||||||
component: any,
|
component: any,
|
||||||
@@ -98,14 +100,13 @@ const openMenuOption = computed(
|
|||||||
|
|
||||||
const handleMenuSelect = async (key: string) => {
|
const handleMenuSelect = async (key: string) => {
|
||||||
if (key === 'share') {
|
if (key === 'share') {
|
||||||
|
// 立即显示模态框
|
||||||
showModal.value = true
|
showModal.value = true
|
||||||
|
|
||||||
|
// 设置模态框数据,将serverAddressesService传递给组件
|
||||||
modalData.value = {
|
modalData.value = {
|
||||||
component: (await import('naive-ui/es/qr-code')).NQrCode,
|
component: ShareQrCodeCard,
|
||||||
props: {
|
props: {}
|
||||||
value: baseUrl,
|
|
||||||
size: 300,
|
|
||||||
errorCorrectionLevel: 'H'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (key.startsWith('plugin:')) {
|
} else if (key.startsWith('plugin:')) {
|
||||||
const pluginName = key.split(':')[1]
|
const pluginName = key.split(':')[1]
|
||||||
@@ -113,6 +114,13 @@ const handleMenuSelect = async (key: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
showModal.value = false
|
||||||
|
nextTick(() => {
|
||||||
|
modalData.value = undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await init(messageProviderRef.value?.alertTip)
|
await init(messageProviderRef.value?.alertTip)
|
||||||
@@ -127,25 +135,15 @@ provide('rootDirectory', rootDirectory)
|
|||||||
<template>
|
<template>
|
||||||
<div class="view" ref="viewRef">
|
<div class="view" ref="viewRef">
|
||||||
<miao-message-provider ref="messageProviderRef">
|
<miao-message-provider ref="messageProviderRef">
|
||||||
<ViewController
|
<ViewController :views="views" :handleClickTitle="handleClickTitle" :createView="createView"
|
||||||
:views="views"
|
:deleteView="deleteView" :openMenuOption="openMenuOption" :handleMenuSelect="handleMenuSelect" />
|
||||||
:handleClickTitle="handleClickTitle"
|
<ViewContainer :views="views" :deleteView="deleteView" :handleClickTitle="handleClickTitle" />
|
||||||
:createView="createView"
|
|
||||||
:deleteView="deleteView"
|
|
||||||
:openMenuOption="openMenuOption"
|
|
||||||
:handleMenuSelect="handleMenuSelect" />
|
|
||||||
<ViewContainer
|
|
||||||
:views="views"
|
|
||||||
:deleteView="deleteView"
|
|
||||||
:handleClickTitle="handleClickTitle" />
|
|
||||||
</miao-message-provider>
|
</miao-message-provider>
|
||||||
</div>
|
</div>
|
||||||
<!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 -->
|
<!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 -->
|
||||||
<MiaoMask v-model:show="showModal" @click="showModal = false">
|
<MiaoMask v-model:show="showModal" @click="handleModalClose">
|
||||||
<component
|
<component @click="(e: any) => e.stopPropagation()" :is="modalData?.component" v-bind="modalData?.props">
|
||||||
@click="(e: any) => e.stopPropagation()"
|
</component>
|
||||||
:is="modalData?.component"
|
|
||||||
v-bind="modalData?.props"></component>
|
|
||||||
</MiaoMask>
|
</MiaoMask>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<n-card title="分享链接" class="share-card">
|
||||||
|
<miao-message-provider ref="messageProviderRef">
|
||||||
|
<n-scrollbar style="max-height: 60vh;">
|
||||||
|
<n-space vertical>
|
||||||
|
<!-- <n-text>扫描下方二维码访问不同设备上的应用</n-text>
|
||||||
|
<n-divider /> -->
|
||||||
|
<n-spin :show="loading">
|
||||||
|
<n-grid :cols="2" :x-gap="16" :y-gap="16">
|
||||||
|
<n-grid-item v-for="(item, index) in shareLinks" :key="index">
|
||||||
|
<n-card embedded class="qrcode-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header bg-zinc-100 p-3 rounded-md">
|
||||||
|
<n-text>{{ item.title }}</n-text>
|
||||||
|
<n-tooltip trigger="hover" placement="top">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button quaternary circle size="small" @click="copyUrl(item.url)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><copy-outline /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
复制链接
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="qrcode-container">
|
||||||
|
<n-qr-code :value="item.url" :size="200" error-correction-level="H"
|
||||||
|
style="box-sizing: content-box;" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div @click="openUrl(item.url)" class="cursor-pointer hover:text-blue-500 hover:underline">
|
||||||
|
<n-ellipsis style="max-width: 100%" :tooltip="{ maxWidth: 300 }">
|
||||||
|
{{ item.url }}
|
||||||
|
</n-ellipsis>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
|
</n-spin>
|
||||||
|
</n-space>
|
||||||
|
</n-scrollbar>
|
||||||
|
</miao-message-provider>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CopyOutline } from '@vicons/ionicons5'
|
||||||
|
import { onMounted, ref, useTemplateRef } from 'vue'
|
||||||
|
import useServerAddresses from '@/hooks/useServerAddresses'
|
||||||
|
import MiaoMessageProvider from '@/components/miaoAlertTipProvider.vue'
|
||||||
|
|
||||||
|
// 服务器地址服务
|
||||||
|
const serverAddressesService = useServerAddresses()
|
||||||
|
|
||||||
|
interface ShareLink {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(true)
|
||||||
|
// 分享链接
|
||||||
|
const shareLinks = ref<ShareLink[]>([])
|
||||||
|
|
||||||
|
// 使用本地消息提示组件
|
||||||
|
const messageProviderRef = useTemplateRef<InstanceType<typeof MiaoMessageProvider>>('messageProviderRef')
|
||||||
|
|
||||||
|
// 组件挂载后获取地址列表
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await serverAddressesService.fetchAddresses()
|
||||||
|
shareLinks.value = serverAddressesService.addresses.value
|
||||||
|
} catch (error) {
|
||||||
|
// 使用本地的alertTip
|
||||||
|
messageProviderRef.value?.alertTip('获取分享链接失败', {
|
||||||
|
type: 'error',
|
||||||
|
timeout: 3000
|
||||||
|
})
|
||||||
|
console.error('获取分享链接失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制URL到剪贴板
|
||||||
|
*/
|
||||||
|
const copyUrl = (url: string) => {
|
||||||
|
navigator.clipboard.writeText(url).then(
|
||||||
|
() => {
|
||||||
|
// 使用本地的alertTip
|
||||||
|
messageProviderRef.value?.alertTip('链接已复制到剪贴板', {
|
||||||
|
type: 'success',
|
||||||
|
timeout: 3000
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.error('复制失败:', err)
|
||||||
|
// 使用本地的alertTip
|
||||||
|
messageProviderRef.value?.alertTip('复制失败', {
|
||||||
|
type: 'error',
|
||||||
|
timeout: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUrl = (url: string) => {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.share-card {
|
||||||
|
min-width: 680px;
|
||||||
|
max-width: 800px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-card {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+2
-1
@@ -14,7 +14,8 @@ export default {
|
|||||||
query: '/query',
|
query: '/query',
|
||||||
rename: '/rename',
|
rename: '/rename',
|
||||||
mkdir: '/mkdir',
|
mkdir: '/mkdir',
|
||||||
cut: '/cut'
|
cut: '/cut',
|
||||||
|
addresses: '/addresses'
|
||||||
},
|
},
|
||||||
enableUpload: true
|
enableUpload: true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import config from '@/config'
|
||||||
|
import { useMiaoFetch } from '@/hooks/useMiaoFetchApi'
|
||||||
|
|
||||||
|
export interface ServerAddress {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器所有可用的访问地址
|
||||||
|
*/
|
||||||
|
export default function useServerAddresses() {
|
||||||
|
const addresses = ref<ServerAddress[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<Error | null>(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从服务器获取可用地址列表
|
||||||
|
*/
|
||||||
|
const fetchAddresses = async () => {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { baseUrl, api } = config
|
||||||
|
const { miaoFetch } = useMiaoFetch({ retry: 1 })
|
||||||
|
|
||||||
|
const response = await miaoFetch({
|
||||||
|
url: `${baseUrl}${api.addresses}`
|
||||||
|
})
|
||||||
|
|
||||||
|
addresses.value = response.data as ServerAddress[]
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取服务器地址失败:', err)
|
||||||
|
error.value = err as Error
|
||||||
|
|
||||||
|
// 如果API失败,提供基本的地址
|
||||||
|
addresses.value = [
|
||||||
|
{ title: '当前地址', url: config.baseUrl },
|
||||||
|
{ title: '本地地址', url: 'http://localhost:17705' }
|
||||||
|
]
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addresses,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
fetchAddresses
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 获取本机的局域网IP地址
|
||||||
|
* 使用Web API获取可用的网络接口信息(如果浏览器支持)
|
||||||
|
* @returns 一个Promise,解析为包含可用局域网IP地址的数组
|
||||||
|
*/
|
||||||
|
export async function getLocalIpAddresses(): Promise<string[]> {
|
||||||
|
// 部分浏览器未实现RTCPeerConnection对象
|
||||||
|
if (!window.RTCPeerConnection) {
|
||||||
|
console.warn('当前浏览器不支持获取局域网IP')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const ips: string[] = []
|
||||||
|
const pc = new RTCPeerConnection({
|
||||||
|
// 使用Google的STUN服务器
|
||||||
|
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
pc.createDataChannel('')
|
||||||
|
|
||||||
|
// 创建一个offer来触发ICE候选者的收集
|
||||||
|
pc.createOffer()
|
||||||
|
.then(offer => pc.setLocalDescription(offer))
|
||||||
|
.catch(err => console.error('获取IP地址时出错:', err))
|
||||||
|
|
||||||
|
// 监听ICE候选者事件
|
||||||
|
pc.onicecandidate = (event) => {
|
||||||
|
if (!event.candidate) {
|
||||||
|
// ICE收集完成
|
||||||
|
pc.close()
|
||||||
|
resolve(ips)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析候选者信息中的IP地址
|
||||||
|
const ipMatch = /([0-9]{1,3}(\.[0-9]{1,3}){3})/.exec(event.candidate.candidate)
|
||||||
|
const ipAddress = ipMatch && ipMatch[1]
|
||||||
|
|
||||||
|
if (ipAddress && !ips.includes(ipAddress) && isPrivateIp(ipAddress)) {
|
||||||
|
ips.push(ipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5秒超时,确保不会无限等待
|
||||||
|
setTimeout(() => {
|
||||||
|
pc.close()
|
||||||
|
resolve(ips)
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个IP地址是否为私有局域网IP
|
||||||
|
* @param ip IP地址
|
||||||
|
* @returns 是否为私有IP
|
||||||
|
*/
|
||||||
|
function isPrivateIp(ip: string): boolean {
|
||||||
|
const parts = ip.split('.').map(part => parseInt(part, 10))
|
||||||
|
|
||||||
|
// 检查是否是局域网IP
|
||||||
|
// 10.0.0.0 - 10.255.255.255
|
||||||
|
// 172.16.0.0 - 172.31.255.255
|
||||||
|
// 192.168.0.0 - 192.168.255.255
|
||||||
|
return (
|
||||||
|
(parts[0] === 10) ||
|
||||||
|
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
||||||
|
(parts[0] === 192 && parts[1] === 168)
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
-1
@@ -40,7 +40,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'^/(get|upload|delete|query|rename|cut|mkdir)': {
|
'^/(get|upload|delete|query|rename|cut|mkdir|addresses)': {
|
||||||
target: 'http://127.0.0.1:17705',
|
target: 'http://127.0.0.1:17705',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path
|
rewrite: (path) => path
|
||||||
|
|||||||
Reference in new issue
Block a user