feat: add tags to plans, refactor order service, adn fix ticket lock
This commit is contained in:
@@ -43,7 +43,7 @@ class OrderController extends Controller
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'trade_no' => 'required|string',
|
'trade_no' => 'required|string',
|
||||||
]);
|
]);
|
||||||
$order = Order::with(['payment','plan'])
|
$order = Order::with(['payment', 'plan'])
|
||||||
->where('user_id', $request->user()->id)
|
->where('user_id', $request->user()->id)
|
||||||
->where('trade_no', $request->input('trade_no'))
|
->where('trade_no', $request->input('trade_no'))
|
||||||
->first();
|
->first();
|
||||||
@@ -77,45 +77,16 @@ class OrderController extends Controller
|
|||||||
$plan = Plan::findOrFail($request->input('plan_id'));
|
$plan = Plan::findOrFail($request->input('plan_id'));
|
||||||
$planService = new PlanService($plan);
|
$planService = new PlanService($plan);
|
||||||
|
|
||||||
// Validate plan purchase
|
|
||||||
$planService->validatePurchase($user, $request->input('period'));
|
$planService->validatePurchase($user, $request->input('period'));
|
||||||
|
|
||||||
return DB::transaction(function () use ($request, $plan, $user, $userService) {
|
$order = OrderService::createFromRequest(
|
||||||
$period = $request->input('period');
|
$user,
|
||||||
$newPeriod = PlanService::getPeriodKey($period);
|
$plan,
|
||||||
|
$request->input('period'),
|
||||||
|
$request->input('coupon_code')
|
||||||
|
);
|
||||||
|
|
||||||
// Create order
|
return $this->success($order->trade_no);
|
||||||
$order = new Order([
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'plan_id' => $plan->id,
|
|
||||||
'period' => $newPeriod,
|
|
||||||
'trade_no' => Helper::generateOrderNo(),
|
|
||||||
'total_amount' => optional($plan->prices)[$newPeriod] * 100
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Apply coupon if provided
|
|
||||||
if ($request->input('coupon_code')) {
|
|
||||||
$this->applyCoupon($order, $request->input('coupon_code'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set order attributes
|
|
||||||
$orderService = new OrderService($order);
|
|
||||||
$orderService->setVipDiscount($user);
|
|
||||||
$orderService->setOrderType($user);
|
|
||||||
$orderService->setInvite($user);
|
|
||||||
|
|
||||||
// Handle user balance
|
|
||||||
if ($user->balance && $order->total_amount > 0) {
|
|
||||||
$this->handleUserBalance($order, $user, $userService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$order->save()) {
|
|
||||||
throw new ApiException(__('Failed to create order'));
|
|
||||||
}
|
|
||||||
HookManager::call('order.after_create', $order);
|
|
||||||
|
|
||||||
return $this->success($order->trade_no);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function applyCoupon(Order $order, string $couponCode): void
|
protected function applyCoupon(Order $order, string $couponCode): void
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ class TicketController extends Controller
|
|||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
if ((int)Ticket::where('status', 0)->where('user_id', $request->user()->id)->lockForUpdate()->count()) {
|
if (Ticket::where('status', 0)->where('user_id', $request->user()->id)->lockForUpdate()->first()) {
|
||||||
throw new \Exception(__('There are other unresolved tickets'));
|
DB::rollBack();
|
||||||
|
return $this->fail([400, '存在未关闭的工单']);
|
||||||
}
|
}
|
||||||
$ticket = Ticket::create(array_merge($request->only([
|
$ticket = Ticket::create(array_merge($request->only([
|
||||||
'subject',
|
'subject',
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class KnowledgeSave extends FormRequest
|
|||||||
'category' => 'required',
|
'category' => 'required',
|
||||||
'language' => 'required',
|
'language' => 'required',
|
||||||
'title' => 'required',
|
'title' => 'required',
|
||||||
'body' => 'required'
|
'body' => 'required',
|
||||||
|
'show' => 'nullable|boolean'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +28,8 @@ class KnowledgeSave extends FormRequest
|
|||||||
'title.required' => '标题不能为空',
|
'title.required' => '标题不能为空',
|
||||||
'category.required' => '分类不能为空',
|
'category.required' => '分类不能为空',
|
||||||
'body.required' => '内容不能为空',
|
'body.required' => '内容不能为空',
|
||||||
'language.required' => '语言不能为空'
|
'language.required' => '语言不能为空',
|
||||||
|
'show.boolean' => '显示状态必须为布尔值'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class PlanSave extends FormRequest
|
|||||||
'speed_limit' => 'integer|nullable|min:0',
|
'speed_limit' => 'integer|nullable|min:0',
|
||||||
'device_limit' => 'integer|nullable|min:0',
|
'device_limit' => 'integer|nullable|min:0',
|
||||||
'capacity_limit' => 'integer|nullable|min:0',
|
'capacity_limit' => 'integer|nullable|min:0',
|
||||||
|
'tags' => 'array|nullable',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +137,7 @@ class PlanSave extends FormRequest
|
|||||||
'device_limit.min' => '设备限制不能为负数',
|
'device_limit.min' => '设备限制不能为负数',
|
||||||
'capacity_limit.integer' => '容量限制必须是整数',
|
'capacity_limit.integer' => '容量限制必须是整数',
|
||||||
'capacity_limit.min' => '容量限制不能为负数',
|
'capacity_limit.min' => '容量限制不能为负数',
|
||||||
|
'tags.array' => '标签格式必须是数组',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class PlanResource extends JsonResource
|
|||||||
'id' => $this->resource['id'],
|
'id' => $this->resource['id'],
|
||||||
'group_id' => $this->resource['group_id'],
|
'group_id' => $this->resource['group_id'],
|
||||||
'name' => $this->resource['name'],
|
'name' => $this->resource['name'],
|
||||||
|
'tags' => $this->resource['tags'],
|
||||||
'content' => $this->formatContent(),
|
'content' => $this->formatContent(),
|
||||||
...$this->getPeriodPrices(),
|
...$this->getPeriodPrices(),
|
||||||
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
* @property int $total_amount
|
* @property int $total_amount
|
||||||
* @property int|null $handling_amount
|
* @property int|null $handling_amount
|
||||||
* @property int|null $balance_amount
|
* @property int|null $balance_amount
|
||||||
|
* @property int|null $refund_amount
|
||||||
|
* @property int|null $surplus_amount
|
||||||
* @property int $type
|
* @property int $type
|
||||||
* @property int $status
|
* @property int $status
|
||||||
* @property array|null $surplus_order_ids
|
* @property array|null $surplus_order_ids
|
||||||
|
|||||||
+4
-1
@@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
|||||||
* @property bool $renew 是否允许续费
|
* @property bool $renew 是否允许续费
|
||||||
* @property bool $sell 是否允许购买
|
* @property bool $sell 是否允许购买
|
||||||
* @property array|null $prices 价格配置
|
* @property array|null $prices 价格配置
|
||||||
|
* @property array|null $tags 标签
|
||||||
* @property int $sort 排序
|
* @property int $sort 排序
|
||||||
* @property string|null $content 套餐描述
|
* @property string|null $content 套餐描述
|
||||||
* @property int|null $reset_traffic_method 流量重置方式
|
* @property int|null $reset_traffic_method 流量重置方式
|
||||||
@@ -85,7 +86,8 @@ class Plan extends Model
|
|||||||
'reset_traffic_method',
|
'reset_traffic_method',
|
||||||
'capacity_limit',
|
'capacity_limit',
|
||||||
'sell',
|
'sell',
|
||||||
'device_limit'
|
'device_limit',
|
||||||
|
'tags'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@@ -95,6 +97,7 @@ class Plan extends Model
|
|||||||
'updated_at' => 'timestamp',
|
'updated_at' => 'timestamp',
|
||||||
'group_id' => 'integer',
|
'group_id' => 'integer',
|
||||||
'prices' => 'array',
|
'prices' => 'array',
|
||||||
|
'tags' => 'array',
|
||||||
'reset_traffic_method' => 'integer',
|
'reset_traffic_method' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use App\Jobs\OrderHandleJob;
|
|||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Plugin\HookManager;
|
||||||
|
use App\Utils\Helper;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -28,6 +30,62 @@ class OrderService
|
|||||||
$this->order = $order;
|
$this->order = $order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an order from a request.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @param Plan $plan
|
||||||
|
* @param string $period
|
||||||
|
* @param string|null $couponCode
|
||||||
|
* @param array|null $telegramMessageIds
|
||||||
|
* @return Order
|
||||||
|
* @throws ApiException
|
||||||
|
*/
|
||||||
|
public static function createFromRequest(
|
||||||
|
User $user,
|
||||||
|
Plan $plan,
|
||||||
|
string $period,
|
||||||
|
?string $couponCode = null,
|
||||||
|
): Order {
|
||||||
|
$userService = app(UserService::class);
|
||||||
|
$planService = new PlanService($plan);
|
||||||
|
|
||||||
|
$planService->validatePurchase($user, $period);
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($user, $plan, $period, $couponCode, $userService) {
|
||||||
|
$newPeriod = PlanService::getPeriodKey($period);
|
||||||
|
|
||||||
|
$order = new Order([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'period' => $newPeriod,
|
||||||
|
'trade_no' => Helper::generateOrderNo(),
|
||||||
|
'total_amount' => (int) (optional($plan->prices)[$newPeriod] * 100),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$orderService = new self($order);
|
||||||
|
|
||||||
|
if ($couponCode) {
|
||||||
|
$orderService->applyCoupon($couponCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderService->setVipDiscount($user);
|
||||||
|
$orderService->setOrderType($user);
|
||||||
|
$orderService->setInvite($user);
|
||||||
|
|
||||||
|
if ($user->balance && $order->total_amount > 0) {
|
||||||
|
$orderService->handleUserBalance($user, $userService);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$order->save()) {
|
||||||
|
throw new ApiException(__('Failed to create order'));
|
||||||
|
}
|
||||||
|
HookManager::call('order.after_create', $order);
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function open()
|
public function open()
|
||||||
{
|
{
|
||||||
$order = $this->order;
|
$order = $this->order;
|
||||||
@@ -68,7 +126,7 @@ class OrderService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->setSpeedLimit($plan->speed_limit);
|
$this->setSpeedLimit($plan->speed_limit);
|
||||||
$this->setDeviceLimit($plan->device_limit);
|
$this->setDeviceLimit($plan->device_limit);
|
||||||
|
|
||||||
if (!$this->user->save()) {
|
if (!$this->user->save()) {
|
||||||
throw new \Exception('用户信息保存失败');
|
throw new \Exception('用户信息保存失败');
|
||||||
@@ -98,10 +156,10 @@ class OrderService
|
|||||||
if ((int) admin_setting('surplus_enable', 1))
|
if ((int) admin_setting('surplus_enable', 1))
|
||||||
$this->getSurplusValue($user, $order);
|
$this->getSurplusValue($user, $order);
|
||||||
if ($order->surplus_amount >= $order->total_amount) {
|
if ($order->surplus_amount >= $order->total_amount) {
|
||||||
$order->refund_amount = $order->surplus_amount - $order->total_amount;
|
$order->refund_amount = (int) ($order->surplus_amount - $order->total_amount);
|
||||||
$order->total_amount = 0;
|
$order->total_amount = 0;
|
||||||
} else {
|
} else {
|
||||||
$order->total_amount = $order->total_amount - $order->surplus_amount;
|
$order->total_amount = (int) ($order->total_amount - $order->surplus_amount);
|
||||||
}
|
}
|
||||||
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) { // 用户订阅未过期且购买订阅与当前订阅相同 === 续费
|
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) { // 用户订阅未过期且购买订阅与当前订阅相同 === 续费
|
||||||
$order->type = Order::TYPE_RENEWAL;
|
$order->type = Order::TYPE_RENEWAL;
|
||||||
@@ -187,7 +245,7 @@ class OrderService
|
|||||||
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
||||||
$result = $trafficUnitPrice * $notUsedTraffic;
|
$result = $trafficUnitPrice * $notUsedTraffic;
|
||||||
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', Plan::PERIOD_RESET_TRAFFIC)->where('status', Order::STATUS_COMPLETED);
|
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', Plan::PERIOD_RESET_TRAFFIC)->where('status', Order::STATUS_COMPLETED);
|
||||||
$order->surplus_amount = $result > 0 ? $result : 0;
|
$order->surplus_amount = (int) ($result > 0 ? $result : 0);
|
||||||
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
|
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +280,7 @@ class OrderService
|
|||||||
$orderSurplusAmount = $avgPrice * $orderSurplusSecond;
|
$orderSurplusAmount = $avgPrice * $orderSurplusSecond;
|
||||||
if (!$orderSurplusSecond || !$orderSurplusAmount)
|
if (!$orderSurplusSecond || !$orderSurplusAmount)
|
||||||
return;
|
return;
|
||||||
$order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
|
$order->surplus_amount = (int) ($orderSurplusAmount > 0 ? $orderSurplusAmount : 0);
|
||||||
$order->surplus_order_ids = array_column($orders, 'id');
|
$order->surplus_order_ids = array_column($orders, 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,4 +402,32 @@ class OrderService
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function applyCoupon(string $couponCode): void
|
||||||
|
{
|
||||||
|
$couponService = new CouponService($couponCode);
|
||||||
|
if (!$couponService->use($this->order)) {
|
||||||
|
throw new ApiException(__('Coupon failed'));
|
||||||
|
}
|
||||||
|
$this->order->coupon_id = $couponService->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleUserBalance(User $user, UserService $userService): void
|
||||||
|
{
|
||||||
|
$remainingBalance = $user->balance - $this->order->total_amount;
|
||||||
|
|
||||||
|
if ($remainingBalance >= 0) {
|
||||||
|
if (!$userService->addBalance($this->order->user_id, -$this->order->total_amount)) {
|
||||||
|
throw new ApiException(__('Insufficient balance'));
|
||||||
|
}
|
||||||
|
$this->order->balance_amount = $this->order->total_amount;
|
||||||
|
$this->order->total_amount = 0;
|
||||||
|
} else {
|
||||||
|
if (!$userService->addBalance($this->order->user_id, -$user->balance)) {
|
||||||
|
throw new ApiException(__('Insufficient balance'));
|
||||||
|
}
|
||||||
|
$this->order->balance_amount = $user->balance;
|
||||||
|
$this->order->total_amount = $this->order->total_amount - $user->balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('v2_plan', function (Blueprint $table) {
|
||||||
|
$table->json('tags')->nullable()->after('content');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('v2_plan', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('tags');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Vendored
+11
-10
File diff suppressed because one or more lines are too long
@@ -1,32 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Shadcn Admin</title>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Admin Dashboard UI built with Shadcn and Vite."
|
|
||||||
/>
|
|
||||||
<script>
|
|
||||||
window.settings = {
|
|
||||||
base_url: 'http://127.0.0.1:8000',
|
|
||||||
title: 'Xboard',
|
|
||||||
version: '1.0.0',
|
|
||||||
logo: 'https://xboard.io/i6mages/logo.png',
|
|
||||||
secure_path: '/22aba88a',
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script src="./locales/en-US.js"></script>
|
|
||||||
<script src="./locales/zh-CN.js"></script>
|
|
||||||
<script src="./locales/ko-KR.js"></script>
|
|
||||||
<script type="module" crossorigin src="./assets/index.js"></script>
|
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index.css">
|
|
||||||
<link rel="stylesheet" crossorigin href="./assets/vendor.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Vendored
+27
-4
@@ -1699,10 +1699,26 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"error": "Please enter a valid name"
|
"error": "Please enter a valid name"
|
||||||
},
|
},
|
||||||
"rate": {
|
"rate": {
|
||||||
"label": "Rate",
|
"label": "Base Rate",
|
||||||
"error": "Rate is required",
|
"error": "Base rate is required",
|
||||||
"error_numeric": "Rate must be a number",
|
"error_numeric": "Base rate must be a number",
|
||||||
"error_gte_zero": "Rate must be greater than or equal to 0"
|
"error_gte_zero": "Base rate must be greater than or equal to 0"
|
||||||
|
},
|
||||||
|
"dynamic_rate": {
|
||||||
|
"enable_label": "Enable Dynamic Rate",
|
||||||
|
"enable_description": "Set different rate multipliers based on time periods",
|
||||||
|
"rules_label": "Time Period Rules",
|
||||||
|
"add_rule": "Add Rule",
|
||||||
|
"rule_title": "Rule {{index}}",
|
||||||
|
"start_time": "Start Time",
|
||||||
|
"end_time": "End Time",
|
||||||
|
"multiplier": "Rate Multiplier",
|
||||||
|
"no_rules": "No rules yet, click the button above to add",
|
||||||
|
"start_time_error": "Start time is required",
|
||||||
|
"end_time_error": "End time is required",
|
||||||
|
"multiplier_error": "Rate multiplier is required",
|
||||||
|
"multiplier_error_numeric": "Rate multiplier must be a number",
|
||||||
|
"multiplier_error_gte_zero": "Rate multiplier must be greater than or equal to 0"
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"label": "Custom Node ID",
|
"label": "Custom Node ID",
|
||||||
@@ -2455,6 +2471,10 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"placeholder": "Enter capacity limit",
|
"placeholder": "Enter capacity limit",
|
||||||
"unit": "Users"
|
"unit": "Users"
|
||||||
},
|
},
|
||||||
|
"tags": {
|
||||||
|
"label": "Tags",
|
||||||
|
"placeholder": "Enter a tag and press Enter to confirm"
|
||||||
|
},
|
||||||
"reset_method": {
|
"reset_method": {
|
||||||
"label": "Traffic Reset Method",
|
"label": "Traffic Reset Method",
|
||||||
"placeholder": "Select reset method",
|
"placeholder": "Select reset method",
|
||||||
@@ -2493,6 +2513,9 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"success": {
|
"success": {
|
||||||
"add": "Plan added successfully",
|
"add": "Plan added successfully",
|
||||||
"update": "Plan updated successfully"
|
"update": "Plan updated successfully"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"validation": "Form validation failed. Please check for errors and try again."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+27
-4
@@ -1666,10 +1666,26 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"error": "请输入有效的节点名称"
|
"error": "请输入有效的节点名称"
|
||||||
},
|
},
|
||||||
"rate": {
|
"rate": {
|
||||||
"label": "倍率",
|
"label": "基础倍率",
|
||||||
"error": "倍率不能为空",
|
"error": "基础倍率不能为空",
|
||||||
"error_numeric": "费率必须是数字",
|
"error_numeric": "基础倍率必须是数字",
|
||||||
"error_gte_zero": "费率必须大于或等于0"
|
"error_gte_zero": "基础倍率必须大于或等于0"
|
||||||
|
},
|
||||||
|
"dynamic_rate": {
|
||||||
|
"enable_label": "启用动态倍率",
|
||||||
|
"enable_description": "根据时间段设置不同的倍率乘数",
|
||||||
|
"rules_label": "时间段规则",
|
||||||
|
"add_rule": "添加规则",
|
||||||
|
"rule_title": "规则 {{index}}",
|
||||||
|
"start_time": "开始时间",
|
||||||
|
"end_time": "结束时间",
|
||||||
|
"multiplier": "倍率乘数",
|
||||||
|
"no_rules": "暂无规则,点击上方按钮添加",
|
||||||
|
"start_time_error": "开始时间不能为空",
|
||||||
|
"end_time_error": "结束时间不能为空",
|
||||||
|
"multiplier_error": "倍率乘数不能为空",
|
||||||
|
"multiplier_error_numeric": "倍率乘数必须是数字",
|
||||||
|
"multiplier_error_gte_zero": "倍率乘数必须大于或等于0"
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"label": "自定义节点ID",
|
"label": "自定义节点ID",
|
||||||
@@ -2421,6 +2437,10 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"placeholder": "请输入容量限制",
|
"placeholder": "请输入容量限制",
|
||||||
"unit": "人"
|
"unit": "人"
|
||||||
},
|
},
|
||||||
|
"tags": {
|
||||||
|
"label": "标签",
|
||||||
|
"placeholder": "输入标签后按回车确认"
|
||||||
|
},
|
||||||
"reset_method": {
|
"reset_method": {
|
||||||
"label": "流量重置方式",
|
"label": "流量重置方式",
|
||||||
"placeholder": "请选择重置方式",
|
"placeholder": "请选择重置方式",
|
||||||
@@ -2459,6 +2479,9 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"success": {
|
"success": {
|
||||||
"add": "套餐添加成功",
|
"add": "套餐添加成功",
|
||||||
"update": "套餐更新成功"
|
"update": "套餐更新成功"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"validation": "表单校验失败,请检查并修正错误后重试。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user