🎨 增加 Go 后端

This commit is contained in:
江西小徐 2024-04-07 11:08:05 +08:00
parent 04c82ab25d
commit 61275be2bf
165 changed files with 7043 additions and 3599 deletions

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.

184
backend/README.md Normal file
View File

@ -0,0 +1,184 @@
# nunu-layout-basic - Basic Layout
Nunu is an application scaffold based on Golang. Its name comes from a game character in League of Legends, a little boy riding on the shoulder of a yeti. Like Nunu, this project also stands on the shoulders of giants. It is a composition of various popular libraries from the Golang ecosystem, which can help you quickly build efficient and reliable applications.
[简体中文介绍](https://github.com/go-nunu/nunu-layout-basic/blob/main/README_zh.md)
![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/banner.png)
## Documentation
* [User Guide](https://github.com/go-nunu/nunu/blob/main/docs/en/guide.md)
* [Architecture](https://github.com/go-nunu/nunu/blob/main/docs/en/architecture.md)
* [Getting Started Tutorial](https://github.com/go-nunu/nunu/blob/main/docs/en/tutorial.md)
## Features
- **Gin**: https://github.com/gin-gonic/gin
- **Gorm**: https://github.com/go-gorm/gorm
- **Wire**: https://github.com/google/wire
- **Viper**: https://github.com/spf13/viper
- **Zap**: https://github.com/uber-go/zap
- **Golang-jwt**: https://github.com/golang-jwt/jwt
- **Go-redis**: https://github.com/go-redis/redis
- **Testify**: https://github.com/stretchr/testify
- **Sonyflake**: https://github.com/sony/sonyflake
- **gocron**: https://github.com/go-co-op/gocron
- More...
## Highlights
* **Low Learning Curve and Customization**: Nunu encapsulates popular libraries that Gophers are familiar with. You can easily customize the application to meet specific requirements.
* **High Performance and Scalability**: Nunu aims to be high-performance and scalable. It utilizes the latest technologies and best practices to ensure your application can handle high traffic and large data volumes.
* **Security and Reliability**: Nunu uses stable and reliable third-party libraries to ensure the security and reliability of your application.
* **Modularity and Extensibility**: Nunu is designed to be modular and extensible. You can easily add new features and functionalities by using third-party libraries or writing your own modules.
* **Comprehensive Documentation and Test Coverage**: Nunu has comprehensive documentation and test coverage. It provides detailed documentation and examples to help you get started quickly. It also includes a test suite to ensure your application works as expected.
## Nunu CLI
![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/screenshot.jpg)
## Directory Structure
```
.
├── 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
```
This is a classic directory structure for a Golang project, which includes the following directories:
- cmd: Contains the entry points of the application, including the main function and dependency injection code.
- server: The main entry point of the application, including the main function and dependency injection code.
- main.go: The main function used to start the application.
- wire.go: The dependency injection code generated using Wire.
- wire_gen.go: The dependency injection code generated using Wire.
- config: Contains the configuration files of the application.
- local.yml: The configuration file for the local environment.
- prod.yml: The configuration file for the production environment.
- internal: Contains the internal code of the application.
- handler: Contains the handlers for handling HTTP requests.
- handler.go: The common handler for handling HTTP requests.
- user.go: The handler for handling user-related HTTP requests.
- middleware: Contains the middleware code.
- cors.go: The CORS (Cross-Origin Resource Sharing) middleware.
- model: Contains the data model code.
- user.go: The user data model.
- repository: Contains the data access code.
- repository.go: The common interface for data access.
- user.go: The implementation of the user data access interface.
- server: Contains the server code.
- http.go: The implementation of the HTTP server.
- service: Contains the business logic code.
- service.go: The common interface for business logic.
- user.go: The implementation of the user business logic.
- pkg: Contains the public packages of the application.
- storage: Contains the storage files of the application.
- go.mod: The Go module file.
- go.sum: The dependency versions file for the Go module.
## Requirements
To use Nunu, you need to have the following software installed on your system:
* Golang 1.16 or higher
* Git
### Installation
You can install Nunu using the following command:
```bash
go install github.com/go-nunu/nunu@latest
```
### Creating a New Project
You can create a new Golang project using the following command:
```bash
nunu new projectName
```
By default, it will pull from the GitHub repository, but you can also use a mirror repository for faster access:
```
// Use the basic template
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-basic.git
// Use the advanced template
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
```
This command will create a directory named `projectName` and generate an elegant Golang project structure within it.
### Creating Components
You can create handlers, services, repositories, and models for your project using the following commands:
```bash
nunu create handler user
nunu create service user
nunu create repository user
nunu create model user
```
or
```
nunu create all user
```
These commands will create components named `UserHandler`, `UserService`, `UserDao`, and `UserModel` respectively and place them in the correct directories.
### Starting the Project
You can quickly start your project using the following command:
```bash
nunu run
```
This command will start your Golang project and support hot-reloading of files.
### Compiling wire.go
You can quickly compile `wire.go` using the following command:
```bash
nunu wire
```
This command will compile your `wire.go` file and generate the required dependencies.
## Contribution
If you find any issues or have any improvement suggestions, please feel free to raise an issue or submit a pull request. Your contributions are highly appreciated!
## License
Nunu is released under the MIT License. See the [LICENSE](LICENSE) file for more information.

186
backend/README_zh.md Normal file
View File

@ -0,0 +1,186 @@
# nunu-layout-basic — 基础布局
Nunu是一个基于Golang的应用脚手架它的名字来自于英雄联盟中的游戏角色一个骑在雪怪肩膀上的小男孩。和努努一样该项目也是站在巨人的肩膀上它是由Golang生态中各种非常流行的库整合而成的它们的组合可以帮助你快速构建一个高效、可靠的应用程序。
[英文介绍](https://github.com/go-nunu/nunu-layout-basic/blob/main/README.md)
![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/banner.png)
## 文档
* [使用指南](https://github.com/go-nunu/nunu/blob/main/docs/zh/guide.md)
* [分层架构](https://github.com/go-nunu/nunu/blob/main/docs/zh/architecture.md)
* [上手教程](https://github.com/go-nunu/nunu/blob/main/docs/zh/tutorial.md)
## 功能
- **Gin**: https://github.com/gin-gonic/gin
- **Gorm**: https://github.com/go-gorm/gorm
- **Wire**: https://github.com/google/wire
- **Viper**: https://github.com/spf13/viper
- **Zap**: https://github.com/uber-go/zap
- **Golang-jwt**: https://github.com/golang-jwt/jwt
- **Go-redis**: https://github.com/go-redis/redis
- **Testify**: https://github.com/stretchr/testify
- **Sonyflake**: https://github.com/sony/sonyflake
- **gocron**: https://github.com/go-co-op/gocron
- More...
## 特性
* **超低学习成本和定制**Nunu封装了Gopher最熟悉的一些流行库。您可以轻松定制应用程序以满足特定需求。
* **高性能和可扩展性**Nunu旨在具有高性能和可扩展性。它使用最新的技术和最佳实践确保您的应用程序可以处理高流量和大量数据。
* **安全可靠**Nunu使用了稳定可靠的第三方库确保您的应用程序安全可靠。
* **模块化和可扩展**Nunu旨在具有模块化和可扩展性。您可以通过使用第三方库或编写自己的模块轻松添加新功能和功能。
* **文档完善和测试完备**Nunu文档完善测试完备。它提供了全面的文档和示例帮助您快速入门。它还包括一套测试套件确保您的应用程序按预期工作。
## Nunu CLI
![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/screenshot.jpg)
## 目录结构
```
.
├── 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
```
这是一个经典的Golang 项目的目录结构,包含以下目录:
- cmd: 存放应用程序的入口点,包括主函数和依赖注入的代码。
- server: 应用程序的主要入口点,包含主函数和依赖注入的代码。
- main.go: 主函数,用于启动应用程序。
- wire.go: 使用Wire库生成的依赖注入代码。
- wire_gen.go: 使用Wire库生成的依赖注入代码。
- config: 存放应用程序的配置文件。
- local.yml: 本地环境的配置文件。
- prod.yml: 生产环境的配置文件。
- internal: 存放应用程序的内部代码。
- handler: 处理HTTP请求的处理程序。
- handler.go: 处理HTTP请求的通用处理程序。
- user.go: 处理用户相关的HTTP请求的处理程序。
- middleware: 存放中间件代码。
- cors.go: 跨域资源共享中间件。
- model: 存放数据模型代码。
- user.go: 用户数据模型。
- repository: 存放数据访问代码。
- repository.go: 数据访问的通用接口。
- user.go: 用户数据访问接口的实现。
- server: 存放服务器代码。
- http.go: HTTP服务器的实现。
- service: 存放业务逻辑代码。
- service.go: 业务逻辑的通用接口。
- user.go: 用户业务逻辑的实现。
- pkg: 存放应用程序的公共包。
- storage: 存放应用程序的存储文件。
- go.mod: Go模块文件。
- go.sum: Go模块的依赖版本文件。
## 要求
要使用Nunu您需要在系统上安装以下软件
* Golang 1.16或更高版本
* Git
### 安装
您可以通过以下命令安装Nunu
```bash
go install github.com/go-nunu/nunu@latest
```
### 创建新项目
您可以使用以下命令创建一个新的Golang项目
```bash
nunu new projectName
```
默认拉取github源你也可以使用国内加速仓库
```
// 使用基础模板
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-basic.git
// 使用高级模板
nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
```
此命令将创建一个名为`projectName`的目录并在其中生成一个优雅的Golang项目结构。
### 创建组件
您可以使用以下命令为项目创建handler、service和dao等组件
```bash
nunu create handler user
nunu create service user
nunu create repository user
nunu create model user
```
```
nunu create all user
```
这些命令将分别创建一个名为`UserHandler``UserService``UserDao``UserModel`的组件,并将它们放置在正确的目录中。
### 启动项目
您可以使用以下命令快速启动项目:
```bash
nunu run
```
此命令将启动您的Golang项目并支持文件更新热重启。
### 编译wire.go
您可以使用以下命令快速编译`wire.go`
```bash
nunu wire
```
此命令将编译您的`wire.go`文件,并生成所需的依赖项。
## 贡献
如果您发现任何问题或有任何改进意见,请随时提出问题或提交拉取请求。我们非常欢迎您的贡献!
## 许可证
Nunu是根据MIT许可证发布的。有关更多信息请参见[LICENSE](LICENSE)文件。

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>
<NuxtLoadingIndicator/>
<NuxtPage/>
<!-- <CommonLayoutSetting v-if="isAdminRoute" class="fixed right-12 top-1/2 z-999" />-->
</NuxtLayout>
</n-message-provider>
</n-modal-provider>
@ -19,21 +18,26 @@
</template>
<script setup lang="ts">
import {darkTheme, lightTheme} from 'naive-ui'
import {naiveThemeOverrides} from "~/settings/settings";
const colorMode = useColorMode()
const themeOverrides = naiveThemeOverrides
const theme = computed(() => {
return colorMode.value === 'system' ? (colorMode.value ? lightTheme : darkTheme) : colorMode.value === 'light' ? lightTheme : darkTheme
})
const whoisStore = useWhoisStore()
const dnsStore = useDnsStore()
const domainStore = useDomainStore()
whoisStore.newWhoisList()
dnsStore.newDnsList()
domainStore.newDomainList()
const styleStore = useStyleStore()
const {common} = storeToRefs(styleStore)
const themeOverrides = computed(() => {
return {
common: common.value, // 使 common.value
}
})
await callOnce(async () => {
await useSettingsStore().webSiteConfigInit()
await useConfigStore().configServerInit()
})
</script>
<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">
const domainListStore = useDomainListStore();
const SupportedTLDs = domainListStore.getSupportedTLDKeys
const isOpen = ref(false)
const {t} = useI18n()
@ -28,13 +26,13 @@ const {t} = useI18n()
closable
>
<div class="flex flex-wrap mt-2 ">
<span
v-for="item in SupportedTLDs"
:key="item"
class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"
>
{{ item }}
</span>
<!-- <span-->
<!-- v-for="item in SupportedTLDs"-->
<!-- :key="item"-->
<!-- class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"-->
<!-- >-->
<!-- {{ item }}-->
<!-- </span>-->
</div>
</NDrawerContent>
</NDrawer>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
const isOpen = ref(false)
const styleStore = useStyleStore()
const settingsStore = useSettingsStore()
const {t} = useI18n()
</script>
@ -33,12 +33,12 @@ const {t} = useI18n()
<h1 class="text-2xl font-bold text-gray-800 mb-5 flex items-center justify-between">
<span
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>
</h1>
<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 ">
<thead>
@ -59,7 +59,7 @@ const {t} = useI18n()
</thead>
<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]">
<td class="px-5 py-5 text-sm ">
<NuxtLink :to="item.path">{{ item.domain }}</NuxtLink>
@ -72,7 +72,7 @@ const {t} = useI18n()
</td>
<td class="px-5 py-5 text-sm ">
<NButton
@click="styleStore.deleteHistory(item.id)"
@click="settingsStore.deleteHistory(item.id)"
>{{ t('common.actions.delete') }}
</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>
const switchLocalePath = useSwitchLocalePath()
const timeStore = useTimeStore()
const settingsStore = useSettingsStore()
const dnsStore = useDnsStore()
const {newsDnsArr} = storeToRefs(dnsStore)
const configStore = useConfigStore()
const {configServer} = storeToRefs(configStore)
const handlePost = async (name: string) => {
dnsStore.moveToTop(name)
//
await refreshNuxtData('dns')
}
@ -18,7 +16,7 @@ const handlePost = async (name: string) => {
<template>
<div>
<HeadlessListbox
v-model="dnsStore.newsDnsArr"
v-model="configServer.dnsArr"
as="div"
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"
>
<div
v-for="lang in newsDnsArr"
v-for="lang in configServer.dnsArr"
:key="lang.name"
@click="handlePost(lang.name)"
: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">
const timeStore = useTimeStore()
const settingsStore = useSettingsStore()
const {getIsDomainList, getIsHistory,} = storeToRefs(settingsStore)
const {t} = useI18n()
</script>
@ -10,7 +10,7 @@ const {t} = useI18n()
<div class="flex space-x-2">
<!-- 左边的新元素 -->
<n-tooltip
v-if="settingsStore.isDomainList"
v-if="getIsDomainList"
trigger="hover" placement="top">
<template #trigger>
<CommonDomainList/>
@ -19,7 +19,7 @@ const {t} = useI18n()
</n-tooltip>
<n-tooltip
v-if="settingsStore.getHistory"
v-if="getIsHistory"
trigger="hover" placement="top">
<template #trigger>
<CommonHistory/>
@ -27,15 +27,6 @@ const {t} = useI18n()
<span>{{ t('popper.history') }}</span>
</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 class="flex space-x-2">
<!-- 右边的现有元素 -->
@ -63,7 +54,7 @@ const {t} = useI18n()
<template #trigger>
<CommonTimeZonesChange/>
</template>
<span>{{ timeStore.timeZones }}</span>
<span>{{ settingsStore.timeZones }}</span>
</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,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

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 390 KiB

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

View File

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

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