From 175888afb948f1084941708c5f88bf51b4591d04 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 16:58:51 +0800 Subject: [PATCH 01/21] Update ./docs --- README.md | 2 +- README_zh-cn.md | 2 +- docs/cil_zh-cn.md | 299 ++++++++++++++++++++++++++++++++++++++++++++++ docs/cli.md | 299 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 600 insertions(+), 2 deletions(-) create mode 100644 docs/cil_zh-cn.md create mode 100644 docs/cli.md diff --git a/README.md b/README.md index 8bb8b08..807575b 100644 --- a/README.md +++ b/README.md @@ -89,4 +89,4 @@ This project need a PostgreSQL database to store. ``` Now you can access the GAH frontend in `http://localhost:`. -**__More Information,Please See The [`docs/api.md`](docs/api.md)__.** +**__More Information,Please See The [`./docs`](./docs)__.** diff --git a/README_zh-cn.md b/README_zh-cn.md index 8ad18b2..60f0ea5 100644 --- a/README_zh-cn.md +++ b/README_zh-cn.md @@ -89,4 +89,4 @@ Go Account Hub(简称 GAH)是一个用 Go 语言编写的用户中心,可 ``` 现在你可以通过 `http://localhost:` 访问 GAH 前端。 -**__更多信息,请参阅 [`docs/api_zh-cn.md`](docs/api_zh-cn.md)。__** +**__更多信息,请参阅 [`./docs`](./docs)。__** diff --git a/docs/cil_zh-cn.md b/docs/cil_zh-cn.md new file mode 100644 index 0000000..174565d --- /dev/null +++ b/docs/cil_zh-cn.md @@ -0,0 +1,299 @@ +# GoAccountHub CLI 文档 + +GoAccountHub 使用 [urfave/cli v2](https://github.com/urfave/cli) 构建命令行界面。以下内容根据 `cliAction/` 目录下的代码生成。 + +- 应用名:`account_hub` +- 应用说明:`account hub example` + +> 下文示例以编译出的二进制文件名 `GoAccountHub` 为例(可自行重命名或设置别名,`README` 中写作 `gah`)。 + +## 命令总览 + +| 命令 | 说明 | +| --- | --- | +| `start` | 启动 GoAccountHub 服务 | +| `password` | 修改 Root Admin 密码 | +| `generate` | 生成配置文件 | +| `generate-test` | 生成测试用配置文件 | +| `add-key` | 新增 Application Key | +| `del-key` | 删除 Application Key | + +查看全局帮助与版本信息: + +```bash +GoAccountHub --help +GoAccountHub --help +``` + +--- + +## start + +启动 GoAccountHub 服务。 + +**用法** + +```bash +GoAccountHub start [选项] +``` + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `config.json` | 配置文件路径 | + +**行为** + +1. 读取指定配置文件中配置。 +2. 将前端相关配置写入 `./GAHFrontend/.env`: + ``` + PORT= + VITE_API_PROXY_TARGET=http://127.0.0.1: + ``` +3. 启动 API 服务。 + +> ⚠️ 注意:启动服务前需要确保前端根目录存在 `.env` 文件,服务启动时会按配置覆写该文件。 + +**示例** + +```bash +GoAccountHub start +GoAccountHub start -c ./config.json +``` + +--- + +## password + +修改 Root Admin 密码,密码以 SHA256 形式写入配置文件。 + +**用法** + +```bash +GoAccountHub password [选项] +``` + +**参数** + +| 参数 | 是否必填 | 说明 | +| --- | --- | --- | +| `` | 是 | 新的 Root Admin 密码;缺省时会提示 `⚠️ Password is Empty` 且不做修改 | + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | 配置文件路径 | + +**行为** + +1. 计算 `root_admin_password_hash = SHA256(password)`。 +2. 计算 `root_admin_uu_hash = SHA256("root" + passwordHash + Unix 时间戳)`。 +3. 将以上两个字段写回配置文件。 + +**示例** + +```bash +GoAccountHub password your_password +GoAccountHub password your_password -c ./config.json +``` + +--- + +## generate + +生成一份空白配置文件。 + +**用法** + +```bash +GoAccountHub generate [选项] +``` + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | 配置文件路径 | + +**行为** + +将空的 `Config` 结构序列化写入目标文件,所有字段均为零值,需手动填写。 + +**示例** + +```bash +GoAccountHub generate +GoAccountHub generate -c ./config.json +``` + +--- + +## generate-test + +生成一份预置好测试数据的配置文件,默认 Root Admin 密码为 `123`。 + +**用法** + +```bash +GoAccountHub generate-test [选项] +``` + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | 配置文件路径 | + +**行为** + +写入如下预置配置: + +| 字段 | 值 | +| --- | --- | +| `port` | `8080` | +| `database_name` | `account_hub` | +| `database_host` | `127.0.0.1` | +| `database_port` | `3306` | +| `database_user` | `postgres` | +| `database_password` | `postgres` | +| `root_admin_password_hash` | `SHA256("123")` | +| `root_admin_uu_hash` | `SHA256("root" + SHA256("123") + Unix 时间戳)` | +| `switch_config.allow_multi_character` | `false` | + +执行后输出 `✅ Config File is Generated, Default Password is 123`。 + +**示例** + +```bash +GoAccountHub generate-test +``` + +--- + +## add-key + +新增一个 Application Key 并写入数据库。 + +**用法** + +```bash +GoAccountHub add-key [选项] +``` + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | 配置文件路径 | +| `--site` | `-s` | `default` | 该 Key 对应的应用站点 | + +**行为** + +1. 读取配置并连接数据库(执行完成后自动关闭连接)。 +2. 构造 `ApplicationKey`: + - `application_using_site = ` + - `key = SHA256(site + time.Now().String())` +3. 写入数据库,输出 `✅ Add Key Success`。 + +> ℹ️ 说明:生成的 Key 仅存入数据库,不会打印到终端,如需使用请从数据库中查询。 + +**示例** + +```bash +GoAccountHub add-key +GoAccountHub add-key -s mysite +``` + +--- + +## del-key + +删除数据库中的 Application Key。 + +**用法** + +```bash +GoAccountHub del-key [选项] +``` + +**选项** + +| 选项 | 别名 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | 配置文件路径 | +| `--key` | `-k` | `default` | 要删除的 Application Key | + +**行为** + +1. 读取配置并连接数据库(执行完成后自动关闭连接)。 +2. 按 key 删除记录,输出 `✅ Delete Key Success`。 + +**示例** + +```bash +GoAccountHub del-key -k +``` + +--- + +## 配置文件字段说明 + +配置文件为 JSON 格式,结构定义见 `config/config.go`。 + +```json +{ + "port": "8081", + "database_name": "GoAccountHub", + "database_host": "127.0.0.1", + "database_port": "5432", + "database_user": "postgres", + "database_password": "postgres", + "frontend_port": "8082", + "root_admin_password_hash": "a665a45920422f9d417......", + "root_admin_uu_hash": "0fbaf45ee863c0......", + "switch_config": { + "allow_multi_character": true, + "allow_admin_logout": false + } +} +``` + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `port` | string | API 服务端口 | +| `database_name` | string | 数据库名 | +| `database_host` | string | 数据库地址 | +| `database_port` | string | 数据库端口 | +| `database_user` | string | 数据库用户名 | +| `database_password` | string | 数据库密码 | +| `frontend_port` | string | 前端端口;启动服务时写入 `GAHFrontend/.env` | +| `root_admin_password_hash` | string | Root Admin 密码哈希,由 `password` / `generate-test` 生成,无需手填 | +| `root_admin_uu_hash` | string | Root Admin 唯一标识哈希,由 CLI 生成,无需手填 | +| `switch_config` | object | 系统开关配置 | +| `switch_config.allow_multi_character` | bool | 是否允许一个账号拥有多个角色 | +| `switch_config.allow_admin_logout` | bool | 是否允许管理员登出(开发有误,后续将移除) | + +--- + +## 典型使用流程 + +```bash +# 1. 生成配置文件 +GoAccountHub generate + +# 2. 设置 Root Admin 密码 +GoAccountHub password + +# 3. 编辑配置文件,填写数据库等参数(见上文配置字段说明) + +# 4. 启动服务 +GoAccountHub start +``` + +服务启动后访问 `http://localhost:`,正常返回: + +```json +{"code":200,"message":"Welcome to GoAccountHub"} +``` diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..eddfe63 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,299 @@ +# GoAccountHub CLI Documentation + +GoAccountHub builds its command-line interface with [urfave/cli v2](https://github.com/urfave/cli). The following content is generated from the code in the `cliAction/` directory. + +- App name: `account_hub` +- App usage: `account hub example` + +> The examples below use the compiled binary name `GoAccountHub` (you may rename it or set an alias; it is referred to as `gah` in the `README`). + +## Command Overview + +| Command | Description | +| --- | --- | +| `start` | Start GoAccountHub | +| `password` | Edit Root Admin Password | +| `generate` | Generate Config File | +| `generate-test` | Generate Test Config File | +| `add-key` | Add Application Key | +| `del-key` | Delete Application Key | + +To view global help and version information: + +```bash +GoAccountHub --help +GoAccountHub --help +``` + +--- + +## start + +Start the GoAccountHub server. + +**Usage** + +```bash +GoAccountHub start [options] +``` + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `config.json` | Config file path | + +**Behavior** + +1. Reads the configuration from the specified config file. +2. Writes the frontend-related config to `./GAHFrontend/.env`: + ``` + PORT= + VITE_API_PROXY_TARGET=http://127.0.0.1: + ``` +3. Starts the API server. + +> ⚠️ Note: Before starting the server, make sure a `.env` file exists in the frontend root directory. The server overwrites this file according to the config on startup. + +**Examples** + +```bash +GoAccountHub start +GoAccountHub start -c ./config.json +``` + +--- + +## password + +Edit the Root Admin password. The password is written to the config file as a SHA256 hash. + +**Usage** + +```bash +GoAccountHub password [options] +``` + +**Arguments** + +| Argument | Required | Description | +| --- | --- | --- | +| `` | Yes | The new Root Admin password; if omitted, prints `⚠️ Password is Empty` and makes no change | + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | Config file path | + +**Behavior** + +1. Computes `root_admin_password_hash = SHA256(password)`. +2. Computes `root_admin_uu_hash = SHA256("root" + passwordHash + Unix timestamp)`. +3. Writes both fields back to the config file. + +**Examples** + +```bash +GoAccountHub password your_password +GoAccountHub password your_password -c ./config.json +``` + +--- + +## generate + +Generate a blank config file. + +**Usage** + +```bash +GoAccountHub generate [options] +``` + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | Config file path | + +**Behavior** + +Serializes an empty `Config` struct and writes it to the target file. All fields are zero values and must be filled in manually. + +**Examples** + +```bash +GoAccountHub generate +GoAccountHub generate -c ./config.json +``` + +--- + +## generate-test + +Generate a config file pre-filled with test data. The default Root Admin password is `123`. + +**Usage** + +```bash +GoAccountHub generate-test [options] +``` + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | Config file path | + +**Behavior** + +Writes the following preset config: + +| Field | Value | +| --- | --- | +| `port` | `8080` | +| `database_name` | `account_hub` | +| `database_host` | `127.0.0.1` | +| `database_port` | `3306` | +| `database_user` | `postgres` | +| `database_password` | `postgres` | +| `root_admin_password_hash` | `SHA256("123")` | +| `root_admin_uu_hash` | `SHA256("root" + SHA256("123") + Unix timestamp)` | +| `switch_config.allow_multi_character` | `false` | + +Prints `✅ Config File is Generated, Default Password is 123` on completion. + +**Examples** + +```bash +GoAccountHub generate-test +``` + +--- + +## add-key + +Add an Application Key and store it in the database. + +**Usage** + +```bash +GoAccountHub add-key [options] +``` + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | Config file path | +| `--site` | `-s` | `default` | The application site this key belongs to | + +**Behavior** + +1. Reads the config and connects to the database (the connection is closed automatically after execution). +2. Builds an `ApplicationKey`: + - `application_using_site = ` + - `key = SHA256(site + time.Now().String())` +3. Writes it to the database and prints `✅ Add Key Success`. + +> ℹ️ Note: The generated key is only stored in the database and is not printed to the terminal. Query the database if you need to use it. + +**Examples** + +```bash +GoAccountHub add-key +GoAccountHub add-key -s mysite +``` + +--- + +## del-key + +Delete an Application Key from the database. + +**Usage** + +```bash +GoAccountHub del-key [options] +``` + +**Options** + +| Option | Alias | Default | Description | +| --- | --- | --- | --- | +| `--config` | `-c` | `./config.json` | Config file path | +| `--key` | `-k` | `default` | The Application Key to delete | + +**Behavior** + +1. Reads the config and connects to the database (the connection is closed automatically after execution). +2. Deletes the record by key and prints `✅ Delete Key Success`. + +**Examples** + +```bash +GoAccountHub del-key -k +``` + +--- + +## Config File Fields + +The config file is in JSON format. The struct is defined in `config/config.go`. + +```json +{ + "port": "8081", + "database_name": "GoAccountHub", + "database_host": "127.0.0.1", + "database_port": "5432", + "database_user": "postgres", + "database_password": "postgres", + "frontend_port": "8082", + "root_admin_password_hash": "a665a45920422f9d417......", + "root_admin_uu_hash": "0fbaf45ee863c0......", + "switch_config": { + "allow_multi_character": true, + "allow_admin_logout": false + } +} +``` + +| Field | Type | Description | +| --- | --- | --- | +| `port` | string | API server port | +| `database_name` | string | Database name | +| `database_host` | string | Database host | +| `database_port` | string | Database port | +| `database_user` | string | Database username | +| `database_password` | string | Database password | +| `frontend_port` | string | Frontend port; written to `GAHFrontend/.env` when the server starts | +| `root_admin_password_hash` | string | Root Admin password hash, generated by `password` / `generate-test`; no need to set manually | +| `root_admin_uu_hash` | string | Root Admin unique hash, generated by the CLI; no need to set manually | +| `switch_config` | object | System switch config | +| `switch_config.allow_multi_character` | bool | Whether an account may have multiple characters | +| `switch_config.allow_admin_logout` | bool | Whether admin logout is allowed (implemented incorrectly, will be removed) | + +--- + +## Typical Workflow + +```bash +# 1. Generate the config file +GoAccountHub generate + +# 2. Set the Root Admin password +GoAccountHub password + +# 3. Edit the config file and fill in database parameters etc. (see Config File Fields above) + +# 4. Start the server +GoAccountHub start +``` + +After the server starts, access `http://localhost:`. A successful response looks like: + +```json +{"code":200,"message":"Welcome to GoAccountHub"} +``` From df97bd12c07ef78af2e2af7df9e0eb829c4aad11 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 22:45:13 +0800 Subject: [PATCH 02/21] Change field name --- cliAction/addApplicationKey.go | 2 +- sqlTable/applicationKey.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cliAction/addApplicationKey.go b/cliAction/addApplicationKey.go index c4d5297..ad6a45f 100644 --- a/cliAction/addApplicationKey.go +++ b/cliAction/addApplicationKey.go @@ -41,7 +41,7 @@ func AddApplicationKeyAction() *cli.Command { sqlDB.Close() }() var applicationKey sqlTable.ApplicationKey - applicationKey.ApplicationUsingSite = c.String("site") + applicationKey.ApplicationUser = c.String("site") applicationKey.Key = hash.SHA256(c.String("site") + time.Now().String()) err := sqlOperator.AddApplicationKey(db, applicationKey) if err != nil { diff --git a/sqlTable/applicationKey.go b/sqlTable/applicationKey.go index 6b6245b..1602996 100644 --- a/sqlTable/applicationKey.go +++ b/sqlTable/applicationKey.go @@ -4,7 +4,8 @@ import "gorm.io/gorm" type ApplicationKey struct { gorm.Model - ID int64 `gorm:"primaryKey"` - ApplicationUsingSite string `gorm:"column:application_using_site"` - Key string `gorm:"column:key"` + ID int64 `gorm:"primaryKey"` + //UNIQUE + ApplicationUser string `gorm:"column:application_user"` + Key string `gorm:"column:key"` } From a94802d3a7a32bb3478529effda52c6ec31c598e Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 22:57:31 +0800 Subject: [PATCH 03/21] Add key Operation field --- sqlTable/admin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlTable/admin.go b/sqlTable/admin.go index c47b37d..e86aad9 100644 --- a/sqlTable/admin.go +++ b/sqlTable/admin.go @@ -25,6 +25,8 @@ type Permission struct { CanOperateUser bool `gorm:"column:can_operate_user" json:"can_operate_user"` //Character Permission CanOperateCharacter bool `gorm:"column:can_operate_character" json:"can_operate_character"` + //AppKey Permission + CanOperateAppKey bool `gorm:"column:can_operate_app_key" json:"can_operate_app_key"` } type AdminForEdit struct { From 61830f6338a2ed61a1f0fe7ec4613c9e256ed871 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 22:57:58 +0800 Subject: [PATCH 04/21] Change field name --- cliAction/addApplicationKey.go | 2 +- sqlTable/applicationKey.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cliAction/addApplicationKey.go b/cliAction/addApplicationKey.go index ad6a45f..ab48ecb 100644 --- a/cliAction/addApplicationKey.go +++ b/cliAction/addApplicationKey.go @@ -41,7 +41,7 @@ func AddApplicationKeyAction() *cli.Command { sqlDB.Close() }() var applicationKey sqlTable.ApplicationKey - applicationKey.ApplicationUser = c.String("site") + applicationKey.KeyUser = c.String("site") applicationKey.Key = hash.SHA256(c.String("site") + time.Now().String()) err := sqlOperator.AddApplicationKey(db, applicationKey) if err != nil { diff --git a/sqlTable/applicationKey.go b/sqlTable/applicationKey.go index 1602996..fec1d09 100644 --- a/sqlTable/applicationKey.go +++ b/sqlTable/applicationKey.go @@ -6,6 +6,6 @@ type ApplicationKey struct { gorm.Model ID int64 `gorm:"primaryKey"` //UNIQUE - ApplicationUser string `gorm:"column:application_user"` - Key string `gorm:"column:key"` + KeyUser string `gorm:"column:application_user"` + Key string `gorm:"column:key"` } From c589ddcf175e3761d482c97876998c3ef6840a14 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 23:02:42 +0800 Subject: [PATCH 05/21] Fix: column name wrong --- sqlTable/applicationKey.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlTable/applicationKey.go b/sqlTable/applicationKey.go index fec1d09..db1bdc2 100644 --- a/sqlTable/applicationKey.go +++ b/sqlTable/applicationKey.go @@ -6,6 +6,6 @@ type ApplicationKey struct { gorm.Model ID int64 `gorm:"primaryKey"` //UNIQUE - KeyUser string `gorm:"column:application_user"` + KeyUser string `gorm:"column:key_user"` Key string `gorm:"column:key"` } From 93d4d7d39f3b8d297987b73f3dd53c393313a4ed Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 23:06:09 +0800 Subject: [PATCH 06/21] Add key add api --- adminControllor/keyAdd.go | 54 +++++++++++++++++++++++++++++++++++++++ router/router.go | 2 ++ 2 files changed, 56 insertions(+) create mode 100644 adminControllor/keyAdd.go diff --git a/adminControllor/keyAdd.go b/adminControllor/keyAdd.go new file mode 100644 index 0000000..6a1b95e --- /dev/null +++ b/adminControllor/keyAdd.go @@ -0,0 +1,54 @@ +package adminControllor + +import ( + "net/http" + "regexp" + "time" + + "github.com/TrafficLight6/GoAccountHub/hash" + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type KeyAddRequestBody struct { + KeyUserName string `json:"key_user_name"` +} + +func KeyAdd(c *gin.Context) { + var body KeyAddRequestBody + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Invalid request body"}) + return + } + //Check name is empty + if body.KeyUserName == "" { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Key user name is empty"}) + return + } + //Check name is ascii string + if !regexp.MustCompile(`^[\x20-\x7E]+$`).MatchString(body.KeyUserName) { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Key user name must be in printable ASCII"}) + return + } + //Get Db + db := c.Value("db").(*gorm.DB) + //Check key user name is exist + var key sqlTable.ApplicationKey + if err := db.Where("name = ?", body.KeyUserName).First(&key).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Key user name is already exist"}) + return + } + //Add key user name + newKey := sqlTable.ApplicationKey{ + KeyUser: body.KeyUserName, + Key: hash.SHA256(body.KeyUserName + time.Now().String()), + } + //Save key user name + if err := db.Create(&newKey).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed to add key user name"}) + return + } + //Return key user name + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "key_user_name": newKey.KeyUser, "key": newKey.Key}) +} diff --git a/router/router.go b/router/router.go index 552a187..2efb6f6 100644 --- a/router/router.go +++ b/router/router.go @@ -71,6 +71,8 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { v1.PUT("/character/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterEdit) v1.POST("/character/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterGet) v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterRange) + //Key Api + v1.POST("/key/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyAdd) } { From 2ae92db4ac90c1f9d96ce1c574ba79d39826f92e Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 23:10:13 +0800 Subject: [PATCH 07/21] Fix:fix SQL condition wrong --- adminControllor/keyAdd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adminControllor/keyAdd.go b/adminControllor/keyAdd.go index 6a1b95e..962e77f 100644 --- a/adminControllor/keyAdd.go +++ b/adminControllor/keyAdd.go @@ -35,7 +35,7 @@ func KeyAdd(c *gin.Context) { db := c.Value("db").(*gorm.DB) //Check key user name is exist var key sqlTable.ApplicationKey - if err := db.Where("name = ?", body.KeyUserName).First(&key).Error; err != nil { + if err := db.Where("key_user = ?", body.KeyUserName).First(&key).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Key user name is already exist"}) return } From 6483f1c0a26f761ca08d6b188404c60ae146111d Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 23:11:10 +0800 Subject: [PATCH 08/21] Add key deleting api --- adminControllor/keyDelete.go | 35 +++++++++++++++++++++++++++++++++++ router/router.go | 1 + 2 files changed, 36 insertions(+) create mode 100644 adminControllor/keyDelete.go diff --git a/adminControllor/keyDelete.go b/adminControllor/keyDelete.go new file mode 100644 index 0000000..fe79594 --- /dev/null +++ b/adminControllor/keyDelete.go @@ -0,0 +1,35 @@ +package adminControllor + +import ( + "net/http" + + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type KeyDeleteRequestBody struct { + KeyUserName string `json:"key_user_name"` +} + +func KeyDelete(c *gin.Context) { + var body KeyDeleteRequestBody + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Invalid request body"}) + return + } + //Check key user name is empty + if body.KeyUserName == "" { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "key_user_name is empty"}) + return + } + //Get db + db := c.Value("db").(*gorm.DB) + //Delete key user name + if err := db.Delete(&sqlTable.ApplicationKey{}, "key_user = ?", body.KeyUserName).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": err.Error()}) + return + } + //Return success + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "Success"}) +} diff --git a/router/router.go b/router/router.go index 2efb6f6..766f936 100644 --- a/router/router.go +++ b/router/router.go @@ -73,6 +73,7 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterRange) //Key Api v1.POST("/key/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyAdd) + v1.DELETE("/key/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyDelete) } { From 1fb3e0adfe3e92be2bf2e715b2b94cbf61ba0898 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sat, 12 Sep 2026 23:19:55 +0800 Subject: [PATCH 09/21] Add key range --- adminControllor/keyRange.go | 74 +++++++++++++++++++++++++++++++++++++ router/router.go | 1 + 2 files changed, 75 insertions(+) create mode 100644 adminControllor/keyRange.go diff --git a/adminControllor/keyRange.go b/adminControllor/keyRange.go new file mode 100644 index 0000000..1edbf3f --- /dev/null +++ b/adminControllor/keyRange.go @@ -0,0 +1,74 @@ +package adminControllor + +import ( + "net/http" + "strings" + + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type KeyRangeRequestBody struct { + //It the Length is -1, It Means Get All Key from BeginTableId to The End + BeginTableId int `json:"begin_table_id"` + Length int `json:"length"` + SearchCondition KeySearchCondition `json:"search_condition"` +} + +type KeySearchCondition struct { + //If The Field is Empty, It Means Ignore This Condition + KeyUser string `json:"key_user"` +} + +func KeyRange(c *gin.Context) { + var body KeyRangeRequestBody + if err := c.ShouldBindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Invalid Request Body"}) + c.Abort() + return + } + if body.BeginTableId < 0 { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "BeginTableId Begin From 1"}) + c.Abort() + return + } + //Check Length is Valid + if body.Length < -1 { + c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Length Must Be Greater Than -1"}) + c.Abort() + return + } + var isReturnAll bool + if body.Length == -1 { + isReturnAll = true + } else { + isReturnAll = false + } + //Get Db + db := c.Value("db").(*gorm.DB) + //Build Search Query (only non-empty fields, combined with AND) + condition := body.SearchCondition + var conditions []string + var args []interface{} + if condition.KeyUser != "" { + conditions = append(conditions, "key_user LIKE ?") + args = append(args, "%"+condition.KeyUser+"%") + } + query := db.Model(&sqlTable.ApplicationKey{}) + if len(conditions) > 0 { + query = query.Where(strings.Join(conditions, " AND "), args...) + } + //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable + query = query.Order("id ASC") + //Search + var keys []sqlTable.ApplicationKey + if isReturnAll { + query.Find(&keys) + } else { + query.Limit(body.Length).Offset((body.BeginTableId - 1) * body.Length).Find(&keys) + } + //Return Result + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "Success", "data": keys}) + return +} diff --git a/router/router.go b/router/router.go index 766f936..61150be 100644 --- a/router/router.go +++ b/router/router.go @@ -73,6 +73,7 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterRange) //Key Api v1.POST("/key/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyAdd) + v1.POST("/key/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyRange) v1.DELETE("/key/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_app_key"), adminControllor.KeyDelete) } From b4043a29c6a96f89dd7e93af0a9bf1a598056b92 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Sun, 13 Sep 2026 08:24:54 +0800 Subject: [PATCH 10/21] Change frontend comments and text prompts to English --- .../customize/AddAdminDialogFrom.vue | 30 ++-- .../customize/AddCharacterDialogFrom.vue | 20 +-- .../customize/AddUserDialogFrom.vue | 16 +- .../customize/EditAdminDialogFrom.vue | 30 ++-- .../customize/EditCharacterDialog.vue | 20 +-- .../components/customize/EditUserDialog.vue | 20 +-- .../components/customize/MetaDataEditor.vue | 35 ++-- GAHFrontend/src/components/login/login.vue | 40 ++--- GAHFrontend/src/components/main/bar.vue | 36 ++--- .../src/components/main/page/admin.vue | 144 ++++++++--------- .../src/components/main/page/character.vue | 152 +++++++++--------- GAHFrontend/src/components/main/page/home.vue | 34 ++-- GAHFrontend/src/components/main/page/user.vue | 128 +++++++-------- GAHFrontend/src/lib/request.js | 34 ++-- GAHFrontend/src/router/index.js | 8 +- GAHFrontend/vite.config.js | 2 +- 16 files changed, 377 insertions(+), 372 deletions(-) diff --git a/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue index 4694858..1e914fe 100644 --- a/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue +++ b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue @@ -1,25 +1,25 @@ @@ -36,7 +36,7 @@ defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 adminAdd.go 的请求体 +// Form fields map to the request body of adminAdd.go const form = reactive({ username: '', password: '', diff --git a/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue b/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue index 33255c6..d67f239 100644 --- a/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue +++ b/GAHFrontend/src/components/customize/AddCharacterDialogFrom.vue @@ -1,23 +1,23 @@ @@ -35,7 +35,7 @@ defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 characterAdd.go 的请求体 +// Form fields map to the request body of characterAdd.go const form = reactive({ character_name: '', password: '', diff --git a/GAHFrontend/src/components/customize/AddUserDialogFrom.vue b/GAHFrontend/src/components/customize/AddUserDialogFrom.vue index f73c73a..9324b3f 100644 --- a/GAHFrontend/src/components/customize/AddUserDialogFrom.vue +++ b/GAHFrontend/src/components/customize/AddUserDialogFrom.vue @@ -1,20 +1,20 @@ @@ -32,7 +32,7 @@ defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 userAdd.go 的请求体 +// Form fields map to the request body of userAdd.go const form = reactive({ username: '', password: '', diff --git a/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue index ed15606..17427be 100644 --- a/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue +++ b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue @@ -1,22 +1,22 @@ @@ -29,7 +29,7 @@ const props = defineProps({ type: Boolean, default: false, }, - // 被编辑的管理员行数据,用于回填表单 + // Admin row being edited, used to populate the form admin: { type: Object, default: null, @@ -38,7 +38,7 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 adminEdit.go 的请求体 +// Form fields map to the request body of adminEdit.go const form = reactive({ uu_hash: '', admin_info: { @@ -54,7 +54,7 @@ const form = reactive({ }, }) -// 打开弹窗或切换目标行时回填表单 +// Populate the form when the dialog opens or the target row changes watch( () => [props.modelValue, props.admin], () => { diff --git a/GAHFrontend/src/components/customize/EditCharacterDialog.vue b/GAHFrontend/src/components/customize/EditCharacterDialog.vue index 9e9aaab..44bef2b 100644 --- a/GAHFrontend/src/components/customize/EditCharacterDialog.vue +++ b/GAHFrontend/src/components/customize/EditCharacterDialog.vue @@ -1,20 +1,20 @@ @@ -28,7 +28,7 @@ const props = defineProps({ type: Boolean, default: false, }, - // 被编辑的角色行数据,用于回填表单 + // Character row being edited, used to populate the form character: { type: Object, default: null, @@ -37,7 +37,7 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 characterEdit.go 的请求体 +// Form fields map to the request body of characterEdit.go const form = reactive({ user_uu_hash: '', character_uu_hash: '', @@ -46,7 +46,7 @@ const form = reactive({ meta_data: '', }) -// 打开弹窗或切换目标行时回填表单 +// Populate the form when the dialog opens or the target row changes watch( () => [props.modelValue, props.character], () => { diff --git a/GAHFrontend/src/components/customize/EditUserDialog.vue b/GAHFrontend/src/components/customize/EditUserDialog.vue index 6860646..45889e9 100644 --- a/GAHFrontend/src/components/customize/EditUserDialog.vue +++ b/GAHFrontend/src/components/customize/EditUserDialog.vue @@ -1,20 +1,20 @@ @@ -28,7 +28,7 @@ const props = defineProps({ type: Boolean, default: false, }, - // 被编辑的用户行数据,用于回填表单 + // User row being edited, used to populate the form user: { type: Object, default: null, @@ -37,7 +37,7 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue', 'submit']) -// 表单字段对应 userEdit.go 的请求体 +// Form fields map to the request body of userEdit.go const form = reactive({ uu_hash: '', username: '', @@ -45,7 +45,7 @@ const form = reactive({ meta_data: '', }) -// 打开弹窗或切换目标行时回填表单 +// Populate the form when the dialog opens or the target row changes watch( () => [props.modelValue, props.user], () => { diff --git a/GAHFrontend/src/components/customize/MetaDataEditor.vue b/GAHFrontend/src/components/customize/MetaDataEditor.vue index 911dbc4..78e3abc 100644 --- a/GAHFrontend/src/components/customize/MetaDataEditor.vue +++ b/GAHFrontend/src/components/customize/MetaDataEditor.vue @@ -10,22 +10,22 @@ + \ No newline at end of file diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index 79760ed..2cf08c0 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -1,22 +1,22 @@ @@ -71,10 +71,10 @@ const adminList = ref([]) const PAGE_SIZE = 100 const loading = ref(false) -// 后端是否已无更多数据 +// Whether the backend has no more data const noMore = ref(false) -// 筛选条件表单(提交后由后端筛选) +// Filter form (filtered by the backend after submit) const searchForm = reactive({ username: '', uu_hash: '', @@ -88,13 +88,13 @@ const searchForm = reactive({ }, }) -// 当前生效的筛选条件,随每次 range 请求发给后端 +// Currently active filter, sent to the backend with each range request const searchCondition = ref({}) -// 添加管理员弹窗 +// Add admin dialog const dialogVisible = ref(false) -// 编辑管理员弹窗 +// Edit admin dialog const editVisible = ref(false) const editRow = ref(null) @@ -103,13 +103,13 @@ const canGetAdmin = computed(() => { return Boolean(adminInfo.value.permission?.can_get_admin) }) -// 权限列统一渲染:命中为“是”,否则“否” -const permCell = (key) => ({ rowData }) => h('span', rowData.Permission?.[key] ? '是' : '否') +// Unified permission cell rendering: "Yes" when granted, otherwise "No" +const permCell = (key) => ({ rowData }) => h('span', rowData.Permission?.[key] ? 'Yes' : 'No') -// 勾选状态:以行 ID 记录 +// Selection state: recorded by row ID const selectedIds = ref([]) const isSelected = (id) => selectedIds.value.includes(id) -// 全选判定基于当前数据 +// Select-all is based on the current data const allSelected = computed( () => adminList.value.length > 0 && adminList.value.every((row) => isSelected(row.ID)) ) @@ -127,12 +127,12 @@ const toggleAll = () => { : [...new Set([...selectedIds.value, ...shownIds])] } -// 取消全部选中 +// Clear all selections const handleCheckboxCancel = () => { selectedIds.value = [] } -// 查询:把筛选条件交给后端,重新从第一页取 +// Search: send the filter to the backend and refetch from the first page const handleSearch = () => { searchCondition.value = { username: searchForm.username.trim(), @@ -147,7 +147,7 @@ const handleSearch = () => { range(1) } -// 重置:清空筛选条件并重新从第一页取全部 +// Reset: clear the filter and refetch everything from the first page const handleReset = () => { searchForm.username = '' searchForm.uu_hash = '' @@ -158,12 +158,12 @@ const handleReset = () => { range(1) } -// 打开添加管理员弹窗 +// Open the add admin dialog const handleAdd = () => { dialogVisible.value = true } -// 提交添加:调用 /admin/add,成功后刷新列表 +// Submit add: call /admin/add and refresh the list on success const handleAddSubmit = async (form) => { try { await post('/admin/add', { @@ -171,72 +171,72 @@ const handleAddSubmit = async (form) => { password: form.password, permission: { ...form.permission }, }, { autoRedirect401: false }) - ElMessage.success('添加管理员成功') + ElMessage.success('Admin added successfully') dialogVisible.value = false range(1) } catch { - // 失败提示已由 request 封装统一弹出 + // Failure messages are already shown by the request wrapper } } -// 执行删除并同步列表(后端每次只接收一个 uu_hash,故逐条调用) +// Perform deletion and sync the list (the backend accepts one uu_hash per request, so call one by one) const deleteAdmins = async (rows) => { const results = await Promise.allSettled( rows.map((item) => del('/admin/delete', { uu_hash: item.UUHash }, { autoRedirect401: false, showError: false })) ) const successIds = rows.filter((_, index) => results[index].status === 'fulfilled').map((item) => item.ID) const failed = results.filter((result) => result.status === 'rejected') - // 成功的行取消勾选 + // Deselect successfully deleted rows selectedIds.value = selectedIds.value.filter((id) => !successIds.includes(id)) if (failed.length === 0) { - ElMessage.success(`删除成功,共 ${successIds.length} 条`) + ElMessage.success(`Deleted successfully, ${successIds.length} in total`) } else if (successIds.length === 0) { - ElMessage.error(failed[0].reason?.message || '删除失败') + ElMessage.error(failed[0].reason?.message || 'Delete failed') } else { - ElMessage.warning(`成功 ${successIds.length} 条,失败 ${failed.length} 条:${failed[0].reason?.message ?? '未知原因'}`) + ElMessage.warning(`Succeeded: ${successIds.length}, failed: ${failed.length}: ${failed[0].reason?.message ?? 'Unknown reason'}`) } if (successIds.length > 0) range(1) } -// 单个删除:确认后删除该行 +// Single delete: confirm, then delete the row const handleDelete = async (row) => { try { - await ElMessageBox.confirm(`确定删除管理员"${row.Username}"吗?`, '删除确认', { + await ElMessageBox.confirm(`Are you sure you want to delete admin "${row.Username}"?`, 'Delete Confirmation', { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消', + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', }) } catch { - // 取消删除 + // Deletion canceled return } deleteAdmins([row]) } -// 批量删除:确认后删除所有选中行 +// Batch delete: confirm, then delete all selected rows const handleBatchDelete = async () => { const rows = adminList.value.filter((item) => selectedIds.value.includes(item.ID)) if (rows.length === 0) return try { - await ElMessageBox.confirm(`确定删除选中的 ${rows.length} 条管理员吗?`, '删除确认', { + await ElMessageBox.confirm(`Are you sure you want to delete the ${rows.length} selected admins?`, 'Delete Confirmation', { type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消', + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', }) } catch { - // 取消删除 + // Deletion canceled return } deleteAdmins(rows) } -// 打开编辑管理员弹窗 +// Open the edit admin dialog const handleEdit = (row) => { editRow.value = row editVisible.value = true } -// 提交编辑:调用 /admin/edit,成功后刷新列表 +// Submit edit: call /admin/edit and refresh the list on success const handleEditSubmit = async (form) => { try { await put('/admin/edit', { @@ -246,21 +246,21 @@ const handleEditSubmit = async (form) => { permission: { ...form.admin_info.permission }, }, }, { autoRedirect401: false }) - ElMessage.success('修改管理员成功') + ElMessage.success('Admin updated successfully') editVisible.value = false range(1) } catch { - // 失败提示已由 request 封装统一弹出 + // Failure messages are already shown by the request wrapper } } -// 复制文本到剪贴板 +// Copy text to the clipboard const handleCopy = async (text) => { try { await navigator.clipboard.writeText(text) - ElMessage.success('已复制') + ElMessage.success('Copied') } catch { - ElMessage.error('复制失败') + ElMessage.error('Copy failed') } } @@ -280,7 +280,7 @@ const columns = [ }), }, { key: 'ID', dataKey: 'ID', title: 'ID', width: 70 }, - { key: 'Username', dataKey: 'Username', title: '用户名', width: 140 }, + { key: 'Username', dataKey: 'Username', title: 'Username', width: 140 }, { key: 'UUHash', title: 'UUHash', @@ -289,24 +289,24 @@ const columns = [ h('span', rowData.UUHash), h(ElButton, { size: 'small', - title: '复制 UUHash', + title: 'Copy UUHash', onClick: () => handleCopy(rowData.UUHash), }, () => h(ElIcon, null, () => h(DocumentCopy))), ]), }, - { key: 'can_add_admin', title: '添加管理员', width: 110, cellRenderer: permCell('can_add_admin') }, - { key: 'can_delete_admin', title: '删除管理员', width: 110, cellRenderer: permCell('can_delete_admin') }, - { key: 'can_edit_admin', title: '修改管理员', width: 110, cellRenderer: permCell('can_edit_admin') }, - { key: 'can_get_admin', title: '查看管理员', width: 110, cellRenderer: permCell('can_get_admin') }, - { key: 'can_operate_user', title: '操作用户', width: 100, cellRenderer: permCell('can_operate_user') }, - { key: 'can_operate_character', title: '操作角色', width: 100, cellRenderer: permCell('can_operate_character') }, + { key: 'can_add_admin', title: 'Add Admin', width: 110, cellRenderer: permCell('can_add_admin') }, + { key: 'can_delete_admin', title: 'Delete Admin', width: 110, cellRenderer: permCell('can_delete_admin') }, + { key: 'can_edit_admin', title: 'Edit Admin', width: 110, cellRenderer: permCell('can_edit_admin') }, + { key: 'can_get_admin', title: 'View Admin', width: 110, cellRenderer: permCell('can_get_admin') }, + { key: 'can_operate_user', title: 'Operate Users', width: 100, cellRenderer: permCell('can_operate_user') }, + { key: 'can_operate_character', title: 'Operate Characters', width: 100, cellRenderer: permCell('can_operate_character') }, { key: 'actions', - title: '操作', + title: 'Actions', width: 200, cellRenderer: ({ rowData }) => h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ElButton, { type: 'danger', size: 'small', onClick: () => handleDelete(rowData) }, () => '删除管理员'), - h(ElButton, { type: 'primary', size: 'small', onClick: () => handleEdit(rowData) }, () => '编辑管理员'), + h(ElButton, { type: 'danger', size: 'small', onClick: () => handleDelete(rowData) }, () => 'Delete Admin'), + h(ElButton, { type: 'primary', size: 'small', onClick: () => handleEdit(rowData) }, () => 'Edit Admin'), ]), }, ] @@ -320,13 +320,13 @@ const range = async (start = 1) => { adminList.value = merged.sort((a, b) => a.ID - b.ID) noMore.value = list.length < PAGE_SIZE } catch { - // 401 已由 request 封装自动跳转登录页 + // 401 already redirects to the login page via the request wrapper } finally { loading.value = false } } -// 滚动到底部:后端还有数据时继续请求下一页 100 行 +// Scroll to bottom: keep requesting the next 100 rows while the backend has more data const handleEndReached = () => { if (loading.value || noMore.value) return range(Math.floor(adminList.value.length / PAGE_SIZE) + 1) @@ -337,7 +337,7 @@ onMounted(async () => { const res = await post('/admin/info') adminInfo.value = res.data } catch { - // 401 已由 request 封装自动跳转登录页,网络错误时不再继续鉴权 + // 401 already redirects to the login page via the request wrapper; stop auth check on network errors return } if (!canGetAdmin.value) { @@ -346,4 +346,4 @@ onMounted(async () => { } range() }) - + \ No newline at end of file diff --git a/GAHFrontend/src/components/main/page/character.vue b/GAHFrontend/src/components/main/page/character.vue index 074ade9..399edd2 100644 --- a/GAHFrontend/src/components/main/page/character.vue +++ b/GAHFrontend/src/components/main/page/character.vue @@ -1,16 +1,16 @@