mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-03-14 08:48:13 -04:00
Merge branch 'cedar2025:master' into master
This commit is contained in:
commit
ec64fe0039
36
.github/ISSUE_TEMPLATE/bug-report.md
vendored
36
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -1,37 +1,39 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report an issue
|
||||
title: "Issue: "
|
||||
name: 🐛 问题反馈 | Bug Report
|
||||
about: 提交使用过程中遇到的问题 | Report an issue
|
||||
title: "问题:"
|
||||
labels: '🐛 bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!-- 🔴 请注意:XrayR等非XBoard问题请前往相应项目提问 -->
|
||||
<!-- 🔴 Note: For XrayR and other non-XBoard issues, please report to their respective projects -->
|
||||
|
||||
> ⚠️ 请务必按照模板填写完整信息,没有详细描述的issue可能会被忽略或关闭
|
||||
> ⚠️ Please follow the template to provide complete information, issues without detailed description may be ignored or closed
|
||||
|
||||
**Basic Info**
|
||||
**基本信息 | Basic Info**
|
||||
```yaml
|
||||
Version:
|
||||
Deployment: [Docker/Manual]
|
||||
PHP Version:
|
||||
Database:
|
||||
XBoard版本 | Version:
|
||||
部署方式 | Deployment: [Docker/手动部署]
|
||||
PHP版本 | Version:
|
||||
数据库 | Database:
|
||||
```
|
||||
|
||||
**Description**
|
||||
<!-- Briefly describe the issue you encountered -->
|
||||
**问题描述 | Description**
|
||||
<!-- 简要描述你遇到的问题 -->
|
||||
|
||||
|
||||
**Steps to Reproduce**
|
||||
<!-- How to reproduce this issue? -->
|
||||
**复现步骤 | Steps**
|
||||
<!-- 如何复现这个问题? -->
|
||||
1.
|
||||
2.
|
||||
|
||||
**Screenshots**
|
||||
<!-- Drag and drop images here (please hide sensitive information) -->
|
||||
**相关截图 | Screenshots**
|
||||
<!-- 拖拽图片到这里(请注意隐藏敏感信息)-->
|
||||
|
||||
**Logs**
|
||||
<!-- Logs from storage/logs directory -->
|
||||
**日志信息 | Logs**
|
||||
<!-- storage/logs 目录下的日志 -->
|
||||
```log
|
||||
// Paste log content here
|
||||
// 粘贴日志内容到这里
|
||||
```
|
27
.github/ISSUE_TEMPLATE/feature-request.md
vendored
27
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -1,27 +1,28 @@
|
||||
---
|
||||
name: ✨ Feature Request
|
||||
about: Suggest an idea
|
||||
title: "Suggestion: "
|
||||
name: ✨ 功能请求 | Feature Request
|
||||
about: 提交新功能建议或改进意见 | Suggest an idea
|
||||
title: "建议:"
|
||||
labels: '✨ enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> ⚠️ 请务必按照模板详细描述你的需求,没有详细描述的issue可能会被忽略或关闭
|
||||
> ⚠️ Please follow the template to describe your request in detail, issues without detailed description may be ignored or closed
|
||||
|
||||
**Description**
|
||||
<!-- Describe the feature or improvement you'd like to suggest -->
|
||||
**需求描述 | Description**
|
||||
<!-- 描述你希望添加的功能或改进建议 -->
|
||||
|
||||
|
||||
**Use Case**
|
||||
<!-- Describe the scenarios where this feature would be used and what problems it solves -->
|
||||
**使用场景 | Use Case**
|
||||
<!-- 描述这个功能会在什么场景下使用,解决什么问题 -->
|
||||
|
||||
|
||||
**Suggestion**
|
||||
<!-- How do you expect this feature to work? You can describe the specific implementation -->
|
||||
**功能建议 | Suggestion**
|
||||
<!-- 你期望这个功能是什么样的?可以描述一下具体实现方式 -->
|
||||
```yaml
|
||||
Type: [New Feature/Feature Enhancement/UI Improvement]
|
||||
Expected:
|
||||
功能形式 | Type: [新功能/功能优化/界面改进]
|
||||
预期效果 | Expected:
|
||||
```
|
||||
|
||||
**Additional Information**
|
||||
<!-- Any additional information or reference examples -->
|
||||
**补充说明 | Additional**
|
||||
<!-- 其他补充说明或者参考示例 -->
|
@ -94,7 +94,7 @@ class ClientController extends Controller
|
||||
*/
|
||||
private function isBrowserAccess(Request $request): bool
|
||||
{
|
||||
$userAgent = strtolower($request->input('flag', $request->header('User-Agent')));
|
||||
$userAgent = strtolower($request->input('flag', $request->header('User-Agent', '')));
|
||||
return str_contains($userAgent, 'mozilla')
|
||||
|| str_contains($userAgent, 'chrome')
|
||||
|| str_contains($userAgent, 'safari')
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\V1\User;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CouponResource;
|
||||
use App\Services\CouponService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@ -11,19 +12,14 @@ class CouponController extends Controller
|
||||
{
|
||||
public function check(Request $request)
|
||||
{
|
||||
// $request->validate([
|
||||
// 'code' => 'required|string',
|
||||
// 'plan_id' => 'required|integer',
|
||||
// 'period' => 'nullable|string',
|
||||
// ]);
|
||||
if (empty($request->input('code'))) {
|
||||
return $this->fail([422,__('Coupon cannot be empty')]);
|
||||
return $this->fail([422, __('Coupon cannot be empty')]);
|
||||
}
|
||||
$couponService = new CouponService($request->input('code'));
|
||||
$couponService->setPlanId($request->input('plan_id'));
|
||||
$couponService->setUserId($request->user()->id);
|
||||
$couponService->setPeriod($request->input('period'));
|
||||
$couponService->check();
|
||||
return $this->success($couponService->getCoupon());
|
||||
return $this->success(CouponResource::make($couponService->getCoupon()));
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class TicketController extends Controller
|
||||
*/
|
||||
private function fetchTickets(Request $request)
|
||||
{
|
||||
$ticketModel = Ticket::query()
|
||||
$ticketModel = Ticket::with('user')
|
||||
->when($request->has('status'), function ($query) use ($request) {
|
||||
$query->where('status', $request->input('status'));
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ use App\Services\AuthService;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UserController extends Controller
|
||||
@ -397,4 +398,35 @@ class UserController extends Controller
|
||||
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户及其关联数据
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|exists:App\Models\User,id'
|
||||
], [
|
||||
'id.required' => '用户ID不能为空',
|
||||
'id.exists' => '用户不存在'
|
||||
]);
|
||||
$user = User::find($request->input('id'));
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$user->orders()->delete();
|
||||
$user->codes()->delete();
|
||||
$user->stat()->delete();
|
||||
$user->tickets()->delete();
|
||||
$user->delete();
|
||||
DB::commit();
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
\Log::error($e);
|
||||
return $this->fail([500, '删除失败']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
app/Http/Resources/CouponResource.php
Normal file
34
app/Http/Resources/CouponResource.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* 优惠券资源类
|
||||
*
|
||||
* @property array|null $limit_plan_ids 限制可用的套餐ID列表
|
||||
*/
|
||||
class CouponResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* 将资源转换为数组
|
||||
*
|
||||
* @param Request $request 请求实例
|
||||
* @return array<string, mixed> 转换后的数组
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
...$this->resource->toArray(),
|
||||
'limit_plan_ids' => $this->when(
|
||||
!empty($this->limit_plan_ids),
|
||||
fn() => collect($this->limit_plan_ids)
|
||||
->map(fn(mixed $id): string => (string) $id)
|
||||
->values()
|
||||
->all()
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
@ -108,6 +108,7 @@ class AdminRoute
|
||||
$router->post('/ban', [UserController::class, 'ban']);
|
||||
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
||||
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
||||
$router->post('/destroy', [UserController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// Stat
|
||||
|
@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StatServerJob implements ShouldQueue
|
||||
{
|
||||
@ -22,6 +23,15 @@ class StatServerJob implements ShouldQueue
|
||||
|
||||
public $tries = 3;
|
||||
public $timeout = 60;
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds to wait before retrying the job.
|
||||
*/
|
||||
public function backoff(): array
|
||||
{
|
||||
return [1, 5, 10];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
@ -40,7 +50,6 @@ class StatServerJob implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Calculate record timestamp
|
||||
$recordAt = $this->recordType === 'm'
|
||||
? strtotime(date('Y-m-01'))
|
||||
: strtotime(date('Y-m-d'));
|
||||
@ -51,28 +60,33 @@ class StatServerJob implements ShouldQueue
|
||||
$u += $traffic[0];
|
||||
$d += $traffic[1];
|
||||
}
|
||||
DB::transaction(function () use ($u, $d, $recordAt) {
|
||||
$stat = StatServer::lockForUpdate()
|
||||
->where('record_at', $recordAt)
|
||||
->where('server_id', $this->server['id'])
|
||||
->where('server_type', $this->protocol)
|
||||
->where('record_type', $this->recordType)
|
||||
->first();
|
||||
|
||||
if ($stat) {
|
||||
$stat->u += $u;
|
||||
$stat->d += $d;
|
||||
$stat->save();
|
||||
} else {
|
||||
StatServer::create([
|
||||
try {
|
||||
DB::transaction(function () use ($u, $d, $recordAt) {
|
||||
$affected = StatServer::where([
|
||||
'record_at' => $recordAt,
|
||||
'server_id' => $this->server['id'],
|
||||
'server_type' => $this->protocol,
|
||||
'record_type' => $this->recordType,
|
||||
'u' => $u,
|
||||
'd' => $d,
|
||||
])->update([
|
||||
'u' => DB::raw('u + ' . $u),
|
||||
'd' => DB::raw('d + ' . $d),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!$affected) {
|
||||
StatServer::create([
|
||||
'record_at' => $recordAt,
|
||||
'server_id' => $this->server['id'],
|
||||
'server_type' => $this->protocol,
|
||||
'record_type' => $this->recordType,
|
||||
'u' => $u,
|
||||
'd' => $d,
|
||||
]);
|
||||
}
|
||||
}, 3);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StatUserJob implements ShouldQueue
|
||||
{
|
||||
@ -22,6 +23,15 @@ class StatUserJob implements ShouldQueue
|
||||
|
||||
public $tries = 3;
|
||||
public $timeout = 60;
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds to wait before retrying the job.
|
||||
*/
|
||||
public function backoff(): array
|
||||
{
|
||||
return [1, 5, 10];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
@ -40,34 +50,38 @@ class StatUserJob implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Calculate record timestamp
|
||||
$recordAt = $this->recordType === 'm'
|
||||
? strtotime(date('Y-m-01'))
|
||||
: strtotime(date('Y-m-d'));
|
||||
|
||||
foreach ($this->data as $uid => $v) {
|
||||
DB::transaction(function () use ($uid, $v, $recordAt) {
|
||||
$stat = StatUser::lockForUpdate()
|
||||
->where('user_id', $uid)
|
||||
->where('server_rate', $this->server['rate'])
|
||||
->where('record_at', $recordAt)
|
||||
->where('record_type', $this->recordType)
|
||||
->first();
|
||||
if ($stat) {
|
||||
$stat->u += ($v[0] * $this->server['rate']);
|
||||
$stat->d += ($v[1] * $this->server['rate']);
|
||||
$stat->save();
|
||||
} else {
|
||||
StatUser::create([
|
||||
try {
|
||||
DB::transaction(function () use ($uid, $v, $recordAt) {
|
||||
$affected = StatUser::where([
|
||||
'user_id' => $uid,
|
||||
'server_rate' => $this->server['rate'],
|
||||
'record_at' => $recordAt,
|
||||
'record_type' => $this->recordType,
|
||||
'u' => ($v[0] * $this->server['rate']),
|
||||
'd' => ($v[1] * $this->server['rate']),
|
||||
])->update([
|
||||
'u' => DB::raw('u + ' . ($v[0] * $this->server['rate'])),
|
||||
'd' => DB::raw('d + ' . ($v[1] * $this->server['rate'])),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if (!$affected) {
|
||||
StatUser::create([
|
||||
'user_id' => $uid,
|
||||
'server_rate' => $this->server['rate'],
|
||||
'record_at' => $recordAt,
|
||||
'record_type' => $this->recordType,
|
||||
'u' => ($v[0] * $this->server['rate']),
|
||||
'd' => ($v[1] * $this->server['rate']),
|
||||
]);
|
||||
}
|
||||
}, 3);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('StatUserJob failed for user ' . $uid . ': ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,22 +19,9 @@ class Coupon extends Model
|
||||
|
||||
public function getLimitPeriodAttribute($value)
|
||||
{
|
||||
return collect(json_decode($value, true))->map(function ($item) {
|
||||
return collect(json_decode((string) $value, true))->map(function ($item) {
|
||||
return PlanService::getPeriodKey($item);
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function getLimitPlanIdsAttribute($value)
|
||||
{
|
||||
$planIds = json_decode($value, true);
|
||||
|
||||
if (blank($planIds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return collect($planIds)
|
||||
->map(fn($id) => (string) $id)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,16 @@ class User extends Authenticatable
|
||||
return $this->hasMany(InviteCode::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function orders()
|
||||
{
|
||||
return $this->hasMany(Order::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function stat()
|
||||
{
|
||||
return $this->hasMany(StatUser::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
// 关联工单列表
|
||||
public function tickets()
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ class Helper
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function trafficConvert(int $byte)
|
||||
public static function trafficConvert(float $byte)
|
||||
{
|
||||
$kb = 1024;
|
||||
$mb = 1048576;
|
||||
|
2
public/assets/admin/assets/index.css
vendored
2
public/assets/admin/assets/index.css
vendored
File diff suppressed because one or more lines are too long
8
public/assets/admin/assets/index.js
vendored
8
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
32
public/assets/admin/locales/en-US.js
vendored
32
public/assets/admin/locales/en-US.js
vendored
@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"success": "Success",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
"delete": {
|
||||
"success": "Deleted successfully",
|
||||
"failed": "Failed to delete"
|
||||
},
|
||||
"edit": "Edit",
|
||||
"view": "View",
|
||||
"toggleNavigation": "Toggle Navigation",
|
||||
@ -832,6 +836,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"logout": "Logout",
|
||||
"copy": {
|
||||
"success": "Copied successfully",
|
||||
"failed": "Failed to copy",
|
||||
"error": "Copy failed",
|
||||
"errorLog": "Error copying to clipboard"
|
||||
},
|
||||
@ -1337,7 +1342,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
},
|
||||
"ticket": {
|
||||
"title": "Ticket Management",
|
||||
"description": "Here you can view user tickets, including viewing, replying, and closing operations.",
|
||||
"description": "View and manage user tickets, including viewing, replying, and closing operations.",
|
||||
"columns": {
|
||||
"id": "Ticket ID",
|
||||
"subject": "Subject",
|
||||
@ -1354,13 +1359,13 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"processing": "Processing"
|
||||
},
|
||||
"level": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
"low": "Low Priority",
|
||||
"medium": "Medium Priority",
|
||||
"high": "High Priority"
|
||||
},
|
||||
"filter": {
|
||||
"placeholder": "Search {field}...",
|
||||
"no_results": "No results found.",
|
||||
"no_results": "No results found",
|
||||
"selected": "{count} selected",
|
||||
"clear": "Clear filters"
|
||||
},
|
||||
@ -1368,8 +1373,8 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"view_details": "View Details",
|
||||
"close_ticket": "Close Ticket",
|
||||
"close_confirm_title": "Confirm Close Ticket",
|
||||
"close_confirm_description": "After closing, you will not be able to reply. Are you sure you want to close this ticket?",
|
||||
"close_confirm_button": "Close Ticket",
|
||||
"close_confirm_description": "Are you sure you want to close this ticket? You won't be able to reply after closing.",
|
||||
"close_confirm_button": "Confirm Close",
|
||||
"close_success": "Ticket closed successfully",
|
||||
"view_ticket": "View Ticket"
|
||||
},
|
||||
@ -1385,6 +1390,12 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"sending": "Sending...",
|
||||
"send": "Send"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"title": "Ticket List",
|
||||
"search_placeholder": "Search ticket subject or user email",
|
||||
"no_tickets": "No pending tickets",
|
||||
"no_search_results": "No matching tickets found"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
@ -1740,7 +1751,10 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
||||
"reset_secret": "Reset UUID & URL",
|
||||
"orders": "Orders",
|
||||
"invites": "Invites",
|
||||
"traffic_records": "Traffic Records"
|
||||
"traffic_records": "Traffic Records",
|
||||
"delete": "Delete",
|
||||
"delete_confirm_title": "Confirm Delete User",
|
||||
"delete_confirm_description": "This action will permanently delete user {{email}} and all associated data, including orders, coupons, traffic records, and support tickets. This action cannot be undone. Do you want to continue?"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
|
66
public/assets/admin/locales/ko-KR.js
vendored
66
public/assets/admin/locales/ko-KR.js
vendored
@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
||||
"success": "성공",
|
||||
"save": "저장",
|
||||
"cancel": "취소",
|
||||
"delete": "삭제",
|
||||
"confirm": "확인",
|
||||
"delete": {
|
||||
"success": "삭제되었습니다",
|
||||
"failed": "삭제에 실패했습니다"
|
||||
},
|
||||
"edit": "편집",
|
||||
"view": "보기",
|
||||
"toggleNavigation": "네비게이션 전환",
|
||||
@ -831,9 +835,8 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
||||
"settings": "설정",
|
||||
"logout": "로그아웃",
|
||||
"copy": {
|
||||
"success": "복사 성공",
|
||||
"error": "복사 실패",
|
||||
"errorLog": "클립보드에 복사하는 중 오류 발생"
|
||||
"success": "복사되었습니다",
|
||||
"failed": "복사에 실패했습니다"
|
||||
},
|
||||
"table": {
|
||||
"noData": "데이터가 없습니다",
|
||||
@ -1337,54 +1340,60 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
||||
},
|
||||
"ticket": {
|
||||
"title": "티켓 관리",
|
||||
"description": "여기에서 사용자 티켓을 확인할 수 있으며, 조회, 답변 및 종료 작업을 수행할 수 있습니다.",
|
||||
"description": "사용자 티켓을 보고, 답변하고, 닫는 등의 작업을 관리합니다.",
|
||||
"columns": {
|
||||
"id": "티켓 ID",
|
||||
"id": "티켓 번호",
|
||||
"subject": "제목",
|
||||
"level": "우선순위",
|
||||
"status": "상태",
|
||||
"updated_at": "최근 업데이트",
|
||||
"created_at": "생성 시간",
|
||||
"created_at": "생성일",
|
||||
"actions": "작업"
|
||||
},
|
||||
"status": {
|
||||
"closed": "종료됨",
|
||||
"replied": "답변됨",
|
||||
"pending": "대기 중",
|
||||
"processing": "처리 중"
|
||||
"closed": "닫힘",
|
||||
"replied": "답변완료",
|
||||
"pending": "대기중",
|
||||
"processing": "처리중"
|
||||
},
|
||||
"level": {
|
||||
"low": "낮음",
|
||||
"medium": "중간",
|
||||
"high": "높음"
|
||||
"low": "낮은 우선순위",
|
||||
"medium": "중간 우선순위",
|
||||
"high": "높은 우선순위"
|
||||
},
|
||||
"filter": {
|
||||
"placeholder": "{field} 검색...",
|
||||
"no_results": "검색 결과가 없습니다.",
|
||||
"no_results": "결과를 찾을 수 없습니다",
|
||||
"selected": "{count}개 선택됨",
|
||||
"clear": "필터 초기화"
|
||||
},
|
||||
"actions": {
|
||||
"view_details": "상세 보기",
|
||||
"close_ticket": "티켓 종료",
|
||||
"close_confirm_title": "티켓 종료 확인",
|
||||
"close_confirm_description": "종료 후에는 답변할 수 없습니다. 이 티켓을 종료하시겠습니까?",
|
||||
"close_confirm_button": "티켓 종료",
|
||||
"close_success": "티켓이 성공적으로 종료되었습니다",
|
||||
"close_ticket": "티켓 닫기",
|
||||
"close_confirm_title": "티켓 닫기 확인",
|
||||
"close_confirm_description": "이 티켓을 닫으시겠습니까? 닫은 후에는 답변할 수 없습니다.",
|
||||
"close_confirm_button": "닫기 확인",
|
||||
"close_success": "티켓이 성공적으로 닫혔습니다",
|
||||
"view_ticket": "티켓 보기"
|
||||
},
|
||||
"detail": {
|
||||
"no_messages": "메시지가 아직 없습니다",
|
||||
"created_at": "생성 시간",
|
||||
"no_messages": "메시지가 없습니다",
|
||||
"created_at": "생성일",
|
||||
"user_info": "사용자 정보",
|
||||
"traffic_records": "트래픽 기록",
|
||||
"order_records": "주문 기록",
|
||||
"input": {
|
||||
"closed_placeholder": "티켓이 종료되었습니다",
|
||||
"closed_placeholder": "티켓이 닫혔습니다",
|
||||
"reply_placeholder": "답변을 입력하세요...",
|
||||
"sending": "전송 중...",
|
||||
"sending": "전송중...",
|
||||
"send": "전송"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"title": "티켓 목록",
|
||||
"search_placeholder": "티켓 제목 또는 사용자 이메일 검색",
|
||||
"no_tickets": "대기중인 티켓이 없습니다",
|
||||
"no_search_results": "일치하는 티켓을 찾을 수 없습니다"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
@ -1692,9 +1701,12 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
||||
"assign_order": "주문 할당",
|
||||
"copy_url": "구독 URL 복사",
|
||||
"reset_secret": "UUID 및 URL 재설정",
|
||||
"orders": "주문",
|
||||
"invites": "초대",
|
||||
"traffic_records": "트래픽 기록"
|
||||
"orders": "주문 내역",
|
||||
"invites": "초대 내역",
|
||||
"traffic_records": "트래픽 기록",
|
||||
"delete": "삭제",
|
||||
"delete_confirm_title": "사용자 삭제 확인",
|
||||
"delete_confirm_description": "이 작업은 사용자 {{email}}와 관련된 모든 데이터(주문, 쿠폰, 트래픽 기록, 지원 티켓 등)를 영구적으로 삭제합니다. 이 작업은 취소할 수 없습니다. 계속하시겠습니까?"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
|
34
public/assets/admin/locales/zh-CN.js
vendored
34
public/assets/admin/locales/zh-CN.js
vendored
@ -820,7 +820,11 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"success": "成功",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"confirm": "确认",
|
||||
"delete": {
|
||||
"success": "删除成功",
|
||||
"failed": "删除失败"
|
||||
},
|
||||
"edit": "编辑",
|
||||
"view": "查看",
|
||||
"toggleNavigation": "切换导航",
|
||||
@ -837,6 +841,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"logout": "退出登录",
|
||||
"copy": {
|
||||
"success": "复制成功",
|
||||
"failed": "复制失败",
|
||||
"error": "复制失败",
|
||||
"errorLog": "复制到剪贴板时出错"
|
||||
},
|
||||
@ -1346,9 +1351,9 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"processing": "处理中"
|
||||
},
|
||||
"level": {
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高"
|
||||
"low": "低优先",
|
||||
"medium": "中优先",
|
||||
"high": "高优先"
|
||||
},
|
||||
"filter": {
|
||||
"placeholder": "搜索{field}...",
|
||||
@ -1360,8 +1365,8 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"view_details": "查看详情",
|
||||
"close_ticket": "关闭工单",
|
||||
"close_confirm_title": "确认关闭工单",
|
||||
"close_confirm_description": "关闭后将无法继续回复,是否确认关闭该工单?",
|
||||
"close_confirm_button": "关闭工单",
|
||||
"close_confirm_description": "确定要关闭这个工单吗?关闭后将无法继续回复。",
|
||||
"close_confirm_button": "确认关闭",
|
||||
"close_success": "工单已关闭",
|
||||
"view_ticket": "查看工单"
|
||||
},
|
||||
@ -1373,10 +1378,16 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"order_records": "订单记录",
|
||||
"input": {
|
||||
"closed_placeholder": "工单已关闭",
|
||||
"reply_placeholder": "请输入回复内容...",
|
||||
"reply_placeholder": "输入回复内容...",
|
||||
"sending": "发送中...",
|
||||
"send": "发送"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"title": "工单列表",
|
||||
"search_placeholder": "搜索工单标题或用户邮箱",
|
||||
"no_tickets": "暂无待处理工单",
|
||||
"no_search_results": "未找到匹配的工单"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
@ -1707,7 +1718,10 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"reset_secret": "重置UUID及订阅URL",
|
||||
"orders": "TA的订单",
|
||||
"invites": "TA的邀请",
|
||||
"traffic_records": "TA的流量记录"
|
||||
"traffic_records": "TA的流量记录",
|
||||
"delete": "删除",
|
||||
"delete_confirm_title": "确认删除用户",
|
||||
"delete_confirm_description": "此操作将永久删除用户 {{email}} 及其所有相关数据,包括订单、优惠码、流量记录、工单记录等信息。删除后无法恢复,是否继续?"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
@ -1923,12 +1937,12 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
||||
"device": {
|
||||
"label": "设备限制",
|
||||
"placeholder": "请输入设备限制",
|
||||
"unit": "台设备"
|
||||
"unit": "台"
|
||||
},
|
||||
"capacity": {
|
||||
"label": "容量限制",
|
||||
"placeholder": "请输入容量限制",
|
||||
"unit": "个用户"
|
||||
"unit": "人"
|
||||
},
|
||||
"reset_method": {
|
||||
"label": "流量重置方式",
|
||||
|
Loading…
Reference in New Issue
Block a user