mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-15 12:58:14 -05:00
feat: enhance user management and system optimization
New Features: - Add bulk ban and email notification in user management - Add CSV export for batch coupon generation - Optimize subscription description template Bug Fixes: - Fix knowledge base pagination issue - Fix permission group filtering in node management - Fix unauthorized order placement for free subscription periods
This commit is contained in:
parent
8e5732d857
commit
1b728fffc7
@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Builder;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
@ -243,7 +244,7 @@ class UserController extends Controller
|
|||||||
try {
|
try {
|
||||||
$user->update($params);
|
$user->update($params);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
Log::error($e);
|
||||||
return $this->fail([500, '保存失败']);
|
return $this->fail([500, '保存失败']);
|
||||||
}
|
}
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
@ -251,8 +252,9 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function dumpCSV(Request $request)
|
public function dumpCSV(Request $request)
|
||||||
{
|
{
|
||||||
|
ini_set('memory_limit', -1);
|
||||||
$userModel = User::orderBy('id', 'asc');
|
$userModel = User::orderBy('id', 'asc');
|
||||||
$this->filter($request, $userModel);
|
$this->applyFiltersAndSorts($request, $userModel);
|
||||||
$res = $userModel->get();
|
$res = $userModel->get();
|
||||||
$plan = Plan::get();
|
$plan = Plan::get();
|
||||||
for ($i = 0; $i < count($res); $i++) {
|
for ($i = 0; $i < count($res); $i++) {
|
||||||
@ -341,7 +343,7 @@ class UserController extends Controller
|
|||||||
DB::commit();
|
DB::commit();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
\Log::error($e);
|
Log::error($e);
|
||||||
return $this->fail([500, '生成失败']);
|
return $this->fail([500, '生成失败']);
|
||||||
}
|
}
|
||||||
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
|
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
|
||||||
@ -357,11 +359,13 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function sendMail(UserSendMail $request)
|
public function sendMail(UserSendMail $request)
|
||||||
{
|
{
|
||||||
|
ini_set('memory_limit', '-1');
|
||||||
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
||||||
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
|
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
|
||||||
$builder = User::orderBy($sort, $sortType);
|
$builder = User::orderBy($sort, $sortType);
|
||||||
$this->filter($request, $builder);
|
$this->applyFilters($request, $builder);
|
||||||
$users = $builder->get();
|
$users = $builder->get();
|
||||||
|
return $this->success($users->count());
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
SendEmailJob::dispatch(
|
SendEmailJob::dispatch(
|
||||||
[
|
[
|
||||||
@ -386,13 +390,13 @@ class UserController extends Controller
|
|||||||
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
||||||
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
|
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
|
||||||
$builder = User::orderBy($sort, $sortType);
|
$builder = User::orderBy($sort, $sortType);
|
||||||
$this->filter($request, $builder);
|
$this->applyFilters($request, $builder);
|
||||||
try {
|
try {
|
||||||
$builder->update([
|
$builder->update([
|
||||||
'banned' => 1
|
'banned' => 1
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
Log::error($e);
|
||||||
return $this->fail([500, '处理失败']);
|
return $this->fail([500, '处理失败']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +429,7 @@ class UserController extends Controller
|
|||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
\Log::error($e);
|
Log::error($e);
|
||||||
return $this->fail([500, '删除失败']);
|
return $this->fail([500, '删除失败']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
|||||||
class PlanResource extends JsonResource
|
class PlanResource extends JsonResource
|
||||||
{
|
{
|
||||||
private const PRICE_MULTIPLIER = 100;
|
private const PRICE_MULTIPLIER = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
@ -23,7 +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'],
|
||||||
'content' => $this->resource['content'],
|
'content' => $this->formatContent(),
|
||||||
...$this->getPeriodPrices(),
|
...$this->getPeriodPrices(),
|
||||||
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
||||||
'transfer_enable' => $this->resource['transfer_enable'],
|
'transfer_enable' => $this->resource['transfer_enable'],
|
||||||
@ -49,8 +49,8 @@ class PlanResource extends JsonResource
|
|||||||
->mapWithKeys(function (string $newPeriod, string $legacyPeriod): array {
|
->mapWithKeys(function (string $newPeriod, string $legacyPeriod): array {
|
||||||
$price = $this->resource['prices'][$newPeriod] ?? null;
|
$price = $this->resource['prices'][$newPeriod] ?? null;
|
||||||
return [
|
return [
|
||||||
$legacyPeriod => $price !== null
|
$legacyPeriod => $price !== null
|
||||||
? (float) $price * self::PRICE_MULTIPLIER
|
? (float) $price * self::PRICE_MULTIPLIER
|
||||||
: null
|
: null
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
@ -72,4 +72,49 @@ class PlanResource extends JsonResource
|
|||||||
default => (int) $limit,
|
default => (int) $limit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Format content with template variables
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function formatContent(): string
|
||||||
|
{
|
||||||
|
$content = $this->resource['content'];
|
||||||
|
|
||||||
|
$replacements = [
|
||||||
|
'{{transfer}}' => $this->resource['transfer_enable'],
|
||||||
|
'{{speed}}' => $this->resource['speed_limit'] === NULL ? __('No Limit') : $this->resource['speed_limit'],
|
||||||
|
'{{devices}}' => $this->resource['device_limit'] === NULL ? __('No Limit') : $this->resource['device_limit'],
|
||||||
|
'{{reset_method}}' => $this->getResetMethodText(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return str_replace(
|
||||||
|
array_keys($replacements),
|
||||||
|
array_values($replacements),
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get reset method text
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getResetMethodText(): string
|
||||||
|
{
|
||||||
|
$method = $this->resource['reset_traffic_method'];
|
||||||
|
|
||||||
|
if ($method === Plan::RESET_TRAFFIC_FOLLOW_SYSTEM) {
|
||||||
|
$method = admin_setting('reset_traffic_method', Plan::RESET_TRAFFIC_MONTHLY);
|
||||||
|
}
|
||||||
|
return match ($method) {
|
||||||
|
Plan::RESET_TRAFFIC_FIRST_DAY_MONTH => __('First Day of Month'),
|
||||||
|
Plan::RESET_TRAFFIC_MONTHLY => __('Monthly'),
|
||||||
|
Plan::RESET_TRAFFIC_NEVER => __('Never'),
|
||||||
|
Plan::RESET_TRAFFIC_FIRST_DAY_YEAR => __('First Day of Year'),
|
||||||
|
Plan::RESET_TRAFFIC_YEARLY => __('Yearly'),
|
||||||
|
default => __('Monthly')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -104,7 +104,7 @@ class AdminRoute
|
|||||||
$router->get('/getUserInfoById', [UserController::class, 'getUserInfoById']);
|
$router->get('/getUserInfoById', [UserController::class, 'getUserInfoById']);
|
||||||
$router->post('/generate', [UserController::class, 'generate']);
|
$router->post('/generate', [UserController::class, 'generate']);
|
||||||
$router->post('/dumpCSV', [UserController::class, 'dumpCSV']);
|
$router->post('/dumpCSV', [UserController::class, 'dumpCSV']);
|
||||||
$router->post('/user/sendMail', [UserController::class, 'sendMail']);
|
$router->post('/sendMail', [UserController::class, 'sendMail']);
|
||||||
$router->post('/ban', [UserController::class, 'ban']);
|
$router->post('/ban', [UserController::class, 'ban']);
|
||||||
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
||||||
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
||||||
|
@ -75,16 +75,17 @@ class PlanService
|
|||||||
// 转换周期格式为新版格式
|
// 转换周期格式为新版格式
|
||||||
$periodKey = self::getPeriodKey($period);
|
$periodKey = self::getPeriodKey($period);
|
||||||
|
|
||||||
|
|
||||||
|
// 检查价格时使用新版格式
|
||||||
|
if (!isset($this->plan->prices[$periodKey]) || $this->plan->prices[$periodKey] === NULL) {
|
||||||
|
throw new ApiException(__('This payment period cannot be purchased, please choose another period'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($periodKey === Plan::PERIOD_RESET_TRAFFIC) {
|
if ($periodKey === Plan::PERIOD_RESET_TRAFFIC) {
|
||||||
$this->validateResetTrafficPurchase($user);
|
$this->validateResetTrafficPurchase($user);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查价格时使用新版格式
|
|
||||||
if (!isset($this->plan->prices[$periodKey])) {
|
|
||||||
throw new ApiException(__('This payment period cannot be purchased, please choose another period'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user->plan_id !== $this->plan->id && !$this->hasCapacity($this->plan)) {
|
if ($user->plan_id !== $this->plan->id && !$this->hasCapacity($this->plan)) {
|
||||||
throw new ApiException(__('Current product is sold out'));
|
throw new ApiException(__('Current product is sold out'));
|
||||||
}
|
}
|
||||||
|
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
14
public/assets/admin/assets/index.js
vendored
14
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
41
public/assets/admin/locales/en-US.js
vendored
41
public/assets/admin/locales/en-US.js
vendored
@ -1904,6 +1904,45 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"success": "Modified successfully"
|
"success": "Modified successfully"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"title": "Actions",
|
||||||
|
"send_email": "Send Email",
|
||||||
|
"export_csv": "Export CSV",
|
||||||
|
"batch_ban": "Batch Ban",
|
||||||
|
"confirm_ban": {
|
||||||
|
"title": "Confirm Batch Ban",
|
||||||
|
"filtered_description": "This action will ban all users that match your current filters. This action cannot be undone.",
|
||||||
|
"all_description": "This action will ban all users in the system. This action cannot be undone.",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm Ban",
|
||||||
|
"banning": "Banning..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"export": {
|
||||||
|
"success": "Export successful",
|
||||||
|
"failed": "Export failed"
|
||||||
|
},
|
||||||
|
"batch_ban": {
|
||||||
|
"success": "Batch ban successful",
|
||||||
|
"failed": "Batch ban failed"
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"success": "Email sent successfully",
|
||||||
|
"failed": "Failed to send email",
|
||||||
|
"required_fields": "Please fill in all required fields"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"title": "Send Email",
|
||||||
|
"description": "Send email to selected or filtered users",
|
||||||
|
"subject": "Subject",
|
||||||
|
"content": "Content",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"send": "Send"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subscribe": {
|
"subscribe": {
|
||||||
@ -2024,7 +2063,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"template": {
|
"template": {
|
||||||
"button": "Use Template",
|
"button": "Use Template",
|
||||||
"tooltip": "Use default template",
|
"tooltip": "Use default template",
|
||||||
"content": "## Plan Features\n\n- Traffic: {{transfer}} GB\n- Speed: {{speed}} Mbps\n- Devices: {{devices}}\n\n## Usage Notes\n\n1. The plan is valid for {{validity}} days\n2. Traffic resets {{reset_method}}\n3. Maximum {{capacity}} concurrent users"
|
"content": "## Plan Details\n\n- Data: {{transfer}} GB\n- Speed Limit: {{speed}} Mbps\n- Concurrent Devices: {{devices}}\n\n## Service Information\n\n1. Data {{reset_method}}\n2. Multi-platform Support\n3. 24/7 Technical Support"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"force_update": {
|
"force_update": {
|
||||||
|
41
public/assets/admin/locales/ko-KR.js
vendored
41
public/assets/admin/locales/ko-KR.js
vendored
@ -1854,6 +1854,45 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"submit": "제출",
|
"submit": "제출",
|
||||||
"success": "수정 완료"
|
"success": "수정 완료"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"title": "작업",
|
||||||
|
"send_email": "이메일 보내기",
|
||||||
|
"export_csv": "CSV 내보내기",
|
||||||
|
"batch_ban": "일괄 차단",
|
||||||
|
"confirm_ban": {
|
||||||
|
"title": "일괄 차단 확인",
|
||||||
|
"filtered_description": "이 작업은 현재 필터와 일치하는 모든 사용자를 차단합니다. 이 작업은 취소할 수 없습니다.",
|
||||||
|
"all_description": "이 작업은 시스템의 모든 사용자를 차단합니다. 이 작업은 취소할 수 없습니다.",
|
||||||
|
"cancel": "취소",
|
||||||
|
"confirm": "차단 확인",
|
||||||
|
"banning": "차단 중..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"success": "성공",
|
||||||
|
"error": "오류",
|
||||||
|
"export": {
|
||||||
|
"success": "내보내기 성공",
|
||||||
|
"failed": "내보내기 실패"
|
||||||
|
},
|
||||||
|
"batch_ban": {
|
||||||
|
"success": "일괄 차단 성공",
|
||||||
|
"failed": "일괄 차단 실패"
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"success": "이메일 전송 성공",
|
||||||
|
"failed": "이메일 전송 실패",
|
||||||
|
"required_fields": "모든 필수 항목을 입력해주세요"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"title": "이메일 보내기",
|
||||||
|
"description": "선택하거나 필터링된 사용자에게 이메일 보내기",
|
||||||
|
"subject": "제목",
|
||||||
|
"content": "내용",
|
||||||
|
"sending": "전송 중...",
|
||||||
|
"send": "보내기"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subscribe": {
|
"subscribe": {
|
||||||
@ -1974,7 +2013,7 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"template": {
|
"template": {
|
||||||
"button": "템플릿 사용",
|
"button": "템플릿 사용",
|
||||||
"tooltip": "기본 템플릿 사용",
|
"tooltip": "기본 템플릿 사용",
|
||||||
"content": "## 플랜 특징\n\n- 트래픽: {{transfer}} GB\n- 속도: {{speed}} Mbps\n- 기기: {{devices}}대\n\n## 사용 안내\n\n1. 플랜 유효 기간: {{validity}}일\n2. 트래픽 초기화: {{reset_method}}\n3. 최대 동시 접속자: {{capacity}}명"
|
"content": "## 요금제 상세\n\n- 데이터: {{transfer}} GB\n- 속도 제한: {{speed}} Mbps\n- 동시접속 기기: {{devices}}대\n\n## 서비스 안내\n\n1. 데이터 {{reset_method}} 초기화\n2. 멀티플랫폼 지원\n3. 24시간 기술지원"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"force_update": {
|
"force_update": {
|
||||||
|
41
public/assets/admin/locales/zh-CN.js
vendored
41
public/assets/admin/locales/zh-CN.js
vendored
@ -1871,6 +1871,45 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"submit": "提交",
|
"submit": "提交",
|
||||||
"success": "修改成功"
|
"success": "修改成功"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"title": "操作",
|
||||||
|
"send_email": "发送邮件",
|
||||||
|
"export_csv": "导出 CSV",
|
||||||
|
"batch_ban": "批量封禁",
|
||||||
|
"confirm_ban": {
|
||||||
|
"title": "确认批量封禁",
|
||||||
|
"filtered_description": "此操作将封禁所有符合当前筛选条件的用户。此操作无法撤销。",
|
||||||
|
"all_description": "此操作将封禁系统中的所有用户。此操作无法撤销。",
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "确认封禁",
|
||||||
|
"banning": "封禁中..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"success": "成功",
|
||||||
|
"error": "错误",
|
||||||
|
"export": {
|
||||||
|
"success": "导出成功",
|
||||||
|
"failed": "导出失败"
|
||||||
|
},
|
||||||
|
"batch_ban": {
|
||||||
|
"success": "批量封禁成功",
|
||||||
|
"failed": "批量封禁失败"
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"success": "邮件发送成功",
|
||||||
|
"failed": "邮件发送失败",
|
||||||
|
"required_fields": "请填写所有必填字段"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"send_mail": {
|
||||||
|
"title": "发送邮件",
|
||||||
|
"description": "向所选或已筛选的用户发送邮件",
|
||||||
|
"subject": "主题",
|
||||||
|
"content": "内容",
|
||||||
|
"sending": "发送中...",
|
||||||
|
"send": "发送"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subscribe": {
|
"subscribe": {
|
||||||
@ -1991,7 +2030,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"template": {
|
"template": {
|
||||||
"button": "使用模板",
|
"button": "使用模板",
|
||||||
"tooltip": "使用默认模板",
|
"tooltip": "使用默认模板",
|
||||||
"content": "## 套餐特点\n\n- 流量:{{transfer}} GB\n- 速度:{{speed}} Mbps\n- 设备数:{{devices}}\n\n## 使用说明\n\n1. 套餐有效期 {{validity}} 天\n2. 流量{{reset_method}}重置\n3. 最多支持 {{capacity}} 个用户同时在线"
|
"content": "## 套餐详情\n\n- 流量:{{transfer}} GB\n- 速度限制:{{speed}} Mbps\n- 同时在线设备:{{devices}} 台\n\n## 服务说明\n\n1. 流量{{reset_method}}重置\n2. 支持多平台使用\n3. 7×24小时技术支持"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"force_update": {
|
"force_update": {
|
||||||
|
@ -113,5 +113,11 @@
|
|||||||
"QR Code": "QR Code",
|
"QR Code": "QR Code",
|
||||||
"Unlimited": "Unlimited",
|
"Unlimited": "Unlimited",
|
||||||
"Device Limit": "Device Limit",
|
"Device Limit": "Device Limit",
|
||||||
"Devices": "Devices"
|
"Devices": "Devices",
|
||||||
|
"No Limit": "No Limit",
|
||||||
|
"First Day of Month": "First Day of Month",
|
||||||
|
"Monthly": "Monthly",
|
||||||
|
"Never": "Never",
|
||||||
|
"First Day of Year": "First Day of Year",
|
||||||
|
"Yearly": "Yearly"
|
||||||
}
|
}
|
||||||
|
@ -113,5 +113,11 @@
|
|||||||
"QR Code": "二维码",
|
"QR Code": "二维码",
|
||||||
"Unlimited": "长期有效",
|
"Unlimited": "长期有效",
|
||||||
"Device Limit": "设备限制",
|
"Device Limit": "设备限制",
|
||||||
"Devices": "台设备"
|
"Devices": "台设备",
|
||||||
|
"No Limit": "不限制",
|
||||||
|
"First Day of Month": "每月1号",
|
||||||
|
"Monthly": "按月",
|
||||||
|
"Never": "不重置",
|
||||||
|
"First Day of Year": "每年1月1日",
|
||||||
|
"Yearly": "按年"
|
||||||
}
|
}
|
||||||
|
@ -113,5 +113,11 @@
|
|||||||
"QR Code": "二維碼",
|
"QR Code": "二維碼",
|
||||||
"Unlimited": "長期有效",
|
"Unlimited": "長期有效",
|
||||||
"Device Limit": "設備限制",
|
"Device Limit": "設備限制",
|
||||||
"Devices": "台設備"
|
"Devices": "台設備",
|
||||||
|
"No Limit": "不限制",
|
||||||
|
"First Day of Month": "每月1號",
|
||||||
|
"Monthly": "按月",
|
||||||
|
"Never": "不重置",
|
||||||
|
"First Day of Year": "每年1月1日",
|
||||||
|
"Yearly": "按年"
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"description": "Xboard",
|
"description": "Xboard",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"images": [
|
"images": [
|
||||||
"https://raw.githubusercontent.com/cedar2025/Xboard/new/docs/images/dashboard.png"
|
"https://raw.githubusercontent.com/cedar2025/Xboard/master/docs/images/user.png"
|
||||||
],
|
],
|
||||||
"configs": [
|
"configs": [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user