feat: 用户创建充值优化、侧边栏导航修复、在线实例统计修复
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -135,7 +135,9 @@ func handleGetCardTypes(c *gin.Context) {
|
|||||||
"user_id": ct.UserID,
|
"user_id": ct.UserID,
|
||||||
"application_id": ct.ApplicationID,
|
"application_id": ct.ApplicationID,
|
||||||
"name": ct.Name,
|
"name": ct.Name,
|
||||||
|
"recharge_type": ct.RechargeType,
|
||||||
"value": ct.Value,
|
"value": ct.Value,
|
||||||
|
"value_unit": ct.ValueUnit,
|
||||||
"price": ct.Price,
|
"price": ct.Price,
|
||||||
"description": ct.Description,
|
"description": ct.Description,
|
||||||
"status": ct.Status,
|
"status": ct.Status,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"verification-platform-backend/internal/model"
|
"verification-platform-backend/internal/model"
|
||||||
"verification-platform-backend/internal/service"
|
"verification-platform-backend/internal/service"
|
||||||
"verification-platform-backend/pkg/response"
|
"verification-platform-backend/pkg/response"
|
||||||
|
"verification-platform-backend/pkg/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -233,6 +234,8 @@ func handleCreateUser(c *gin.Context) {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ApplicationID uint `json:"application_id"`
|
ApplicationID uint `json:"application_id"`
|
||||||
|
CardTypeID *uint `json:"card_type_id"`
|
||||||
|
CardQuantity int `json:"card_quantity"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
response.Error(c, 400, "参数错误")
|
response.Error(c, 400, "参数错误")
|
||||||
@@ -254,6 +257,14 @@ func handleCreateUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.CardQuantity < 1 {
|
||||||
|
req.CardQuantity = 1
|
||||||
|
}
|
||||||
|
if req.CardQuantity > 100 {
|
||||||
|
response.Error(c, 400, "卡密数量不能超过100")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var app model.Application
|
var app model.Application
|
||||||
if err := database.DB.First(&app, req.ApplicationID).Error; err != nil {
|
if err := database.DB.First(&app, req.ApplicationID).Error; err != nil {
|
||||||
response.Error(c, 404, "应用不存在")
|
response.Error(c, 404, "应用不存在")
|
||||||
@@ -274,6 +285,18 @@ func handleCreateUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cardType *model.CardType
|
||||||
|
if req.CardTypeID != nil && *req.CardTypeID > 0 {
|
||||||
|
var ct model.CardType
|
||||||
|
if err := database.DB.Where("id = ? AND application_id = ?", *req.CardTypeID, req.ApplicationID).First(&ct).Error; err != nil {
|
||||||
|
response.Error(c, 400, "卡密类型不存在或不属于该应用")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cardType = &ct
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := database.DB.Begin()
|
||||||
|
|
||||||
user := model.AppUser{
|
user := model.AppUser{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
@@ -283,14 +306,128 @@ func handleCreateUser(c *gin.Context) {
|
|||||||
ApplicationID: app.ID,
|
ApplicationID: app.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Create(&user).Error; err != nil {
|
if err := tx.Create(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
response.Error(c, 500, "创建用户失败")
|
response.Error(c, 500, "创建用户失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
service.LogOperation(c, "create", "app_user", &user.ID, fmt.Sprintf("创建用户: %s (应用: %s)", user.Username, app.Name), nil)
|
var cards []model.Card
|
||||||
|
if cardType != nil {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
response.Success(c, user)
|
for i := 0; i < req.CardQuantity; i++ {
|
||||||
|
cardKey := "CK" + utils.GenerateRandomString(16)
|
||||||
|
card := model.Card{
|
||||||
|
ApplicationID: req.ApplicationID,
|
||||||
|
CardTypeID: cardType.ID,
|
||||||
|
CardKey: cardKey,
|
||||||
|
CreatorID: userID,
|
||||||
|
AppUserID: &user.ID,
|
||||||
|
Status: "used",
|
||||||
|
}
|
||||||
|
card.UsedAt = &now
|
||||||
|
|
||||||
|
if err := tx.Create(&card).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "生成卡密失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.IsTrialUser = false
|
||||||
|
|
||||||
|
if cardType.Value == -1 {
|
||||||
|
if cardType.RechargeType == "subscription" {
|
||||||
|
permanentExpiry := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||||
|
user.ExpiryAt = &permanentExpiry
|
||||||
|
user.Balance = -1
|
||||||
|
} else {
|
||||||
|
user.Balance = -1
|
||||||
|
user.ExpiryAt = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch cardType.RechargeType {
|
||||||
|
case "subscription":
|
||||||
|
var baseTime time.Time
|
||||||
|
if user.ExpiryAt != nil && user.ExpiryAt.After(now) {
|
||||||
|
baseTime = *user.ExpiryAt
|
||||||
|
} else {
|
||||||
|
baseTime = now
|
||||||
|
}
|
||||||
|
var duration time.Duration
|
||||||
|
switch cardType.ValueUnit {
|
||||||
|
case "minute":
|
||||||
|
duration = time.Duration(cardType.Value) * time.Minute
|
||||||
|
case "hour":
|
||||||
|
duration = time.Duration(cardType.Value) * time.Hour
|
||||||
|
case "day":
|
||||||
|
duration = time.Duration(cardType.Value) * 24 * time.Hour
|
||||||
|
case "month":
|
||||||
|
duration = time.Duration(cardType.Value) * 30 * 24 * time.Hour
|
||||||
|
case "year":
|
||||||
|
duration = time.Duration(cardType.Value) * 365 * 24 * time.Hour
|
||||||
|
default:
|
||||||
|
duration = time.Duration(cardType.Value) * time.Second
|
||||||
|
}
|
||||||
|
newExpiry := baseTime.Add(duration)
|
||||||
|
user.ExpiryAt = &newExpiry
|
||||||
|
case "balance":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
user.Balance += cardType.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rechargeRecord := model.RechargeRecord{
|
||||||
|
UserID: user.ID,
|
||||||
|
OrderNo: generateUserOrderNo("R"),
|
||||||
|
CardID: &card.ID,
|
||||||
|
CardCode: card.CardKey,
|
||||||
|
Amount: cardType.Price,
|
||||||
|
Status: "success",
|
||||||
|
PaymentType: "card",
|
||||||
|
Remark: "创建用户充值 - " + cardType.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(&rechargeRecord).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "创建充值记录失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cards = append(cards, card)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Save(&user).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, 500, "充值失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
response.Error(c, 500, "创建用户失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logDesc := fmt.Sprintf("创建用户: %s (应用: %s)", user.Username, app.Name)
|
||||||
|
if cardType != nil {
|
||||||
|
logDesc += fmt.Sprintf(",充值卡密: %s x%d", cardType.Name, req.CardQuantity)
|
||||||
|
}
|
||||||
|
service.LogOperation(c, "create", "app_user", &user.ID, logDesc, nil)
|
||||||
|
|
||||||
|
result := gin.H{
|
||||||
|
"user": user,
|
||||||
|
}
|
||||||
|
if len(cards) > 0 {
|
||||||
|
result["cards"] = cards
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateUserOrderNo(prefix string) string {
|
||||||
|
return prefix + time.Now().Format("20060102150405") + utils.GenerateRandomString(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetUser(c *gin.Context) {
|
func handleGetUser(c *gin.Context) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Boxes, Code, CreditCard, DollarSign, FileLock, Gauge, GitBranch, HardDrive, Hash, Key, Mail, Megaphone, MessageSquare, Network, Plug, ScrollText, Settings, Shield, Smartphone, Users, Variable } from 'lucide-vue-next'
|
import { Boxes, Code, CreditCard, DollarSign, FileLock, Gauge, GitBranch, HardDrive, Hash, Key, Mail, Megaphone, MessageSquare, Monitor, Network, Plug, ScrollText, Settings, Shield, Smartphone, Users, Variable } from 'lucide-vue-next'
|
||||||
import { onMounted, onUnmounted, reactive } from 'vue'
|
import { onMounted, onUnmounted, reactive } from 'vue'
|
||||||
|
|
||||||
import NavTeam from '@/components/app-sidebar/nav-team.vue'
|
import NavTeam from '@/components/app-sidebar/nav-team.vue'
|
||||||
@@ -119,6 +119,16 @@ const navMain = [
|
|||||||
url: '/admin/users',
|
url: '/admin/users',
|
||||||
icon: Users,
|
icon: Users,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '设备管理',
|
||||||
|
url: '/admin/devices',
|
||||||
|
icon: Smartphone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '在线实例',
|
||||||
|
url: '/admin/sessions',
|
||||||
|
icon: Monitor,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '代理管理',
|
title: '代理管理',
|
||||||
url: '/admin/agents',
|
url: '/admin/agents',
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ function getAssetUrl(path: string) {
|
|||||||
<UiSidebarMenuItem>
|
<UiSidebarMenuItem>
|
||||||
<UiSidebarMenuButton size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
<UiSidebarMenuButton size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-center rounded-lg aspect-square size-8 overflow-hidden"
|
class="flex items-center justify-center rounded-lg aspect-square size-8 overflow-hidden shrink-0"
|
||||||
:class="typeof activeTeam.logo === 'string' && activeTeam.logo ? '' : 'bg-sidebar-primary text-sidebar-primary-foreground'"
|
:class="typeof activeTeam.logo === 'string' && activeTeam.logo ? '' : 'bg-sidebar-primary text-sidebar-primary-foreground'"
|
||||||
>
|
>
|
||||||
<img v-if="typeof activeTeam.logo === 'string' && activeTeam.logo" :src="getAssetUrl(activeTeam.logo)" class="size-full object-contain" alt="" />
|
<img v-if="typeof activeTeam.logo === 'string' && activeTeam.logo" :src="getAssetUrl(activeTeam.logo)" class="size-full object-contain" alt="" />
|
||||||
<component v-else-if="typeof activeTeam.logo === 'function'" :is="activeTeam.logo" class="size-4" />
|
<component v-else-if="typeof activeTeam.logo === 'function'" :is="activeTeam.logo" class="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid flex-1 text-sm leading-tight">
|
<div class="grid flex-1 text-sm leading-tight group-data-[collapsible=icon]:hidden">
|
||||||
<span class="font-semibold truncate">{{ activeTeam.name }}</span>
|
<span class="font-semibold truncate">{{ activeTeam.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</UiSidebarMenuButton>
|
</UiSidebarMenuButton>
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ const filteredSessions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const totalSessions = computed(() => filteredSessions.value.length)
|
const totalSessions = computed(() => filteredSessions.value.length)
|
||||||
const onlineDevices = computed(() => new Set(filteredSessions.value.map(s => s.device_identifier)).size)
|
const onlineApps = computed(() => new Set(filteredSessions.value.map(s => s.application_id)).size)
|
||||||
const onlineUsers = computed(() => new Set(filteredSessions.value.map(s => s.username)).size)
|
const onlineDevices = computed(() => new Set(filteredSessions.value.filter(s => s.device_identifier).map(s => s.device_identifier)).size)
|
||||||
|
const onlineUsers = computed(() => new Set(filteredSessions.value.filter(s => s.username).map(s => s.username)).size)
|
||||||
|
|
||||||
async function fetchApplications() {
|
async function fetchApplications() {
|
||||||
try {
|
try {
|
||||||
@@ -159,13 +160,13 @@ onMounted(() => {
|
|||||||
<UiCard>
|
<UiCard>
|
||||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||||
<UiCardTitle class="text-sm font-medium">
|
<UiCardTitle class="text-sm font-medium">
|
||||||
应用数量
|
在线应用数
|
||||||
</UiCardTitle>
|
</UiCardTitle>
|
||||||
<Boxes class="size-4 text-muted-foreground" />
|
<Boxes class="size-4 text-muted-foreground" />
|
||||||
</UiCardHeader>
|
</UiCardHeader>
|
||||||
<UiCardContent>
|
<UiCardContent>
|
||||||
<div class="text-2xl font-bold">
|
<div class="text-2xl font-bold">
|
||||||
{{ applications.length }}
|
{{ onlineApps }}
|
||||||
</div>
|
</div>
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Eye, Loader2, UserPlus } from 'lucide-vue-next'
|
import { CreditCard, Eye, Loader2, UserPlus } from 'lucide-vue-next'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
@@ -16,14 +16,27 @@ interface Application {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CardType {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
recharge_type: string
|
||||||
|
value: number
|
||||||
|
value_unit: string
|
||||||
|
price: number
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const applications = ref<Application[]>([])
|
const applications = ref<Application[]>([])
|
||||||
|
const cardTypes = ref<CardType[]>([])
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
application_id: '',
|
application_id: '',
|
||||||
|
card_type_id: 'none',
|
||||||
|
card_quantity: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchApplications() {
|
async function fetchApplications() {
|
||||||
@@ -36,6 +49,26 @@ async function fetchApplications() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchCardTypes(applicationId: string) {
|
||||||
|
if (!applicationId) {
|
||||||
|
cardTypes.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await api.get<{ card_types: CardType[] }>(`/dev/card-types?application_id=${applicationId}`)
|
||||||
|
cardTypes.value = Array.isArray(data?.card_types) ? data.card_types : []
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取卡密类型失败:', error)
|
||||||
|
cardTypes.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => form.value.application_id, (newVal) => {
|
||||||
|
form.value.card_type_id = 'none'
|
||||||
|
fetchCardTypes(newVal)
|
||||||
|
})
|
||||||
|
|
||||||
const selectedApplication = computed(() => {
|
const selectedApplication = computed(() => {
|
||||||
if (form.value.application_id) {
|
if (form.value.application_id) {
|
||||||
return applications.value.find(app => String(app.id) === form.value.application_id)
|
return applications.value.find(app => String(app.id) === form.value.application_id)
|
||||||
@@ -43,8 +76,31 @@ const selectedApplication = computed(() => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const selectedCardType = computed(() => {
|
||||||
|
if (form.value.card_type_id && form.value.card_type_id !== 'none') {
|
||||||
|
return cardTypes.value.find(ct => String(ct.id) === form.value.card_type_id)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
function formatCardTypeValue(ct: CardType) {
|
||||||
|
if (ct.value === -1)
|
||||||
|
return t('admin.users.create.permanent')
|
||||||
|
if (ct.recharge_type === 'subscription') {
|
||||||
|
const unitMap: Record<string, string> = {
|
||||||
|
minute: t('admin.users.create.minutes'),
|
||||||
|
hour: t('admin.users.create.hours'),
|
||||||
|
day: t('admin.users.create.days'),
|
||||||
|
month: t('admin.users.create.months'),
|
||||||
|
year: t('admin.users.create.years'),
|
||||||
|
}
|
||||||
|
return `${ct.value} ${unitMap[ct.value_unit] || ct.value_unit}`
|
||||||
|
}
|
||||||
|
return `${ct.value} ${t('admin.users.create.points')}`
|
||||||
|
}
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
const isFormValid = computed(() => {
|
||||||
return form.value.username && form.value.email && form.value.password && form.value.application_id
|
return form.value.username && form.value.password && form.value.application_id
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
@@ -52,10 +108,6 @@ async function handleSave() {
|
|||||||
toast.error(t('admin.users.create.usernameRequired'))
|
toast.error(t('admin.users.create.usernameRequired'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!form.value.email) {
|
|
||||||
toast.error(t('admin.users.create.emailRequired'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.value.password) {
|
if (!form.value.password) {
|
||||||
toast.error(t('admin.users.create.passwordRequired'))
|
toast.error(t('admin.users.create.passwordRequired'))
|
||||||
return
|
return
|
||||||
@@ -67,12 +119,17 @@ async function handleSave() {
|
|||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await api.post('/dev/app-users', {
|
const payload: any = {
|
||||||
username: form.value.username,
|
username: form.value.username,
|
||||||
email: form.value.email,
|
email: form.value.email,
|
||||||
password: form.value.password,
|
password: form.value.password,
|
||||||
application_id: Number(form.value.application_id),
|
application_id: Number(form.value.application_id),
|
||||||
})
|
}
|
||||||
|
if (form.value.card_type_id && form.value.card_type_id !== 'none') {
|
||||||
|
payload.card_type_id = Number(form.value.card_type_id)
|
||||||
|
payload.card_quantity = form.value.card_quantity || 1
|
||||||
|
}
|
||||||
|
await api.post('/dev/app-users', payload)
|
||||||
toast.success(t('admin.users.create.success'))
|
toast.success(t('admin.users.create.success'))
|
||||||
router.push('/admin/users')
|
router.push('/admin/users')
|
||||||
}
|
}
|
||||||
@@ -135,18 +192,76 @@ onMounted(() => {
|
|||||||
<UiInput id="username" v-model="form.username" :placeholder="t('admin.users.create.usernamePlaceholder')" />
|
<UiInput id="username" v-model="form.username" :placeholder="t('admin.users.create.usernamePlaceholder')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<UiLabel for="password">
|
||||||
|
{{ t('admin.users.create.password') }}
|
||||||
|
</UiLabel>
|
||||||
|
<UiInput id="password" v-model="form.password" type="password" :placeholder="t('admin.users.create.passwordPlaceholder')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel for="email">
|
<UiLabel for="email">
|
||||||
{{ t('admin.users.create.email') }}
|
{{ t('admin.users.create.email') }}
|
||||||
</UiLabel>
|
</UiLabel>
|
||||||
<UiInput id="email" v-model="form.email" type="email" :placeholder="t('admin.users.create.emailPlaceholder')" />
|
<UiInput id="email" v-model="form.email" type="email" :placeholder="t('admin.users.create.emailPlaceholder')" />
|
||||||
</div>
|
</div>
|
||||||
|
</UiCardContent>
|
||||||
|
</UiCard>
|
||||||
|
|
||||||
|
<UiCard>
|
||||||
|
<UiCardHeader>
|
||||||
|
<UiCardTitle class="flex items-center gap-2">
|
||||||
|
<CreditCard class="size-5" />
|
||||||
|
{{ t('admin.users.create.rechargeCard') }}
|
||||||
|
</UiCardTitle>
|
||||||
|
<UiCardDescription>{{ t('admin.users.create.rechargeCardDesc') }}</UiCardDescription>
|
||||||
|
</UiCardHeader>
|
||||||
|
<UiCardContent class="space-y-6">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel for="password">
|
<UiLabel for="card_type">
|
||||||
{{ t('admin.users.create.password') }}
|
{{ t('admin.users.create.cardType') }}
|
||||||
</UiLabel>
|
</UiLabel>
|
||||||
<UiInput id="password" v-model="form.password" type="password" :placeholder="t('admin.users.create.passwordPlaceholder')" />
|
<UiSelect v-model="form.card_type_id">
|
||||||
|
<UiSelectTrigger>
|
||||||
|
<UiSelectValue :placeholder="cardTypes.length > 0 ? t('admin.users.create.selectCardType') : t('admin.users.create.selectApplicationFirst')" />
|
||||||
|
</UiSelectTrigger>
|
||||||
|
<UiSelectContent>
|
||||||
|
<UiSelectItem value="none">
|
||||||
|
{{ t('admin.users.create.noRecharge') }}
|
||||||
|
</UiSelectItem>
|
||||||
|
<UiSelectItem v-for="ct in cardTypes" :key="ct.id" :value="String(ct.id)">
|
||||||
|
{{ ct.name }}
|
||||||
|
</UiSelectItem>
|
||||||
|
</UiSelectContent>
|
||||||
|
</UiSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedCardType" class="space-y-2">
|
||||||
|
<UiLabel for="card_quantity">
|
||||||
|
{{ t('admin.users.create.cardQuantity') }}
|
||||||
|
</UiLabel>
|
||||||
|
<UiNumberField v-model="form.card_quantity" :min="1" :max="100" class="max-w-[200px]">
|
||||||
|
<UiNumberFieldContent>
|
||||||
|
<UiNumberFieldDecrement />
|
||||||
|
<UiNumberFieldInput />
|
||||||
|
<UiNumberFieldIncrement />
|
||||||
|
</UiNumberFieldContent>
|
||||||
|
</UiNumberField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedCardType" class="rounded-lg border bg-muted/50 p-3 space-y-2">
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.users.create.cardType') }}</span>
|
||||||
|
<span>{{ selectedCardType.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.users.create.cardValue') }}</span>
|
||||||
|
<span>{{ formatCardTypeValue(selectedCardType) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.users.create.cardQuantity') }}</span>
|
||||||
|
<span>{{ form.card_quantity }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
@@ -174,6 +289,10 @@ onMounted(() => {
|
|||||||
<span class="text-muted-foreground">{{ t('admin.users.create.emailLabel') }}</span>
|
<span class="text-muted-foreground">{{ t('admin.users.create.emailLabel') }}</span>
|
||||||
<span>{{ form.email || '-' }}</span>
|
<span>{{ form.email || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="selectedCardType" class="flex justify-between text-sm">
|
||||||
|
<span class="text-muted-foreground">{{ t('admin.users.create.rechargeCard') }}</span>
|
||||||
|
<span class="text-primary">{{ selectedCardType.name }} x{{ form.card_quantity }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No"
|
"no": "No",
|
||||||
|
"optional": "Optional"
|
||||||
},
|
},
|
||||||
"premium": {
|
"premium": {
|
||||||
"premium": "premium",
|
"premium": "premium",
|
||||||
@@ -294,7 +295,23 @@
|
|||||||
"applicationRequired": "Please select application",
|
"applicationRequired": "Please select application",
|
||||||
"success": "User created successfully",
|
"success": "User created successfully",
|
||||||
"failed": "Failed to create user",
|
"failed": "Failed to create user",
|
||||||
"saveBtn": "Save Changes"
|
"saveBtn": "Save Changes",
|
||||||
|
"rechargeCard": "Recharge Card",
|
||||||
|
"rechargeCardDesc": "Optional, generate cards and auto-recharge when creating user",
|
||||||
|
"cardType": "Card Type",
|
||||||
|
"selectCardType": "Select card type",
|
||||||
|
"selectApplicationFirst": "Select application first",
|
||||||
|
"noRecharge": "No recharge",
|
||||||
|
"cardQuantity": "Quantity",
|
||||||
|
"cardQuantityPlaceholder": "Enter quantity",
|
||||||
|
"cardValue": "Recharge Value",
|
||||||
|
"permanent": "Permanent",
|
||||||
|
"points": "Points",
|
||||||
|
"minutes": "Minutes",
|
||||||
|
"hours": "Hours",
|
||||||
|
"days": "Days",
|
||||||
|
"months": "Months",
|
||||||
|
"years": "Years"
|
||||||
},
|
},
|
||||||
"editFailed": "Failed to load user info",
|
"editFailed": "Failed to load user info",
|
||||||
"editSuccess": "User updated successfully",
|
"editSuccess": "User updated successfully",
|
||||||
@@ -370,34 +387,6 @@
|
|||||||
"banned": "Banned"
|
"banned": "Banned"
|
||||||
},
|
},
|
||||||
"lastLoginTime": "Last Login",
|
"lastLoginTime": "Last Login",
|
||||||
"create": {
|
|
||||||
"title": "Add User",
|
|
||||||
"basicInfo": "Basic Information",
|
|
||||||
"basicInfoDesc": "Fill in the user's basic information",
|
|
||||||
"application": "Application",
|
|
||||||
"selectApplication": "Select application",
|
|
||||||
"username": "Username",
|
|
||||||
"usernamePlaceholder": "Enter username",
|
|
||||||
"email": "Email",
|
|
||||||
"emailPlaceholder": "Enter email",
|
|
||||||
"password": "Password",
|
|
||||||
"passwordPlaceholder": "Enter password",
|
|
||||||
"deviceId": "Device ID",
|
|
||||||
"deviceIdPlaceholder": "Device ID (optional)",
|
|
||||||
"preview": "Preview",
|
|
||||||
"app": "Application",
|
|
||||||
"usernameLabel": "Username",
|
|
||||||
"emailLabel": "Email",
|
|
||||||
"deviceIdLabel": "Device ID",
|
|
||||||
"submit": "Add User",
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"usernameRequired": "Please enter username",
|
|
||||||
"emailRequired": "Please enter email",
|
|
||||||
"passwordRequired": "Please enter password",
|
|
||||||
"applicationRequired": "Please select application",
|
|
||||||
"success": "User created successfully",
|
|
||||||
"failed": "Failed to create user"
|
|
||||||
},
|
|
||||||
"expiry": {
|
"expiry": {
|
||||||
"permanent": "Unlimited",
|
"permanent": "Unlimited",
|
||||||
"unlimitedTime": "Unlimited Time",
|
"unlimitedTime": "Unlimited Time",
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
"previous": "上一页",
|
"previous": "上一页",
|
||||||
"next": "下一页",
|
"next": "下一页",
|
||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否"
|
"no": "否",
|
||||||
|
"optional": "可选"
|
||||||
},
|
},
|
||||||
"premium": {
|
"premium": {
|
||||||
"premium": "会员计划",
|
"premium": "会员计划",
|
||||||
@@ -294,7 +295,23 @@
|
|||||||
"applicationRequired": "请选择应用",
|
"applicationRequired": "请选择应用",
|
||||||
"success": "创建成功",
|
"success": "创建成功",
|
||||||
"failed": "创建失败",
|
"failed": "创建失败",
|
||||||
"saveBtn": "保存修改"
|
"saveBtn": "保存修改",
|
||||||
|
"rechargeCard": "充值卡密",
|
||||||
|
"rechargeCardDesc": "可选,创建用户时同时生成卡密并自动充值",
|
||||||
|
"cardType": "卡密类型",
|
||||||
|
"selectCardType": "选择卡密类型",
|
||||||
|
"selectApplicationFirst": "请先选择应用",
|
||||||
|
"noRecharge": "不充值",
|
||||||
|
"cardQuantity": "卡密数量",
|
||||||
|
"cardQuantityPlaceholder": "输入数量",
|
||||||
|
"cardValue": "充值内容",
|
||||||
|
"permanent": "永久",
|
||||||
|
"points": "点",
|
||||||
|
"minutes": "分钟",
|
||||||
|
"hours": "小时",
|
||||||
|
"days": "天",
|
||||||
|
"months": "月",
|
||||||
|
"years": "年"
|
||||||
},
|
},
|
||||||
"editFailed": "获取用户信息失败",
|
"editFailed": "获取用户信息失败",
|
||||||
"editSuccess": "更新用户成功",
|
"editSuccess": "更新用户成功",
|
||||||
@@ -371,34 +388,6 @@
|
|||||||
"banned": "已封禁"
|
"banned": "已封禁"
|
||||||
},
|
},
|
||||||
"lastLoginTime": "最后登录",
|
"lastLoginTime": "最后登录",
|
||||||
"create": {
|
|
||||||
"title": "添加用户",
|
|
||||||
"basicInfo": "基本信息",
|
|
||||||
"basicInfoDesc": "填写用户的基本信息",
|
|
||||||
"application": "所属应用",
|
|
||||||
"selectApplication": "选择应用",
|
|
||||||
"username": "用户名",
|
|
||||||
"usernamePlaceholder": "请输入用户名",
|
|
||||||
"email": "邮箱",
|
|
||||||
"emailPlaceholder": "请输入邮箱",
|
|
||||||
"password": "密码",
|
|
||||||
"passwordPlaceholder": "请输入密码",
|
|
||||||
"deviceId": "设备指纹",
|
|
||||||
"deviceIdPlaceholder": "设备指纹(可选)",
|
|
||||||
"preview": "预览",
|
|
||||||
"app": "应用",
|
|
||||||
"usernameLabel": "用户名",
|
|
||||||
"emailLabel": "邮箱",
|
|
||||||
"deviceIdLabel": "设备指纹",
|
|
||||||
"submit": "添加用户",
|
|
||||||
"cancel": "取消",
|
|
||||||
"usernameRequired": "请填写用户名",
|
|
||||||
"emailRequired": "请填写邮箱",
|
|
||||||
"passwordRequired": "请填写密码",
|
|
||||||
"applicationRequired": "请选择所属应用",
|
|
||||||
"success": "创建成功",
|
|
||||||
"failed": "创建失败"
|
|
||||||
},
|
|
||||||
"expiry": {
|
"expiry": {
|
||||||
"permanent": "无限制",
|
"permanent": "无限制",
|
||||||
"unlimitedTime": "无限时长",
|
"unlimitedTime": "无限时长",
|
||||||
|
|||||||
Reference in New Issue
Block a user