Compare commits

...

6 Commits
next ... main

Author SHA1 Message Date
江西小徐
d91bdb3bd0 🔥 移动目录 2024-04-07 11:54:49 +08:00
江西小徐
bce2e833b5 🔥 移动目录 2024-04-07 11:53:20 +08:00
江西小徐
6a2c5f7f5f 🧑‍💻 等待完善 2024-04-07 11:50:57 +08:00
江西小徐
3c69d62491 🧑‍💻 等待完善 2024-04-07 11:50:55 +08:00
江西小徐
92d1600a6b 🔀 基础合并 三天后上传新版本 2024-04-07 11:48:23 +08:00
江西小徐
61275be2bf 🎨 增加 Go 后端 2024-04-07 11:08:05 +08:00
161 changed files with 6752 additions and 3599 deletions

View File

@ -12,6 +12,7 @@ Nuxt-Whois 是一个基于 Nuxt3、Tailwind CSS 和 Xep-Whois 构建的Whois查
## 更新说明 ## 更新说明
- 2024.4.7 增加 Go 后端 完善后台管理 增加用户自定义主题色 (目前基础合并,三天后会提交正式,禁止同步当前)
- 2024.3.25 抛弃原来 NuxtUi 改用 NaiveUi 重构中 后台增加中 当前版本无法上线使用 - 2024.3.25 抛弃原来 NuxtUi 改用 NaiveUi 重构中 后台增加中 当前版本无法上线使用
- 2024.3.18 重构V2版本 预计三天内完成。 - 2024.3.18 重构V2版本 预计三天内完成。

View File

@ -1,27 +0,0 @@
import {_colors, _fontFamily} from "#tailwind-config/theme.mjs";
export default defineAppConfig({
naiveui: {
themeConfig: {
shared: {
common: {
fontFamily: _fontFamily.sans.join(", "),
},
},
light: {
common: {
primaryColor: _colors.blue[600],
primaryColorHover: _colors.blue[500],
primaryColorPressed: _colors.blue[700],
},
},
dark: {
common: {
primaryColor: _colors.blue[500],
primaryColorHover: _colors.blue[400],
primaryColorPressed: _colors.blue[600],
},
},
},
},
});

3
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/storage/
.idea
*.log

21
backend/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Nunu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
backend/README.md Normal file
View File

@ -0,0 +1,47 @@
# Whois Go
等待完善
## Features
- **Gin**: https://github.com/gin-gonic/gin
- **Wire**: https://github.com/google/wire
- **Viper**: https://github.com/spf13/viper
- More...
## 项目结构
```
.
├── cmd
│ └── server
│ ├── main.go
│ ├── wire.go
│ └── wire_gen.go
├── config
│ ├── local.yml
│ └── prod.yml
├── internal
│ ├── handler
│ │ ├── handler.go
│ │ └── user.go
│ ├── middleware
│ │ └── cors.go
│ ├── model
│ │ └── user.go
│ ├── repository
│ │ ├── repository.go
│ │ └── user.go
│ ├── server
│ │ └── http.go
│ └── service
│ ├── service.go
│ └── user.go
├── pkg
├── LICENSE
├── README.md
├── README_zh.md
├── go.mod
└── go.sum
```

25
backend/api/v1/config.go Normal file
View File

@ -0,0 +1,25 @@
package v1
// WhoisApiInterface is the interface for WhoisApi
type WhoisApiInterface struct {
Label string `json:"label"`
Name string `json:"name"`
Order int `json:"order"`
Show bool `json:"show"`
Disabled bool `json:"disabled"`
}
// DNSApiInterface is the interface for DNSApi
type DNSApiInterface struct {
Label string `json:"label"`
Name string `json:"name"`
Order int `json:"order"`
Show bool `json:"show"`
Disabled bool `json:"disabled"`
IName string `json:"iName"`
Flag string `json:"flag"`
}
type FileConfig struct {
Config string `json:"config" binding:"required"`
}

62
backend/api/v1/respone.go Normal file
View File

