diff --git a/admin/controller.go b/admin/controller.go index 6ac4f99..0029f94 100644 --- a/admin/controller.go +++ b/admin/controller.go @@ -136,6 +136,27 @@ func RedeemListAPI(c *gin.Context) { c.JSON(http.StatusOK, GetRedeemData(db)) } +func RedeemSegmentAPI(c *gin.Context) { + quota := utils.ParseFloat32(c.Query("quota")) + onlyUnused := utils.ParseBool(c.Query("unused")) + + db := utils.GetDBFromContext(c) + + data, err := GetRedeemSegment(db, quota, onlyUnused) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": false, + "error": err.Error(), + }) + return + + } + c.JSON(http.StatusOK, gin.H{ + "status": true, + "data": data, + }) +} + func InvitationPaginationAPI(c *gin.Context) { db := utils.GetDBFromContext(c) diff --git a/admin/invitation.go b/admin/invitation.go index 5a90286..33ac97b 100644 --- a/admin/invitation.go +++ b/admin/invitation.go @@ -22,9 +22,14 @@ func GetInvitationPagination(db *sql.DB, page int64) PaginationForm { } } + // get used_user from auth table by `user_id` rows, err := globals.QueryDb(db, ` - SELECT code, quota, type, used, updated_at FROM invitation - ORDER BY id DESC LIMIT ? OFFSET ? + SELECT invitation.code, invitation.quota, invitation.type, invitation.used, + invitation.created_at, invitation.updated_at, + COALESCE(auth.username, '-') as username + FROM invitation + LEFT JOIN auth ON auth.id = invitation.used_id + ORDER BY invitation.id DESC LIMIT ? OFFSET ? `, pagination, page*pagination) if err != nil { return PaginationForm{ @@ -35,14 +40,16 @@ func GetInvitationPagination(db *sql.DB, page int64) PaginationForm { for rows.Next() { var invitation InvitationData - var date []uint8 - if err := rows.Scan(&invitation.Code, &invitation.Quota, &invitation.Type, &invitation.Used, &date); err != nil { + var createdAt []uint8 + var updatedAt []uint8 + if err := rows.Scan(&invitation.Code, &invitation.Quota, &invitation.Type, &invitation.Used, &createdAt, &updatedAt, &invitation.Username); err != nil { return PaginationForm{ Status: false, Message: err.Error(), } } - invitation.UpdatedAt = utils.ConvertTime(date).Format("2006-01-02 15:04:05") + invitation.CreatedAt = utils.ConvertTime(createdAt).Format("2006-01-02 15:04:05") + invitation.UpdatedAt = utils.ConvertTime(updatedAt).Format("2006-01-02 15:04:05") invitations = append(invitations, invitation) } diff --git a/admin/redeem.go b/admin/redeem.go index 95809e1..66b778b 100644 --- a/admin/redeem.go +++ b/admin/redeem.go @@ -31,6 +31,36 @@ func GetRedeemData(db *sql.DB) []RedeemData { return data } +func GetRedeemSegment(db *sql.DB, quota float32, onlyUnused bool) ([]string, error) { + var codes []string + var rows *sql.Rows + var err error + + if onlyUnused { + rows, err = globals.QueryDb(db, ` + SELECT code FROM redeem WHERE quota = ? AND used = 0 + `, quota) + } else { + rows, err = globals.QueryDb(db, ` + SELECT code FROM redeem WHERE quota = ? + `, quota) + } + + if err != nil { + return codes, err + } + + for rows.Next() { + var code string + if err := rows.Scan(&code); err != nil { + return codes, err + } + codes = append(codes, code) + } + + return codes, nil +} + func GenerateRedeemCodes(db *sql.DB, num int, quota float32) RedeemGenerateResponse { arr := make([]string, 0) idx := 0 diff --git a/admin/router.go b/admin/router.go index 3422469..b545347 100644 --- a/admin/router.go +++ b/admin/router.go @@ -20,6 +20,7 @@ func Register(app *gin.RouterGroup) { app.POST("/admin/invitation/delete", DeleteInvitationAPI) app.GET("/admin/redeem/list", RedeemListAPI) + app.GET("/admin/redeem/segment", RedeemSegmentAPI) app.POST("/admin/redeem/generate", GenerateRedeemAPI) app.GET("/admin/user/list", UserPaginationAPI) diff --git a/admin/types.go b/admin/types.go index cc92b68..9354a40 100644 --- a/admin/types.go +++ b/admin/types.go @@ -45,7 +45,9 @@ type InvitationData struct { Quota float32 `json:"quota"` Type string `json:"type"` Used bool `json:"used"` + CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` + Username string `json:"username"` } type RedeemData struct { diff --git a/app/src/admin/api/chart.ts b/app/src/admin/api/chart.ts index 1f9ff36..ee6bb6b 100644 --- a/app/src/admin/api/chart.ts +++ b/app/src/admin/api/chart.ts @@ -138,6 +138,18 @@ export async function getRedeemList(): Promise { } } +export async function getRedeemSegment(quota: number, only_unused: boolean) { + try { + const response = await axios.get( + `/admin/redeem/segment?quota=${quota}&unused=${only_unused}`, + ); + return response.data as RedeemResponse; + } catch (e) { + console.warn(e); + return []; + } +} + export async function generateRedeem( quota: number, number: number, diff --git a/app/src/admin/types.ts b/app/src/admin/types.ts index a892592..bc443ad 100644 --- a/app/src/admin/types.ts +++ b/app/src/admin/types.ts @@ -47,6 +47,8 @@ export type InvitationData = { quota: number; type: string; used: boolean; + username: string; + created_at: string; updated_at: string; }; @@ -69,6 +71,9 @@ export type Redeem = { }; export type RedeemResponse = Redeem[]; +export type RedeemSegmentResponse = CommonResponse & { + data: string[]; +}; export type InvitationGenerateResponse = { status: boolean; diff --git a/app/src/assets/pages/chat.less b/app/src/assets/pages/chat.less index 6a17e1b..650a1fb 100644 --- a/app/src/assets/pages/chat.less +++ b/app/src/assets/pages/chat.less @@ -135,7 +135,6 @@ border-radius: var(--radius); border: 1px solid hsl(var(--border-hover)); transition: 0.25s linear; - cursor: pointer; &:hover { border-color: hsl(var(--border-active)); diff --git a/app/src/components/admin/InvitationTable.tsx b/app/src/components/admin/InvitationTable.tsx index 886a089..9cef0a1 100644 --- a/app/src/components/admin/InvitationTable.tsx +++ b/app/src/components/admin/InvitationTable.tsx @@ -175,7 +175,9 @@ function InvitationTable() { {t("admin.quota")} {t("admin.type")} {t("admin.used")} - {t("admin.updated-at")} + {t("admin.used-username")} + {t("admin.created-at")} + {t("admin.used-at")} {t("admin.action")} @@ -183,11 +185,15 @@ function InvitationTable() { {(data.data || []).map((invitation, idx) => ( {invitation.code} - {invitation.quota} + + {invitation.quota} + {invitation.type} {t(`admin.used-${invitation.used}`)} + {invitation.username || "-"} + {invitation.created_at} {invitation.updated_at} void }) { const { t } = useTranslation(); @@ -157,7 +158,11 @@ function RedeemTable() { {data.map((redeem, idx) => ( - {redeem.quota} + + + {redeem.quota} + + {redeem.total} {redeem.used} diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index 61c0280..6ddbddd 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -482,7 +482,10 @@ "operate-success-prompt": "您的操作已成功执行。", "operate-failed": "操作失败", "operate-failed-prompt": "操作失败,原因:{{reason}}", + "created-at": "创建时间", "updated-at": "更新时间", + "used-at": "领取时间", + "used-username": "领取用户", "used-true": "已使用", "used-false": "未使用", "generate": "批量生成", diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index f97af28..50c53a9 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -48,9 +48,9 @@ "fast": "Fast", "english-model": "English Model", "badges": { - "non-billing": "FREE PASS", - "times-billing": "{{price}}/time", - "token-billing": "Input {{input}}/1k tokens Output {{output}}/1k tokens" + "non-billing": "free", + "times-billing": "{{price}} / time", + "token-billing": "{{input}} / 1k input tokens {{output}} / 1k output tokens" } }, "market": { @@ -672,9 +672,12 @@ "unban-action-desc": "Are you sure you want to unblock this user?", "billing": "Income", "chatnio-format-only": "This format is unique to Chat Nio", - "exit": "Log out of the background", + "exit": "Exit", "view": "View", - "broadcast-tip": "Notifications will only show the most recent one and will only be notified once. Site announcements can be set in the system settings. The pop-up window will be displayed on the homepage for the first time and subsequent viewing will be supported." + "broadcast-tip": "Notifications will only show the most recent one and will only be notified once. Site announcements can be set in the system settings. The pop-up window will be displayed on the homepage for the first time and subsequent viewing will be supported.", + "created-at": "Creation Time", + "used-at": "Collection time", + "used-username": "Claim User" }, "mask": { "title": "Mask Settings", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index 630f379..95bf306 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -674,7 +674,10 @@ "chatnio-format-only": "このフォーマットはChat Nioに固有です", "exit": "バックグラウンドからログアウト", "view": "確認", - "broadcast-tip": "通知には最新の通知のみが表示され、一度だけ通知されます。サイトのお知らせは、システム設定で設定できます。ポップアップウィンドウがホームページに初めて表示され、その後の表示がサポートされます。" + "broadcast-tip": "通知には最新の通知のみが表示され、一度だけ通知されます。サイトのお知らせは、システム設定で設定できます。ポップアップウィンドウがホームページに初めて表示され、その後の表示がサポートされます。", + "created-at": "作成日時", + "used-at": "乗車時間", + "used-username": "ユーザーを請求する" }, "mask": { "title": "プリセット設定", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index b143e39..1aee5ba 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -674,7 +674,10 @@ "chatnio-format-only": "Этот формат уникален для Chat Nio", "exit": "Выйти из фонового режима", "view": "проверить", - "broadcast-tip": "Уведомления будут отображаться только самые последние и будут уведомлены только один раз. Объявления сайта можно задать в системных настройках. Всплывающее окно будет отображаться на главной странице в первый раз и будет поддерживаться последующий просмотр." + "broadcast-tip": "Уведомления будут отображаться только самые последние и будут уведомлены только один раз. Объявления сайта можно задать в системных настройках. Всплывающее окно будет отображаться на главной странице в первый раз и будет поддерживаться последующий просмотр.", + "created-at": "Время создания", + "used-at": "Время награждения", + "used-username": "Получить пользователя" }, "mask": { "title": "Настройки маски", diff --git a/utils/char.go b/utils/char.go index 815b2e4..a9129e6 100644 --- a/utils/char.go +++ b/utils/char.go @@ -122,6 +122,22 @@ func ParseInt64(value string) int64 { } } +func ParseFloat32(value string) float32 { + if res, err := strconv.ParseFloat(value, 32); err == nil { + return float32(res) + } else { + return 0 + } +} + +func ParseBool(value string) bool { + if res, err := strconv.ParseBool(value); err == nil { + return res + } else { + return false + } +} + func ConvertSqlTime(t time.Time) string { return t.Format("2006-01-02 15:04:05") }