Compare commits

..

7 Commits

Author SHA1 Message Date
db065073e2 修复一下页面配置更改时无法修改配置描述 2024-10-16 14:37:16 +08:00
befaa17426 📝更新readme 2024-10-15 17:26:10 +08:00
fae96eccf5 📝更新readme 2024-10-15 17:22:39 +08:00
4a0ec7bdc9 📝更新readme 2024-10-15 17:21:02 +08:00
331849522f 🎨将邮箱smtp配置改为系统当中配置 2024-10-15 17:16:22 +08:00
fa04d9c83a 🎨首页控制面板的客户端链接列表新增右键刷新 2024-10-10 15:03:57 +08:00
958b1b4aec ⬆️升级依赖 2024-10-10 14:29:21 +08:00
19 changed files with 136 additions and 63 deletions

View File

@@ -34,14 +34,6 @@ file:
accessSecret: # oss必填
bucketName: # oss必填
# 邮件设置
mail:
host:
port:
user:
password:
skipTls:
# 一些系统配置
wireguard:
restartMode: DELAY
@@ -78,14 +70,25 @@ services:
账户: admin
密码: admin123
```
## 配置示例
```text
1. 邮箱配置如下:
code: EMAIL_SMTP
配置项:
1. host: "xxxx.xxx"
2. port: "123"
3. user: "haha"
4. password: "haha123"
5. skipTls: "false"
```
## 示
![img.png](img.png)
![img_1.png](img_1.png)
![img_7.png](img_7.png)
![img_8.png](img_8.png)
![img_2.png](img_2.png)
![img_3.png](img_3.png)
![img_4.png](img_4.png)
![img_5.png](img_5.png)
![img_6.png](img_6.png)
## 页面展
![img.png](document/img.png)
![img_1.png](document/img_1.png)
![img_7.png](document/img_7.png)
![img_8.png](document/img_8.png)
![img_2.png](document/img_2.png)
![img_3.png](document/img_3.png)
![img_4.png](document/img_4.png)
![img_5.png](document/img_5.png)
![img_6.png](document/img_6.png)

View File

@@ -7,6 +7,5 @@ type config struct {
Database *database `yaml:"database"`
Cache *cache `yaml:"redis"`
File *file `yaml:"file"`
Mail *mail `yaml:"email"`
Wireguard *wireguard `yaml:"wireguard"`
}

View File

@@ -1,9 +0,0 @@
package config
type mail struct {
Host string `json:"host" yaml:"host"`
Port int `json:"port" yaml:"port"`
User string `json:"user" yaml:"user"`
Password string `json:"password" yaml:"password"`
SkipTls bool `json:"skipTls" yaml:"skipTls"`
}

View File

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 209 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

33
go.mod
View File

@@ -5,14 +5,27 @@ go 1.21
require (
gitee.ltd/lxh/logger v1.0.15
github.com/cowardmrx/go_aliyun_oss v1.0.7
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/pprof v1.5.0
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.22.0
github.com/go-resty/resty/v2 v2.13.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/json-iterator/go v1.1.12
github.com/mojocn/base64Captcha v1.3.6
github.com/redis/go-redis/v9 v9.5.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cast v1.6.0
github.com/spf13/viper v1.19.0
golang.org/x/crypto v0.23.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.10
@@ -28,26 +41,19 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.7.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-playground/locales v0.14.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-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
@@ -55,10 +61,8 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lixh00/loki-client-go v1.0.1 // indirect
@@ -71,7 +75,6 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mojocn/base64Captcha v1.3.6 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@@ -84,10 +87,8 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@@ -96,21 +97,19 @@ require (
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

14
go.sum
View File

@@ -210,8 +210,6 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU=
github.com/gin-contrib/pprof v1.5.0/go.mod h1:GqFL6LerKoCQ/RSWnkYczkTJ+tOAUVN/8sbnEtaqOKs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -309,8 +307,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
@@ -931,8 +927,9 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqR
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1028,8 +1025,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1121,8 +1118,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -243,7 +243,14 @@ func (ClientApi) Download(c *gin.Context) {
return
}
err = utils.Mail().SendMail(data.Email, fmt.Sprintf("客户端: %s", data.Name), "请查收附件", outPath)
// 获取邮箱配置
emailConf, err := service.Setting().GetByCode("EMAIL_SMTP")
if err != nil {
response.R(c).FailedWithError("获取邮箱配置失败请先到设置页面的【其他】里面添加code为【EMAIL_SMTP】的具体配置")
return
}
err = utils.Mail(emailConf).SendMail(data.Email, fmt.Sprintf("客户端: %s", data.Name), "请查收附件", outPath)
if err != nil {
response.R(c).FailedWithError("发送邮件失败")
return

View File

@@ -90,6 +90,17 @@ func (s setting) GetAllSetting(blackList []string) (data []vo.SettingItem, err e
return
}
// GetByCode
// @description: 获取指定code的配置
// @receiver s
// @param code
// @return data
// @return err
func (s setting) GetByCode(code string) (data *model.Setting, err error) {
err = s.Model(&model.Setting{}).Where("code = ?", code).Take(&data).Error
return
}
// Export
// @description: 导出
// @receiver s

View File

@@ -2,28 +2,35 @@ package utils
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/jordan-wright/email"
"github.com/spf13/cast"
"mime"
"net/smtp"
"net/textproto"
"path/filepath"
"wireguard-ui/config"
"wireguard-ui/model"
)
type mail struct {
*email.Email
addr string
auth smtp.Auth
conf map[string]string
}
func Mail() *mail {
func Mail(conf *model.Setting) *mail {
// 解析配置文件
var mailConf = make(map[string]string)
_ = json.Unmarshal([]byte(conf.Data), &mailConf)
var m mail
em := email.NewEmail()
m.Email = em
m.auth = smtp.PlainAuth("", config.Config.Mail.User, config.Config.Mail.Password, config.Config.Mail.Host)
m.addr = fmt.Sprintf("%s:%d", config.Config.Mail.Host, config.Config.Mail.Port)
m.auth = smtp.PlainAuth("", mailConf["user"], mailConf["password"], mailConf["host"])
m.addr = fmt.Sprintf("%s:%s", mailConf["host"], mailConf["port"])
m.conf = mailConf
return &m
}
@@ -47,7 +54,7 @@ func (m *mail) VerifyConfig() (err error) {
// @param attacheFilePath
// @return err
func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error) {
m.From = fmt.Sprintf("wg-dashboard <%s>", config.Config.Mail.User)
m.From = fmt.Sprintf("wg-dashboard <%s>", m.conf["user"])
m.To = []string{to}
m.Subject = subject
m.Text = []byte(content)
@@ -61,13 +68,13 @@ func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error
atch.Header = emHeader
}
if config.Config.Mail.SkipTls {
if cast.ToBool(m.conf["skipTls"]) {
return m.Send(m.addr, m.auth)
}
tlsConfig := &tls.Config{}
tlsConfig.InsecureSkipVerify = config.Config.Mail.SkipTls
tlsConfig.ServerName = config.Config.Mail.Host
tlsConfig.InsecureSkipVerify = cast.ToBool(m.conf["skipTls"])
tlsConfig.ServerName = m.conf["host"]
return m.SendWithTLS(m.addr, m.auth, tlsConfig)
}

View File

@@ -174,6 +174,9 @@
<n-radio :value="false" :checked="editFormModel.data[index] === false" @change="editFormModel.data[index] = false"></n-radio>
</n-radio-group>
</n-form-item>
<n-form-item label="配置描述">
<n-input v-model:value="editFormModel.describe" />
</n-form-item>
<n-form-item>
<n-button type="info" @click="updateSetting">确认</n-button>
</n-form-item>

View File

@@ -27,6 +27,18 @@
remote
:columns="connectionsColumns"
:data="connectionsData.data"
:row-props="rowProps"
/>
<n-dropdown
placement="bottom-start"
trigger="manual"
size="small"
:x="xRef"
:y="yRef"
:options="rightMenuOpts"
@select="rowSelect"
@clickoutside="rowClick"
:show="showDropdownRef"
/>
</n-card>
</AppPage>
@@ -35,7 +47,7 @@
<script setup>
import { useUserStore } from '@/store'
import api from '@/views/workbench/api'
import { debounce } from '@/utils'
import { debounce, renderIcon } from '@/utils'
import { NTag } from 'naive-ui'
const userStore = useUserStore()
@@ -152,6 +164,49 @@ const connectionsColumns = [
}
]
// 链接列表邮件刷新菜单
const rightMenuOpts = [
{
label: () => h('span',{ style: { color: 'green' }}, '刷新'),
key: "refresh",
icon: renderIcon('tabler:refresh',{ size: 14 })
}
]
// 右键菜单的设置
const showDropdownRef = ref(false);
const xRef = ref(0);
const yRef = ref(0);
// 右键菜单的基本位置逻辑
function rowProps(row) {
return {
onContextmenu: (e) => {
// $message.info(JSON.stringify(row, null, 2));
e.preventDefault();
showDropdownRef.value = false;
nextTick().then(() => {
showDropdownRef.value = true;
xRef.value = e.clientX;
yRef.value = e.clientY;
});
}
};
}
// 右键菜单的逻辑
function rowSelect(row) {
switch (row) {
case "refresh":
getClientConnections()
showDropdownRef.value = false
}
}
function rowClick() {
showDropdownRef.value = false
}
// 表格数据
const tableData = ref({
data: []