@ -0,0 +1,62 @@
package v1
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Response struct {
Code int `json:"code"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
}
const (
ERROR = 500
SUCCESS = 200
)
func Result(code int, data interface{}, msg string, c *gin.Context) {
// 开始时间
c.JSON(http.StatusOK, Response{
code,
data,
msg,
})
}
func Ok(c *gin.Context) {
Result(SUCCESS, map[string]interface{}{}, "操作成功", c)
}
func OkWithMessage(message string, c *gin.Context) {
Result(SUCCESS, map[string]interface{}{}, message, c)
}
func OkWithData(data interface{}, c *gin.Context) {
Result(SUCCESS, data, "查询成功", c)
}
func OkWithDetailed(data interface{}, message string, c *gin.Context) {
Result(SUCCESS, data, message, c)
}
func Fail(c *gin.Context) {
Result(ERROR, map[string]interface{}{}, "操作失败", c)
}
func FailWithMessage(message string, c *gin.Context) {
Result(ERROR, map[string]interface{}{}, message, c)
}
func NoAuth(message string, c *gin.Context) {
c.JSON(http.StatusUnauthorized, Response{
7,
nil,
message,
})
}
func FailWithDetailed(data interface{}, message string, c *gin.Context) {
Result(ERROR, data, message, c)
}

82
backend/api/v1/tianhu.go Normal file
View File

@ -0,0 +1,82 @@
package v1
import "time"
// TianHuWhoisResponse represents the top-level structure of the WHOIS response.
type TianHuWhoisResponse struct {
Code string `json:"code"`
Message string `json:"message"`
Data TianHuWhoisData `json:"data"`
}
// TianHuWhoisData represents the detailed data part of the WHOIS response.
type TianHuWhoisData struct {
URL string `json:"url"`
Result string `json:"result"`
Status int `json:"status"`
Formatted TianHuFormattedWhois `json:"formatted"`
TLD string `json:"tld"`
Timezone TianHuWhoisTimezone `json:"timezone"`
}
// TianHuFormattedWhois represents the formatted WHOIS data.
type TianHuFormattedWhois struct {
Key string `json:"key"`
Domain TianHuDomainInfo `json:"domain"`
Registrar TianHuRegistrarInfo `json:"registrar"`
Registrant TianHuRegistrantInfo `json:"registrant"`
Administrative TianHuAdministrativeInfo `json:"administrative"`
Technical TianHuTechnicalInfo `json:"technical"`
Billing TianHuBillingInfo `json:"billing"`
}
// TianHuDomainInfo contains information about the domain.
type TianHuDomainInfo struct {
NameServers []string `json:"name_servers"`
Status []string `json:"status"`
Domain string `json:"domain"`
ID string `json:"id"`
WhoisServer string `json:"whois_server"`
UpdatedDate time.Time `json:"updated_date"`
CreatedDate time.Time `json:"created_date"`
ExpiredDate time.Time `json:"expired_date"`
DNSSEC bool `json:"dnssec"`
}
// TianHuRegistrarInfo contains information about the registrar.
type TianHuRegistrarInfo struct {
Key string `json:"key"`
ReferralURL string `json:"referral_url"`
RegistrarName string `json:"registrar_name"`
RegistrarIANAID string `json:"registrar_ianaid"`
RegistrarEmail string `json:"registrar_email"`
RegistrarPhone string `json:"registrar_phone"`
}
// TianHuRegistrantInfo, TianHuAdministrativeInfo, TianHuTechnicalInfo, and TianHuBillingInfo
// can be defined similarly, depending on the details you wish to include.
type TianHuRegistrantInfo struct {
Key string `json:"key"`
// Additional fields can be added here.
}
type TianHuAdministrativeInfo struct {
Key string `json:"key"`
// Additional fields can be added here.
}
type TianHuTechnicalInfo struct {
Key string `json:"key"`
// Additional fields can be added here.
}
type TianHuBillingInfo struct {
Key string `json:"key"`
// Additional fields can be added here.
}
// TianHuWhoisTimezone represents the timezone information of the WHOIS data.
type TianHuWhoisTimezone struct {
UTCOffset int `json:"utcoffset"`
Demo time.Time `json:"demo"`
}

14
backend/api/v1/whois.go Normal file
View File

@ -0,0 +1,14 @@
package v1
type WhoisServer struct {
Name string `json:"name" binding:"required"`
Domain string `json:"domain" binding:"required"`
}
// WhocxRequestParams 请求参数结构体
type WhocxRequestParams struct {
Domain string `json:"domain"` // 域名 (必选)
Whois string `json:"whois"` // 域名的whois原始信息 (必选)
Lang string `json:"lang,omitempty"` // 语言代码 (可选)
TimeZone string `json:"time_zone,omitempty"` // 时区 (可选)
}

View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"go.uber.org/zap"
"whois-go/pkg/config"
"whois-go/pkg/http"
"whois-go/pkg/log"
)
func main() {
conf := config.NewConfig()
logger := log.NewLog(conf)
logger.Info("server start", zap.String("host", "http://127.0.0.1:"+conf.GetString("http.port")))
app, cleanup, err := newApp(conf, logger)
if err != nil {
panic(err)
}
defer cleanup()
http.Run(app, fmt.Sprintf(":%d", conf.GetInt("http.port")))
}

View File

@ -0,0 +1,43 @@
//go:build wireinject
// +build wireinject
package main
import (
"github.com/gin-gonic/gin"
"github.com/google/wire"
"github.com/spf13/viper"
"whois-go/internal/handler"
"whois-go/internal/repository"
"whois-go/internal/server"
"whois-go/internal/service"
"whois-go/pkg/log"
)
var ServerSet = wire.NewSet(server.NewServerHTTP)
var RepositorySet = wire.NewSet(
repository.NewDb,
repository.NewRepository,
)
var ServiceSet = wire.NewSet(
service.NewService,
service.NewUserService,
service.NewSystemService,
)
var HandlerSet = wire.NewSet(
handler.NewHandler,
handler.NewUserHandler,
handler.NewSystemHandler,
)
func newApp(*viper.Viper, *log.Logger) (*gin.Engine, func(), error) {
panic(wire.Build(
ServerSet,
//RepositorySet,
ServiceSet,
HandlerSet,
))
}

View File

@ -0,0 +1,40 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"github.com/gin-gonic/gin"
"github.com/google/wire"
"github.com/spf13/viper"
"whois-go/internal/handler"
"whois-go/internal/repository"
"whois-go/internal/server"
"whois-go/internal/service"
"whois-go/pkg/log"
)
// Injectors from wire.go:
func newApp(viperViper *viper.Viper, logger *log.Logger) (*gin.Engine, func(), error) {
handlerHandler := handler.NewHandler(logger)
serviceService := service.NewService(logger)
systemService := service.NewSystemService(serviceService, logger)
systemHandler := handler.NewSystemHandler(handlerHandler, systemService)
engine := server.NewServerHTTP(logger, systemHandler)
return engine, func() {
}, nil
}
// wire.go:
var ServerSet = wire.NewSet(server.NewServerHTTP)
var RepositorySet = wire.NewSet(repository.NewDb, repository.NewRepository)
var ServiceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewSystemService)
var HandlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewSystemHandler)

View File

@ -0,0 +1,47 @@
[
{
"label": "本地接口",
"name": "nuxt",
"order": 0,
"show": true,
"disabled": false,
"iName": "本地 DNS",
"flag": "material-symbols:dns-outline"
},
{
"label": "Google",
"name": "google",
"order": 1,
"show": true,
"disabled": false,
"iName": "Google",
"flag": "flat-color-icons:google"
},
{
"label": "AliYun",
"name": "aliyun",
"order": 2,
"show": true,
"disabled": false,
"iName": "AliYun",
"flag": "ant-design:aliyun-outlined"
},
{
"label": "Tencent",
"name": "tencent",
"order": 3,
"show": true,
"disabled": false,
"iName": "Tencent",
"flag": "emojione:cloud"
},
{
"label": "Cloudflare",
"name": "cloudflare",
"order": 4,
"show": true,
"disabled": false,
"iName": "CloudFlare",
"flag": "skill-icons:cloudflare-light"
}
]

View File

@ -0,0 +1,18 @@
[
{
"label": "本地接口",
"name": "nuxt",
"order": 0,
"show": false,
"disabled": true,
"iName": "本地 DNS",
"flag": "material-symbols:dns-outline"
},
{
"label": "TIAN.HU",
"name": "tianhu",
"order": 1,
"show": true,
"disabled": false
}
]

View File

@ -0,0 +1,23 @@
[
{
"label": "本地接口",
"name": "nuxt",
"order": 0,
"show": true,
"disabled": false
},
{
"label": "WHO.CX",
"name": "whocx",
"order": 1,
"show": false,
"disabled": true
},
{
"label": "TIAN.HU",
"name": "tianhu",
"order": 2,
"show": true,
"disabled": false
}
]

34
backend/config/local.yml Normal file
View File

@ -0,0 +1,34 @@
env: local
http:
port: 8000
#网站配置
login:
username: admin
password: 123456
siteConfig:
logoLeftText: NuxtLeftLogo
logoRightText: NuxtRightLogo
defaultSelectOptions:
- label: Whois
value: whois
- label: Dns
value: dns
- label: Domain
value: domain
whoisSeverApi:
- whocx: https://who.cx/api/whois_extract
- tianhu: https://api.tian.hu/whois.php
dnsSeverApi:
- google: https://dns.google/resolve
- cloudflare: http://1.1.1.1/dns-query
- aliyun: https://223.5.5.5/resolve
- tencent: https://doh.pub/dns-query
log:
log_level: debug
encoding: console # json or console
log_file_name: "./storage/logs/server.log"
max_backups: 30 # 日志文件最多保存多少个备份
max_age: 7 # 文件最多保存多少天
max_size: 1024 # 每个日志文件保存的最大尺寸 单位M
compress: true # 是否压缩

27
backend/config/prod.yml Normal file
View File

@ -0,0 +1,27 @@
env: local
http:
port: 8000
security:
api_sign:
app_key: 123456
app_security: 123456
jwt:
key: 1234
data:
mysql:
user: root:123456@tcp(127.0.0.1:3380)/user?charset=utf8mb4&parseTime=True&loc=Local
redis:
addr: 127.0.0.1:6350
password: ""
db: 0
read_timeout: 0.2s
write_timeout: 0.2s
log:
log_level: info
encoding: json # json or console
log_file_name: "./storage/logs/server.log"
max_backups: 30 # 日志文件最多保存多少个备份
max_age: 7 # 文件最多保存多少天
max_size: 1024 # 每个日志文件保存的最大尺寸 单位M
compress: true # 是否压缩

28
backend/go.mod Normal file
View File

@ -0,0 +1,28 @@
module whois-go
go 1.16
require (
github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/google/uuid v1.4.0
github.com/google/wire v0.6.0
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/pkg/errors v0.9.1
github.com/sony/sonyflake v1.1.0
github.com/spf13/viper v1.18.2
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0
golang.org/x/arch v0.7.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/net v0.23.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/gorm v1.25.9
)

2592
backend/go.sum Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
package config
type Server struct {
SiteConfig SiteConfig `mapstructure:"siteConfig" json:"siteConfig" yaml:"siteConfig"`
Login LoginConfig `mapstructure:"login" json:"login" yaml:"login"`
}

View File

@ -0,0 +1,6 @@
package config
type LoginConfig struct {
Username string `mapstructure:"username" json:"username" yaml:"username"`
Password string `mapstructure:"password" json:"password" yaml:"password"`
}

View File

@ -0,0 +1,13 @@
package config
type SiteConfig struct {
LogoLeftText string `mapstructure:"logoLeftText" json:"logoLeftText" yaml:"logoLeftText"` // Logo左文字
LogoRightText string `mapstructure:"logoRightText" json:"logoRightText" yaml:"logoRightText"` // logo右文字
DefaultSelectOptions []SelectOptions `mapstructure:"defaultSelectOptions" json:"defaultSelectOptions" yaml:"defaultSelectOptions"` // 默认选择项
WhoisServerApi map[string]string `mapstructure:"whoisSeverApi" json:"whoisSeverApi" yaml:"whoisSeverApi"`
}
type SelectOptions struct {
Label string `json:"label"`
Value string `json:"value"`
}

View File

@ -0,0 +1,9 @@
package global
import "whois-go/internal/config"
var (
// Version is the version of the application.
G_Version = "0.1"
G_CONFIG config.Server
)

View File

@ -0,0 +1,15 @@
package handler
import (
"whois-go/pkg/log"
)
type Handler struct {
logger *log.Logger
}
func NewHandler(logger *log.Logger) *Handler {
return &Handler{
logger: logger,
}
}

View File

@ -0,0 +1,78 @@
package handler
import (
"github.com/gin-gonic/gin"
"net/http"
v1 "whois-go/api/v1"
"whois-go/internal/service"
)
type SystemHandler interface {
GetWebSiteConfig(ctx *gin.Context)
GetWhoisServer(c *gin.Context)
GetWhois(c *gin.Context)
}
type systemHandler struct {
*Handler
systemService service.SystemService
}
func NewSystemHandler(handler *Handler, systemService service.SystemService) SystemHandler {
return &systemHandler{
Handler: handler,
systemService: systemService,
}
}
func (h *systemHandler) GetWebSiteConfig(c *gin.Context) {
config, err := h.systemService.GetSystemConfig()
if err != nil {
v1.FailWithMessage("获取失败", c)
return
}
v1.OkWithDetailed(config, "获取成功", c)
}
func (h *systemHandler) GetWhoisServer(c *gin.Context) {
var fileConfig v1.FileConfig
// 从请求体中绑定JSON数据到configs变量
if err := c.BindJSON(&fileConfig); err != nil {
// 如果解析出错,返回错误信息
v1.FailWithMessage("解析失败", c)
return
}
config, err := h.systemService.GetWhoisJson(fileConfig.Config)
if err != nil {
v1.FailWithMessage("获取失败", c)
return
}
v1.OkWithDetailed(config, "获取成功", c)
}
func (h *systemHandler) GetWhois(c *gin.Context) {
var whoisServer v1.WhoisServer
if err := c.BindJSON(&whoisServer); err != nil {
// 如果解析出错,返回错误信息
v1.FailWithMessage("解析失败", c)
return
}
//判断whoisServer的值来进行不同的函数
switch whoisServer.Name {
case "whocx":
resp := h.systemService.WhoisServerIsWhocx(whoisServer.Domain)
v1.OkWithDetailed(resp, "获取成功", c)
return
case "tianhu":
resp, err := h.systemService.WhoisServerIsTianhu(whoisServer.Domain)
if err != nil {
v1.FailWithMessage("获取失败", c)
return
}
c.JSON(http.StatusOK, resp)
return
default:
v1.FailWithMessage("未知的Whois服务器", c)
return
}
}

View File

@ -0,0 +1,20 @@
package handler
import (
"whois-go/internal/service"
)
type UserHandler interface {
}
type userHandler struct {
*Handler
userService service.UserService
}
func NewUserHandler(handler *Handler, userService service.UserService) UserHandler {
return &userHandler{
Handler: handler,
userService: userService,
}
}

View File

@ -0,0 +1,23 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin"))
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.Header("Access-Control-Allow-Methods", c.GetHeader("Access-Control-Request-Method"))
c.Header("Access-Control-Allow-Headers", c.GetHeader("Access-Control-Request-Headers"))
c.Header("Access-Control-Max-Age", "7200")
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}

View File

@ -0,0 +1,29 @@
package repository
import (
"whois-go/pkg/log"
"gorm.io/gorm"
)
type Repository struct {
db *gorm.DB
//rdb *redis.Client
logger *log.Logger
}
func NewRepository(logger *log.Logger, db *gorm.DB) *Repository {
return &Repository{
db: db,
//rdb: rdb,
logger: logger,
}
}
func NewDb() *gorm.DB {
// TODO: init db
//db, err := gorm.Open(mysql.Open(conf.GetString("data.mysql.user")), &gorm.Config{})
//if err != nil {
// panic(err)
//}
//return db
return &gorm.DB{}
}

View File

@ -0,0 +1,37 @@
package server
import (
"github.com/gin-gonic/gin"
"whois-go/internal/handler"
"whois-go/internal/middleware"
"whois-go/pkg/helper/resp"
"whois-go/pkg/log"
)
func NewServerHTTP(
logger *log.Logger,
systemHandler handler.SystemHandler,
) *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.Use(
middleware.CORSMiddleware(),
)
r.GET("/", func(ctx *gin.Context) {
resp.HandleSuccess(ctx, map[string]interface{}{
"say": "This is GoLang!",
})
})
// 获取配置文件内容
api := r.Group("/api")
api.POST("/getWebSiteConfig", systemHandler.GetWebSiteConfig)
api.POST("/getWhoisServer", systemHandler.GetWhoisServer)
server := api.Group("/server")
server.POST("/getWhois", systemHandler.GetWhois)
server.POST("/getDns", systemHandler.GetWhois)
return r
}

View File

@ -0,0 +1,13 @@
package service
import "whois-go/pkg/log"
type Service struct {
logger *log.Logger
}
func NewService(logger *log.Logger) *Service {
return &Service{
logger: logger,
}
}

View File

@ -0,0 +1,94 @@
package service
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
v1 "whois-go/api/v1"
"whois-go/internal/config"
"whois-go/internal/global"
"whois-go/internal/utils"
"whois-go/pkg/log"
)
type SystemService interface {
GetSystemConfig() (config.SiteConfig, error)
GetWhoisJson(fileConfig string) ([]v1.WhoisApiInterface, error)
WhoisServerIsWhocx(domain string) string
WhoisServerIsTianhu(domain string) (v1.TianHuWhoisResponse, error)
}
func NewSystemService(service *Service, logger *log.Logger) SystemService {
return &systemService{
Service: service,
logger: logger,
}
}
type systemService struct {
*Service
logger *log.Logger
}
// GetSystemConfig 读取配置文件
func (s *systemService) GetSystemConfig() (config.SiteConfig, error) {
return global.G_CONFIG.SiteConfig, nil
}
func (s *systemService) GetWhoisJson(fileConfig string) ([]v1.WhoisApiInterface, error) {
file, err := os.Open(fmt.Sprintf("config/json/%s.json", fileConfig))
if err != nil {
s.logger.Fatal(fmt.Sprintf("Error opening file: %s", err))
}
defer file.Close()
// 读取文件内容
bytes, err := ioutil.ReadAll(file)
// 反序列化JSON到结构体切片
var apis []v1.WhoisApiInterface
if err = json.Unmarshal(bytes, &apis); err != nil {
s.logger.Fatal(fmt.Sprintf("Error unmarshalling JSON: %s", err))
}
return apis, err
}
func (s *systemService) WhoisServerIsWhocx(domain string) string {
whocxURL := global.G_CONFIG.SiteConfig.WhoisServerApi["whocx"]
params := url.Values{}
params.Set("domain", domain)
params.Set("whois", "Domain Name: ")
params.Set("lang", "zh")
params.Set("time_zone", "8")
request, err := utils.SendPostRequest(whocxURL, params)
if err != nil {
return ""
}
return request
}
func (s *systemService) WhoisServerIsTianhu(domain string) (v1.TianHuWhoisResponse, error) {
tianhuURL := global.G_CONFIG.SiteConfig.WhoisServerApi["tianhu"]
params := url.Values{}
//domain=baidu.com&action=searchWhois
params.Set("domain", domain)
params.Set("action", "searchWhois")
var tianhuResp v1.TianHuWhoisResponse
// Append query parameters to the URL
fullURL := tianhuURL + "?" + params.Encode()
// Perform the GET request
response, err := http.Get(fullURL)
defer response.Body.Close()
// Read the response body
body, err := ioutil.ReadAll(response.Body)
err = json.Unmarshal(body, &tianhuResp)
return tianhuResp, err
}

View File

@ -0,0 +1,18 @@
package service
type UserService interface {
}
type userService struct {
*Service
}
func NewUserService(service *Service) UserService {
return &userService{
Service: service,
}
}
func (s *userService) GetUserById(id int64) {
}

View File

@ -0,0 +1,55 @@
package utils
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// FetchData 从给定的URL获取数据
func FetchData(url string) ([]byte, error) {
// 发起GET请求
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应体失败: %v", err)
}
return body, nil
}
// SendPostRequest 发送POST请求的函数
func SendPostRequest(requestURL string, params url.Values) (string, error) {
// 创建POST请求
req, err := http.NewRequest("POST", requestURL, strings.NewReader(params.Encode()))
if err != nil {
return "", fmt.Errorf("创建请求失败: %v", err)
}
// 设置请求头,表明请求体是表单数据
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应体失败: %v", err)
}
return string(body), nil
}

View File

@ -0,0 +1,44 @@
package config
import (
"flag"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"os"
"whois-go/internal/global"
)
func NewConfig() *viper.Viper {
envConf := os.Getenv("APP_CONF")
if envConf == "" {
flag.StringVar(&envConf, "conf", "config/local.yml", "config path, eg: -conf config/local.yml")
flag.Parse()
}
if envConf == "" {
envConf = "config/local.yml"
}
fmt.Println("load conf file:", envConf)
return getConfig(envConf)
}
func getConfig(path string) *viper.Viper {
conf := viper.New()
conf.SetConfigFile(path)
err := conf.ReadInConfig()
if err != nil {
panic(err)
}
conf.WatchConfig()
conf.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err = conf.Unmarshal(&global.G_CONFIG); err != nil {
fmt.Println(err)
}
})
if err = conf.Unmarshal(&global.G_CONFIG); err != nil {
panic(err)
}
return conf
}

View File

@ -0,0 +1,24 @@
package convert
const (
base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
func IntToBase62(n int) string {
if n == 0 {
return string(base62[0])
}
var result []byte
for n > 0 {
result = append(result, base62[n%62])
n /= 62
}
// 反转字符串
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return string(result)
}

View File

@ -0,0 +1,11 @@
package md5
import (
"crypto/md5"
"encoding/hex"
)
func Md5(str string) string {
hash := md5.Sum([]byte(str))
return hex.EncodeToString(hash[:])
}

View File

@ -0,0 +1,28 @@
package resp
import (
"github.com/gin-gonic/gin"
"net/http"
)
type response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func HandleSuccess(ctx *gin.Context, data interface{}) {
if data == nil {
data = map[string]string{}
}
resp := response{Code: 0, Message: "success", Data: data}
ctx.JSON(http.StatusOK, resp)
}
func HandleError(ctx *gin.Context, httpCode, code int, message string, data interface{}) {
if data == nil {
data = map[string]string{}
}
resp := response{Code: code, Message: message, Data: data}
ctx.JSON(httpCode, resp)
}

View File

@ -0,0 +1,32 @@
package sid
import (
"whois-go/pkg/helper/convert"
"github.com/pkg/errors"
"github.com/sony/sonyflake"
)
type Sid struct {
sf *sonyflake.Sonyflake
}
func NewSid() *Sid {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
if sf == nil {
panic("sonyflake not created")
}
return &Sid{sf}
}
func (s Sid) GenString() (string, error) {
// 生成分布式ID
id, err := s.sf.NextID()
if err != nil {
return "", errors.Wrap(err, "failed to generate sonyflake ID")
}
// 将ID转换为字符串
return convert.IntToBase62(int(id)), nil
}
func (s Sid) GenUint64() (uint64, error) {
// 生成分布式ID
return s.sf.NextID()
}

View File

@ -0,0 +1,7 @@
package uuid
import "github.com/google/uuid"
func GenUUID() string {
return uuid.NewString()
}

48
backend/pkg/http/http.go Normal file
View File

@ -0,0 +1,48 @@
package http
import (
"context"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func Run(r *gin.Engine, addr string) {
srv := &http.Server{
Addr: addr,
Handler: r,
}
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
// kill (no param) default send syscall.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown: ", err)
}
log.Println("Server exiting")
}

115
backend/pkg/log/log.go Normal file
View File

@ -0,0 +1,115 @@
package log
import (
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"time"
)
const LOGGER_KEY = "zapLogger"
type Logger struct {
*zap.Logger
}
func NewLog(conf *viper.Viper) *Logger {
return initZap(conf)
}
func initZap(conf *viper.Viper) *Logger {
// 日志地址 "out.log" 自定义
lp := conf.GetString("log.log_file_name")
// 日志级别 DEBUG,ERROR, INFO
lv := conf.GetString("log.log_level")
var level zapcore.Level
//debug<info<warn<error<fatal<panic
switch lv {
case "debug":
level = zap.DebugLevel
case "info":
level = zap.InfoLevel
case "warn":
level = zap.WarnLevel
case "error":
level = zap.ErrorLevel
default:
level = zap.InfoLevel
}
hook := lumberjack.Logger{
Filename: lp, // 日志文件路径
MaxSize: conf.GetInt("log.max_size"), // 每个日志文件保存的最大尺寸 单位M
MaxBackups: conf.GetInt("log.max_backups"), // 日志文件最多保存多少个备份
MaxAge: conf.GetInt("log.max_age"), // 文件最多保存多少天
Compress: conf.GetBool("log.compress"), // 是否压缩
}
var encoder zapcore.Encoder
if conf.GetString("log.encoding") == "console" {
encoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "Logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseColorLevelEncoder,
EncodeTime: timeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
})
} else {
encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})
}
core := zapcore.NewCore(
encoder, // 编码器配置
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
level, // 日志级别
)
if conf.GetString("env") != "prod" {
return &Logger{zap.New(core, zap.Development(), zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
}
return &Logger{zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
}
// 自定义时间编码器
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
//enc.AppendString(t.Format("2006-01-02 15:04:05"))
enc.AppendString(t.Format("2006-01-02 15:04:05.000000000"))
}
// NewContext 给指定的context添加字段
func (l *Logger) NewContext(ctx *gin.Context, fields ...zapcore.Field) {
ctx.Set(LOGGER_KEY, l.WithContext(ctx).With(fields...))
}
// WithContext 从指定的context返回一个zap实例
func (l *Logger) WithContext(ctx *gin.Context) *Logger {
if ctx == nil {
return l
}
zl, _ := ctx.Get(LOGGER_KEY)
ctxLogger, ok := zl.(*zap.Logger)
if ok {
return &Logger{ctxLogger}
}
return l
}

View File

@ -1,84 +0,0 @@
<script lang="ts" setup>
import {useTimeStore} from "~/stores/time";
const availableTimeZones = ref([
{ id: 1, name: 'UTC-12', displayName: 'International Date Line West' },
{ id: 2, name: 'UTC-11', displayName: 'Coordinated Universal Time-11' },
{ id: 3, name: 'UTC-10', displayName: 'Hawaii' },
{ id: 4, name: 'UTC-9', displayName: 'Alaska' },
{ id: 5, name: 'UTC-8', displayName: 'Pacific Time (US & Canada)' },
{ id: 6, name: 'UTC-7', displayName: 'Mountain Time (US & Canada)' },
{ id: 7, name: 'UTC-6', displayName: 'Central Time (US & Canada), Mexico City' },
{ id: 8, name: 'UTC-5', displayName: 'Eastern Time (US & Canada), Bogota, Lima' },
{ id: 9, name: 'UTC-4', displayName: 'Atlantic Time (Canada), Caracas, La Paz' },
{ id: 10, name: 'UTC-3', displayName: 'Buenos Aires, Georgetown' },
{ id: 11, name: 'UTC-2', displayName: 'Coordinated Universal Time-02' },
{ id: 12, name: 'UTC-1', displayName: 'Azores' },
{ id: 13, name: 'UTC', displayName: 'Coordinated Universal Time' },
{ id: 14, name: 'UTC+1', displayName: 'Brussels, Copenhagen, Madrid, Paris' },
{ id: 15, name: 'UTC+2', displayName: 'Athens, Bucharest, Istanbul' },
{ id: 16, name: 'UTC+3', displayName: 'Moscow, St. Petersburg, Nairobi' },
{ id: 17, name: 'UTC+3:30', displayName: 'Tehran' },
{ id: 18, name: 'UTC+4', displayName: 'Abu Dhabi, Muscat' },
{ id: 19, name: 'UTC+4:30', displayName: 'Kabul' },
{ id: 20, name: 'UTC+5', displayName: 'Islamabad, Karachi, Tashkent' },
{ id: 21, name: 'UTC+5:30', displayName: 'Chennai, Kolkata, Mumbai, New Delhi' },
{ id: 22, name: 'UTC+5:45', displayName: 'Kathmandu' },
{ id: 23, name: 'UTC+6', displayName: 'Astana, Dhaka' },
{ id: 24, name: 'UTC+6:30', displayName: 'Yangon (Rangoon)' },
{ id: 25, name: 'UTC+7', displayName: 'Bangkok, Hanoi, Jakarta' },
{ id: 26, name: 'UTC+8', displayName: 'Beijing, Hong Kong, Singapore, Taipei' },
{ id: 27, name: 'UTC+9', displayName: 'Osaka, Sapporo, Tokyo' },
{ id: 28, name: 'UTC+9:30', displayName: 'Adelaide, Darwin' },
{ id: 29, name: 'UTC+10', displayName: 'Brisbane, Canberra, Melbourne, Sydney' },
{ id: 30, name: 'UTC+11', displayName: 'Solomon Is., New Caledonia' },
{ id: 31, name: 'UTC+12', displayName: 'Auckland, Wellington' },
{ id: 32, name: 'UTC+13', displayName: 'Nuku alofa' }
]);
const timeStore = useTimeStore()
</script>
<template>
<div>
<HeadlessListbox
v-model="timeStore.timeZones"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Theme
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Color">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="ri:time-zone-line" class=" text-lg dark:text-white" />
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 max-h-60 overflow-y-auto rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<HeadlessListboxOption
v-for="item in availableTimeZones"
:key="item.id"
:value="item.name"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
timeStore.timeZones === item.name,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
timeStore.timeZones !== item.name,
}"
>
<span class="truncate">
{{ item.name }}
</span>
<span class="flex items-center justify-center text-sm">
</span>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

View File

@ -1,43 +0,0 @@
<script setup lang="ts">
defineProps({
data: {
type: Object,
required: true
}
})
</script>
<template>
<div class="flex bg-gray-100 p-8">
<div class="w-full mx-auto">
<div class="bg-white shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
<h2 class="text-xl font-bold">{{ item.name }}</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<p class="font-semibold">Type</p>
<p>{{ item.type }}</p>
</div>
<div>
<p class="font-semibold">TTL</p>
<p>{{ item.TTL }}</p>
</div>
<div class="col-span-2">
<p class="font-semibold">Data</p>
<p>{{ item.data }}</p>
</div>
</div>
<div class="flex space-x-2 mt-4">
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,43 +0,0 @@
export const trimDomain = (domain: string): string => {
return domain.trim().toLowerCase(); // 确保域名为小写
};
export const splitDomain = (domain: string): string[] => {
return domain.split('.');
};
//
// const SupportedTLDs = new Set(Object.keys(domainStore.SupportedTLDs));
//
// export const updateDomainForTLD = (parts: string[]): string => {
// const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 确保为小写
// let domainToKeep: string;
// if (SupportedTLDs.has(potentialTLD)) {
// domainToKeep = parts.length > 2 ? parts.slice(-3).join('.') : parts.join('.');
// } else {
// domainToKeep = parts.slice(-2).join('.');
// }
// return domainToKeep;
// };
//
//
// export const validateDomain = (parts: string[]): boolean => {
// if (parts.length < 2) {
// const message = useMessage()
// message.warning('域名格式不正确')
// return false;
// }
// return true;
// };
//
//
// const isTLDValid = (parts: string[]): boolean => {
// const lastPart = parts[parts.length - 1].toLowerCase(); // 获取最后一部分,并确保为小写
// const potentialTLD = parts.slice(-2).join('.').toLowerCase(); // 获取可能的多部分TLD并确保为小写
//
// if (!SupportedTLDs.has(lastPart) && !SupportedTLDs.has(potentialTLD)) {
// const message = useMessage()
// message.warning('域名后缀不合法')
// return false;
// }
// return true;
// };

View File

View File

@ -0,0 +1,12 @@
enum Api {
blogInfo = '/',
report = '/report'
}
/**
*
* @returns
*/
export function getBlogInfo(option: any) {
return ""
}

View File

@ -0,0 +1,23 @@
enum Api {
webSiteConfig = '/getWebSiteConfig',
whoisServer = "/getWhoisServer"
}
// 获取网站配置
export function GetWebSiteConfig() {
return useHttp.post<WebSiteConfig>("/getWebSiteConfig")
}
//获取whois服务器
export function GetWhoisServer(config: string) {
return useHttp.post<any>("/getWhoisServer", {
config: config,
})
}
export function GetWhois(domain: string, name: string) {
return useHttp.post<any>("/getWhois", {
domain: domain,
name: name,
})
}

View File

@ -0,0 +1,5 @@
// api/front/index.ts
import * as home from './home';
// Re-export home
export {home};

2
frontend/apis/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * as admin from './admin/index';
export * as front from './front/index';

View File

@ -11,7 +11,6 @@
<NuxtLayout> <NuxtLayout>
<NuxtLoadingIndicator/> <NuxtLoadingIndicator/>
<NuxtPage/> <NuxtPage/>
<!-- <CommonLayoutSetting v-if="isAdminRoute" class="fixed right-12 top-1/2 z-999" />-->
</NuxtLayout> </NuxtLayout>
</n-message-provider> </n-message-provider>
</n-modal-provider> </n-modal-provider>
@ -19,21 +18,26 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {darkTheme, lightTheme} from 'naive-ui' import {darkTheme, lightTheme} from 'naive-ui'
import {naiveThemeOverrides} from "~/settings/settings";
const colorMode = useColorMode() const colorMode = useColorMode()
const themeOverrides = naiveThemeOverrides
const theme = computed(() => { const theme = computed(() => {
return colorMode.value === 'system' ? (colorMode.value ? lightTheme : darkTheme) : colorMode.value === 'light' ? lightTheme : darkTheme return colorMode.value === 'system' ? (colorMode.value ? lightTheme : darkTheme) : colorMode.value === 'light' ? lightTheme : darkTheme
}) })
const whoisStore = useWhoisStore() const styleStore = useStyleStore()
const dnsStore = useDnsStore() const {common} = storeToRefs(styleStore)
const domainStore = useDomainStore() const themeOverrides = computed(() => {
whoisStore.newWhoisList() return {
dnsStore.newDnsList() common: common.value, // 使 common.value
domainStore.newDomainList() }
})
await callOnce(async () => {
await useSettingsStore().webSiteConfigInit()
await useConfigStore().configServerInit()
})
</script> </script>
<style> <style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -0,0 +1,82 @@
/**********************************
* @Author: Ronnie Zhang
* @LastEditor: Ronnie Zhang
* @LastEditTime: 2023/12/05 21:26:28
* @Email: zclzone@outlook.com
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
}
#app {
width: 100%;
height: 100%;
}
/* transition fade-slide */
.fade-slide-leave-active,
.fade-slide-enter-active {
transition: all 0.3s;
}
.fade-slide-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.fade-slide-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 自定义滚动条样式 */
.cus-scroll {
overflow: auto;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
}
.cus-scroll-x {
overflow-x: auto;
&::-webkit-scrollbar {
width: 0;
height: 8px;
}
}
.cus-scroll-y {
overflow-y: auto;
&::-webkit-scrollbar {
width: 8px;
height: 0;
}
}
.cus-scroll,
.cus-scroll-x,
.cus-scroll-y {
&::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 4px;
}
&:hover {
&::-webkit-scrollbar-thumb {
background: #bfbfbf;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--primary-color);
}
}
}

View File

@ -0,0 +1,49 @@
/**********************************
* @Author: Ronnie Zhang
* @LastEditor: Ronnie Zhang
* @LastEditTime: 2023/12/05 21:26:38
* @Email: zclzone@outlook.com
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
html {
box-sizing: border-box;
}
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
a {
text-decoration: none;
color: inherit;
}
a:hover,
a:link,
a:visited,
a:active {
text-decoration: none;
}
ol,
ul {
list-style: none;
}
input,
textarea {
outline: none;
border: none;
resize: none;
}
img,
video {
max-width: 100%;
height: auto;
}

View File

@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const domainListStore = useDomainListStore();
const SupportedTLDs = domainListStore.getSupportedTLDKeys
const isOpen = ref(false) const isOpen = ref(false)
const {t} = useI18n() const {t} = useI18n()
@ -28,13 +26,13 @@ const {t} = useI18n()
closable closable
> >
<div class="flex flex-wrap mt-2 "> <div class="flex flex-wrap mt-2 ">
<span <!-- <span-->
v-for="item in SupportedTLDs" <!-- v-for="item in SupportedTLDs"-->
:key="item" <!-- :key="item"-->
class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300" <!-- class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"-->
> <!-- >-->
{{ item }} <!-- {{ item }}-->
</span> <!-- </span>-->
</div> </div>
</NDrawerContent> </NDrawerContent>
</NDrawer> </NDrawer>

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const isOpen = ref(false) const isOpen = ref(false)
const styleStore = useStyleStore() const settingsStore = useSettingsStore()
const {t} = useI18n() const {t} = useI18n()
</script> </script>
@ -33,12 +33,12 @@ const {t} = useI18n()
<h1 class="text-2xl font-bold text-gray-800 mb-5 flex items-center justify-between"> <h1 class="text-2xl font-bold text-gray-800 mb-5 flex items-center justify-between">
<span <span
class="text-sm text-gray-500 py-1 px-3 rounded-full dark:text-white dark:bg-[#000000FF]"> class="text-sm text-gray-500 py-1 px-3 rounded-full dark:text-white dark:bg-[#000000FF]">
{{ t('history.tips', {length: styleStore.getHistory.length}) }} {{ t('history.tips', {length: settingsStore.getHistory.length}) }}
</span> </span>
</h1> </h1>
<div class=" shadow-md rounded-lg "> <div class=" shadow-md rounded-lg ">
<!-- 条件渲染如果有历史记录则显示表格否则显示提示 --> <!-- 条件渲染如果有历史记录则显示表格否则显示提示 -->
<div v-if="styleStore.getHistory.length"> <div v-if="settingsStore.getHistory.length">
<!-- 表格头部和内容 --> <!-- 表格头部和内容 -->
<table class="min-w-full leading-normal "> <table class="min-w-full leading-normal ">
<thead> <thead>
@ -59,7 +59,7 @@ const {t} = useI18n()
</thead> </thead>
<tbody> <tbody>
<!-- 这里将使用循环来动态展示查询历史 --> <!-- 这里将使用循环来动态展示查询历史 -->
<tr v-for="item in styleStore.getHistory" :key="item.id" <tr v-for="item in settingsStore.getHistory" :key="item.id"
class="border-b border-gray-200 dark:text-white dark:bg-[#000000FF]"> class="border-b border-gray-200 dark:text-white dark:bg-[#000000FF]">
<td class="px-5 py-5 text-sm "> <td class="px-5 py-5 text-sm ">
<NuxtLink :to="item.path">{{ item.domain }}</NuxtLink> <NuxtLink :to="item.path">{{ item.domain }}</NuxtLink>
@ -72,7 +72,7 @@ const {t} = useI18n()
</td> </td>
<td class="px-5 py-5 text-sm "> <td class="px-5 py-5 text-sm ">
<NButton <NButton
@click="styleStore.deleteHistory(item.id)" @click="settingsStore.deleteHistory(item.id)"
>{{ t('common.actions.delete') }} >{{ t('common.actions.delete') }}
</NButton> </NButton>

View File

@ -0,0 +1,82 @@
<script lang="ts" setup>
const availableTimeZones = ref([
{id: 1, name: 'UTC-12', displayName: 'International Date Line West'},
{id: 2, name: 'UTC-11', displayName: 'Coordinated Universal Time-11'},
{id: 3, name: 'UTC-10', displayName: 'Hawaii'},
{id: 4, name: 'UTC-9', displayName: 'Alaska'},
{id: 5, name: 'UTC-8', displayName: 'Pacific Time (US & Canada)'},
{id: 6, name: 'UTC-7', displayName: 'Mountain Time (US & Canada)'},
{id: 7, name: 'UTC-6', displayName: 'Central Time (US & Canada), Mexico City'},
{id: 8, name: 'UTC-5', displayName: 'Eastern Time (US & Canada), Bogota, Lima'},
{id: 9, name: 'UTC-4', displayName: 'Atlantic Time (Canada), Caracas, La Paz'},
{id: 10, name: 'UTC-3', displayName: 'Buenos Aires, Georgetown'},
{id: 11, name: 'UTC-2', displayName: 'Coordinated Universal Time-02'},
{id: 12, name: 'UTC-1', displayName: 'Azores'},
{id: 13, name: 'UTC', displayName: 'Coordinated Universal Time'},
{id: 14, name: 'UTC+1', displayName: 'Brussels, Copenhagen, Madrid, Paris'},
{id: 15, name: 'UTC+2', displayName: 'Athens, Bucharest, Istanbul'},
{id: 16, name: 'UTC+3', displayName: 'Moscow, St. Petersburg, Nairobi'},
{id: 17, name: 'UTC+3:30', displayName: 'Tehran'},
{id: 18, name: 'UTC+4', displayName: 'Abu Dhabi, Muscat'},
{id: 19, name: 'UTC+4:30', displayName: 'Kabul'},
{id: 20, name: 'UTC+5', displayName: 'Islamabad, Karachi, Tashkent'},
{id: 21, name: 'UTC+5:30', displayName: 'Chennai, Kolkata, Mumbai, New Delhi'},
{id: 22, name: 'UTC+5:45', displayName: 'Kathmandu'},
{id: 23, name: 'UTC+6', displayName: 'Astana, Dhaka'},
{id: 24, name: 'UTC+6:30', displayName: 'Yangon (Rangoon)'},
{id: 25, name: 'UTC+7', displayName: 'Bangkok, Hanoi, Jakarta'},
{id: 26, name: 'UTC+8', displayName: 'Beijing, Hong Kong, Singapore, Taipei'},
{id: 27, name: 'UTC+9', displayName: 'Osaka, Sapporo, Tokyo'},
{id: 28, name: 'UTC+9:30', displayName: 'Adelaide, Darwin'},
{id: 29, name: 'UTC+10', displayName: 'Brisbane, Canberra, Melbourne, Sydney'},
{id: 30, name: 'UTC+11', displayName: 'Solomon Is., New Caledonia'},
{id: 31, name: 'UTC+12', displayName: 'Auckland, Wellington'},
{id: 32, name: 'UTC+13', displayName: 'Nuku alofa'}
]);
const settingsStore = useSettingsStore()
</script>
<template>
<div>
<HeadlessListbox
v-model="settingsStore.timeZones"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Theme
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Color">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="ri:time-zone-line" class=" text-lg dark:text-white"/>
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 max-h-60 overflow-y-auto rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<HeadlessListboxOption
v-for="item in availableTimeZones"
:key="item.id"
:value="item.name"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
settingsStore.timeZones === item.name,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
settingsStore.timeZones !== item.name,
}"
>
<span class="truncate">
{{ item.name }}
</span>
<span class="flex items-center justify-center text-sm">
</span>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

View File

@ -1,14 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
const switchLocalePath = useSwitchLocalePath() const switchLocalePath = useSwitchLocalePath()
const timeStore = useTimeStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const dnsStore = useDnsStore()
const {newsDnsArr} = storeToRefs(dnsStore) const configStore = useConfigStore()
const {configServer} = storeToRefs(configStore)
const handlePost = async (name: string) => { const handlePost = async (name: string) => {
dnsStore.moveToTop(name)
// //
await refreshNuxtData('dns') await refreshNuxtData('dns')
} }
@ -18,7 +16,7 @@ const handlePost = async (name: string) => {
<template> <template>
<div> <div>
<HeadlessListbox <HeadlessListbox
v-model="dnsStore.newsDnsArr" v-model="configServer.dnsArr"
as="div" as="div"
class="relative flex items-center" class="relative flex items-center"
> >
@ -37,7 +35,7 @@ const handlePost = async (name: string) => {
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0" class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
> >
<div <div
v-for="lang in newsDnsArr" v-for="lang in configServer.dnsArr"
:key="lang.name" :key="lang.name"
@click="handlePost(lang.name)" @click="handlePost(lang.name)"
:class="{ :class="{

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
defineProps({
data: {
type: Object,
required: true
}
})
</script>
<template>
<div class="flex p-8">
<div class="w-full mx-auto">
<div class=" shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
<h2 class="text-xl font-bold">{{ item.name }}</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<p class="font-semibold">Type</p>
<p>{{ item.type }}</p>
</div>
<div>
<p class="font-semibold">TTL</p>
<p>{{ item.TTL }}</p>
</div>
<div class="col-span-2">
<p class="font-semibold">Data</p>
<p>{{ item.data }}</p>
</div>
</div>
<div class="flex space-x-2 mt-4">
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const timeStore = useTimeStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const {getIsDomainList, getIsHistory,} = storeToRefs(settingsStore)
const {t} = useI18n() const {t} = useI18n()
</script> </script>
@ -10,7 +10,7 @@ const {t} = useI18n()
<div class="flex space-x-2"> <div class="flex space-x-2">
<!-- 左边的新元素 --> <!-- 左边的新元素 -->
<n-tooltip <n-tooltip
v-if="settingsStore.isDomainList" v-if="getIsDomainList"
trigger="hover" placement="top"> trigger="hover" placement="top">
<template #trigger> <template #trigger>
<CommonDomainList/> <CommonDomainList/>
@ -19,7 +19,7 @@ const {t} = useI18n()
</n-tooltip> </n-tooltip>
<n-tooltip <n-tooltip
v-if="settingsStore.getHistory" v-if="getIsHistory"
trigger="hover" placement="top"> trigger="hover" placement="top">
<template #trigger> <template #trigger>
<CommonHistory/> <CommonHistory/>
@ -27,15 +27,6 @@ const {t} = useI18n()
<span>{{ t('popper.history') }}</span> <span>{{ t('popper.history') }}</span>
</n-tooltip> </n-tooltip>
<!-- <n-tooltip-->
<!-- v-if="settingsStore.getHistory"-->
<!-- trigger="hover" placement="top" >-->
<!-- <template #trigger>-->
<!-- <CommonDnsList @action="handleActionFromDnsList" />-->
<!-- </template>-->
<!-- <span>{{t('popper.dns')}}</span>-->
<!-- </n-tooltip>-->
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<!-- 右边的现有元素 --> <!-- 右边的现有元素 -->
@ -63,7 +54,7 @@ const {t} = useI18n()
<template #trigger> <template #trigger>
<CommonTimeZonesChange/> <CommonTimeZonesChange/>
</template> </template>
<span>{{ timeStore.timeZones }}</span> <span>{{ settingsStore.timeZones }}</span>
</n-tooltip> </n-tooltip>
<n-tooltip <n-tooltip

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
const props = defineProps({
data: Object,
})
</script>
<template>
{{ data }}
</template>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
const props = defineProps({
data: Object,
})
</script>
<template>
{{ data }}
</template>
<style scoped>
</style>

View File

@ -0,0 +1,26 @@
// composables/useCurrentServer.js 或者在一个组件中
export const useCurrentServer = () => {
// 使用useHydration
useHydration('currentServer',
// 服务端执行的逻辑,返回初始值
() => {
return {
currentServer: {
whois: 'nuxt',
dns: 'nuxt',
domain: 'nuxt',
}
};
},
// 客户端执行的逻辑,接收服务端设置的值
(serverValue) => {
// 在客户端处理或使用服务端传来的值
console.log('Received from server:', serverValue);
// 这里你可以根据需要将serverValue设置到Pinia store或Vue响应式状态中
// 例如更新Pinia store中的状态
const configStore = useConfigStore();
configStore.setCurrentServer(serverValue);
}
);
}

View File

@ -0,0 +1,12 @@
// 自动导出
export const useHttp = {
post<T = any>(url: string, config?: any): Promise<T> {
const runtimeConfig = useRuntimeConfig()
const baseUrl = runtimeConfig.public.baseUrl
return $fetch(url, {
baseURL: baseUrl,
method: 'POST',
body: config,
})
},
}

View File

@ -0,0 +1,112 @@
<script setup lang="ts">
import {type MenuOption, NIcon} from "naive-ui";
import type {Component} from "vue";
import {Settings, Wrench, Pentagon, MessageCircleMore} from 'lucide-vue-next';
function renderIcon(icon: Component) {
return () => h(NIcon, null, {default: () => h(icon)})
}
const route = useRoute()
const router = useRouter()
const menusOptions: MenuOption[] = [
{
label: '控制台',
key: '/admin/dashboard',
icon: renderIcon(Pentagon)
},
{
label: 'Whois管理',
key: 'whois',
icon: renderIcon(Settings),
children: [
{
label: '后缀管理',
key: '/admin/whois/website',
icon: renderIcon(Wrench),
}
]
},
{
label: 'Dns管理',
key: 'dns',
icon: renderIcon(Settings),
children: [
{
label: '后缀管理',
key: '/admin/whois/website',
icon: renderIcon(Wrench),
}
]
},
{
label: 'Domain管理',
key: 'domain',
icon: renderIcon(Settings),
children: [
{
label: '后缀管理',
key: '/admin/whois/website',
icon: renderIcon(Wrench),
}
]
},
{
label: '系统管理',
key: 'settings',
icon: renderIcon(Settings),
children: [
{
label: '网站设置',
key: '/admin/settings/website',
icon: renderIcon(Wrench),
},
{
label: '公告管理',
key: '/admin/settings/bulletin',
icon: renderIcon(MessageCircleMore),
}
]
}
]
const activeKey = computed(() => route.path)
console.log(route.path)
function handleMenuSelect(key: any, item: any) {
if (isExternal(item.key)) {
router.push(item.key)
} else {
router.push(item.key)
}
}
</script>
<template>
<div class="flex h-screen bg-gray-100">
<!-- Left Sidebar -->
<div class="w-64 text-black">
<n-menu
ref="menu"
class="side-menu"
accordion
:indent="18"
:collapsed-icon-size="22"
:collapsed-width="64"
:options="menusOptions"
:value="activeKey"
@update:value="handleMenuSelect"
/>
</div>
<!-- Right Content -->
<div class="flex-1 p-10">
<slot/>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,132 @@
<script setup lang="ts">
const route = useRoute()
const router = useRouter();
const {t} = useI18n()
const localePath = useLocalePath()
const message = useMessage()
const settingsStore = useSettingsStore()
const {isObj, domainSearch, selectedOption, webSiteConfig} = storeToRefs(settingsStore)
//
const stylePage = computed(() => {
return route.meta?.stylePage
})
//
const handleAction = async (url) => {
const etDomainSearch = ExtractDomain(domainSearch.value);
switch (url) {
case 'whois':
// 使
if (DomainRegex.test(etDomainSearch)) {
//
domainSearch.value = etDomainSearch;
//
const urlType = etDomainSearch.replace(/\./g, '_');
await router.push(localePath(`/whois/${urlType}.html`));
} else if (Ipv4Regex.test(domainSearch.value)) {
//
const urlType = domainSearch.value.replace(/\./g, '_');
await router.push(localePath(`/whois/${urlType}.html`));
} else {
message.error('请输入有效的域名或IP地址')
return;
}
break;
case 'dns':
// 使
if (DomainRegex.test(etDomainSearch)) {
//
domainSearch.value = etDomainSearch;
//
const urlType = etDomainSearch.replace(/\./g, '_');
await router.push(localePath(`/dns/${urlType}.html`));
} else if (Ipv4Regex.test(domainSearch.value)) {
//
const urlType = domainSearch.value.replace(/\./g, '_');
await router.push(localePath(`/dns/${urlType}.html`));
} else {
message.error('请输入有效的域名或IP地址')
return;
}
return
}
}
const handleSelectOptions = (value: any) => {
settingsStore.setSelectedOption(value);
}
</script>
<template>
<div
class="w-full text-xs bg-[#F1F3F4] dark:bg-transparent"
:class="{ 'h-[90vh]': !stylePage }"
>
<div
class=" max-w-screen-lg mx-auto px-[1em] pb-[10vh] "
:class="{ 'pt-[25vh]': !stylePage, 'pt-[5vh]': stylePage }"
>
<nav
v-if="isObj.isLogo"
class=" w-full text-[#464747] h-5 ">
<NuxtLink class="mb-3 font-bold text-2xl inline-block text-current no-underline dark:text-white"
:to="localePath('/')"
>
<h1 class="inline-block text-current no-underline dark:text-white">{{ webSiteConfig.logoLeftText }}</h1>
<sup class="text-[#59a8d7] dark:text-[#ace4f8]">{{ webSiteConfig.logoRightText }}</sup>
</NuxtLink>
</nav>
<div class="mt-6">
<ClientOnly>
<div class="flex items-center space-x-2 mb-3 dark:text-white"
>
<!-- 容器div用于水平布局 -->
<div class="flex-grow">
<NInputGroup>
<n-select
class="w-1/4"
size="large"
v-model:value="selectedOption"
:options="webSiteConfig.defaultSelectOptions"
@update:value="handleSelectOptions"
/>
<NAutoComplete
v-model:value="domainSearch"
@keyup.enter="handleAction(settingsStore.selectedOption)"
:input-props="{ autocomplete: 'off' }"
type="text"
:placeholder="t('index.placeholder')"
size="large"
clearable
autofocus
class="w-full ">
</NAutoComplete>
</NInputGroup>
</div>
<!-- 使用v-if基于state.domain的值来控制按钮的显示 -->
<NButton type="primary"
size="large"
@click="handleAction(settingsStore.selectedOption)"
v-if="settingsStore.domainSearch">
{{ t('index.onSubmit') }}
</NButton>
</div>
</ClientOnly>
</div>
<CommonBulletin
v-if="!stylePage && isObj.isBulletin"
:text="`➡️ ${t('index.tips') }`"
/>
<TabList @action="handleAction"/>
<slot/>
</div>
</div>
<CommonFooter/>
</template>
<style scoped>
</style>

View File

@ -12,29 +12,39 @@ export default defineNuxtConfig({
'nuxt-simple-robots', 'nuxt-simple-robots',
'nuxt-headlessui', // 组件库 'nuxt-headlessui', // 组件库
'@bg-dev/nuxt-naiveui', // 组件库 '@bg-dev/nuxt-naiveui', // 组件库
'@nuxtjs/tailwindcss', // 组件库
'nuxt-icon', 'nuxt-icon',
'@nuxtjs/color-mode', '@nuxtjs/color-mode',
'@nuxtjs/tailwindcss',
], ],
css: ['~/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
experimental: {
appManifest: false,
},
features: { features: {
inlineStyles: true, inlineStyles: true,
}, },
runtimeConfig: { runtimeConfig: {
public: { public: {
Domain: process.env.WebSiteDomain || 'Nuxt Whois', baseUrl: process.env.BASE_URL
DomainSuffix: process.env.WebSiteDomainSuffix || 'Dns',
} }
}, },
app: { app: {
head: { head: {
title: process.env.WebSiteTitle || 'Nuxt Whois', title: 'Nuxt Whois',
meta: [ meta: [
{charset: 'utf-8'}, {charset: 'utf-8'},
{name: 'viewport', content: 'width=device-width, initial-scale=1'}, {name: 'viewport', content: 'width=device-width, initial-scale=1'},
], ],
link: [ link: [
{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'} {rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
] ],
// script: [{src: "/darkModelVerify.js"}],
} }
}, },
i18n: { i18n: {
@ -57,12 +67,4 @@ export default defineNuxtConfig({
colorMode: { colorMode: {
classSuffix: '', classSuffix: '',
}, },
tailwindcss: {
cssPath: '~/assets/css/tailwind.css',
exposeConfig: {
write: true,
},
viewer: true,
}
}) })

View File

@ -10,26 +10,28 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@nuxt/ui": "^2.14.2",
"@pinia/nuxt": "^0.5.1", "@pinia/nuxt": "^0.5.1",
"lucide-vue-next": "^0.363.0", "lucide-vue-next": "^0.365.0",
"nuxt": "^3.11.1", "nuxt": "^3.11.2",
"socks": "^2.8.1", "socks": "^2.8.1",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@bg-dev/nuxt-naiveui": "^1.10.4", "@bg-dev/nuxt-naiveui": "^1.12.2",
"@nuxtjs/color-mode": "^3.3.3", "@nuxtjs/color-mode": "^3.3.3",
"@nuxtjs/i18n": "^8.2.0", "@nuxtjs/i18n": "^8.3.0",
"@nuxtjs/tailwindcss": "^6.11.4", "@nuxtjs/tailwindcss": "^6.11.4",
"@pinia-plugin-persistedstate/nuxt": "^1.2.0", "@pinia-plugin-persistedstate/nuxt": "^1.2.0",
"autoprefixer": "^10.4.19",
"less": "^4.2.0", "less": "^4.2.0",
"nuxt-headlessui": "^1.1.5", "nuxt-headlessui": "^1.2.0",
"nuxt-icon": "^0.6.10", "nuxt-icon": "^0.6.10",
"nuxt-simple-robots": "4.0.0-rc.16", "nuxt-simple-robots": "4.0.0-rc.16",
"sass": "^1.72.0", "postcss": "^8.4.38",
"typescript": "5.4.3" "sass": "^1.74.1",
"tailwindcss": "^3.4.3",
"typescript": "5.4.4"
} }
} }

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
definePageMeta({
layout: "admin",
})
</script>
<template>
123
</template>
<style scoped>
</style>

View File

@ -0,0 +1,99 @@
<template>
<div class="login-container flex items-center justify-center p-[20px]">
<div class="login-box w-[960px] h-[560px] md:w-[100%] md:px-[50px] px-[80px] py-[30px]">
<div class="flex items-center justify-center md:hidden">
<img :src="LoginPic" alt="login-pic"/>
</div>
<div class="flex justify-center w-[360px] md:w-[100%] flex-col items-center space-y-8">
<div class="space-y-2">
<div class="flex justify-center items-center space-x-[10px]">
<n-gradient-text :gradient="gradient" class="text-[24px] p-5">Nuxt Whois 后台管理</n-gradient-text>
</div>
</div>
<n-form
ref="formRef"
:model="formModel"
:rules="formRules"
label-placement="left"
size="large"
class="w-[100%]"
:show-require-mark="false"
:show-label="false"
>
<n-form-item path="username">
<n-input v-model:value="formModel.username" round placeholder="请输入用户名">
<template #prefix>
<Icon name="mdi:user-outline"/>
</template>
</n-input>
</n-form-item>
<n-form-item path="password">
<n-input v-model:value="formModel.password" round placeholder="请输入密码">
<template #prefix>
<Icon name="material-symbols:lock-outline"/>
</template>
</n-input>
</n-form-item>
<n-form-item>
<div class="flex justify-between w-[100%] px-[2px]">
<div class="flex-initial">
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
</div>
<div class="flex-initial order-last">
<n-button text type="primary">忘记密码</n-button>
</div>
</div>
</n-form-item>
<n-form-item>
<n-button type="primary" size="large" round @click="handleSubmitForm" :loading="submitLoading" block>登录
</n-button>
</n-form-item>
</n-form>
</div>
</div>
</div>
</template>
<script setup>
definePageMeta({
layout: "empty",
})
import LoginPic from "assets/images/login-pic.png";
const router = useRouter();
const message = useMessage();
const autoLogin = ref(false);
const submitLoading = ref(false);
const formRef = ref(null);
const formModel = reactive({
username: "",
password: "",
});
const formRules = {
username: {required: true, message: "请输入用户名", trigger: "blur"},
password: {required: true, message: "请输入密码", trigger: "blur"},
};
const gradient = {
deg: 92.06,
from: "#33c2ff 0%",
// to: `${appStore.appTheme} 100%`,
};
const handleSubmitForm = (e) => {
};
</script>
<style lang="less" scoped>
.login-container {
background: linear-gradient(-135deg, #d765cf, #495fd1);
min-height: 100%;
.login-box {
@apply shadow-md rounded-xl bg-white flex justify-between;
}
}
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
definePageMeta({
layout: "full",
})
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,62 @@
<script setup lang="ts">
definePageMeta({
layout: "full",
})
const siteName = ref('');
const siteNameAlt = ref('');
function saveSettings() {
// API
console.log('保存的网站名称:', siteName.value);
}
const active = ref(false);
const GnOptions = ref([
{
label: 'Whois',
value: 'whois'
}, {
label: 'Dns',
value: 'dns'
}, {
label: 'Domain',
value: 'domain'
}
])
const GnValue = ref('whois')
</script>
<template>
<div class="bg-gray-100 p-10">
<div class=" bg-white rounded-lg shadow-md p-5">
<h2 class="text-xl font-semibold mb-5">网站设置</h2>
<div class="flex mb-6 -mx-2">
<div class="flex-1 px-2">
<label for="site-name" class="block mb-2 text-sm font-medium text-gray-900">网站Logo名称</label>
<n-input id="site-name" v-model:value="siteName" placeholder="请输入网站名称"/>
</div>
<div class="flex-1 px-2">
<label for="site-name-alt" class="block mb-2 text-sm font-medium text-gray-900">Logo右上角名称</label>
<n-input id="site-name-alt" v-model:value="siteNameAlt" placeholder="请输入备用网站名称"/>
</div>
</div>
<div class="flex mb-6 -mx-2">
<div class="flex-1 px-2">
<label for="site-name" class="block mb-2 text-sm font-medium text-gray-900">开启极简模式</label>
<n-switch v-model:value="active"/>
</div>
<div class="flex-1 px-2">
<label for="site-name-alt" class="block mb-2 text-sm font-medium text-gray-900">功能开启选择</label>
<n-select v-model:value="GnValue" multiple :options="GnOptions"/>
</div>
</div>
<div class="flex justify-end">
<n-button type="primary" @click="saveSettings">保存设置</n-button>
</div>
</div>
</div>
</template>
<style scoped>
</style>

Some files were not shown because too many files have changed in this diff Show More