浏览代码

release: version 25.1.0 - The Restoration

Anankke 3 月之前
父节点
当前提交
f671938392
共有 100 个文件被更改,包括 3139 次插入1877 次删除
  1. 11 0
      .github/dependabot.yml
  2. 二进制
      .github/jetbrains.png
  3. 0 36
      .github/workflows/lint.yml
  4. 0 37
      .github/workflows/minify.yml
  5. 0 22
      .github/workflows/stale.yml
  6. 0 48
      .github/workflows/unit.yaml
  7. 2 0
      .gitignore
  8. 1 0
      SECURITY.md
  9. 2 1
      app/predefine.php
  10. 38 31
      app/routes.php
  11. 8 5
      composer.json
  12. 246 181
      composer.lock
  13. 6 32
      config/.config.example.php
  14. 32 8
      config/clients.json
  15. 317 128
      config/settings.json
  16. 0 10
      db/2022.12_to_2022.12.1.sql
  17. 46 19
      db/migrations/2023020100-init.php
  18. 42 0
      db/migrations/2024041000-add_syslog.php
  19. 29 0
      db/migrations/2024052400-add_ann_docs_status_sort.php
  20. 39 0
      db/migrations/2024061600-update_price_type.php
  21. 二进制
      public/images/alipay.png
  22. 32 0
      public/images/alipay.svg
  23. 60 0
      public/images/qq.svg
  24. 二进制
      public/images/qqpay.png
  25. 26 0
      public/images/tether.svg
  26. 二进制
      public/images/usdt.png
  27. 二进制
      public/images/wechat.png
  28. 16 0
      public/images/wechat.svg
  29. 1 1
      public/index.php
  30. 18 27
      resources/email/finance.tpl
  31. 12 14
      resources/email/footer.tpl
  32. 0 1
      resources/email/header.tpl
  33. 18 27
      resources/email/new_user.tpl
  34. 21 29
      resources/email/password_reset.tpl
  35. 18 27
      resources/email/test.tpl
  36. 32 41
      resources/email/traffic_report.tpl
  37. 20 29
      resources/email/verify_code.tpl
  38. 18 27
      resources/email/warn.tpl
  39. 0 1
      resources/locale/en-US.json
  40. 28 0
      resources/locale/en_US.php
  41. 0 1
      resources/locale/ja-JP.json
  42. 28 0
      resources/locale/ja_JP.php
  43. 0 1
      resources/locale/zh-CN.json
  44. 0 1
      resources/locale/zh-TW.json
  45. 28 0
      resources/locale/zh_CN.php
  46. 28 0
      resources/locale/zh_TW.php
  47. 48 23
      resources/views/tabler/admin/announcement/create.tpl
  48. 36 6
      resources/views/tabler/admin/announcement/edit.tpl
  49. 2 2
      resources/views/tabler/admin/announcement/index.tpl
  50. 6 6
      resources/views/tabler/admin/coupon.tpl
  51. 2 2
      resources/views/tabler/admin/detect.tpl
  52. 46 20
      resources/views/tabler/admin/docs/create.tpl
  53. 39 11
      resources/views/tabler/admin/docs/edit.tpl
  54. 2 2
      resources/views/tabler/admin/docs/index.tpl
  55. 18 37
      resources/views/tabler/admin/footer.tpl
  56. 2 2
      resources/views/tabler/admin/giftcard.tpl
  57. 8 8
      resources/views/tabler/admin/index.tpl
  58. 4 4
      resources/views/tabler/admin/log/sub.tpl
  59. 2 2
      resources/views/tabler/admin/node/edit.tpl
  60. 4 4
      resources/views/tabler/admin/node/index.tpl
  61. 2 2
      resources/views/tabler/admin/order/index.tpl
  62. 4 4
      resources/views/tabler/admin/product/index.tpl
  63. 25 15
      resources/views/tabler/admin/setting/billing.tpl
  64. 47 7
      resources/views/tabler/admin/setting/captcha.tpl
  65. 37 10
      resources/views/tabler/admin/setting/email.tpl
  66. 214 203
      resources/views/tabler/admin/setting/im.tpl
  67. 297 0
      resources/views/tabler/admin/setting/llm.tpl
  68. 3 3
      resources/views/tabler/admin/setting/ref.tpl
  69. 0 7
      resources/views/tabler/admin/setting/reg.tpl
  70. 22 17
      resources/views/tabler/admin/setting/sub.tpl
  71. 1 1
      resources/views/tabler/admin/setting/support.tpl
  72. 68 0
      resources/views/tabler/admin/syslog/index.tpl
  73. 49 0
      resources/views/tabler/admin/syslog/view.tpl
  74. 2 2
      resources/views/tabler/admin/ticket/index.tpl
  75. 30 78
      resources/views/tabler/admin/ticket/view.tpl
  76. 120 91
      resources/views/tabler/admin/user/edit.tpl
  77. 4 4
      resources/views/tabler/admin/user/index.tpl
  78. 3 0
      resources/views/tabler/captcha/ajax.tpl
  79. 4 1
      resources/views/tabler/captcha/div.tpl
  80. 11 1
      resources/views/tabler/captcha/js.tpl
  81. 9 18
      resources/views/tabler/datatable.tpl
  82. 3 2
      resources/views/tabler/footer.tpl
  83. 4 8
      resources/views/tabler/gateway/epay.tpl
  84. 3 4
      resources/views/tabler/gateway/f2f.tpl
  85. 3 16
      resources/views/tabler/gateway/paypal.tpl
  86. 10 8
      resources/views/tabler/gateway/stripe.tpl
  87. 18 2
      resources/views/tabler/header.tpl
  88. 0 1
      resources/views/tabler/live_chat.tpl
  89. 2 2
      resources/views/tabler/tinymce.tpl
  90. 1 1
      resources/views/tabler/user/docs/index.tpl
  91. 133 104
      resources/views/tabler/user/edit.tpl
  92. 347 163
      resources/views/tabler/user/index.tpl
  93. 1 1
      resources/views/tabler/user/invoice/index.tpl
  94. 109 113
      resources/views/tabler/user/invoice/view.tpl
  95. 35 0
      resources/views/tabler/user/money.tpl
  96. 16 53
      resources/views/tabler/user/order/create.tpl
  97. 1 1
      resources/views/tabler/user/order/index.tpl
  98. 2 0
      resources/views/tabler/user/order/view.tpl
  99. 56 19
      resources/views/tabler/user/profile.tpl
  100. 1 1
      resources/views/tabler/user/rate.tpl

+ 11 - 0
.github/dependabot.yml

@@ -0,0 +1,11 @@
+version: 2
+updates:
+  - package-ecosystem: "composer"
+    directory: "/"
+    schedule:
+      interval: "monthly"
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"

二进制
.github/jetbrains.png


+ 0 - 36
.github/workflows/lint.yml

@@ -1,36 +0,0 @@
-name: Lint PHP Code
-on:
-  push:
-  pull_request:
-    types: [opened, reopened, synchronize]
-
-jobs:
-  php-file-changed:
-    runs-on: ubuntu-latest
-    outputs:
-      php: ${{ steps.filter.outputs.php }}
-    steps:
-    - uses: actions/checkout@v4
-      with:
-        fetch-depth: 0
-    - uses: dorny/paths-filter@v3
-      id: filter
-      with:
-        filters: |
-          php:
-            - '**/*.php'
-  lint:
-    needs: php-file-changed
-    if: ${{ needs.php-file-changed.outputs.php == 'true' }}
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-      - uses: shivammathur/setup-php@v2
-        with:
-          php-version: 8.3
-      - run: |
-          composer install --no-interaction --no-progress --no-suggest --quiet
-          php vendor/bin/phpinsights analyse --no-interaction --format=github-action \
-          --min-style=100 --min-architecture=100 --min-quality=100

+ 0 - 37
.github/workflows/minify.yml

@@ -1,37 +0,0 @@
-name: Auto Minify CSS/JS file
-
-on:
-  push:
-    paths:
-      - 'public/assets/css/**'
-      - 'public/assets/js/**'
-      - 'public/theme/**'
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-      - name: Auto minify tabler theme CSS file
-        uses: nizarmah/auto-minify@v3
-        with:
-          directory: 'public/theme/tabler/css'
-      - name: Auto minify tabler theme JS file
-        uses: nizarmah/auto-minify@v3
-        with:
-          directory: 'public/theme/tabler/js'
-      - name: Auto minify asset CSS file
-        uses: nizarmah/auto-minify@v3
-        with:
-          directory: 'public/assets/css'
-      - name: Auto minify asset JS file
-        uses: nizarmah/auto-minify@v3
-        with:
-          directory: 'public/assets/js'
-      - name: Auto committing minified files
-        uses: stefanzweifel/git-auto-commit-action@v5
-        with:
-          repository: 'public'
-          commit_message: "chore: auto minified theme CSS/JS files"

+ 0 - 22
.github/workflows/stale.yml

@@ -1,22 +0,0 @@
-name: Mark stale issues and pull requests
-
-on:
-  schedule:
-  - cron: '0 0 * * *'
-
-jobs:
-  stale:
-    runs-on: ubuntu-latest
-    permissions:
-      issues: write
-      pull-requests: write
-    steps:
-    - uses: actions/stale@v9
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        days-before-stale: 180
-        days-before-close: 14
-        stale-issue-message: 'This issue has had no activity for over 180 days and will be closed in 14 days.'
-        stale-pr-message: 'This PR has had no activity for over 180 days and will be closed in 14 days.'
-        stale-issue-label: 'staled'
-        stale-pr-label: 'staled'

+ 0 - 48
.github/workflows/unit.yaml

@@ -1,48 +0,0 @@
-name: PHP Unit Tests
-
-on:
-  push:
-  pull_request:
-    types: [ opened, reopened, synchronize ]
-
-jobs:
-  php-file-changed:
-    runs-on: ubuntu-latest
-    outputs:
-      php: ${{ steps.filter.outputs.php }}
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-      - uses: dorny/paths-filter@v3
-        id: filter
-        with:
-          filters: |
-            php:
-              - '**/*.php'
-
-  php82:
-    needs: php-file-changed
-    if: ${{ needs.php-file-changed.outputs.php == 'true' }}
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - uses: shivammathur/setup-php@v2
-      with:
-        php-version: 8.2
-    - run: |
-        composer install --no-interaction --no-progress --no-suggest --quiet
-        php vendor/bin/phpunit
-
-  php83:
-    needs: php-file-changed
-    if: ${{ needs.php-file-changed.outputs.php == 'true' }}
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - uses: shivammathur/setup-php@v2
-        with:
-          php-version: 8.3
-      - run: |
-          composer install --no-interaction --no-progress --no-suggest --quiet
-          php vendor/bin/phpunit

+ 2 - 0
.gitignore

@@ -1,3 +1,4 @@
+*~
 .idea
 .htaccess
 */.DS_Store
@@ -48,3 +49,4 @@ package-lock.json
 
 *.cache
 
+storage/LocalClientVersion.json

+ 1 - 0
SECURITY.md

@@ -0,0 +1 @@
+Contact [email protected] or use Github Security Advisories feature to report a vulnerability, do not use issue tracker.

+ 2 - 1
app/predefine.php

@@ -4,4 +4,5 @@ declare(strict_types=1);
 
 // Global constants
 const BASE_PATH = __DIR__ . '/..';
-const VERSION = '2024.1';
+const VERSION = '25.1.0';
+const VERSION_CODENAME = 'The Restoration';

+ 38 - 31
app/routes.php

@@ -24,7 +24,6 @@ return static function (Slim\App $app): void {
     $app->get('/oauth/{type}', App\Controllers\OAuthController::class . ':index');
     // 通用订阅
     $app->get('/sub/{token}/{subtype}', App\Controllers\SubController::class . ':index');
-
     // User
     $app->group('/user', static function (RouteCollectorProxy $group): void {
         $group->get('', App\Controllers\UserController::class . ':index');
@@ -37,7 +36,7 @@ return static function (Slim\App $app): void {
         $group->get('/docs', App\Controllers\User\DocsController::class . ':index');
         $group->get('/docs/{id:[0-9]+}/view', App\Controllers\User\DocsController::class . ':detail');
         // 个人资料
-        $group->get('/profile', App\Controllers\UserController::class . ':profile');
+        $group->get('/profile', App\Controllers\User\ProfileController::class . ':index');
         // Invite
         $group->get('/invite', App\Controllers\User\InviteController::class . ':index');
         $group->post('/invite/reset', App\Controllers\User\InviteController::class . ':reset');
@@ -56,33 +55,28 @@ return static function (Slim\App $app): void {
         $group->get('/ticket/create', App\Controllers\User\TicketController::class . ':create');
         $group->post('/ticket', App\Controllers\User\TicketController::class . ':add');
         $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\User\TicketController::class . ':detail');
-        $group->put('/ticket/{id:[0-9]+}', App\Controllers\User\TicketController::class . ':update');
+        $group->post('/ticket/{id:[0-9]+}', App\Controllers\User\TicketController::class . ':reply');
         // 资料编辑
         $group->get('/edit', App\Controllers\User\InfoController::class . ':index');
-        $group->post('/email', App\Controllers\User\InfoController::class . ':updateEmail');
-        $group->post('/username', App\Controllers\User\InfoController::class . ':updateUsername');
-        $group->post('/unbind_im', App\Controllers\User\InfoController::class . ':unbindIM');
-        $group->post('/password', App\Controllers\User\InfoController::class . ':updatePassword');
-        $group->post('/passwd_reset', App\Controllers\User\InfoController::class . ':resetPasswd');
-        $group->post('/apitoken_reset', App\Controllers\User\InfoController::class . ':resetApiToken');
-        $group->post('/method', App\Controllers\User\InfoController::class . ':updateMethod');
-        $group->post('/url_reset', App\Controllers\User\InfoController::class . ':resetURL');
-        $group->post('/daily_mail', App\Controllers\User\InfoController::class . ':updateDailyMail');
-        $group->post('/contact_method', App\Controllers\User\InfoController::class . ':updateContactMethod');
-        $group->post('/theme', App\Controllers\User\InfoController::class . ':updateTheme');
-        $group->post('/kill', App\Controllers\User\InfoController::class . ':sendToGulag');
+        $group->post('/edit/email', App\Controllers\User\InfoController::class . ':updateEmail');
+        $group->post('/edit/username', App\Controllers\User\InfoController::class . ':updateUsername');
+        $group->post('/edit/unbind_im', App\Controllers\User\InfoController::class . ':unbindIm');
+        $group->post('/edit/password', App\Controllers\User\InfoController::class . ':updatePassword');
+        $group->post('/edit/passwd_reset', App\Controllers\User\InfoController::class . ':resetPasswd');
+        $group->post('/edit/apitoken_reset', App\Controllers\User\InfoController::class . ':resetApiToken');
+        $group->post('/edit/method', App\Controllers\User\InfoController::class . ':updateMethod');
+        $group->post('/edit/url_reset', App\Controllers\User\InfoController::class . ':resetUrl');
+        $group->post('/edit/daily_mail', App\Controllers\User\InfoController::class . ':updateDailyMail');
+        $group->post('/edit/contact_method', App\Controllers\User\InfoController::class . ':updateContactMethod');
+        $group->post('/edit/theme', App\Controllers\User\InfoController::class . ':updateTheme');
+        $group->post('/edit/theme_mode', App\Controllers\User\InfoController::class . ':updateThemeMode');
+        $group->post('/edit/kill', App\Controllers\User\InfoController::class . ':sendToGulag');
         // 发送验证邮件
-        $group->post('/send', App\Controllers\AuthController::class . ':sendVerify');
+        $group->post('/edit/send', App\Controllers\AuthController::class . ':sendVerify');
         // MFA
         $group->post('/ga_check', App\Controllers\User\MFAController::class . ':checkGa');
         $group->post('/ga_set', App\Controllers\User\MFAController::class . ':setGa');
         $group->post('/ga_reset', App\Controllers\User\MFAController::class . ':resetGa');
-        // 深色模式切换
-        $group->post('/switch_theme_mode', App\Controllers\UserController::class . ':switchThemeMode');
-        // 订阅记录
-        $group->get('/subscribe', App\Controllers\User\SubLogController::class . ':index');
-        // 流量记录
-        $group->get('/traffic', App\Controllers\User\TrafficLogController::class . ':index');
         // 账户余额
         $group->get('/money', App\Controllers\User\MoneyController::class . ':index');
         $group->post('/giftcard', App\Controllers\User\MoneyController::class . ':applyGiftCard');
@@ -114,9 +108,7 @@ return static function (Slim\App $app): void {
     $app->group('/payment', static function (RouteCollectorProxy $group): void {
         $group->get('/notify/{type}', App\Services\Payment::class . ':notify');
         $group->post('/notify/{type}', App\Services\Payment::class . ':notify');
-        $group->post('/status/{type}', App\Services\Payment::class . ':getStatus');
     });
-
     // Auth
     $app->group('/auth', static function (RouteCollectorProxy $group): void {
         $group->get('/login', App\Controllers\AuthController::class . ':login');
@@ -126,15 +118,13 @@ return static function (Slim\App $app): void {
         $group->post('/send', App\Controllers\AuthController::class . ':sendVerify');
         $group->get('/logout', App\Controllers\AuthController::class . ':logout');
     })->add(new Guest());
-
     // Password
     $app->group('/password', static function (RouteCollectorProxy $group): void {
         $group->get('/reset', App\Controllers\PasswordController::class . ':reset');
         $group->post('/reset', App\Controllers\PasswordController::class . ':handleReset');
         $group->get('/token/{token}', App\Controllers\PasswordController::class . ':token');
-        $group->post('/token/{token}', App\Controllers\PasswordController::class . ':handleToken');
+        $group->post('/token', App\Controllers\PasswordController::class . ':handleToken');
     })->add(new Guest());
-
     // Admin
     $app->group('/admin', static function (RouteCollectorProxy $group): void {
         $group->get('', App\Controllers\AdminController::class . ':index');
@@ -160,9 +150,9 @@ return static function (Slim\App $app): void {
         $group->get('/ticket', App\Controllers\Admin\TicketController::class . ':index');
         $group->post('/ticket', App\Controllers\Admin\TicketController::class . ':add');
         $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\Admin\TicketController::class . ':detail');
-        $group->put('/ticket/{id:[0-9]+}/close', App\Controllers\Admin\TicketController::class . ':close');
-        $group->put('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':update');
-        $group->put('/ticket/{id:[0-9]+}/ai', App\Controllers\Admin\TicketController::class . ':updateAI');
+        $group->post('/ticket/{id:[0-9]+}/close', App\Controllers\Admin\TicketController::class . ':close');
+        $group->post('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':reply');
+        $group->post('/ticket/{id:[0-9]+}/llm_reply', App\Controllers\Admin\TicketController::class . ':llmReply');
         $group->delete('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':delete');
         $group->post('/ticket/ajax', App\Controllers\Admin\TicketController::class . ':ajax');
         // Ann
@@ -225,6 +215,10 @@ return static function (Slim\App $app): void {
         // 支付网关日志
         $group->get('/gateway', App\Controllers\Admin\PaylistController::class . ':index');
         $group->post('/gateway/ajax', App\Controllers\Admin\PaylistController::class . ':ajax');
+        // 系统日志
+        $group->get('/syslog', App\Controllers\Admin\SysLogController::class . ':index');
+        $group->get('/syslog/{id:[0-9]+}/view', App\Controllers\Admin\SysLogController::class . ':detail');
+        $group->post('/syslog/ajax', App\Controllers\Admin\SysLogController::class . ':ajax');
         // 系统状态
         $group->get('/system', App\Controllers\Admin\SystemController::class . ':index');
         $group->post('/system/check_update', App\Controllers\Admin\SystemController::class . ':checkUpdate');
@@ -235,6 +229,10 @@ return static function (Slim\App $app): void {
             '/setting/billing/set_stripe_webhook',
             App\Controllers\Admin\Setting\BillingController::class . ':setStripeWebhook'
         );
+        $group->post(
+            '/setting/billing/set_paypal_webhook',
+            App\Controllers\Admin\Setting\BillingController::class . ':setPaypalWebhook'
+        );
         $group->get('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':index');
         $group->post('/setting/captcha', App\Controllers\Admin\Setting\CaptchaController::class . ':save');
         $group->get('/setting/cron', App\Controllers\Admin\Setting\CronController::class . ':index');
@@ -245,6 +243,16 @@ return static function (Slim\App $app): void {
         $group->post('/setting/feature', App\Controllers\Admin\Setting\FeatureController::class . ':save');
         $group->get('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':index');
         $group->post('/setting/im', App\Controllers\Admin\Setting\ImController::class . ':save');
+        $group->post(
+            '/setting/im/reset_webhook_token/{type}',
+            App\Controllers\Admin\Setting\ImController::class . ':resetWebhookToken'
+        );
+        $group->post(
+            '/setting/im/set_webhook/{type}',
+            App\Controllers\Admin\Setting\ImController::class . ':setWebhook'
+        );
+        $group->get('/setting/llm', App\Controllers\Admin\Setting\LlmController::class . ':index');
+        $group->post('/setting/llm', App\Controllers\Admin\Setting\LlmController::class . ':save');
         $group->get('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':index');
         $group->post('/setting/ref', App\Controllers\Admin\Setting\RefController::class . ':save');
         $group->get('/setting/reg', App\Controllers\Admin\Setting\RegController::class . ':index');
@@ -297,7 +305,6 @@ return static function (Slim\App $app): void {
         $group->post('/invoice/{id:[0-9]+}/mark_paid', App\Controllers\Admin\InvoiceController::class . ':markPaid');
         $group->post('/invoice/ajax', App\Controllers\Admin\InvoiceController::class . ':ajax');
     })->add(new Admin());
-
     // WebAPI
     $app->group('/mod_mu', static function (RouteCollectorProxy $group): void {
         // 节点

+ 8 - 5
composer.json

@@ -17,6 +17,7 @@
         "alibabacloud/dm-20170622": "^1",
         "alipaysdk/openapi": "*@dev",
         "aws/aws-sdk-php": "^3",
+        "danielsreichenbach/geoip2-update": "^2",
         "geoip2/geoip2": "^3",
         "guzzlehttp/guzzle": "^7",
         "guzzlehttp/psr7": "^2",
@@ -24,23 +25,25 @@
         "illuminate/pagination": "^11",
         "irazasyed/telegram-bot-sdk": "^3",
         "lcobucci/jwt": "^5",
+        "league/html-to-markdown": "^5",
         "mailchimp/transactional": "^1",
         "mailgun/mailgun-php": "^4",
+        "monolog/monolog": "^3",
         "nikolaposa/rate-limit": "^3",
         "openai-php/client": "^0",
         "phpmailer/phpmailer": "^6",
         "postal/postal": "^2",
         "ramsey/uuid": "^4",
+        "resend/resend-php": "^0.12.0",
         "sendgrid/sendgrid": "^8",
         "sentry/sdk": "^4",
         "slim/http": "^1",
         "slim/slim": "^4",
         "smarty/smarty": "^5",
         "srmklive/paypal": "^3",
-        "stripe/stripe-php": "^14",
+        "stripe/stripe-php": "^15",
         "symfony/http-client": "^7",
         "symfony/translation": "^7",
-        "tronovav/geoip2-update": "^2",
         "twig/twig": "^3",
         "vectorface/googleauthenticator": "^3",
         "voku/anti-xss": "^4"
@@ -61,14 +64,14 @@
     "require-dev": {
         "dg/bypass-finals": "^1",
         "nunomaduro/phpinsights": "*",
-        "phpunit/phpunit": "^10"
+        "phpunit/phpunit": "^10|^11"
     },
     "scripts": {
         "update-dev-windows": [
-            "composer update --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis --ignore-platform-req=ext-yaml"
+            "composer update --ignore-platform-req=ext-posix"
         ],
         "install-dev-windows": [
-            "composer install --ignore-platform-req=ext-posix --ignore-platform-req=ext-redis --ignore-platform-req=ext-yaml"
+            "composer install --ignore-platform-req=ext-posix"
         ]
     },
     "type": "project"

文件差异内容过多而无法显示
+ 246 - 181
composer.lock


+ 6 - 32
config/.config.example.php

@@ -67,8 +67,7 @@ $_ENV['mail_filter_list'] = [];
 $_ENV['class_expire_reset_traffic'] = 0; // 等级到期时重置为的流量值,单位GB,小于0时不重置
 $_ENV['enable_kill'] = false;            // 是否允许用户注销账户
 $_ENV['enable_change_email'] = true;     // 是否允许用户更改賬戶郵箱
-
-#用户流量余量不足邮件提醒
+#用户流量不足提醒
 $_ENV['notify_limit_mode'] = false; // false为关闭,per为按照百分比提醒,mb为按照固定剩余流量提醒
 $_ENV['notify_limit_value'] = 500;  // 当上一项为per时,此处填写百分比;当上一项为mb时,此处填写流量
 
@@ -87,9 +86,8 @@ $_ENV['auto_detect_ban_time'] = 60;          // 每次封禁的时长 (分钟)
 //节点检测---------------------------------------------------------------------------------------------------------------
 //TODO: move these settings to DB
 #GFW检测
-$_ENV['detect_gfw_port'] = 443;                                                    //所有节点服务器都打开的TCP端口
-$_ENV['detect_gfw_url'] = 'http://example.com:8080/v1/tcping?ip={ip}&port={port}'; //检测节点是否被gfw墙了的API的URL
-
+$_ENV['detect_gfw_port'] = 443;                                                //所有节点服务器都打开的TCP端口
+$_ENV['detect_gfw_url'] = 'https://example.com/v1/tcping?ip={ip}&port={port}'; // https://github.com/SSPanel-NeXT/NetStatus-API-Go
 #离线检测
 $_ENV['enable_detect_offline'] = true;
 
@@ -100,41 +98,17 @@ $_ENV['rememberMeDuration'] = 7;          //登录时记住账号时长天数
 $_ENV['timeZone'] = 'Asia/Shanghai';        //需使用 PHP 兼容的时区格式
 $_ENV['theme'] = 'tabler';                //默认主题
 $_ENV['locale'] = 'zh-CN';                //默认语言
-$_ENV['jump_delay'] = 1200;               //跳转延时,单位ms
+$_ENV['jump_delay'] = 1000;               //跳转延时,单位ms
 $_ENV['keep_connect'] = false;            // 流量耗尽用户限速至 1Mbps
 
-//Generative AI---------------------------------------------------------------------------------------------------------
-//Large language model powered ticket reply and more
-//TODO: move these settings to DB
-$_ENV['llm_backend'] = 'openai'; // openai/google-ai/huggingface/cf-workers-ai/anthropic
-#OpenAI ChatGPT
-$_ENV['openai_api_key'] = '';
-$_ENV['openai_model'] = 'gpt-4-turbo-preview';
-#Google AI API
-$_ENV['google_ai_api_key'] = '';
-$_ENV['google_ai_model_id'] = 'gemini-1.5-pro-latest';
-#Vertex AI API
-$_ENV['vertex_ai_access_token'] = '';
-$_ENV['vertex_ai_location'] = 'us-central1';
-$_ENV['vertex_ai_model_id'] = 'gemini-1.0-pro';
-$_ENV['vertex_ai_project_id'] = '';
-#Hugging Face Inference API
-$_ENV['huggingface_api_key'] = '';
-$_ENV['huggingface_endpoint_url'] = '';
-#Cloudflare Workers AI
-$_ENV['cf_workers_ai_account_id'] = '';
-$_ENV['cf_workers_ai_api_token'] = '';
-$_ENV['cf_workers_ai_model_id'] = '@cf/meta/llama-2-7b-chat-int8';
-#Anthropic
-$_ENV['anthropic_api_key'] = '';
-$_ENV['anthropic_model_id'] = 'claude-3-opus-20240229';
-
 //Other-----------------------------------------------------------------------------------------------------------------
 // cdn.jsdelivr.net / fastly.jsdelivr.net / testingcf.jsdelivr.net
 $_ENV['jsdelivr_url'] = 'fastly.jsdelivr.net';
 // https://sentry.io for production debugging
 $_ENV['sentry_dsn'] = '';
 // Maxmind GeoIP2 database
+//TODO: move these settings to DB
+$_ENV['maxmind_account_id'] = '';
 $_ENV['maxmind_license_key'] = '';
 $_ENV['geoip_locale'] = 'en';
 // ClientDownload 命令解决 API 访问频率高而被限制使用的 Github access token

+ 32 - 8
config/clients.json

@@ -1,22 +1,22 @@
 {
     "clients":[
         {
-            "name": "Clash Verge",
+            "name": "Clash Nyanpasu",
             "tagMethod": "github_release",
-            "gitRepo": "clash-verge-rev/clash-verge-rev",
+            "gitRepo": "LibNyanpasu/clash-nyanpasu",
             "savePath": "public/clients/",
             "downloads": [
                 {
-                    "sourceName": "Clash.Verge_%tagName1%_x64-setup.exe",
-                    "saveName": "Clash.Verge.exe"
+                    "sourceName": "Clash.Nyanpasu_%tagName1%_x64-setup.exe",
+                    "saveName": "Clash.Nyanpasu.exe"
                 },
                 {
-                    "sourceName": "Clash.Verge_%tagName1%_aarch64.dmg",
-                    "saveName": "Clash.Verge_aarch64.dmg"
+                    "sourceName": "clash-nyanpasu_%tagName1%_amd64.AppImage",
+                    "saveName": "Clash.Nyanpasu.AppImage"
                 },
                 {
-                    "sourceName": "clash-verge_%tagName1%_amd64.AppImage.tar.gz",
-                    "saveName": "Clash.Verge.AppImage.tar.gz"
+                    "sourceName": "Clash.Nyanpasu_%tagName1%_aarch64.dmg",
+                    "saveName": "Clash.Nyanpasu_aarch64.dmg"
                 }
             ]
         },
@@ -47,6 +47,30 @@
                     "saveName": "SFM.dmg"
                 }
             ]
+        },
+        {
+            "name": "Hiddify",
+            "tagMethod": "github_release",
+            "gitRepo": "hiddify/hiddify-next",
+            "savePath": "public/clients/",
+            "downloads": [
+                {
+                    "sourceName": "Hiddify-Android-universal.apk",
+                    "saveName": "Hiddify.apk"
+                },
+                {
+                    "sourceName": "Hiddify-Linux-x64.AppImage",
+                    "saveName": "Hiddify.AppImage"
+                },
+                {
+                    "sourceName": "Hiddify-MacOS.dmg",
+                    "saveName": "Hiddify.dmg"
+                },
+                {
+                    "sourceName": "Hiddify-Windows-Setup-x64.exe",
+                    "saveName": "Hiddify.exe"
+                }
+            ]
         }
     ]
 }

+ 317 - 128
config/settings.json

@@ -575,6 +575,24 @@
         "default": "",
         "mark": "AlibabaCloud DM From Alias"
     },
+    {
+        "item": "resend_api_key",
+        "value": "",
+        "class": "email",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Resend API Key"
+    },
+    {
+        "item": "resend_from",
+        "value": "",
+        "class": "email",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Resend From"
+    },
     {
         "item": "email_verify_code_ttl",
         "value": "3600",
@@ -711,211 +729,175 @@
         "mark": "hCaptcha Secret"
     },
     {
-        "item": "enable_telegram",
-        "value": "0",
-        "class": "im",
-        "is_public": 1,
-        "type": "bool",
-        "default": "0",
-        "mark": "是否启用Telegram机器人"
-    },
-    {
-        "item": "telegram_token",
+        "item": "recaptcha_enterprise_key_id",
         "value": "",
-        "class": "im",
+        "class": "captcha",
         "is_public": 0,
         "type": "string",
         "default": "",
-        "mark": "Telegram Bot Token"
+        "mark": "reCAPTCHA Enterprise Key"
     },
     {
-        "item": "telegram_chatid",
-        "value": "-1",
-        "class": "im",
+        "item": "recaptcha_enterprise_project_id",
+        "value": "",
+        "class": "captcha",
         "is_public": 0,
-        "type": "int",
-        "default": "-1",
-        "mark": "Telegram 群组会话 ID"
-    },
-    {
-        "item": "telegram_bot",
-        "value": "_bot",
-        "class": "im",
-        "is_public": 1,
         "type": "string",
-        "default": "_bot",
-        "mark": "Telegram 机器人账号"
+        "default": "",
+        "mark": "reCAPTCHA Enterprise Project ID"
     },
     {
-        "item": "telegram_request_token",
+        "item": "recaptcha_enterprise_api_key",
         "value": "",
-        "class": "im",
+        "class": "captcha",
         "is_public": 0,
         "type": "string",
         "default": "",
-        "mark": "Telegram Webhook 密钥"
+        "mark": "reCAPTCHA Enterprise API Key"
     },
     {
-        "item": "telegram_add_node",
+        "item": "im_bot_group_notify_add_node",
         "value": "1",
         "class": "im",
         "is_public": 0,
         "type": "bool",
         "default": "1",
-        "mark": "是否启用Telegram机器人添加节点通知"
-    },
-    {
-        "item": "telegram_add_node_text",
-        "value": "%node_name% 已被添加",
-        "class": "im",
-        "is_public": 0,
-        "type": "string",
-        "default": "%node_name% 已被添加",
-        "mark": "Telegram机器人添加节点通知文本"
+        "mark": "Enable IM bot group add node notify"
     },
     {
-        "item": "telegram_update_node",
-        "value": "1",
+        "item": "im_bot_group_notify_update_node",
+        "value": "0",
         "class": "im",
         "is_public": 0,
         "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人修改节点通知"
+        "default": "0",
+        "mark": "Enable IM bot group update node notify"
     },
     {
-        "item": "telegram_update_node_text",
-        "value": "%node_name% 已被修改",
+        "item": "im_bot_group_notify_delete_node",
+        "value": "0",
         "class": "im",
         "is_public": 0,
-        "type": "string",
-        "default": "%node_name% 已被修改",
-        "mark": "Telegram机器人修改节点通知文本"
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable IM bot group delete node notify"
     },
     {
-        "item": "telegram_delete_node",
-        "value": "1",
+        "item": "im_bot_group_notify_node_gfwed",
+        "value": "0",
         "class": "im",
         "is_public": 0,
         "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人删除节点通知"
+        "default": "0",
+        "mark": "Enable IM bot group node gfwed notify"
     },
     {
-        "item": "telegram_delete_node_text",
-        "value": "%node_name% 被刪除了",
+        "item": "im_bot_group_notify_node_ungfwed",
+        "value": "0",
         "class": "im",
         "is_public": 0,
-        "type": "string",
-        "default": "%node_name% 被刪除了",
-        "mark": "Telegram机器人删除节点通知文本"
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable IM bot group node ungfwed notify"
     },
     {
-        "item": "telegram_node_gfwed",
-        "value": "1",
+        "item": "im_bot_group_notify_node_online",
+        "value": "0",
         "class": "im",
         "is_public": 0,
         "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人节点被墙通知"
+        "default": "0",
+        "mark": "Enable IM bot group node online notify"
     },
     {
-        "item": "telegram_node_gfwed_text",
-        "value": "喵喵喵~ %node_name% 节点被墙了喵~",
+        "item": "im_bot_group_notify_node_offline",
+        "value": "0",
         "class": "im",
         "is_public": 0,
-        "type": "string",
-        "default": "喵喵喵~ %node_name% 节点被墙了喵~",
-        "mark": "Telegram机器人节点被墙通知"
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable IM bot group node offline notify"
     },
     {
-        "item": "telegram_node_ungfwed",
+        "item": "im_bot_group_notify_daily_job",
         "value": "1",
         "class": "im",
         "is_public": 0,
         "type": "bool",
         "default": "1",
-        "mark": "是否启用Telegram机器人节点被墙恢复通知"
+        "mark": "Enable IM bot group daily job notify"
     },
     {
-        "item": "telegram_node_ungfwed_text",
-        "value": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
+        "item": "im_bot_group_notify_diary",
+        "value": "0",
         "class": "im",
         "is_public": 0,
-        "type": "string",
-        "default": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
-        "mark": "Telegram机器人节点被墙恢复文本"
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable IM bot group diary notify"
     },
     {
-        "item": "telegram_node_online",
+        "item": "im_bot_group_notify_ann_create",
         "value": "1",
         "class": "im",
         "is_public": 0,
         "type": "bool",
         "default": "1",
-        "mark": "是否启用Telegram机器人节点恢复上线通知"
-    },
-    {
-        "item": "telegram_node_online_text",
-        "value": "%node_name% 被修好了",
-        "class": "im",
-        "is_public": 0,
-        "type": "string",
-        "default": "%node_name% 被修好了",
-        "mark": "Telegram机器人节点恢复上线通知文本"
+        "mark": "Enable IM bot group notify when new announcement is created"
     },
     {
-        "item": "telegram_node_offline",
-        "value": "1",
+        "item": "im_bot_group_notify_ann_update",
+        "value": "0",
         "class": "im",
         "is_public": 0,
         "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人节点离线通知"
+        "default": "0",
+        "mark": "Enable IM bot group notify when announcement is updated"
     },
     {
-        "item": "telegram_node_offline_text",
-        "value": "%node_name% 出现了一些故障",
+        "item": "telegram_token",
+        "value": "",
         "class": "im",
         "is_public": 0,
         "type": "string",
-        "default": "%node_name% 出现了一些故障",
-        "mark": "Telegram机器人节点离线通知文本"
+        "default": "",
+        "mark": "Telegram Bot Token"
     },
     {
-        "item": "telegram_daily_job",
-        "value": "1",
+        "item": "telegram_webhook_token",
+        "value": "",
         "class": "im",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人每日任务通知"
+        "type": "string",
+        "default": "",
+        "mark": "Telegram Webhook Token"
     },
     {
-        "item": "telegram_daily_job_text",
-        "value": "成功执行每日任务",
+        "item": "telegram_bot",
+        "value": "_bot",
         "class": "im",
-        "is_public": 0,
+        "is_public": 1,
         "type": "string",
-        "default": "成功执行每日任务",
-        "mark": "Telegram机器人每日任务通知文本"
+        "default": "_bot",
+        "mark": "Telegram Bot Account Username"
     },
     {
-        "item": "telegram_diary",
-        "value": "1",
+        "item": "telegram_chatid",
+        "value": "-1",
         "class": "im",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人系统运行状况通知"
+        "type": "int",
+        "default": "-1",
+        "mark": "Telegram Chat ID"
     },
     {
-        "item": "telegram_diary_text",
-        "value": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
+        "item": "enable_telegram_group_notify",
+        "value": "0",
         "class": "im",
-        "is_public": 0,
-        "type": "string",
-        "default": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
-        "mark": "Telegram机器人系统运行状况通知文本"
+        "is_public": 1,
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable Telegram group notify"
     },
     {
         "item": "telegram_unbind_kick_member",
@@ -935,15 +917,6 @@
         "default": "0",
         "mark": "是否仅允许已绑定Telegram账户的用户加入群组"
     },
-    {
-        "item": "enable_welcome_message",
-        "value": "0",
-        "class": "im",
-        "is_public": 0,
-        "type": "bool",
-        "default": "0",
-        "mark": "Telegram 机器人发送欢迎消息"
-    },
     {
         "item": "telegram_group_quiet",
         "value": "1",
@@ -981,13 +954,13 @@
         "mark": "允许任意未知的命令触发 /help 的回复"
     },
     {
-        "item": "user_not_bind_reply",
-        "value": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
+        "item": "enable_welcome_message",
+        "value": "0",
         "class": "im",
         "is_public": 0,
-        "type": "string",
-        "default": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
-        "mark": "未绑定账户的回复"
+        "type": "bool",
+        "default": "0",
+        "mark": "Telegram 机器人发送欢迎消息"
     },
     {
         "item": "discord_bot_token",
@@ -1025,6 +998,24 @@
         "default": "0",
         "mark": "Discord Guild ID"
     },
+    {
+        "item": "discord_channel_id",
+        "value": "0",
+        "class": "im",
+        "is_public": 0,
+        "type": "int",
+        "default": "0",
+        "mark": "Discord Channel ID"
+    },
+    {
+        "item": "enable_discord_channel_notify",
+        "value": "0",
+        "class": "im",
+        "is_public": 1,
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable Discord channel notify"
+    },
     {
         "item": "slack_token",
         "value": "",
@@ -1061,6 +1052,24 @@
         "default": "",
         "mark": "Slack Team ID"
     },
+    {
+        "item": "slack_channel_id",
+        "value": "0",
+        "class": "im",
+        "is_public": 0,
+        "type": "int",
+        "default": "0",
+        "mark": "Slack Channel ID"
+    },
+    {
+        "item": "enable_slack_channel_notify",
+        "value": "0",
+        "class": "im",
+        "is_public": 1,
+        "type": "bool",
+        "default": "0",
+        "mark": "Enable Slack channel notify"
+    },
     {
         "item": "crisp_id",
         "value": "",
@@ -1627,5 +1636,185 @@
         "type": "string",
         "default": "",
         "mark": "cryptomus_currency"
+    },
+    {
+        "item": "llm_backend",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "LLM Backend"
+    },
+    {
+        "item": "openai_api_key",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "OpenAI API Key"
+    },
+    {
+        "item": "openai_model_id",
+        "value": "gpt-4o",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "gpt-4o",
+        "mark": "OpenAI Model ID"
+    },
+    {
+        "item": "google_ai_api_key",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Google AI API Key"
+    },
+    {
+        "item": "google_ai_model_id",
+        "value": "gemini-1.5-flash",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "gemini-1.5-flash",
+        "mark": "Google AI Model ID"
+    },
+    {
+        "item": "vertex_ai_access_token",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Vertex AI Access Token"
+    },
+    {
+        "item": "vertex_ai_location",
+        "value": "us-central1",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "us-central1",
+        "mark": "Vertex AI Location"
+    },
+    {
+        "item": "vertex_ai_project_id",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Vertex AI Project ID"
+    },
+    {
+        "item": "vertex_ai_model_id",
+        "value": "gemini-1.5-flash-preview-0514",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "gemini-1.5-flash-preview-0514",
+        "mark": "Vertex AI Model ID"
+    },
+    {
+        "item": "huggingface_api_key",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Hugging Face Inference API Key"
+    },
+    {
+        "item": "huggingface_endpoint_url",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Hugging Face Inference API Endpoint URL"
+    },
+    {
+        "item": "cf_workers_ai_account_id",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Cloudflare Workers AI Account ID"
+    },
+    {
+        "item": "cf_workers_ai_api_token",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Cloudflare Workers AI API Token"
+    },
+    {
+        "item": "cf_workers_ai_model_id",
+        "value": "@cf/meta/llama-3-8b-instruct",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "@cf/meta/llama-3-8b-instruct",
+        "mark": "Cloudflare Workers AI Model ID"
+    },
+    {
+        "item": "anthropic_api_key",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Anthropic API Key"
+    },
+    {
+        "item": "anthropic_model_id",
+        "value": "claude-3-sonnet-20240229",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "claude-3-sonnet-20240229",
+        "mark": "Anthropic Model ID"
+    },
+    {
+        "item": "aws_bedrock_access_key_id",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "AWS Bedrock Access Key ID"
+    },
+    {
+        "item": "aws_bedrock_access_key_secret",
+        "value": "",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "AWS Bedrock Access Key Secret"
+    },
+    {
+        "item": "aws_bedrock_region",
+        "value": "us-west-2",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "us-west-2",
+        "mark": "AWS Bedrock Region"
+    },
+    {
+        "item": "aws_bedrock_model_id",
+        "value": "meta.llama3-8b-instruct-v1:0",
+        "class": "llm",
+        "is_public": 0,
+        "type": "string",
+        "default": "meta.llama3-8b-instruct-v1:0",
+        "mark": "AWS Bedrock Model ID"
     }
 ]

+ 0 - 10
db/2022.12_to_2022.12.1.sql

@@ -1,10 +0,0 @@
--- Update from phinx migration 20230115090200_add_user_coupon
-ALTER TABLE detect_ban_log DROP FOREIGN KEY detect_ban_log_ibfk_1;
-ALTER TABLE detect_log DROP FOREIGN KEY detect_log_ibfk_1;
-ALTER TABLE detect_log DROP FOREIGN KEY detect_log_ibfk_2;
-ALTER TABLE detect_log DROP FOREIGN KEY detect_log_ibfk_3;
-ALTER TABLE detect_log DROP FOREIGN KEY detect_log_ibfk_5;
-ALTER TABLE link DROP FOREIGN KEY link_ibfk_1;
-ALTER TABLE login_ip DROP FOREIGN KEY login_ip_ibfk_1;
-ALTER TABLE paylist DROP FOREIGN KEY paylist_ibfk_1;
-ALTER TABLE user_invite_code DROP FOREIGN KEY user_invite_code_ibfk_1;

+ 46 - 19
db/migrations/2023020100-init.php

@@ -11,9 +11,13 @@ return new class() implements MigrationInterface {
         DB::getPdo()->exec(
             "CREATE TABLE `announcement` (
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '公告ID',
+                `status` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '公告状态',
+                `sort` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '公告排序',
                 `date` datetime NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '公告日期',
                 `content` longtext NOT NULL DEFAULT '' COMMENT '公告内容',
-                PRIMARY KEY (`id`)
+                PRIMARY KEY (`id`),
+                KEY `status` (`status`),
+                KEY `sort` (`sort`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
             CREATE TABLE `config` (
@@ -67,10 +71,14 @@ return new class() implements MigrationInterface {
 
             CREATE TABLE `docs` (
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '文档ID',
+                `status` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '文档状态',
+                `sort` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '文档排序',
                 `date` datetime NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '文档日期',
                 `title` varchar(255) NOT NULL DEFAULT '' COMMENT '文档标题',
                 `content` longtext NOT NULL DEFAULT '' COMMENT '文档内容',
-                PRIMARY KEY (`id`)
+                PRIMARY KEY (`id`),
+                KEY `status` (`status`),
+                KEY `sort` (`sort`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
             CREATE TABLE `email_queue` (
@@ -112,7 +120,7 @@ return new class() implements MigrationInterface {
                 `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '归属用户ID',
                 `order_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '订单ID',
                 `content` longtext NOT NULL DEFAULT '{}' COMMENT '账单内容' CHECK (json_valid(`content`)),
-                `price` double unsigned NOT NULL DEFAULT 0 COMMENT '账单金额',
+                `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '账单金额',
                 `status` varchar(255) NOT NULL DEFAULT '' COMMENT '账单状态',
                 `create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 `update_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '更新时间',
@@ -152,12 +160,12 @@ return new class() implements MigrationInterface {
                 `server` varchar(255) NOT NULL DEFAULT '' COMMENT '节点地址',
                 `custom_config` longtext NOT NULL DEFAULT '{}' COMMENT '自定义配置' CHECK (json_valid(`custom_config`)),
                 `sort` tinyint(2) unsigned NOT NULL DEFAULT 14 COMMENT '节点类型',
-                `traffic_rate` double unsigned NOT NULL DEFAULT 1 COMMENT '流量倍率',
+                `traffic_rate` decimal(5,2) unsigned NOT NULL DEFAULT 1 COMMENT '流量倍率',
                 `is_dynamic_rate` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '是否启用动态流量倍率',
                 `dynamic_rate_type` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '动态流量倍率计算方式',
                 `dynamic_rate_config` longtext NOT NULL DEFAULT '{}' COMMENT '动态流量倍率配置' CHECK (json_valid(`custom_config`)),
                 `node_class` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT '节点等级',
-                `node_speedlimit` double unsigned NOT NULL DEFAULT 0 COMMENT '节点限速',
+                `node_speedlimit` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '节点限速',
                 `node_bandwidth` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '节点流量',
                 `node_bandwidth_limit` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '节点流量限制',
                 `bandwidthlimit_resetday` tinyint(2) unsigned NOT NULL DEFAULT 0 COMMENT '流量重置日',
@@ -201,7 +209,7 @@ return new class() implements MigrationInterface {
                 `product_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称',
                 `product_content` longtext NOT NULL DEFAULT '{}' COMMENT '商品内容' CHECK (json_valid(`product_content`)),
                 `coupon` varchar(255) NOT NULL DEFAULT '' COMMENT '订单优惠码',
-                `price` double unsigned NOT NULL DEFAULT 0 COMMENT '订单金额',
+                `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '订单金额',
                 `status` varchar(255) NOT NULL DEFAULT '' COMMENT '订单状态',
                 `create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 `update_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '更新时间',
@@ -233,7 +241,7 @@ return new class() implements MigrationInterface {
                 `total` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '总金额',
                 `status` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '状态',
                 `invoice_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '账单ID',
-                `tradeno` varchar(255) NOT NULL DEFAULT '' COMMENT '网关单号',
+                `tradeno` varchar(255) NOT NULL DEFAULT '' COMMENT '网关识别码',
                 `gateway` varchar(255) NOT NULL DEFAULT '' COMMENT '支付网关',
                 `datetime` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 PRIMARY KEY (`id`),
@@ -247,7 +255,7 @@ return new class() implements MigrationInterface {
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品ID',
                 `type` varchar(255) NOT NULL DEFAULT 'tabp' COMMENT '类型',
                 `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名称',
-                `price` double unsigned NOT NULL DEFAULT 0 COMMENT '售价',
+                `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '售价',
                 `content` longtext NOT NULL DEFAULT '{}' COMMENT '内容' CHECK (json_valid(`content`)),
                 `limit` longtext NOT NULL DEFAULT '{}' COMMENT '购买限制' CHECK (json_valid(`limit`)),
                 `status` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '销售状态',
@@ -261,6 +269,24 @@ return new class() implements MigrationInterface {
                 KEY `status` (`status`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
             
+            CREATE TABLE `syslog` (
+                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '触发用户',
+                `ip` varchar(255) NOT NULL DEFAULT '' COMMENT '触发IP',
+                `message` varchar(1024) NOT NULL DEFAULT '' COMMENT '日志内容',
+                `level` tinyint(3) unsigned NOT NULL DEFAULT 100 COMMENT '日志等级',
+                `context` longtext NOT NULL DEFAULT '{}' COMMENT '日志内容' CHECK (json_valid(`context`)),
+                `channel` varchar(255) NOT NULL DEFAULT '' COMMENT '日志类别',
+                `datetime` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '记录时间',
+                PRIMARY KEY (`id`),
+                KEY `user_id` (`user_id`),
+                KEY `ip` (`ip`),
+                KEY `message` (`message`),
+                KEY `level` (`level`),
+                KEY `channel` (`channel`),
+                KEY `datetime` (`datetime`)
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+            
             CREATE TABLE `subscribe_log` (
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
                 `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
@@ -309,17 +335,17 @@ return new class() implements MigrationInterface {
                 `last_check_in_time` int(11) unsigned DEFAULT 0 COMMENT '最后签到时间',
                 `last_login_time` int(11) unsigned DEFAULT 0 COMMENT '最后登录时间',
                 `reg_date` datetime NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '注册时间',
-                `money` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额',
+                `money` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额',
                 `ref_by` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '邀请人ID',
                 `method` varchar(255) NOT NULL DEFAULT 'aes-128-gcm' COMMENT '加密方式',
                 `reg_ip` varchar(255) NOT NULL DEFAULT '127.0.0.1' COMMENT '注册IP',
-                `node_speedlimit` double NOT NULL DEFAULT 0 COMMENT '用户限速',
+                `node_speedlimit` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '用户限速',
                 `node_iplimit` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '同时可连接IP数',
                 `is_admin` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '是否管理员',
-                `im_type` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '联系方式类型',
+                `im_type` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '联系方式类型',
                 `im_value` varchar(255) NOT NULL DEFAULT '' COMMENT '联系方式',
-                `contact_method` smallint(6) NOT NULL DEFAULT 1 COMMENT '偏好的联系方式',
-                `daily_mail_enable` tinyint(1) NOT NULL DEFAULT 0 COMMENT '每日报告开关',
+                `contact_method` tinyint(3) unsigned NOT NULL DEFAULT 1 COMMENT '偏好的联系方式',
+                `daily_mail_enable` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '每日报告开关',
                 `class` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT '等级',
                 `class_expire` datetime NOT NULL DEFAULT '1989-06-04 00:05:00' COMMENT '等级过期时间',
                 `theme` varchar(255) NOT NULL DEFAULT 'tabler' COMMENT '网站主题',
@@ -345,6 +371,10 @@ return new class() implements MigrationInterface {
                 UNIQUE KEY `ga_token` (`ga_token`),
                 UNIQUE KEY `api_token` (`api_token`),
                 KEY `is_admin` (`is_admin`),
+                KEY `contact_method` (`contact_method`),
+                KEY `class` (`class`),
+                KEY `class_expire` (`class_expire`),
+                KEY `node_group` (`node_group`),
                 KEY `is_banned` (`is_banned`),
                 KEY `is_shadow_banned` (`is_shadow_banned`),
                 KEY `is_inactive` (`is_inactive`)
@@ -359,7 +389,6 @@ return new class() implements MigrationInterface {
                 `create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 `expire_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '过期时间',
                 PRIMARY KEY (`id`),
-                KEY `id` (`id`),
                 UNIQUE KEY `code` (`code`),
                 KEY `expire_time` (`expire_time`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -376,9 +405,9 @@ return new class() implements MigrationInterface {
             CREATE TABLE `user_money_log` (
                 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
                 `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户ID',
-                `before` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '用户变动前账户余额',
-                `after` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '用户变动后账户余额',
-                `amount` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '变动总额',
+                `before` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '用户变动前账户余额',
+                `after` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '用户变动后账户余额',
+                `amount` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '变动总额',
                 `remark` text NOT NULL DEFAULT '' COMMENT '备注',
                 `create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '创建时间',
                 PRIMARY KEY (`id`),
@@ -391,8 +420,6 @@ return new class() implements MigrationInterface {
 
     public function down(): int
     {
-        echo 'No reverse operation for initial migration' . PHP_EOL . PHP_EOL;
-
         return 2023020100;
     }
 };

+ 42 - 0
db/migrations/2024041000-add_syslog.php

@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+use App\Interfaces\MigrationInterface;
+use App\Services\DB;
+
+return new class() implements MigrationInterface {
+    public function up(): int
+    {
+        DB::getPdo()->exec("
+            CREATE TABLE IF NOT EXISTS `syslog` (
+                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+                `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '触发用户',
+                `ip` varchar(255) NOT NULL DEFAULT '' COMMENT '触发IP',
+                `message` varchar(1024) NOT NULL DEFAULT '' COMMENT '日志内容',
+                `level` tinyint(3) unsigned NOT NULL DEFAULT 100 COMMENT '日志等级',
+                `context` longtext NOT NULL DEFAULT '{}' COMMENT '日志内容' CHECK (json_valid(`context`)),
+                `channel` varchar(255) NOT NULL DEFAULT '' COMMENT '日志类别',
+                `datetime` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '记录时间',
+                PRIMARY KEY (`id`),
+                KEY `user_id` (`user_id`),
+                KEY `ip` (`ip`),
+                KEY `message` (`message`),
+                KEY `level` (`level`),
+                KEY `channel` (`channel`),
+                KEY `datetime` (`datetime`)
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+        ");
+
+        return 2024041000;
+    }
+
+    public function down(): int
+    {
+        DB::getPdo()->exec('
+            DROP TABLE IF EXISTS `syslog`;
+        ');
+
+        return 2024040500;
+    }
+};

+ 29 - 0
db/migrations/2024052400-add_ann_docs_status_sort.php

@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+use App\Interfaces\MigrationInterface;
+use App\Services\DB;
+
+return new class() implements MigrationInterface {
+    public function up(): int
+    {
+        DB::getPdo()->exec("
+            ALTER TABLE announcement ADD COLUMN IF NOT EXISTS `status` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '公告状态';
+            ALTER TABLE announcement ADD COLUMN IF NOT EXISTS `sort` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '公告排序';
+            ALTER TABLE announcement ADD KEY IF NOT EXISTS `status` (`status`);
+            ALTER TABLE announcement ADD KEY IF NOT EXISTS `sort` (`sort`);
+            ALTER TABLE docs ADD COLUMN IF NOT EXISTS `status` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '文档状态';
+            ALTER TABLE docs ADD COLUMN IF NOT EXISTS `sort` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '文档排序';
+            ALTER TABLE docs ADD KEY IF NOT EXISTS `status` (`status`);
+            ALTER TABLE docs ADD KEY IF NOT EXISTS `sort` (`sort`);
+        ");
+
+        return 2024052400;
+    }
+
+    public function down(): int
+    {
+        return 2024052400;
+    }
+};

+ 39 - 0
db/migrations/2024061600-update_price_type.php

@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+use App\Interfaces\MigrationInterface;
+use App\Services\DB;
+
+return new class() implements MigrationInterface {
+    public function up(): int
+    {
+        DB::getPdo()->exec("
+            ALTER TABLE invoice MODIFY COLUMN `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '账单金额';
+            ALTER TABLE node MODIFY COLUMN `traffic_rate` decimal(5,2) unsigned NOT NULL DEFAULT 1 COMMENT '流量倍率';
+            ALTER TABLE node MODIFY COLUMN `node_speedlimit` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '节点限速';
+            ALTER TABLE `order` MODIFY COLUMN `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '订单金额';
+            ALTER TABLE paylist MODIFY COLUMN `tradeno` varchar(255) NOT NULL DEFAULT '' COMMENT '网关识别码';
+            ALTER TABLE product MODIFY COLUMN `price` decimal(12,2) unsigned NOT NULL DEFAULT 0 COMMENT '售价';
+            ALTER TABLE user MODIFY COLUMN `money` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额';
+            ALTER TABLE user MODIFY COLUMN `node_speedlimit` smallint(6) unsigned NOT NULL DEFAULT 0 COMMENT '用户限速';
+            ALTER TABLE user MODIFY COLUMN `im_type` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT '联系方式类型';
+            ALTER TABLE user MODIFY COLUMN `contact_method` tinyint(3) unsigned NOT NULL DEFAULT 1 COMMENT '偏好的联系方式';
+            ALTER TABLE user MODIFY COLUMN `daily_mail_enable` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '每日报告开关';
+            ALTER TABLE user ADD KEY IF NOT EXISTS `contact_method` (`contact_method`);
+            ALTER TABLE user ADD KEY IF NOT EXISTS `class` (`class`);
+            ALTER TABLE user ADD KEY IF NOT EXISTS `class_expire` (`class_expire`);
+            ALTER TABLE user ADD KEY IF NOT EXISTS `node_group` (`node_group`);
+            ALTER TABLE user_money_log MODIFY COLUMN `before` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '用户变动前账户余额';
+            ALTER TABLE user_money_log MODIFY COLUMN `after` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '用户变动后账户余额';
+            ALTER TABLE user_money_log MODIFY COLUMN `amount` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '变动总额';
+        ");
+
+        return 2024061600;
+    }
+
+    public function down(): int
+    {
+        return 2024061600;
+    }
+};

二进制
public/images/alipay.png


+ 32 - 0
public/images/alipay.svg

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   id="svg4458"
+   version="1.1"
+   viewBox="0 0 37.345053 37.663967"
+   height="37.663967mm"
+   width="37.345055mm"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs4452" />
+  <g
+     transform="translate(13.452402,-84.644208)"
+     id="layer1">
+    <g
+       transform="matrix(0.35277777,0,0,-0.35277777,17.912364,84.644208)"
+       id="g3989">
+      <path
+         id="path3991"
+         style="fill:#1677ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 0,0 h -71.95 c -9.37,0 -16.958,-7.655 -16.958,-17.104 v -72.559 c 0,-9.443 7.588,-17.101 16.958,-17.101 H 0 c 9.368,0 16.952,7.658 16.952,17.101 v 72.559 C 16.952,-7.655 9.368,0 0,0" />
+    </g>
+    <g
+       transform="matrix(0.35277777,0,0,-0.35277777,-3.3614425,113.60801)"
+       id="g3993">
+      <path
+         id="path3995"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 0,0 c -16.469,0 -21.338,13.083 -13.199,20.241 2.716,2.419 7.679,3.6 10.325,3.865 9.783,0.975 18.839,-2.789 29.526,-8.05 C 19.14,6.173 9.572,0 0,0 m 58.554,15.064 c -4.236,1.431 -9.92,3.619 -16.251,5.929 3.802,6.666 6.839,14.257 8.835,22.507 H 30.271 v 7.58 h 25.562 v 4.231 H 30.271 V 67.947 H 19.839 c -1.83,0 -1.83,-1.822 -1.83,-1.822 V 55.311 H -7.844 V 51.08 H 18.009 V 43.5 H -3.336 V 39.27 H 38.062 C 36.547,34.006 34.513,29.064 32.099,24.574 18.666,29.044 4.332,32.667 -4.673,30.437 -10.432,29.006 -14.141,26.452 -16.32,23.776 -26.321,11.504 -19.148,-7.136 1.973,-7.136 c 12.488,0 24.518,7.022 33.842,18.594 C 49.722,4.716 77.256,-6.86 77.256,-6.86 V 9.638 c 0,0 -3.458,0.278 -18.702,5.426" />
+    </g>
+  </g>
+</svg>

+ 60 - 0
public/images/qq.svg

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   height="288px"
+   viewBox="0 0 120.99969 143.88988"
+   width="242px"
+   version="1.1"
+   id="svg12"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs12" />
+  <g
+     transform="matrix(.35277779 0 0 .35277779 -44.0002378 -32.999882)"
+     id="qq">
+    <path
+       d="m296.23047 496.73437c-35.52735 0-68.14063-11.89062-89.13672-29.65234-10.66406 3.1875-24.30469 8.3125-32.91016 14.67188-7.36718 5.4375-6.44922 10.98046-5.12109 13.21875 5.82812 9.83593 99.98437 6.28125 127.16797 3.21875zm0 0"
+       fill="#faab07"
+       id="path1" />
+    <path
+       d="m296.23047 496.73437c35.53125 0 68.14062-11.89062 89.14062-29.65234 10.66016 3.1875 24.30078 8.3125 32.91016 14.67188 7.36328 5.4375 6.44531 10.98046 5.11719 13.21875-5.82813 9.83593-99.98438 6.28125-127.16797 3.21875zm0 0"
+       fill="#faab07"
+       id="path2" />
+    <path
+       d="m296.4375 283.80078c58.67187-.39844 105.69141-11.75391 121.625-16.10937 3.79687-1.03907 5.82812-2.90235 5.82812-2.90235.0156-.53515.24219-9.55078.24219-14.20312 0-78.32813-36.97656-157.039065-127.91406-157.042971-90.93359.0039-127.91016 78.714841-127.91016 157.042971 0 4.65234.22657 13.66797.24219 14.20312 0 0 1.65234 1.74219 4.67969 2.58594 14.71094 4.09375 62.61719 16.01562 122.77344 16.42578zm0 0"
+       id="path3" />
+    <path
+       d="m455.87109 349.05469c-3.63672-11.70313-8.60156-25.35157-13.6289-38.46094 0 0-2.89063-.35547-4.35547.0664-45.10938 13.10546-99.78516 21.45703-141.44922 20.95312h-.43359c-41.4336.49609-95.73829-7.75781-140.69532-20.73437-1.71875-.4961-5.10937-.28516-5.10937-.28516-5.02735 13.10937-9.99219 26.75781-13.62891 38.46094-17.34375 55.8125-11.72265 78.91015-7.44531 79.42968 9.17969 1.10938 35.73437-42.01562 35.73437-42.01562 0 43.82031 39.5625 111.10547 130.16016 111.72266.40234-.004 2-.004 2.40234 0 90.59766-.61719 130.16016-67.90235 130.16016-111.72266 0 0 26.55469 43.125 35.73438 42.01562 4.27734-.51953 9.89843-23.61718-7.44532-79.42968"
+       id="path4" />
+    <path
+       d="m263.86328 209.26562c-12.33594.5586-22.875-13.49609-23.53906-31.35937-.66797-17.875 8.78125-32.81641 21.11719-33.375 12.32421-.55078 22.85937 13.49609 23.52734 31.36719.67578 17.875-8.77734 32.8164-21.10547 33.36718"
+       fill="#fff"
+       id="path5" />
+    <path
+       d="m352.11328 177.90625c-.66016 17.86328-11.19922 31.91797-23.53516 31.35937-12.32812-.55468-21.78125-15.49218-21.10546-33.36718.66796-17.8711 11.20312-31.91797 23.52734-31.36719 12.33594.55859 21.78516 15.5 21.11328 33.375"
+       fill="#fff"
+       id="path6" />
+    <path
+       d="m374.03906 234.49609c-3.29687-7.30078-36.49609-15.4375-77.59765-15.4375h-.44141c-41.10156 0-74.30078 8.13672-77.59766 15.4375-.14843.31641-.24218.66797-.24218 1.03907 0 .52734.17968 1 .45312 1.40625 2.78125 4.04687 39.64453 24.05859 77.38672 24.05859h.44141c37.74218 0 74.60546-20.00781 77.38281-24.05859.27734-.40235.45312-.88282.45312-1.41016 0-.37109-.0898-.71875-.23828-1.03516"
+       fill="#faab07"
+       id="path7" />
+    <path
+       d="m279.02734 178.16797c.5625 7.05859-3.30859 13.32812-8.63672 14.01172-5.33593.6875-10.11328-4.47657-10.67968-11.53907-.5586-7.0625 3.30859-13.33203 8.625-14.01171 5.34765-.69141 10.13281 4.48046 10.6914 11.53906"
+       id="path8" />
+    <path
+       d="m312.91797 180.52734c1.08984-1.95312 8.51562-12.22265 23.88672-8.48437 4.03906.98437 5.90625 2.42969 6.30078 2.99609.58203.83985.74219 2.03516.15234 3.64453-1.16797 3.19141-3.58203 3.10547-4.91797 2.48047-.86328-.40234-11.57031-7.53906-21.43359 3.10938-.67969.73047-1.89453.98047-3.04297.11328-1.15234-.8711-1.625-2.63281-.94531-3.85938"
+       id="path9" />
+    <path
+       d="m296.4375 329.04297h-.43359c-28.33594.34375-62.69141-3.41016-95.96485-9.96875-2.84765 16.49219-4.5664 37.22265-3.08984 61.94531 3.73047 62.48438 40.83984 101.76172 98.11719 102.33203h2.32812c57.28125-.57031 94.38672-39.84765 98.12109-102.33203 1.47657-24.72656-.24609-45.45312-3.09375-61.94922-33.27734 6.5625-67.64062 10.31641-95.98437 9.97266"
+       fill="#fff"
+       id="path10" />
+    <path
+       d="m215.72266 323.81641v61.49218s28.16796 5.67969 56.39062 1.7461v-56.71875c-17.87891-1.01172-37.16016-3.26563-56.39062-6.51953"
+       fill="#eb1923"
+       id="path11" />
+    <path
+       d="m423.89062 264.78906s-54.79296 17.29688-127.45312 17.78906h-.43359c-72.54297-.48828-127.26563-17.73046-127.45313-17.78906l-18.35156 45.80469c45.90234 13.83984 102.78906 22.75781 145.80469 22.23828h.43359c43.01562.51953 99.89844-8.39844 145.80469-22.23828zm0 0"
+       fill="#eb1923"
+       id="path12" />
+  </g>
+</svg>

二进制
public/images/qqpay.png


+ 26 - 0
public/images/tether.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 124.4 124.4"
+   version="1.1"
+   id="svg2"
+   width="124.4"
+   height="124.4"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs2" />
+  <circle
+     cx="62.200001"
+     cy="62.200001"
+     r="62.200001"
+     fill="#26a17b"
+     id="circle1" />
+  <g
+     fill="#ffffff"
+     id="g1"
+     transform="translate(-4.4650002,-13.692997)">
+    <path
+       d="m 73.295,81.633 v -0.01 c -0.43,0.03 -2.65,0.16 -7.59,0.16 -3.95,0 -6.72,-0.11 -7.7,-0.16 v 0.01 c -15.19,-0.67 -26.52,-3.31 -26.52,-6.48 0,-3.16 11.34,-5.81 26.52,-6.48 v 10.33 c 0.99,0.07 3.84,0.24 7.77,0.24 4.72,0 7.08,-0.2 7.52,-0.24 v -10.32 c 15.16,0.68 26.46,3.32 26.46,6.48 0,3.16 -11.31,5.8 -26.46,6.48 m 0,-14.03 v -9.24 h 21.15 v -14.09 h -57.58 v 14.09 h 21.15 v 9.24 c -17.19,0.79 -30.11,4.19 -30.11,8.27 0,4.08 12.93,7.48 30.11,8.28 v 29.62 h 15.29 v -29.62 c 17.16,-0.79 30.06,-4.19 30.06,-8.27 0,-4.07 -12.9,-7.48 -30.06,-8.27"
+       id="path1" />
+  </g>
+</svg>

二进制
public/images/usdt.png


二进制
public/images/wechat.png


+ 16 - 0
public/images/wechat.svg

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+    version="1.1"
+    viewBox="0 0 1024 1024"
+    id="wechat-logo"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    style="display:inline;overflow:visible">
+    <defs id="defs1" />
+    <g id="wechat">
+    <path
+        d="M395.846 603.585c-3.921 1.98-7.936 2.925-12.81 2.925-10.9 0-19.791-5.85-24.764-14.625l-2.006-3.864-78.106-167.913c-0.956-1.98-0.956-3.865-0.956-5.845 0-7.83 5.928-13.68 13.863-13.68 2.965 0 5.928 0.944 8.893 2.924l91.965 64.43c6.884 3.864 14.82 6.79 23.708 6.79 4.972 0 9.85-0.945 14.822-2.926L861.71 282.479c-77.149-89.804-204.684-148.384-349.135-148.384-235.371 0-427.242 157.158-427.242 351.294 0 105.368 57.361 201.017 147.323 265.447 6.88 4.905 11.852 13.68 11.852 22.45 0 2.925-0.957 5.85-2.006 8.775-6.881 26.318-18.831 69.334-18.831 71.223-0.958 2.92-2.013 6.79-2.013 10.75 0 7.83 5.929 13.68 13.865 13.68 2.963 0 5.928-0.944 7.935-2.925l92.922-53.674c6.885-3.87 14.82-6.794 22.756-6.794 3.916 0 8.889 0.944 12.81 1.98 43.496 12.644 91.012 19.53 139.48 19.53 235.372 0 427.24-157.158 427.24-351.294 0-58.58-17.78-114.143-48.467-163.003l-491.39 280.07-2.963 1.98z"
+        fill="#09BB07"
+        id="path1" />
+    </g>
+</svg>

+ 1 - 1
public/index.php

@@ -31,7 +31,7 @@ $app = AppFactory::create($response_factory);
 
 $app->add(new ErrorHandler());
 
-$routes = require_once __DIR__ . '/../app/routes.php';
+$routes = require __DIR__ . '/../app/routes.php';
 $routes($app);
 
 $request = ServerRequest::fromGlobals();

+ 18 - 27
resources/email/finance.tpl

@@ -1,35 +1,26 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                {$title}
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
+                                <h2 class="bigTitle">
+                                    {$title}
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                {$text}
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    {$text}
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 12 - 14
resources/email/footer.tpl

@@ -1,18 +1,16 @@
-<div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-     class="wrapperTable">
+<div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align:center;" width="100%">
     <div align="center" valign="top">
-        <div border="0" cellpadding="0" cellspacing="0" width="100%" class="footer">
-            <div>
-                <div align="center" valign="top"
-                     style="padding-top:15px;padding-bottom:30px;padding-left:10px;padding-right:10px;"
-                     class="brandInfo">
-                    <p class="smlText">
-                        <a href="{$config['baseUrl']}" style="color:#505050;text-decoration:none"
-                           target="_blank">{$config['appName']}</a> |
-                        <a href="{$config['baseUrl']}/user/edit" style="color:#505050;text-decoration:none"
-                           target="_blank">修改邮件接收设置</a>
-                    </p>
-                </div>
+        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+            <div align="center" valign="top" style="padding: 15px 10px 30px;">
+                <p class="smlText">
+                    <a href="{$config['baseUrl']}" style="color:#505050;text-decoration:none;" target="_blank">
+                        {$config['appName']}
+                    </a>
+                    &nbsp;|&nbsp;
+                    <a href="{$config['baseUrl']}/user/edit" style="color:#505050;text-decoration:none;" target="_blank">
+                        修改邮件接收设置
+                    </a>
+                </p>
             </div>
         </div>
     </div>

+ 0 - 1
resources/email/header.tpl

@@ -3,7 +3,6 @@
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
     <meta name="viewport" content="width=device-width"/>
     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
-
     <link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet"/>
 
     <style>

+ 18 - 27
resources/email/new_user.tpl

@@ -1,35 +1,26 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                账户已经生成
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align:center;" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px;">
+                                <h2 class="bigTitle">
+                                    账户已经生成
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                {$text}
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    {$text}
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 21 - 29
resources/email/password_reset.tpl

@@ -1,37 +1,29 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                密码重置
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0"
+                     style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px;">
+                                <h2 class="bigTitle">
+                                    密码重置
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                你收到此邮件是因为你在 {$config['appName']} 系统申请了密码重置,如果非本人申请,请忽略此邮件。
-                                <br><br>
-                                <a href="{$resetUrl}" style="color:#505050" target="_blank">点击此链接重置密码</a>
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    你收到此邮件是因为你在 {$config['appName']} 系统申请了密码重置,如果非本人申请,请忽略此邮件。
+                                    <br><br>
+                                    <a href="{$resetUrl}" style="color:#505050" target="_blank">点击此链接重置密码</a>
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 18 - 27
resources/email/test.tpl

@@ -1,35 +1,26 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                邮件发送测试
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align:center;" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
+                                <h2 class="bigTitle">
+                                    邮件发送测试
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                这是一封测试邮件。如果你能收到,说明邮件发送配置有效,可以正常工作。
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px; padding-left:20px; padding-right:20px;">
+                                <p class="midText">
+                                    这是一封测试邮件。如果你能收到,说明邮件发送配置有效,可以正常工作。
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 32 - 41
resources/email/traffic_report.tpl

@@ -1,49 +1,40 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                每日流量报告
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align:center;" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px;">
+                                <h2 class="bigTitle">
+                                    每日流量报告
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                用户名: {$user->user_name}
-                                <br>
-                                Email: {$user->email}
-                                <br><br>
-                                总流量: {$enable_traffic}
-                                <br>
-                                已用流量: {$used_traffic}
-                                <br>
-                                剩余流量: {$unused_traffic}
-                                <br>
-                                今日使用流量: {$lastday_traffic}
-                                <br><br>
-                            </p>
-                            <p class="midText">
-                                {$text}
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    用户名: {$user->user_name}
+                                    <br>
+                                    Email: {$user->email}
+                                    <br><br>
+                                    总流量: {$enable_traffic}
+                                    <br>
+                                    已用流量: {$used_traffic}
+                                    <br>
+                                    剩余流量: {$unused_traffic}
+                                    <br>
+                                    今日使用流量: {$lastday_traffic}
+                                    <br><br>
+                                </p>
+                                <p class="midText">
+                                    {$text}
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 20 - 29
resources/email/verify_code.tpl

@@ -1,37 +1,28 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                邮箱验证
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align:center;" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px;">
+                                <h2 class="bigTitle">
+                                    邮箱验证
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                你请求的邮箱验证代码为: <b style="color:#505050">{$code}</b> <br>
-                                本验证代码在 {$expire} 前有效。<br>
-                                如果此验证码非你本人申请,请忽视此邮件。<br>
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    你请求的邮箱验证代码为:<b style="color:#505050">{$code}</b> <br>
+                                    本验证代码在 {$expire} 前有效。<br>
+                                    如果此验证码非你本人申请,请忽视此邮件。<br>
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 18 - 27
resources/email/warn.tpl

@@ -1,35 +1,26 @@
 {include file='header.tpl'}
 
 <body style="background-color:#EEEEEE;">
-<div style="text-align: center">
-    <div border="0" cellpadding="0" cellspacing="0" width="100%"
-         style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;" id="bodyTable">
-        <div align="center" valign="top" style="padding-right:10px;padding-left:10px;" id="bodyCell">
-            <div border="0" cellpadding="0" cellspacing="0"
-                 style="background-color:#FFFFFF;max-width:600px;text-align: center" width="100%" class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" width="100%" class="logoTable">
-                        <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px">
-                            <h2 class="bigTitle">
-                                系统提示
-                            </h2>
+    <div style="text-align: center">
+        <div border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top:30px;table-layout:fixed;background-color:#EEEEEE;">
+            <div align="center" valign="top" style="padding-right:10px;padding-left:10px;">
+                <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;max-width:600px;text-align:center;" width="100%">
+                    <div align="center" valign="top">
+                        <div border="0" cellpadding="0" cellspacing="0" width="100%">
+                            <div align="center" valign="middle" style="padding-top:60px;padding-bottom:60px;">
+                                <h2 class="bigTitle">
+                                    系统提示
+                                </h2>
+                            </div>
                         </div>
-                    </div>
-                </div>
-            </div>
-            <div border="0" cellpadding="0" cellspacing="0" style="max-width:600px;text-align: center" width="100%"
-                 class="wrapperTable">
-                <div align="center" valign="top">
-                    <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%"
-                         class="oneColumn">
-                        <div align="center" valign="top"
-                             style="padding-bottom:60px;padding-left:20px;padding-right:20px;" class="description">
-                            <p class="midText">
-                                {$text}
-                            </p>
+                        <div border="0" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF" width="100%">
+                            <div align="center" valign="top" style="padding-bottom:60px;padding-left:20px;padding-right:20px;">
+                                <p class="midText">
+                                    {$text}
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
 
-            {include file='footer.tpl'}
+{include file='footer.tpl'}

+ 0 - 1
resources/locale/en-US.json

@@ -1 +0,0 @@
-{}

+ 28 - 0
resources/locale/en_US.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+// Translations for English(Simplified)
+return [
+    'lang' => 'en_US',
+    'lang_name' => 'English(Simplified)',
+    'lang_code' => 'en',
+    'bot' => [
+        'daily_job_run' => 'Successful execution of daily tasks',
+        'detect_rule_added' => 'Detect rule %rule_name% has been added',
+        'diary' => 'Number of people checking in today: %checkin_user%' . PHP_EOL . 'Total traffic used today: %lastday_total%',
+        'node_added' => '%node_name% has been added',
+        'node_deleted' => '%node_name% was deleted',
+        'node_gfwed' => '%node_name% is blocked',
+        'node_offline' => '%node_name% has gone offline',
+        'node_online' => '%node_name% is back online',
+        'node_ungfwed' => '%node_name% recovered',
+        'node_updated' => '%node_name% has been modified',
+        'order_created' => 'Order #%order_id% has been created' . PHP_EOL . 'Link: %order_link%',
+        'test_message' => 'Test message',
+        'ticket_created' => 'Ticket #%ticket_id% has been created' . PHP_EOL . 'Link: %ticket_link%',
+        'user_join_welcome_free' => 'Welcome %user_name%',
+        'user_join_welcome_paid' => 'Welcome VIP%user_class% user %user_name% to join the group',
+        'user_not_bind' => 'You have not bound your account to this website. You can enter the **Edit Account** of the website and bind your account in the lower right corner.',
+    ],
+];

+ 0 - 1
resources/locale/ja-JP.json

@@ -1 +0,0 @@
-{}

+ 28 - 0
resources/locale/ja_JP.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+// Translations for Japanese
+return [
+    'lang' => 'ja_JP',
+    'lang_name' => '日本語',
+    'lang_code' => 'ja',
+    'bot' => [
+        'daily_job_run' => '日次タスクの正常な実行',
+        'detect_rule_added' => '追加された監査ルール %rule_name%',
+        'diary' => '今日チェックインした人の数: %checkin_user%' . PHP_EOL . '今日使用された合計トラフィック: %lastday_total%',
+        'node_added' => '%node_name% が追加されました',
+        'node_deleted' => '%node_name% が削除されました',
+        'node_gfwed' => '%node_name% はブロックされています',
+        'node_offline' => '%node_name% にはいくつかの障害があります',
+        'node_online' => '%node_name% はオンラインに戻りました',
+        'node_ungfwed' => '%node_name% が回復しました',
+        'node_updated' => '%node_name% が変更されました',
+        'order_created' => 'オーダー #%order_id% が作成されました' . PHP_EOL . 'リンク: %order_link%',
+        'test_message' => 'テストメッセージ',
+        'ticket_created' => 'チケット #%ticket_id% が作成されました' . PHP_EOL . 'リンク: %ticket_link%',
+        'user_join_welcome_free' => 'ようこそ %user_name%',
+        'user_join_welcome_paid' => 'VIP%user_class% ユーザー %user_name% のグループへの参加を歓迎します',
+        'user_not_bind' => 'アカウントをこの Web サイトにバインドしていません。Web サイトの **データ編集** に入り、右下隅でアカウントをバインドできます。',
+    ],
+];

+ 0 - 1
resources/locale/zh-CN.json

@@ -1 +0,0 @@
-{}

+ 0 - 1
resources/locale/zh-TW.json

@@ -1 +0,0 @@
-{}

+ 28 - 0
resources/locale/zh_CN.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+// Translations for Chinese(Used in illegally occupied areas of Republic of China)
+return [
+    'lang' => 'zh_CN',
+    'lang_name' => '中文',
+    'lang_code' => 'zh',
+    'bot' => [
+        'daily_job_run' => '成功执行每日任务',
+        'detect_rule_added' => '新增了审计规则 %rule_name%',
+        'diary' => '今日签到人数:%checkin_user%' . PHP_EOL . '今日使用总流量:%lastday_total%',
+        'node_added' => '%node_name% 已被添加',
+        'node_deleted' => '%node_name% 被删除了',
+        'node_gfwed' => '%node_name% 节点被墙了',
+        'node_offline' => '%node_name% 节点出现了一些故障',
+        'node_online' => '%node_name% 节点恢复上线',
+        'node_ungfwed' => '%node_name% 节点恢复了',
+        'node_updated' => '%node_name% 已被修改',
+        'order_created' => '订单 #%order_id% 已创建' . PHP_EOL . '链接:%order_link%',
+        'test_message' => '测试消息',
+        'ticket_created' => '工单 #%ticket_id% 已创建' . PHP_EOL . '链接:%ticket_link%',
+        'user_join_welcome_free' => '欢迎 %user_name%',
+        'user_join_welcome_paid' => '欢迎 VIP%user_class% 用户 %user_name% 加入群组',
+        'user_not_bind' => '你未绑定本站账号,你可以进入网站的 **资料编辑**,在右下方绑定你的账号。',
+    ],
+];

+ 28 - 0
resources/locale/zh_TW.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+// Translations for Chinese(Used in one and only Republic of China(Taiwan))
+return [
+    'lang' => 'zh_TW',
+    'lang_name' => '正體中文',
+    'lang_code' => 'zh',
+    'bot' => [
+        'daily_job_run' => '成功執行每日任務',
+        'detect_rule_added' => '新增了稽核規則 %rule_name%',
+        'diary' => '今日簽到人數:%checkin_user%' . PHP_EOL . '今日使用總流量:%lastday_total%',
+        'node_added' => '%node_name% 已被加',
+        'node_deleted' => '%node_name% 被刪除了',
+        'node_gfwed' => '%node_name% 被牆了',
+        'node_offline' => '%node_name% 出現了一些故障',
+        'node_online' => '%node_name% 恢復上線',
+        'node_ungfwed' => '%node_name% 恢復了',
+        'node_updated' => '%node_name% 已被修改',
+        'order_created' => '訂單 #%order_id% 已建立' . PHP_EOL . '連結:%order_link%',
+        'test_message' => '測試訊息',
+        'ticket_created' => '工單 #%ticket_id% 已建立' . PHP_EOL . '連結:%ticket_link%',
+        'user_join_welcome_free' => '歡迎 %user_name%',
+        'user_join_welcome_paid' => '歡迎 VIP%user_class% 使用者 %user_name% 加入群組',
+        'user_not_bind' => '你未綁定本站帳號,你可以進入網站的 **資料編輯**,在右下方綁定你的帳號。',
+    ],
+];

+ 48 - 23
resources/views/tabler/admin/announcement/create.tpl

@@ -25,31 +25,55 @@
     </div>
     <div class="page-body">
         <div class="container-xl">
-            <div class="card">
-                <div class="card-body">
-                    <div class="mb-3">
-                        <form method="post">
-                            <textarea id="tinymce"></textarea>
-                        </form>
-                    </div>
-                    <div class="mb-3">
-                        <label class="form-label col-3 col-form-label">公告通知的用户等级,0为不分级</label>
-                        <div class="col">
-                            <input id="email_notify_class" type="text" class="form-control" value="">
+            <div class="row row-cards">
+                <div class="col-md-9 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <div class="mb-3">
+                                <form method="post">
+                                    <textarea id="tinymce"></textarea>
+                                </form>
+                            </div>
                         </div>
                     </div>
-                    <div class="mb-3">
-                        <div class="divide-y">
-                            <div>
-                                <label class="row">
-                                    <span class="col">发送邮件通知</span>
-                                    <span class="col-auto">
-                                        <label class="form-check form-check-single form-switch">
-                                            <input id="email_notify" class="form-check-input" type="checkbox"
-                                                   checked="">
-                                        </label>
-                                    </span>
-                                </label>
+                </div>
+                <div class="col-md-3 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <h3 class="card-title">选项</h3>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">状态</label>
+                                <div class="col">
+                                    <select id="status" class="col form-select" value="1">
+                                        <option value="0">未发布</option>
+                                        <option value="1">已发布</option>
+                                        <option value="2">置顶</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label">排序</label>
+                                <div class="col">
+                                    <input id="sort" type="text" class="form-control" value="0">
+                                </div>
+                            </div>
+                            <div class="hr-text">
+                                <span>通知</span>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label">邮件通知的用户等级</label>
+                                <div class="col">
+                                    <input id="email_notify_class" type="text" class="form-control" value="0">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <span class="col">发送邮件通知</span>
+                                <span class="col-auto">
+                                    <label class="form-check form-check-single form-switch">
+                                        <input id="email_notify" class="form-check-input" type="checkbox"
+                                               checked="">
+                                    </label>
+                                </span>
                             </div>
                         </div>
                     </div>
@@ -71,6 +95,7 @@
                 {foreach $update_field as $key}
                 {$key}: $('#{$key}').val(),
                 {/foreach}
+                email_notify_class : $('#email_notify_class').val(),
                 email_notify: $("#email_notify").is(":checked"),
                 content: tinyMCE.activeEditor.getContent(),
             },

+ 36 - 6
resources/views/tabler/admin/announcement/edit.tpl

@@ -25,12 +25,39 @@
     </div>
     <div class="page-body">
         <div class="container-xl">
-            <div class="card">
-                <div class="card-body">
-                    <div class="mb-3">
-                        <form method="post">
-                            <textarea id="tinymce">{$ann->content}</textarea>
-                        </form>
+            <div class="row row-cards">
+                <div class="col-md-9 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <div class="mb-3">
+                                <form method="post">
+                                    <textarea id="tinymce">{$ann->content}</textarea>
+                                </form>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <h3 class="card-title">选项</h3>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">状态</label>
+                                <div class="col">
+                                    <select id="status" class="col form-select" value="{$ann->status}">
+                                        <option value="0" {if $ann->status == 0}selected{/if}>未发布</option>
+                                        <option value="1" {if $ann->status == 1}selected{/if}>已发布</option>
+                                        <option value="2" {if $ann->status == 2}selected{/if}>置顶</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label">排序</label>
+                                <div class="col">
+                                    <input id="sort" type="text" class="form-control" value="{$ann->sort}">
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -47,6 +74,9 @@
             type: 'PUT',
             dataType: "json",
             data: {
+                {foreach $update_field as $key}
+                {$key}: $('#{$key}').val(),
+                {/foreach}
                 content: tinyMCE.activeEditor.getContent(),
             },
             success: function (data) {

+ 2 - 2
resources/views/tabler/admin/announcement/index.tpl

@@ -79,8 +79,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 6 - 6
resources/views/tabler/admin/coupon.tpl

@@ -156,8 +156,8 @@
                 },
                 success: function (data) {
                     if (data.ret === 1) {
-                        $('#success-noreload-message').text(data.msg);
-                        $('#success-noreload-dialog').modal('show');
+                        $('#success-message').text(data.msg);
+                        $('#success-dialog').modal('show');
                         reloadTableAjax();
                     } else {
                         $('#fail-message').text(data.msg);
@@ -177,8 +177,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);
@@ -199,8 +199,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-dialog').text(data.msg);
-                            $('#success-noreload-message').modal('show');
+                            $('#success-dialog').text(data.msg);
+                            $('#success-message').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 2 - 2
resources/views/tabler/admin/detect.tpl

@@ -147,8 +147,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 46 - 20
resources/views/tabler/admin/docs/create.tpl

@@ -14,10 +14,9 @@
                 </div>
                 <div class="col-auto ms-auto d-print-none">
                     <div class="btn-list">
-                        <button href="#" class="btn btn-primary" data-bs-toggle="modal"
-                                data-bs-target="#generate-ai-content">
-                            <i class="icon ti ti-robot"></i>
-                            AI 文档生成
+                        <button href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#generate-docs">
+                            <i class="icon ti ti-robot-face"></i>
+                            LLM 文档生成
                         </button>
                         <button id="create" href="#" class="btn btn-primary">
                             <i class="icon ti ti-device-floppy"></i>
@@ -30,25 +29,50 @@
     </div>
     <div class="page-body">
         <div class="container-xl">
-            <div class="card">
-                <div class="card-body">
-                    <div class="mb-3">
-                        <label class="form-label col-3 col-form-label">文档标题</label>
-                        <div class="col">
-                            <input id="title" type="text" class="form-control" value="">
+            <div class="row row-cards">
+                <div class="col-md-9 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <div class="mb-3">
+                                <label class="form-label col-3 col-form-label">文档标题</label>
+                                <div class="col">
+                                    <input id="title" type="text" class="form-control" value="">
+                                </div>
+                            </div>
+                            <div class="mb-3">
+                                <form method="post">
+                                    <textarea id="tinymce"></textarea>
+                                </form>
+                            </div>
                         </div>
                     </div>
-                    <div class="mb-3">
-                        <form method="post">
-                            <textarea id="tinymce"></textarea>
-                        </form>
+                </div>
+                <div class="col-md-3 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <h3 class="card-title">选项</h3>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">状态</label>
+                                <div class="col">
+                                    <select id="status" class="col form-select" value="1">
+                                        <option value="0">未发布</option>
+                                        <option value="1">已发布</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label">排序</label>
+                                <div class="col">
+                                    <input id="sort" type="text" class="form-control" value="0">
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
         </div>
     </div>
-
-    <div class="modal modal-blur fade" id="generate-ai-content" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal modal-blur fade" id="generate-docs" tabindex="-1" role="dialog" aria-hidden="true">
         <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
             <div class="modal-content">
                 <div class="modal-header">
@@ -57,7 +81,7 @@
                 </div>
                 <div class="modal-body">
                     <div class="mb-3">
-                        <input id="question" class="form-control" rows="12" placeholder="请输入文档生成问题"></input>
+                        <input id="question" class="form-control" rows="12" placeholder="请输入文档生成提示">
                     </div>
                 </div>
                 <div class="modal-footer">
@@ -82,8 +106,8 @@
             },
             success: function (data) {
                 if (data.ret === 1) {
-                    $('#success-noreload-message').text(data.msg);
-                    $('#success-noreload-dialog').modal('show');
+                    $('#success-message').text(data.msg);
+                    $('#success-dialog').modal('show');
                     tinyMCE.activeEditor.setContent(data.content);
                 } else {
                     $('#fail-message').text(data.msg);
@@ -99,7 +123,9 @@
             type: 'POST',
             dataType: "json",
             data: {
-                title: $("#title").val(),
+                {foreach $update_field as $key}
+                {$key}: $('#{$key}').val(),
+                {/foreach}
                 content: tinyMCE.activeEditor.getContent(),
             },
             success: function (data) {

+ 39 - 11
resources/views/tabler/admin/docs/edit.tpl

@@ -25,18 +25,44 @@
     </div>
     <div class="page-body">
         <div class="container-xl">
-            <div class="card">
-                <div class="card-body">
-                    <div class="mb-3">
-                        <label class="form-label col-3 col-form-label">文档标题</label>
-                        <div class="col">
-                            <input id="title" type="text" class="form-control" value="{$doc->title}">
+            <div class="row row-cards">
+                <div class="col-md-9 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <div class="mb-3">
+                                <label class="form-label col-3 col-form-label">文档标题</label>
+                                <div class="col">
+                                    <input id="title" type="text" class="form-control" value="{$doc->title}">
+                                </div>
+                            </div>
+                            <div class="mb-3">
+                                <form method="post">
+                                    <textarea id="tinymce">{$doc->content}</textarea>
+                                </form>
+                            </div>
                         </div>
                     </div>
-                    <div class="mb-3">
-                        <form method="post">
-                            <textarea id="tinymce">{$doc->content}</textarea>
-                        </form>
+                </div>
+                <div class="col-md-3 col-sm-12">
+                    <div class="card">
+                        <div class="card-body">
+                            <h3 class="card-title">选项</h3>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">状态</label>
+                                <div class="col">
+                                    <select id="status" class="col form-select" value="{$doc->status}">
+                                        <option value="0" {if $doc->status == 0} selected {/if}>未发布</option>
+                                        <option value="1" {if $doc->status == 1} selected {/if}>已发布</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label">排序</label>
+                                <div class="col">
+                                    <input id="sort" type="text" class="form-control" value="{$doc->sort}">
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -53,7 +79,9 @@
             type: 'PUT',
             dataType: "json",
             data: {
-                title: $("#title").val(),
+                {foreach $update_field as $key}
+                {$key}: $('#{$key}').val(),
+                {/foreach}
                 content: tinyMCE.activeEditor.getContent(),
             },
             success: function (data) {

+ 2 - 2
resources/views/tabler/admin/docs/index.tpl

@@ -79,8 +79,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 18 - 37
resources/views/tabler/admin/footer.tpl

@@ -22,30 +22,6 @@
     </div>
 </div>
 
-<div class="modal modal-blur fade" id="success-noreload-dialog" tabindex="-1" role="dialog" aria-hidden="true">
-    <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
-        <div class="modal-content">
-            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
-            <div class="modal-status bg-success"></div>
-            <div class="modal-body text-center py-4">
-                <i class="ti ti-circle-check icon mb-2 text-green icon-lg" style="font-size:3.5rem;"></i>
-                <p id="success-noreload-message" class="text-secondary">成功</p>
-            </div>
-            <div class="modal-footer">
-                <div class="w-100">
-                    <div class="row">
-                        <div class="col">
-                            <a id="success-noreload-confirm" href="" class="btn w-100" data-bs-dismiss="modal">
-                                好
-                            </a>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>
-
 <div class="modal modal-blur fade" id="fail-dialog" tabindex="-1" role="dialog" aria-hidden="true">
     <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
         <div class="modal-content">
@@ -98,13 +74,6 @@
                     </li>
                 </ul>
             </div>
-            <div class="col-12 col-lg-auto mt-3 mt-lg-0">
-                <ul class="list-inline list-inline-dots mb-0">
-                    <li class="list-inline-item">
-                        Theme by <a href="https://tabler.io/" class="link-secondary">Tabler</a>
-                    </li>
-                </ul>
-            </div>
         </div>
     </div>
 </footer>
@@ -113,11 +82,12 @@
 <!-- js -->
 <script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 <script>
-    let successDialog = new bootstrap.Modal(document.getElementById('success-dialog'));
-    let failDialog = new bootstrap.Modal(document.getElementById('fail-dialog'));
+    let successDialog = new tabler.bootstrap.Modal(document.getElementById('success-dialog'));
+    let failDialog = new tabler.bootstrap.Modal(document.getElementById('fail-dialog'));
 
     htmx.on("htmx:afterRequest", function(evt) {
         if (evt.detail.xhr.getResponseHeader('HX-Refresh') === 'true' ||
+            evt.detail.xhr.getResponseHeader('HX-Redirect') ||
             evt.detail.xhr.getResponseHeader('HX-Trigger'))
         {
             return;
@@ -125,6 +95,21 @@
 
         let res = JSON.parse(evt.detail.xhr.response);
 
+        if (typeof res.data !== 'undefined') {
+            for (let key in res.data) {
+                if (res.data.hasOwnProperty(key)) {
+                    let element = document.getElementById(key);
+
+                    if (element) {
+                        if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
+                            element.value = res.data[key];
+                        } else {
+                            element.innerHTML = res.data[key];
+                        }
+                    }
+                }
+            }
+        }
         if (res.ret === 1) {
             document.getElementById("success-message").innerHTML = res.msg;
             successDialog.show();
@@ -133,10 +118,6 @@
             failDialog.show();
         }
     });
-
-    $("#success-confirm").click(function () {
-        location.reload();
-    });
 </script>
 <script>console.table([['数据库查询', '执行时间'], ['{count($queryLog)} 次', '{$optTime} ms']])</script>
 

+ 2 - 2
resources/views/tabler/admin/giftcard.tpl

@@ -154,8 +154,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 8 - 8
resources/views/tabler/admin/index.tpl

@@ -172,7 +172,7 @@
                 grid: {
                     strokeDashArray: 3,
                 },
-                colors: [tabler.getColor("azure"), tabler.getColor("cyan"), tabler.getColor("orange")],
+                colors: ["#0080FF", "#00FFFF", "#FF4500"],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -184,7 +184,7 @@
                     },
                     itemMargin: {
                         horizontal: 8,
-                        vertical: 8
+                        vertical: 15
                     },
                 },
                 tooltip: {
@@ -212,7 +212,7 @@
                 grid: {
                     strokeDashArray: 2,
                 },
-                colors: [tabler.getColor("lime"), tabler.getColor("red")],
+                colors: ["#BFFF00", "#FF0000"],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -224,7 +224,7 @@
                     },
                     itemMargin: {
                         horizontal: 8,
-                        vertical: 8
+                        vertical: 15
                     },
                 },
                 tooltip: {
@@ -252,7 +252,7 @@
                 grid: {
                     strokeDashArray: 4,
                 },
-                colors: [tabler.getColor("yellow"), tabler.getColor("lime")],
+                colors: ["#FFFF00", "#BFFF00"],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -264,7 +264,7 @@
                     },
                     itemMargin: {
                         horizontal: 8,
-                        vertical: 8
+                        vertical: 15
                     },
                 },
                 tooltip: {
@@ -292,7 +292,7 @@
                 grid: {
                     strokeDashArray: 3,
                 },
-                colors: [tabler.getColor("green"), tabler.getColor("lime"), tabler.getColor("yellow")],
+                colors: ["#00FF00", "#BFFF00", "#FFFF00"],
                 legend: {
                     show: true,
                     position: 'bottom',
@@ -304,7 +304,7 @@
                     },
                     itemMargin: {
                         horizontal: 8,
-                        vertical: 8
+                        vertical: 15
                     },
                 },
                 tooltip: {

+ 4 - 4
resources/views/tabler/admin/log/sub.tpl

@@ -23,11 +23,11 @@
                         <div class="table-responsive">
                             <table id="data-table" class="table card-table table-vcenter text-nowrap datatable">
                                 <thead>
-                                <tr>
-                                    {foreach $details['field'] as $key => $value}
+                                    <tr>
+                                        {foreach $details['field'] as $key => $value}
                                         <th>{$value}</th>
-                                    {/foreach}
-                                </tr>
+                                        {/foreach}
+                                    </tr>
                                 </thead>
                             </table>
                         </div>

+ 2 - 2
resources/views/tabler/admin/node/edit.tpl

@@ -230,8 +230,8 @@
 <script>
     let clipboard = new ClipboardJS('.copy');
     clipboard.on('success', function (e) {
-        $('#success-noreload-message').text('已复制到剪切板');
-        $('#success-noreload-dialog').modal('show');
+        $('#success-message').text('已复制到剪切板');
+        $('#success-dialog').modal('show');
     });
 
     const container = document.getElementById('custom_config');

+ 4 - 4
resources/views/tabler/admin/node/index.tpl

@@ -81,8 +81,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);
@@ -103,8 +103,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 2 - 2
resources/views/tabler/admin/order/index.tpl

@@ -112,8 +112,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 4 - 4
resources/views/tabler/admin/product/index.tpl

@@ -79,8 +79,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);
@@ -101,8 +101,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 25 - 15
resources/views/tabler/admin/setting/billing.tpl

@@ -57,15 +57,15 @@
                             <div class="tab-content">
                                 <div class="tab-pane active show" id="gateway">
                                     {foreach $payment_gateways as $key => $value}
-                                        <div class="form-group mb-3 row">
-                                            <div class="row align-items-center">
-                                                <label class="form-label col-3 col-form-label">{$key}</label>
-                                                <label class="col-auto ms-auto form-check form-check-single form-switch">
-                                                    <input id="{$value}_enable" class="form-check-input" type="checkbox"
-                                                           {if in_array($value, $active_payment_gateway)}checked="" {/if}>
-                                                </label>
-                                            </div>
+                                    <div class="form-group mb-3 row">
+                                        <div class="row align-items-center">
+                                            <label class="form-label col-3 col-form-label">{$key}</label>
+                                            <label class="col-auto ms-auto form-check form-check-single form-switch">
+                                                <input id="{$value}_enable" class="form-check-input" type="checkbox"
+                                                       {if in_array($value, $active_payment_gateway)}checked="" {/if}>
+                                            </label>
                                         </div>
+                                    </div>
                                     {/foreach}
                                 </div>
                                 <div class="tab-pane" id="f2f">
@@ -269,7 +269,7 @@
                                 <div class="tab-pane" id="paypal">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">模式</label>
+                                            <label class="form-label col-3 col-form-label">Mode</label>
                                             <div class="col">
                                                 <select id="paypal_mode" class="col form-select"
                                                         value="{$settings['paypal_mode']}">
@@ -281,28 +281,38 @@
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">PayPal客戶ID</label>
+                                            <label class="form-label col-3 col-form-label">Client ID</label>
                                             <div class="col">
                                                 <input id="paypal_client_id" type="text" class="form-control"
                                                        value="{$settings['paypal_client_id']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">PayPal客戶密钥</label>
+                                            <label class="form-label col-3 col-form-label">Client Secret</label>
                                             <div class="col">
                                                 <input id="paypal_client_secret" type="text" class="form-control"
                                                        value="{$settings['paypal_client_secret']}">
                                             </div>
+                                            <div class="col-auto">
+                                                <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/billing/set_paypal_webhook" hx-swap="none"
+                                                        hx-vals='js:{
+                                                            paypal_client_id: document.getElementById("paypal_client_id").value,
+                                                            paypal_client_secret: document.getElementById("paypal_client_secret").value,
+                                                        }'>
+                                                    Set Webhook
+                                                </button>
+                                            </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">PayPal货币</label>
+                                            <label class="form-label col-3 col-form-label">Currency</label>
                                             <div class="col">
                                                 <input id="paypal_currency" type="text" class="form-control"
                                                        value="{$settings['paypal_currency']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">PayPal语言</label>
+                                            <label class="form-label col-3 col-form-label">Language</label>
                                             <div class="col">
                                                 <input id="paypal_locale" type="text" class="form-control"
                                                        value="{$settings['paypal_locale']}">
@@ -386,10 +396,10 @@
                     dataType: "json",
                     data: {
                         {foreach $update_field as $key}
-                        {$key}: $('#{$key}').val(),
+                            {$key}: $('#{$key}').val(),
                         {/foreach}
                         {foreach $payment_gateways as $key => $value}
-                        {$key}: $("#{$key}_enable").is(":checked"),
+                            {$value}: $("#{$value}_enable").is(":checked"),
                         {/foreach}
                     },
                     success: function (data) {

+ 47 - 7
resources/views/tabler/admin/setting/captcha.tpl

@@ -42,6 +42,11 @@
                                 <li class="nav-item">
                                     <a href="#hcaptcha" class="nav-link" data-bs-toggle="tab">hCaptcha</a>
                                 </li>
+                                <li class="nav-item">
+                                    <a href="#recaptcha_enterprise" class="nav-link" data-bs-toggle="tab">
+                                        reCAPTCHA Enterprise
+                                    </a>
+                                </li>
                             </ul>
                         </div>
                         <div class="card-body">
@@ -65,6 +70,10 @@
                                                             {if $settings['captcha_provider'] === "hcaptcha"}selected{/if}>
                                                         hCaptcha
                                                     </option>
+                                                    <option value="recaptcha_enterprise"
+                                                            {if $settings['captcha_provider'] === "recaptcha_enterprise"}selected{/if}>
+                                                        reCAPTCHA Enterprise
+                                                    </option>
                                                 </select>
                                             </div>
                                         </div>
@@ -131,14 +140,14 @@
                                 <div class="tab-pane" id="turnstile">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Turnstile Site Key</label>
+                                            <label class="form-label col-3 col-form-label">Site Key</label>
                                             <div class="col">
                                                 <input id="turnstile_sitekey" type="text" class="form-control"
                                                        value="{$settings['turnstile_sitekey']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Turnstile Secret</label>
+                                            <label class="form-label col-3 col-form-label">Secret</label>
                                             <div class="col">
                                                 <input id="turnstile_secret" type="text" class="form-control"
                                                        value="{$settings['turnstile_secret']}">
@@ -149,14 +158,14 @@
                                 <div class="tab-pane" id="geetest">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Geetest ID</label>
+                                            <label class="form-label col-3 col-form-label">ID</label>
                                             <div class="col">
                                                 <input id="geetest_id" type="text" class="form-control"
                                                        value="{$settings['geetest_id']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Geetest Key</label>
+                                            <label class="form-label col-3 col-form-label">Key</label>
                                             <div class="col">
                                                 <input id="geetest_key" type="text" class="form-control"
                                                        value="{$settings['geetest_key']}">
@@ -167,14 +176,14 @@
                                 <div class="tab-pane" id="hcaptcha">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">hCaptcha Site Key</label>
+                                            <label class="form-label col-3 col-form-label">Site Key</label>
                                             <div class="col">
                                                 <input id="hcaptcha_sitekey" type="text" class="form-control"
                                                        value="{$settings['hcaptcha_sitekey']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">hCaptcha Secret</label>
+                                            <label class="form-label col-3 col-form-label">Secret</label>
                                             <div class="col">
                                                 <input id="hcaptcha_secret" type="text" class="form-control"
                                                        value="{$settings['hcaptcha_secret']}">
@@ -182,6 +191,37 @@
                                         </div>
                                     </div>
                                 </div>
+                                <div class="tab-pane" id="recaptcha_enterprise">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                Key
+                                            </label>
+                                            <div class="col">
+                                                <input id="recaptcha_enterprise_key_id" type="text" class="form-control"
+                                                       value="{$settings['recaptcha_enterprise_key_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                Project ID
+                                            </label>
+                                            <div class="col">
+                                                <input id="recaptcha_enterprise_project_id" type="text" class="form-control"
+                                                       value="{$settings['recaptcha_enterprise_project_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                API Key
+                                            </label>
+                                            <div class="col">
+                                                <input id="recaptcha_enterprise_api_key" type="text" class="form-control"
+                                                       value="{$settings['recaptcha_enterprise_api_key']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
                             </div>
                         </div>
                     </div>
@@ -213,4 +253,4 @@
             });
         </script>
 
-{include file='admin/footer.tpl'}
+        {include file='admin/footer.tpl'}

+ 37 - 10
resources/views/tabler/admin/setting/email.tpl

@@ -60,6 +60,9 @@
                                 <li class="nav-item">
                                     <a href="#postmark" class="nav-link" data-bs-toggle="tab">Postmark</a>
                                 </li>
+                                <li class="nav-item">
+                                    <a href="#resend" class="nav-link" data-bs-toggle="tab">Resend</a>
+                                </li>
                             </ul>
                         </div>
                         <div class="card-body">
@@ -72,34 +75,40 @@
                                                 <select id="email_driver" class="col form-select"
                                                         value="{$settings['email_driver']}">
                                                     <option value="none"
-                                                            {if $settings['email_driver'] === "none"}selected{/if}>none
+                                                            {if $settings['email_driver'] === "none"}selected{/if}>
+                                                        None
                                                     </option>
                                                     <option value="smtp"
-                                                            {if $settings['email_driver'] === "smtp"}selected{/if}>smtp
+                                                            {if $settings['email_driver'] === "smtp"}selected{/if}>
+                                                        SMTP
                                                     </option>
                                                     <option value="mailgun"
                                                             {if $settings['email_driver'] === "mailgun"}selected{/if}>
-                                                        mailgun
+                                                        Mailgun
                                                     </option>
                                                     <option value="sendgrid"
                                                             {if $settings['email_driver'] === "sendgrid"}selected{/if}>
-                                                        sendgrid
+                                                        Sendgrid
                                                     </option>
                                                     <option value="postal"
                                                             {if $settings['email_driver'] === "postal"}selected{/if}>
-                                                        postal
+                                                        Postal
                                                     </option>
                                                     <option value="ses"
                                                             {if $settings['email_driver'] === "ses"}selected{/if}>
-                                                        ses
+                                                        AWS SES
                                                     </option>
                                                     <option value="mailchimp"
                                                             {if $settings['email_driver'] === "mailchimp"}selected{/if}>
-                                                        mailchimp
+                                                        Mailchimp
                                                     </option>
                                                     <option value="alibabacloud"
                                                             {if $settings['email_driver'] === "alibabacloud"}selected{/if}>
-                                                        alibabacloud
+                                                        AlibabaCloud DM
+                                                    </option>
+                                                    <option value="resend"
+                                                            {if $settings['email_driver'] === "resend"}selected{/if}>
+                                                        Resend
                                                     </option>
                                                     <option value="postmark"
                                                             {if $settings['email_driver'] === "postmark"}selected{/if}>
@@ -421,6 +430,7 @@
                                         </div>
                                     </div>
                                 </div>
+                                </div>
                                 <div class="tab-pane" id="postmark">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
@@ -446,6 +456,23 @@
                                         </div>
                                     </div>
                                 </div>
+                                <div class="tab-pane" id="resend">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Api Key</label>
+                                            <div class="col">
+                                                <input id="resend_api_key" type="text" class="form-control"
+                                                       value="{$settings['resend_api_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">From</label>
+                                            <div class="col">
+                                                <input id="resend_from" type="text" class="form-control"
+                                                       value="{$settings['resend_from']}">
+                                            </div>
+                                        </div>
+                                    </div>
                             </div>
                         </div>
                     </div>
@@ -486,8 +513,8 @@
                     },
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                         } else {
                             $('#fail-message').text(data.msg);
                             $('#fail-dialog').modal('show');

+ 214 - 203
resources/views/tabler/admin/setting/im.tpl

@@ -31,7 +31,7 @@
                         <div class="card-header">
                             <ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
                                 <li class="nav-item">
-                                    <a href="#notification" class="nav-link active" data-bs-toggle="tab">通知设定</a>
+                                    <a href="#notification" class="nav-link active" data-bs-toggle="tab">Notification</a>
                                 </li>
                                 <li class="nav-item">
                                     <a href="#telegram" class="nav-link" data-bs-toggle="tab">Telegram Bot</a>
@@ -49,239 +49,262 @@
                                 <div class="tab-pane active show" id="notification">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">添加节点通知</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node Addition
+                                            </label>
                                             <div class="col">
-                                                <select id="telegram_add_node" class="col form-select"
-                                                        value="{$settings['telegram_add_node']}">
+                                                <select id="im_bot_group_notify_add_node" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_add_node']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_add_node']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_add_node']}selected{/if}>
+                                                        False
                                                     </option>
-                                                    <option value="1" {if $settings['telegram_add_node']}selected{/if}>
-                                                        开启
+                                                    <option value="1"
+                                                            {if $settings['im_bot_group_notify_add_node']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">添加节点通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node Update
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_add_node_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_add_node_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">修改节点通知</label>
-                                            <div class="col">
-                                                <select id="telegram_update_node" class="col form-select"
-                                                        value="{$settings['telegram_update_node']}">
+                                                <select id="im_bot_group_notify_update_node" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_update_node']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_update_node']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_update_node']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_update_node']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_update_node']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">修改节点通知文本</label>
-                                            <div class="col">
-                                                <input id="telegram_update_node_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_update_node_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">删除节点通知</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node Deletion
+                                            </label>
                                             <div class="col">
-                                                <select id="telegram_delete_node" class="col form-select"
-                                                        value="{$settings['telegram_delete_node']}">
+                                                <select id="im_bot_group_notify_delete_node" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_delete_node']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_delete_node']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_delete_node']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_delete_node']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_delete_node']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">删除节点通知文本</label>
-                                            <div class="col">
-                                                <input id="telegram_delete_node_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_delete_node_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点被墙通知</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node GFWed
+                                            </label>
                                             <div class="col">
-                                                <select id="telegram_node_gfwed" class="col form-select"
-                                                        value="{$settings['telegram_node_gfwed']}">
+                                                <select id="im_bot_group_notify_node_gfwed" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_node_gfwed']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_node_gfwed']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_node_gfwed']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_node_gfwed']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_node_gfwed']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点被墙通知文本</label>
-                                            <div class="col">
-                                                <input id="telegram_node_gfwed_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_node_gfwed_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点被墙恢复通知</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node UnGFWed
+                                            </label>
                                             <div class="col">
-                                                <select id="telegram_node_ungfwed" class="col form-select"
-                                                        value="{$settings['telegram_node_ungfwed']}">
+                                                <select id="im_bot_group_notify_node_ungfwed" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_node_ungfwed']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_node_ungfwed']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_node_ungfwed']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_node_ungfwed']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_node_ungfwed']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点被墙恢复通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node Online
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_node_ungfwed_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_node_ungfwed_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点离线通知</label>
-                                            <div class="col">
-                                                <select id="telegram_node_offline" class="col form-select"
-                                                        value="{$settings['telegram_node_offline']}">
+                                                <select id="im_bot_group_notify_node_online" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_node_online']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_node_offline']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_node_online']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_node_offline']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_node_online']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点离线通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Node Offline
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_node_offline_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_node_offline_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点上线通知</label>
-                                            <div class="col">
-                                                <select id="telegram_node_online" class="col form-select"
-                                                        value="{$settings['telegram_node_online']}">
+                                                <select id="im_bot_group_notify_node_offline" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_node_offline']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_node_online']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_node_offline']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['telegram_node_online']}selected{/if}>开启
+                                                            {if $settings['im_bot_group_notify_node_offline']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">节点上线通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Daily Job
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_node_online_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_node_online_text']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">每日任务通知</label>
-                                            <div class="col">
-                                                <select id="telegram_daily_job" class="col form-select"
-                                                        value="{$settings['telegram_daily_job']}">
+                                                <select id="im_bot_group_notify_daily_job" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_daily_job']}">
                                                     <option value="0"
-                                                            {if ! $settings['telegram_daily_job']}selected{/if}>关闭
+                                                            {if ! $settings['im_bot_group_notify_daily_job']}selected{/if}>
+                                                        False
                                                     </option>
-                                                    <option value="1" {if $settings['telegram_daily_job']}selected{/if}>
-                                                        开启
+                                                    <option value="1"
+                                                            {if $settings['im_bot_group_notify_daily_job']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">每日任务通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                System Dairy
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_daily_job_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_daily_job_text']}">
+                                                <select id="im_bot_group_notify_diary" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_diary']}">
+                                                    <option value="0" {if ! $settings['im_bot_group_notify_diary']}selected{/if}>
+                                                        False
+                                                    </option>
+                                                    <option value="1" {if $settings['im_bot_group_notify_diary']}selected{/if}>
+                                                        True
+                                                    </option>
+                                                </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">系统运行状况通知</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Announcement Creation
+                                            </label>
                                             <div class="col">
-                                                <select id="telegram_diary" class="col form-select"
-                                                        value="{$settings['telegram_diary']}">
-                                                    <option value="0" {if ! $settings['telegram_diary']}selected{/if}>
-                                                        关闭
+                                                <select id="im_bot_group_notify_ann_create" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_ann_create']}">
+                                                    <option value="0"
+                                                            {if ! $settings['im_bot_group_notify_ann_create']}selected{/if}>
+                                                        False
                                                     </option>
-                                                    <option value="1" {if $settings['telegram_diary']}selected{/if}>
-                                                        开启
+                                                    <option value="1"
+                                                            {if $settings['im_bot_group_notify_ann_create']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">系统运行状况通知文本</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Announcement Update
+                                            </label>
                                             <div class="col">
-                                                <input id="telegram_diary_text" type="text" class="form-control"
-                                                       value="{$settings['telegram_diary_text']}">
+                                                <select id="im_bot_group_notify_ann_update" class="col form-select"
+                                                        value="{$settings['im_bot_group_notify_ann_update']}">
+                                                    <option value="0"
+                                                            {if ! $settings['im_bot_group_notify_ann_update']}selected{/if}>
+                                                        False
+                                                    </option>
+                                                    <option value="1"
+                                                            {if $settings['im_bot_group_notify_ann_update']}selected{/if}>
+                                                        True
+                                                    </option>
+                                                </select>
                                             </div>
                                         </div>
                                     </div>
                                 </div>
                                 <div class="tab-pane" id="telegram">
                                     <div class="card-body">
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">是否启用 Telegram
-                                                机器人</label>
-                                            <div class="col">
-                                                <select id="enable_telegram" class="col form-select"
-                                                        value="{$settings['enable_telegram']}">
-                                                    <option value="0" {if ! $settings['enable_telegram']}selected{/if}>
-                                                        关闭
-                                                    </option>
-                                                    <option value="1" {if $settings['enable_telegram']}selected{/if}>
-                                                        开启
-                                                    </option>
-                                                </select>
-                                            </div>
-                                        </div>
                                         <div class="form-group mb-3 row">
                                             <label class="form-label col-3 col-form-label">Bot Token</label>
                                             <div class="col">
                                                 <input id="telegram_token" type="text" class="form-control"
                                                        value="{$settings['telegram_token']}">
                                             </div>
+                                            <div class="col-auto">
+                                                <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/im/set_webhook/telegram" hx-swap="none"
+                                                        hx-vals='js:{
+                                                            bot_token: document.getElementById("telegram_token").value
+                                                        }'>
+                                                    Set Webhook
+                                                </button>
+                                            </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Telegram 群组会话 ID</label>
+                                            <label class="form-label col-3 col-form-label">Webhook Token</label>
                                             <div class="col">
-                                                <input id="telegram_chatid" type="text" class="form-control"
-                                                       value="{$settings['telegram_chatid']}">
+                                                <input id="telegram_webhook_token" type="text" class="form-control"
+                                                       value="{$settings['telegram_webhook_token']}" disabled>
+                                            </div>
+                                            <div class="col-auto">
+                                                <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/im/reset_webhook_token/telegram" hx-swap="none">
+                                                    Reset Webhook Token
+                                                </button>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Telegram 机器人账号</label>
+                                            <label class="form-label col-3 col-form-label">Bot Account Username</label>
                                             <div class="col">
                                                 <input id="telegram_bot" type="text" class="form-control"
                                                        value="{$settings['telegram_bot']}">
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Telegram Webhook 密钥</label>
+                                            <label class="form-label col-3 col-form-label">Group ID</label>
                                             <div class="col">
-                                                <input id="telegram_request_token" type="text" class="form-control"
-                                                       value="{$settings['telegram_request_token']}">
+                                                <input id="telegram_chatid" type="text" class="form-control"
+                                                       value="{$settings['telegram_chatid']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Telegram group notify
+                                            </label>
+                                            <div class="col">
+                                                <select id="enable_telegram_group_notify" class="col form-select"
+                                                        value="{$settings['enable_telegram_group_notify']}">
+                                                    <option value="0" {if ! $settings['enable_telegram_group_notify']}selected{/if}>
+                                                        False
+                                                    </option>
+                                                    <option value="1" {if $settings['enable_telegram_group_notify']}selected{/if}>
+                                                        True
+                                                    </option>
+                                                </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
@@ -385,18 +408,14 @@
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">未绑定账户的回复</label>
-                                            <div class="col">
-                                                <input id="user_not_bind_reply" type="text" class="form-control"
-                                                       value="{$settings['user_not_bind_reply']}">
-                                            </div>
-                                        </div>
-                                        <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Telegram 用户 ID</label>
-                                            <input type="text" class="form-control" id="telegram_user_id" value="">
+                                            <label class="form-label col-3 col-form-label">Telegram Chat ID(Group/DM)</label>
+                                            <input type="text" class="form-control" id="telegram_chat_id" value="">
                                             <div class="row my-3">
                                                 <div class="col">
-                                                    <button id="test-telegram" class="btn btn-primary">发送测试信息
+                                                    <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/test/telegram" hx-swap="none"
+                                                        hx-vals='js:{ telegram_chat_id: document.getElementById("telegram_chat_id").value }'>
+                                                        Send Test Message
                                                     </button>
                                                 </div>
                                             </div>
@@ -434,11 +453,37 @@
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Discord 用户 ID</label>
-                                            <input type="text" class="form-control" id="discord_user_id" value="">
+                                            <label class="form-label col-3 col-form-label">Discord Channel ID</label>
+                                            <div class="col">
+                                                <input id="discord_channel_id" type="text" class="form-control"
+                                                       value="{$settings['discord_channel_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Discord channel notify
+                                            </label>
+                                            <div class="col">
+                                                <select id="enable_discord_channel_notify" class="col form-select"
+                                                        value="{$settings['enable_discord_channel_notify']}">
+                                                    <option value="0" {if ! $settings['enable_discord_channel_notify']}selected{/if}>
+                                                        False
+                                                    </option>
+                                                    <option value="1" {if $settings['enable_discord_channel_notify']}selected{/if}>
+                                                        True
+                                                    </option>
+                                                </select>
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Discord User ID/Channel ID</label>
+                                            <input type="text" class="form-control" id="discord_channel_id" value="">
                                             <div class="row my-3">
                                                 <div class="col">
-                                                    <button id="test-discord" class="btn btn-primary">发送测试信息
+                                                    <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/test/discord" hx-swap="none"
+                                                        hx-vals='js:{ discord_channel_id: document.getElementById("discord_channel_id").value }'>
+                                                        Send Test Message
                                                     </button>
                                                 </div>
                                             </div>
@@ -476,11 +521,37 @@
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">Slack 用户 ID</label>
-                                            <input type="text" class="form-control" id="slack_user_id" value="">
+                                            <label class="form-label col-3 col-form-label">Slack Channel ID</label>
+                                            <div class="col">
+                                                <input id="slack_channel_id" type="text" class="form-control"
+                                                       value="{$settings['slack_channel_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Slack channel notify
+                                            </label>
+                                            <div class="col">
+                                                <select id="enable_slack_channel_notify" class="col form-select"
+                                                        value="{$settings['enable_slack_channel_notify']}">
+                                                    <option value="0" {if ! $settings['enable_slack_channel_notify']}selected{/if}>
+                                                        False
+                                                    </option>
+                                                    <option value="1" {if $settings['enable_slack_channel_notify']}selected{/if}>
+                                                        True
+                                                    </option>
+                                                </select>
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Slack User ID/Channel ID</label>
+                                            <input type="text" class="form-control" id="slack_channel_id" value="">
                                             <div class="row my-3">
                                                 <div class="col">
-                                                    <button id="test-slack" class="btn btn-primary">发送测试信息
+                                                    <button class="btn btn-primary"
+                                                        hx-post="/admin/setting/test/slack" hx-swap="none"
+                                                        hx-vals='js:{ slack_channel_id: document.getElementById("slack_channel_id").value }'>
+                                                        Send Test Message
                                                     </button>
                                                 </div>
                                             </div>
@@ -516,66 +587,6 @@
                     }
                 })
             });
-
-            $("#test-telegram").click(function () {
-                $.ajax({
-                    url: '/admin/setting/test/telegram',
-                    type: 'POST',
-                    dataType: "json",
-                    data: {
-                        telegram_user_id: $('#telegram_user_id').val(),
-                    },
-                    success: function (data) {
-                        if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
-                        } else {
-                            $('#fail-message').text(data.msg);
-                            $('#fail-dialog').modal('show');
-                        }
-                    }
-                })
-            });
-
-            $("#test-discord").click(function () {
-                $.ajax({
-                    url: '/admin/setting/test/discord',
-                    type: 'POST',
-                    dataType: "json",
-                    data: {
-                        discord_user_id: $('#discord_user_id').val(),
-                    },
-                    success: function (data) {
-                        if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
-                        } else {
-                            $('#fail-message').text(data.msg);
-                            $('#fail-dialog').modal('show');
-                        }
-                    }
-                })
-            });
-
-            $("#test-slack").click(function () {
-                $.ajax({
-                    url: '/admin/setting/test/slack',
-                    type: 'POST',
-                    dataType: "json",
-                    data: {
-                        slack_user_id: $('#slack_user_id').val(),
-                    },
-                    success: function (data) {
-                        if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
-                        } else {
-                            $('#fail-message').text(data.msg);
-                            $('#fail-dialog').modal('show');
-                        }
-                    }
-                })
-            });
         </script>
 
         {include file='admin/footer.tpl'}

+ 297 - 0
resources/views/tabler/admin/setting/llm.tpl

@@ -0,0 +1,297 @@
+{include file='admin/header.tpl'}
+
+<div class="page-wrapper">
+    <div class="container-xl">
+        <div class="page-header d-print-none text-white">
+            <div class="row align-items-center">
+                <div class="col">
+                    <h2 class="page-title">
+                        <span class="home-title">LLM</span>
+                    </h2>
+                    <div class="page-pretitle my-3">
+                        <span class="home-subtitle">设置站点的大型语言模型服务</span>
+                    </div>
+                </div>
+                <div class="col-auto ms-auto d-print-none">
+                    <div class="btn-list">
+                        <a id="save-setting" href="#" class="btn btn-primary">
+                            <i class="icon ti ti-device-floppy"></i>
+                            保存
+                        </a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="page-body">
+        <div class="container-xl">
+            <div class="row row-deck row-cards">
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">
+                            <ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
+                                <li class="nav-item">
+                                    <a href="#backend" class="nav-link active" data-bs-toggle="tab">设置</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#openai" class="nav-link" data-bs-toggle="tab">OpenAI</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#google-ai" class="nav-link" data-bs-toggle="tab">Google AI</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#vertex-ai" class="nav-link" data-bs-toggle="tab">Vertex AI</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#huggingface" class="nav-link" data-bs-toggle="tab">Hugging Face</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#cf-workers-ai" class="nav-link" data-bs-toggle="tab">Cloudflare Workers AI</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#anthropic" class="nav-link" data-bs-toggle="tab">Anthropic</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a href="#aws-bedrock" class="nav-link" data-bs-toggle="tab">AWS Bedrock</a>
+                                </li>
+                            </ul>
+                        </div>
+                        <div class="card-body">
+                            <div class="tab-content">
+                                <div class="tab-pane active show" id="backend">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Backend</label>
+                                            <div class="col">
+                                                <select id="llm_backend" class="col form-select"
+                                                        value="{$settings['llm_backend']}">
+                                                    <option value=""
+                                                            {if $settings['llm_backend'] === ""}selected{/if}>
+                                                        None
+                                                    </option>
+                                                    <option value="openai"
+                                                            {if $settings['llm_backend'] === "openai"}selected{/if}>
+                                                        OpenAI
+                                                    </option>
+                                                    <option value="google-ai"
+                                                            {if $settings['llm_backend'] === "google-ai"}selected{/if}>
+                                                        Google AI
+                                                    </option>
+                                                    <option value="vertex-ai"
+                                                            {if $settings['llm_backend'] === "vertex-ai"}selected{/if}>
+                                                        Vertex AI
+                                                    </option>
+                                                    <option value="huggingface"
+                                                            {if $settings['llm_backend'] === "huggingface"}selected{/if}>
+                                                        Hugging Face
+                                                    </option>
+                                                    <option value="cf-workers-ai"
+                                                            {if $settings['llm_backend'] === "cf-workers-ai"}selected{/if}>
+                                                        Cloudflare Workers AI
+                                                    </option>
+                                                    <option value="anthropic"
+                                                            {if $settings['llm_backend'] === "anthropic"}selected{/if}>
+                                                        Anthropic
+                                                    </option>
+                                                    <option value="aws-bedrock"
+                                                            {if $settings['llm_backend'] === "aws-bedrock"}selected{/if}>
+                                                        AWS Bedrock
+                                                    </option>
+                                                </select>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="openai">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">API Key</label>
+                                            <div class="col">
+                                                <input id="openai_api_key" type="text" class="form-control"
+                                                       value="{$settings['openai_api_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="openai_model_id" type="text" class="form-control"
+                                                       value="{$settings['openai_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="google-ai">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">API Key</label>
+                                            <div class="col">
+                                                <input id="google_ai_api_key" type="text" class="form-control"
+                                                       value="{$settings['google_ai_api_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="google_ai_model_id" type="text" class="form-control"
+                                                       value="{$settings['google_ai_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="vertex-ai">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Access Token</label>
+                                            <div class="col">
+                                                <input id="vertex_ai_access_token" type="text" class="form-control"
+                                                       value="{$settings['vertex_ai_access_token']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Location</label>
+                                            <div class="col">
+                                                <input id="vertex_ai_location" type="text" class="form-control"
+                                                       value="{$settings['vertex_ai_location']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Project ID</label>
+                                            <div class="col">
+                                                <input id="vertex_ai_project_id" type="text" class="form-control"
+                                                       value="{$settings['vertex_ai_project_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="vertex_ai_model_id" type="text" class="form-control"
+                                                       value="{$settings['vertex_ai_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="huggingface">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">API Key</label>
+                                            <div class="col">
+                                                <input id="huggingface_api_key" type="text" class="form-control"
+                                                       value="{$settings['huggingface_api_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Endpoint URL</label>
+                                            <div class="col">
+                                                <input id="huggingface_endpoint_url" type="text" class="form-control"
+                                                       value="{$settings['huggingface_endpoint_url']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="cf-workers-ai">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Account ID</label>
+                                            <div class="col">
+                                                <input id="cf_workers_ai_account_id" type="text" class="form-control"
+                                                       value="{$settings['cf_workers_ai_account_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">API Token</label>
+                                            <div class="col">
+                                                <input id="cf_workers_ai_api_token" type="text" class="form-control"
+                                                       value="{$settings['cf_workers_ai_api_token']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="cf_workers_ai_model_id" type="text" class="form-control"
+                                                       value="{$settings['cf_workers_ai_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="anthropic">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">API Key</label>
+                                            <div class="col">
+                                                <input id="anthropic_api_key" type="text" class="form-control"
+                                                       value="{$settings['anthropic_api_key']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="anthropic_model_id" type="text" class="form-control"
+                                                       value="{$settings['anthropic_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="tab-pane" id="aws-bedrock">
+                                    <div class="card-body">
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Access Key ID</label>
+                                            <div class="col">
+                                                <input id="aws_bedrock_access_key_id" type="text" class="form-control"
+                                                       value="{$settings['aws_bedrock_access_key_id']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Access Key Secret</label>
+                                            <div class="col">
+                                                <input id="aws_bedrock_access_key_secret" type="text" class="form-control"
+                                                       value="{$settings['aws_bedrock_access_key_secret']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Region</label>
+                                            <div class="col">
+                                                <input id="aws_bedrock_region" type="text" class="form-control"
+                                                       value="{$settings['aws_bedrock_region']}">
+                                            </div>
+                                        </div>
+                                        <div class="form-group mb-3 row">
+                                            <label class="form-label col-3 col-form-label">Model ID</label>
+                                            <div class="col">
+                                                <input id="aws_bedrock_model_id" type="text" class="form-control"
+                                                       value="{$settings['aws_bedrock_model_id']}">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <script>
+            $("#save-setting").click(function () {
+                $.ajax({
+                    url: '/admin/setting/llm',
+                    type: 'POST',
+                    dataType: "json",
+                    data: {
+                        {foreach $update_field as $key}
+                        {$key}: $('#{$key}').val(),
+                        {/foreach}
+                    },
+                    success: function (data) {
+                        if (data.ret === 1) {
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
+                        } else {
+                            $('#fail-message').text(data.msg);
+                            $('#fail-dialog').modal('show');
+                        }
+                    }
+                })
+            });
+        </script>
+
+        {include file='admin/footer.tpl'}

+ 3 - 3
resources/views/tabler/admin/setting/ref.tpl

@@ -31,7 +31,7 @@
                         <div class="card-header">
                             <ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
                                 <li class="nav-item">
-                                    <a href="#invite" class="nav-link active" data-bs-toggle="tab">邀请设置</a>
+                                    <a href="#invite" class="nav-link active" data-bs-toggle="tab">邀请奖励</a>
                                 </li>
                                 <li class="nav-item">
                                     <a href="#rebate" class="nav-link" data-bs-toggle="tab">返利</a>
@@ -63,7 +63,7 @@
                                 <div class="tab-pane" id="rebate">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">邀请模式</label>
+                                            <label class="form-label col-3 col-form-label">返利模式</label>
                                             <div class="col">
                                                 <select id="invite_mode" class="col form-select"
                                                         value="{$settings['invite_mode']}">
@@ -79,7 +79,7 @@
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">返利模式</label>
+                                            <label class="form-label col-3 col-form-label">返利奖励模式</label>
                                             <div class="col">
                                                 <select id="invite_reward_mode" class="col form-select"
                                                         value="{$settings['invite_reward_mode']}">

+ 0 - 7
resources/views/tabler/admin/setting/reg.tpl

@@ -36,9 +36,6 @@
                                 <li class="nav-item">
                                     <a href="#default_value" class="nav-link" data-bs-toggle="tab">默认值</a>
                                 </li>
-                                <li class="nav-item">
-                                    <a href="#limit" class="nav-link" data-bs-toggle="tab">账户限制</a>
-                                </li>
                             </ul>
                         </div>
                         <div class="card-body">
@@ -162,10 +159,6 @@
                                                        value="{$settings['reg_method']}">
                                             </div>
                                         </div>
-                                    </div>
-                                </div>
-                                <div class="tab-pane" id="limit">
-                                    <div class="card-body">
                                         <div class="form-group mb-3 row">
                                             <label class="form-label col-3 col-form-label">连接 IP 限制</label>
                                             <div class="col">

+ 22 - 17
resources/views/tabler/admin/setting/sub.tpl

@@ -40,68 +40,73 @@
                                 <div class="tab-pane active show" id="sub">
                                     <div class="card-body">
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">启用 Shadowsocks
-                                                订阅(仅影响前端显示与ss/sip002/sip008订阅)</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Shadowsocks Subscription
+                                            </label>
                                             <div class="col">
                                                 <select id="enable_ss_sub" class="col form-select"
                                                         value="{$settings['enable_ss_sub']}">
                                                     <option value="0" {if ! $settings['enable_ss_sub']}selected{/if}>
-                                                        关闭
+                                                        False
                                                     </option>
                                                     <option value="1" {if $settings['enable_ss_sub']}selected{/if}>
-                                                        开启
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">启用 V2Ray
-                                                订阅(仅影响前端显示与v2ray订阅)</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Vmess Subscription
+                                            </label>
                                             <div class="col">
                                                 <select id="enable_v2_sub" class="col form-select"
                                                         value="{$settings['enable_v2_sub']}">
                                                     <option value="0" {if ! $settings['enable_v2_sub']}selected{/if}>
-                                                        关闭
+                                                        False
                                                     </option>
                                                     <option value="1" {if $settings['enable_v2_sub']}selected{/if}>
-                                                        开启
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">启用 Trojan
-                                                订阅(仅影响前端显示与trojan订阅)</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                Enable Trojan Subscription
+                                            </label>
                                             <div class="col">
                                                 <select id="enable_trojan_sub" class="col form-select"
                                                         value="{$settings['enable_trojan_sub']}">
-                                                    <option value="0"
-                                                            {if ! $settings['enable_trojan_sub']}selected{/if}>关闭
+                                                    <option value="0" {if ! $settings['enable_trojan_sub']}selected{/if}>
+                                                        False
                                                     </option>
                                                     <option value="1" {if $settings['enable_trojan_sub']}selected{/if}>
-                                                        开启
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                         <div class="form-group mb-3 row">
-                                            <label class="form-label col-3 col-form-label">用户修改账户登录密码时,是否强制更换订阅地址</label>
+                                            <label class="form-label col-3 col-form-label">
+                                                修改账户登录密码时重置订阅地址
+                                            </label>
                                             <div class="col">
                                                 <select id="enable_forced_replacement" class="col form-select"
                                                         value="{$settings['enable_forced_replacement']}">
                                                     <option value="0"
                                                             {if ! $settings['enable_forced_replacement']}selected{/if}>
-                                                        关闭
+                                                        False
                                                     </option>
                                                     <option value="1"
-                                                            {if $settings['enable_forced_replacement']}selected{/if}>开启
+                                                            {if $settings['enable_forced_replacement']}selected{/if}>
+                                                        True
                                                     </option>
                                                 </select>
                                             </div>
                                         </div>
                                     </div>
                                 </div>
-
                             </div>
                         </div>
                     </div>

+ 1 - 1
resources/views/tabler/admin/setting/support.tpl

@@ -48,7 +48,7 @@
                                                 <select id="live_chat" class="col form-select"
                                                         value="{$settings['live_chat']}">
                                                     <option value="none"
-                                                            {if $settings['live_chat'] === "none"}selected{/if}>
+                                                            {if $settings['live_chat'] === "none"}selected{/if}>None
                                                     </option>
                                                     <option value="crisp"
                                                             {if $settings['live_chat'] === "crisp"}selected{/if}>Crisp

+ 68 - 0
resources/views/tabler/admin/syslog/index.tpl

@@ -0,0 +1,68 @@
+{include file='admin/header.tpl'}
+
+<div class="page-wrapper">
+    <div class="container-xl">
+        <div class="page-header d-print-none text-white">
+            <div class="row align-items-center">
+                <div class="col">
+                    <h2 class="page-title">
+                        <span class="home-title my-3">系统日志</span>
+                    </h2>
+                    <div class="page-pretitle">
+                        <span class="home-subtitle">查看系统运行日志</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="page-body">
+        <div class="container-xl">
+            <div class="row row-deck row-cards">
+                <div class="col-12">
+                    <div class="card">
+                        <div class="table-responsive">
+                            <table id="data-table" class="table card-table table-vcenter text-nowrap datatable">
+                                <thead>
+                                <tr>
+                                    {foreach $details['field'] as $key => $value}
+                                        <th>{$value}</th>
+                                    {/foreach}
+                                </tr>
+                                </thead>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    {include file='datatable.tpl'}
+
+    <script>
+        tableConfig.serverSide = true;
+        tableConfig.ajax = {
+            url: '/admin/syslog/ajax',
+            type: 'POST',
+            dataSrc: 'syslogs.data'
+        };
+        tableConfig.order = [
+            [0, 'desc']
+        ];
+        tableConfig.columnDefs = [
+            {
+                orderable: false,
+                targets: [0,4]
+            },
+        ];
+
+        let table = new DataTable('#data-table', tableConfig);
+
+        function loadTable() {
+            table;
+        }
+
+        loadTable();
+    </script>
+
+    {include file='admin/footer.tpl'}

+ 49 - 0
resources/views/tabler/admin/syslog/view.tpl

@@ -0,0 +1,49 @@
+{include file='admin/header.tpl'}
+
+<div class="page-wrapper">
+    <div class="container-xl">
+        <div class="page-header d-print-none text-white">
+            <div class="row align-items-center">
+                <div class="col">
+                    <h2 class="page-title">
+                        <span class="home-title">系统日志 #{$syslog->id}</span>
+                    </h2>
+                    <div class="page-pretitle my-3">
+                        <span class="home-subtitle">日志详情</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="page-body">
+        <div class="container-xl">
+            <div class="card">
+                <div class="card-body">
+                    <div class="datagrid">
+                        <div class="datagrid-item">
+                            <div class="datagrid-title">触发用户</div>
+                            <div class="datagrid-content">{$syslog->user_id}</div>
+                        </div>
+                        <div class="datagrid-item">
+                            <div class="datagrid-title">触发IP</div>
+                            <div class="datagrid-content">{$syslog->ip}</div>
+                        </div>
+                        <div class="datagrid-item">
+                            <div class="datagrid-title">日志内容</div>
+                            <div class="datagrid-content">{$syslog->message}</div>
+                        </div>
+                        <div class="datagrid-item">
+                            <div class="datagrid-title">日志等级</div>
+                            <div class="datagrid-content">{$syslog->level_text}</div>
+                        </div>
+                        <div class="datagrid-item">
+                            <div class="datagrid-title">日志类别</div>
+                            <div class="datagrid-content">{$syslog->channel_text}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    {include file='admin/footer.tpl'}

+ 2 - 2
resources/views/tabler/admin/ticket/index.tpl

@@ -93,8 +93,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 30 - 78
resources/views/tabler/admin/ticket/view.tpl

@@ -15,15 +15,16 @@
                 <div class="col-auto">
                     <div class="btn-list">
                         {if $ticket->status !== 'closed'}
-                            <button href="#" class="btn btn-red" data-bs-toggle="modal"
-                                    data-bs-target="#close_ticket_confirm_dialog">
-                                <i class="icon ti ti-x"></i>
-                                关闭
-                            </button>
+                        <button href="#" class="btn btn-red" data-bs-toggle="modal"
+                                data-bs-target="#close_ticket_confirm_dialog">
+                            <i class="icon ti ti-x"></i>
+                            关闭
+                        </button>
                         {/if}
-                        <button id="add_ai_reply" href="#" class="btn btn-primary">
+                        <button href="#" class="btn btn-primary" hx-post="/admin/ticket/{$ticket->id}/llm_reply"
+                                hx-swap="none">
                             <i class="icon ti ti-robot"></i>
-                            AI 回复
+                            LLM 回复
                         </button>
                         <button href="#" class="btn btn-primary" data-bs-toggle="modal"
                                 data-bs-target="#add-reply">
@@ -52,23 +53,23 @@
                         <div class="card-body">
                             <div class="divide-y">
                                 {foreach $comments as $comment}
-                                    <div>
-                                        <div class="row">
-                                            <div class="col">
-                                                <div>
-                                                    {nl2br($comment->comment)}
-                                                </div>
-                                                <div class="text-secondary my-1">{$comment->commenter_name}
-                                                    回复于 {$comment->datetime}
-                                                </div>
+                                <div>
+                                    <div class="row">
+                                        <div class="col">
+                                            <div>
+                                                {$comment->comment}
+                                            </div>
+                                            <div class="text-secondary my-1">{$comment->commenter_name}
+                                                回复于 {$comment->datetime}
                                             </div>
-                                            <div class="col-auto">
-                                                <div>
-                                                    # {$comment->comment_id + 1}
-                                                </div>
+                                        </div>
+                                        <div class="col-auto">
+                                            <div>
+                                                # {$comment->comment_id + 1}
                                             </div>
                                         </div>
                                     </div>
+                                </div>
                                 {/foreach}
                             </div>
                         </div>
@@ -93,7 +94,13 @@
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn me-auto" data-bs-dismiss="modal">取消</button>
-                    <button id="reply" type="button" class="btn btn-primary" data-bs-dismiss="modal">回复</button>
+                    <button class="btn btn-primary" data-bs-dismiss="modal"
+                        hx-post="/admin/ticket/{$ticket->id}" hx-swap="none"
+                        hx-vals='js:{
+                            comment: document.getElementById("reply-comment").value,
+                        }'>
+                        回复
+                    </button>
                 </div>
             </div>
         </div>
@@ -104,7 +111,8 @@
             <div class="modal-content">
                 <div class="modal-header">
                     <h5 class="modal-title">关闭工单</h5>
-                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                    <button class="btn-close" data-bs-dismiss="modal" aria-label="Close"
+                            hx-post="/admin/ticket/{$ticket->id}/close" hx-swap="none"></button>
                 </div>
                 <div class="modal-body">
                     <div class="mb-3">
@@ -122,60 +130,4 @@
         </div>
     </div>
 
-    <script>
-        $("#reply").click(function () {
-            $.ajax({
-                url: "/admin/ticket/{$ticket->id}",
-                type: 'PUT',
-                dataType: "json",
-                data: {
-                    comment: $('#reply-comment').val()
-                },
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#success-message').text(data.msg);
-                        $('#success-dialog').modal('show');
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-
-        $("#add_ai_reply").click(function () {
-            $.ajax({
-                url: "/admin/ticket/{$ticket->id}/ai",
-                type: 'PUT',
-                dataType: "json",
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#success-message').text(data.msg);
-                        $('#success-dialog').modal('show');
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-
-        $("#confirm_close").click(function () {
-            $.ajax({
-                url: "/admin/ticket/{$ticket->id}/close",
-                type: 'PUT',
-                dataType: "json",
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#success-message').text(data.msg);
-                        $('#success-dialog').modal('show');
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-    </script>
-
 {include file='admin/footer.tpl'}

+ 120 - 91
resources/views/tabler/admin/user/edit.tpl

@@ -29,11 +29,11 @@
                 <div class="col-md-4 col-sm-12">
                     <div class="card">
                         <div class="card-header card-header-light">
-                            <h3 class="card-title">基础信息</h3>
+                            <h3 class="card-title">账户信息</h3>
                         </div>
                         <div class="card-body">
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">注册邮箱</label>
+                                <label class="form-label col-3 col-form-label">邮箱</label>
                                 <div class="col">
                                     <input id="email" type="email" class="form-control" value="{$edit_user->email}">
                                 </div>
@@ -45,13 +45,6 @@
                                            value="{$edit_user->user_name}">
                                 </div>
                             </div>
-                            <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">备注</label>
-                                <div class="col">
-                                    <input id="remark" type="text" class="form-control" value="{$edit_user->remark}"
-                                           placeholder="仅管理员可见">
-                                </div>
-                            </div>
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label">账户密码</label>
                                 <div class="col">
@@ -62,43 +55,62 @@
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-3 col-form-label">账户余额</label>
                                 <div class="col">
-                                    <input id="money" type="number" step="0.1" class="form-control"
+                                    <input id="money" type="number" step="1" class="form-control"
                                            value="{$edit_user->money}">
                                 </div>
                             </div>
-                            <div class="hr-text">
-                                <span>时间设置</span>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">邀请人</label>
+                                <div class="col">
+                                    <input id="ref_by" type="text" class="form-control" value="{$edit_user->ref_by}">
+                                </div>
                             </div>
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-4 col-form-label">等级过期时间</label>
+                                <label class="form-label col-3 col-form-label">SS端口</label>
                                 <div class="col">
-                                    <input id="class_expire" type="text" class="form-control"
-                                           value="{$edit_user->class_expire}">
+                                    <input id="port" type="text" class="form-control" value="{$edit_user->port}">
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-4 col-form-label">免费用户流量重置日</label>
+                                <label class="form-label col-3 col-form-label">SS加密方式</label>
                                 <div class="col">
-                                    <input id="auto_reset_day" type="text" class="form-control"
-                                           value="{$edit_user->auto_reset_day}">
+                                    <select id="method" class="col form-select" value="{$edit_user->method}">
+                                        {foreach $ss_methods as $method}
+                                            <option value="{$method}" {if $edit_user->method === $method}selected{/if}>
+                                                {$method}
+                                            </option>
+                                        {/foreach}
+                                    </select>
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-4 col-form-label">
-                                    重置的免费流量(GB)
-                                </label>
+                                <label class="form-label col-3 col-form-label">注册IP</label>
                                 <div class="col">
-                                    <input id="auto_reset_bandwidth" type="text" class="form-control"
-                                           value="{$edit_user->auto_reset_bandwidth}">
+                                    <input type="text" class="form-control" value="{$edit_user->reg_ip}" disabled/>
                                 </div>
                             </div>
-                            <div class="hr-text">
-                                <span>邀请注册</span>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">注册日期</label>
+                                <div class="col">
+                                    <input type="text" class="form-control" value="{$edit_user->reg_date}" disabled/>
+                                </div>
                             </div>
                             <div class="form-group mb-3 row">
-                                <label class="form-label col-4 col-form-label">邀请人</label>
+                                <label class="form-label col-3 col-form-label">最后使用时间</label>
                                 <div class="col">
-                                    <input id="ref_by" type="text" class="form-control" value="{$edit_user->ref_by}">
+                                    <input type="text" class="form-control" value="{$edit_user->last_use_time}" disabled/>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">最后签到时间</label>
+                                <div class="col">
+                                    <input type="text" class="form-control" value="{$edit_user->last_check_in_time}" disabled/>
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">最后登录时间</label>
+                                <div class="col">
+                                    <input type="text" class="form-control" value="{$edit_user->last_login_time}" disabled/>
                                 </div>
                             </div>
                         </div>
@@ -107,7 +119,7 @@
                 <div class="col-md-4 col-sm-12">
                     <div class="card">
                         <div class="card-header card-header-light">
-                            <h3 class="card-title">其他信息</h3>
+                            <h3 class="card-title">使用限制</h3>
                         </div>
                         <div class="card-body">
                             <div class="form-group mb-3 row">
@@ -120,19 +132,86 @@
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-4 col-form-label">当期用量</label>
                                 <div class="col">
-                                    <input id="usedTraffic" type="text" class="form-control"
+                                    <input type="text" class="form-control"
                                            value="{$edit_user->usedTraffic()}" disabled/>
                                 </div>
                             </div>
                             <div class="form-group mb-3 row">
                                 <label class="form-label col-4 col-form-label">累计用量</label>
                                 <div class="col">
-                                    <input id="usedTraffic" type="text" class="form-control"
+                                    <input type="text" class="form-control"
                                            value="{$edit_user->totalTraffic()}" disabled/>
                                 </div>
                             </div>
-                            <div class="hr-text">
-                                <span>高级选项</span>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">节点群组</label>
+                                <div class="col">
+                                    <input id="node_group" type="text" class="form-control"
+                                           value="{$edit_user->node_group}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">账户等级</label>
+                                <div class="col">
+                                    <input id="class" type="text" class="form-control"
+                                           value="{$edit_user->class}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">等级过期时间</label>
+                                <div class="col">
+                                    <input id="class_expire" type="text" class="form-control"
+                                           value="{$edit_user->class_expire}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">免费用户流量重置日</label>
+                                <div class="col">
+                                    <input id="auto_reset_day" type="text" class="form-control"
+                                           value="{$edit_user->auto_reset_day}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">重置的免费流量(GB)</label>
+                                <div class="col">
+                                    <input id="auto_reset_bandwidth" type="text" class="form-control"
+                                           value="{$edit_user->auto_reset_bandwidth}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">速度限制 (Mbps)</label>
+                                <div class="col">
+                                    <input id="node_speedlimit" type="text" class="form-control"
+                                           value="{$edit_user->node_speedlimit}">
+                                </div>
+                            </div>
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-4 col-form-label">同時连接 IP 限制</label>
+                                <div class="col">
+                                    <input id="node_iplimit" type="text" class="form-control"
+                                           value="{$edit_user->node_iplimit}">
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-4 col-sm-12">
+                    <div class="card">
+                        <div class="card-header card-header-light">
+                            <h3 class="card-title">其他设置</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="form-group mb-3 row">
+                                <label class="form-label col-3 col-form-label">显示语言</label>
+                                <div class="col">
+                                    <select id="locale" class="col form-select" value="{$edit_user->locale}">
+                                        {foreach $locales as $locale}
+                                        <option value="{$locale}" {if $edit_user->locale === $locale}selected{/if}>
+                                            {$locale}
+                                        </option>
+                                        {/foreach}
+                                    </select>
+                                </div>
                             </div>
                             <div class="form-group mb-3 row">
                                 <span class="col">管理员</span>
@@ -153,7 +232,7 @@
                                 </span>
                             </div>
                             <div class="form-group mb-3 row">
-                                <span class="col">账户异常状态</span>
+                                <span class="col">账户异常状态(Shadow Banned)</span>
                                 <span class="col-auto form-check-single form-switch">
                                     <input id="is_shadow_banned" class="form-check-input" type="checkbox"
                                            {if $edit_user->is_shadow_banned}checked=""{/if}>
@@ -168,68 +247,18 @@
                                     </label>
                                 </span>
                             </div>
-                            <div class="form-group mb-3 row">
-                                <span class="col">手动封禁理由</span>
+                            <div class="form-group mb-3 col-12">
+                                <span class="form-label col-12 col-form-label">手动封禁理由</span>
                                 <span class="col-auto">
-                                    <input id="banned_reason" type="text" class="form-control"
-                                           value="{$edit_user->banned_reason}">
+                                    <textarea id="banned_reason" class="form-control"
+                                              value="{$edit_user->banned_reason}"></textarea>
                                 </span>
                             </div>
-                        </div>
-                    </div>
-                </div>
-                <div class="col-md-4 col-sm-12">
-                    <div class="card">
-                        <div class="card-header card-header-light">
-                            <h3 class="card-title">连接设置</h3>
-                        </div>
-                        <div class="card-body">
-                            <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">端口</label>
-                                <div class="col">
-                                    <input id="port" type="text" class="form-control" value="{$edit_user->port}">
-                                </div>
-                            </div>
-                            <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">密码</label>
-                                <div class="col">
-                                    <input id="passwd" type="text" class="form-control" value="{$edit_user->passwd}" disabled/>
-                                </div>
-                            </div>
-                            <div class="form-group mb-3 row">
-                                <label class="form-label col-3 col-form-label">加密</label>
-                                <div class="col">
-                                    <input id="method" type="text" class="form-control" value="{$edit_user->method}">
-                                </div>
-                            </div>
-                            <div class="hr-text">
-                                <span>使用限制</span>
-                            </div>
                             <div class="form-group mb-3 col-12">
-                                <label class="form-label col-12 col-form-label">节点群组</label>
+                                <label class="form-label col-12 col-form-label">账户备注</label>
                                 <div class="col">
-                                    <input id="node_group" type="text" class="form-control"
-                                           value="{$edit_user->node_group}">
-                                </div>
-                            </div>
-                            <div class="form-group mb-3 col-12">
-                                <label class="form-label col-12 col-form-label">账户等级</label>
-                                <div class="col">
-                                    <input id="class" type="text" class="form-control" value="{$edit_user->class}">
-                                </div>
-                            </div>
-                            <div class="form-group mb-3 col-12">
-                                <label class="form-label col-12 col-form-label">速度限制 (Mbps)</label>
-                                <div class="col">
-                                    <input id="node_speedlimit" type="text" class="form-control"
-                                           value="{$edit_user->node_speedlimit}">
-                                </div>
-                            </div>
-                            <div class="form-group mb-3 col-12">
-                                <label class="form-label col-12 col-form-label">同時连接 IP 限制</label>
-                                <div class="col">
-                                    <input id="node_iplimit" type="text" class="form-control"
-                                           value="{$edit_user->node_iplimit}">
+                                    <textarea id="remark" class="form-control" value="{$edit_user->remark}"
+                                              placeholder="仅管理员可见"></textarea>
                                 </div>
                             </div>
                         </div>
@@ -251,9 +280,9 @@
                 {$key}: $('#{$key}').val(),
                 {/foreach}
                 is_admin: $("#is_admin").is(":checked"),
-                is_banned: $("#is_banned").is(":checked"),
                 ga_enable: $("#ga_enable").is(":checked"),
                 is_shadow_banned: $("#is_shadow_banned").is(":checked"),
+                is_banned: $("#is_banned").is(":checked"),
             },
             success: function (data) {
                 if (data.ret === 1) {

+ 4 - 4
resources/views/tabler/admin/user/index.tpl

@@ -132,8 +132,8 @@
                 },
                 success: function (data) {
                     if (data.ret === 1) {
-                        $('#success-noreload-message').text(data.msg);
-                        $('#success-noreload-dialog').modal('show');
+                        $('#success-message').text(data.msg);
+                        $('#success-dialog').modal('show');
                         reloadTableAjax();
                     } else {
                         $('#fail-message').text(data.msg);
@@ -153,8 +153,8 @@
                     dataType: "json",
                     success: function (data) {
                         if (data.ret === 1) {
-                            $('#success-noreload-message').text(data.msg);
-                            $('#success-noreload-dialog').modal('show');
+                            $('#success-message').text(data.msg);
+                            $('#success-dialog').modal('show');
                             reloadTableAjax();
                         } else {
                             $('#fail-message').text(data.msg);

+ 3 - 0
resources/views/tabler/captcha/ajax.tpl

@@ -7,3 +7,6 @@
 {if $public_setting['captcha_provider'] === 'hcaptcha'}
     hcaptcha: hcaptcha.getResponse(),
 {/if}
+{if $public_setting['captcha_provider'] === 'recaptcha_enterprise'}
+    recaptcha_enterprise: grecaptcha.enterprise.getResponse(),
+{/if}

+ 4 - 1
resources/views/tabler/captcha/div.tpl

@@ -1,5 +1,5 @@
 {if $public_setting['captcha_provider'] === 'turnstile'}
-    <div id="cf-turnstile" class="cf-turnstile" data-sitekey="{$captcha['turnstile_sitekey']}" data-theme="light"></div>
+    <div id="cf-turnstile" class="cf-turnstile" data-sitekey="{$captcha['turnstile_sitekey']}"></div>
 {/if}
 {if $public_setting['captcha_provider'] === 'geetest'}
     <div id="geetest"></div>
@@ -7,3 +7,6 @@
 {if $public_setting['captcha_provider'] === 'hcaptcha'}
     <div class="h-captcha" data-sitekey="{$captcha['hcaptcha_sitekey']}"></div>
 {/if}
+{if $public_setting['captcha_provider'] === 'recaptcha_enterprise'}
+    <div id="recaptcha"></div>
+{/if}

+ 11 - 1
resources/views/tabler/captcha/js.tpl

@@ -19,5 +19,15 @@
     </script>
 {/if}
 {if $public_setting['captcha_provider'] === 'hcaptcha'}
-    <script src='https://www.hCaptcha.com/1/api.js' async defer></script>
+    <script src='https://js.hcaptcha.com/1/api.js' async defer></script>
+{/if}
+{if $public_setting['captcha_provider'] === 'recaptcha_enterprise'}
+    <script src='https://www.recaptcha.net/recaptcha/enterprise.js?onload=initReCAPTCHA&render=explicit' async defer></script>
+    <script>
+        var initReCAPTCHA = function () {
+            grecaptcha.enterprise.render('recaptcha', {
+                'sitekey': '{$captcha['recaptcha_enterprise_key_id']}',
+            });
+        };
+    </script>
 {/if}

+ 9 - 18
resources/views/tabler/datatable.tpl

@@ -1,5 +1,5 @@
-<link href="//cdn.datatables.net/v/bs5/dt-2.0.4/datatables.min.css" rel="stylesheet"/>
-<script src="//cdn.datatables.net/v/bs5/dt-2.0.4/datatables.min.js"></script>
+<link href="//cdn.datatables.net/v/bs5/dt-2.0.8/datatables.min.css" rel="stylesheet"/>
+<script src="//cdn.datatables.net/v/bs5/dt-2.0.8/datatables.min.js"></script>
 
 <script>
     let tableConfig = {
@@ -14,22 +14,13 @@
             {/foreach}
         ],
         initComplete: function () {
-            let tableHeader = $('div.dt-length').parent().parent()
-            let tableBody = $('div.dt-scroll').parent().parent()
-            let tableFooter = $('div.dt-info').parent().parent()
-            let length = $('div.dt-length').parent()
-            let search = $('div.dt-search').parent()
-            let info = $('div.dt-info').parent()
-            let paging = $('div.dt-paging').parent()
-
-            tableHeader.removeClass('mt-2').addClass('row px-3 py-3')
-            tableBody.removeClass('mt-2')
-            tableFooter.removeClass('mt-2').addClass('row card-footer')
-            length.removeClass('col-md-auto me-auto').addClass('col-auto')
-            search.removeClass('col-md-auto me-auto ms-auto').addClass('col-auto')
-            info.removeClass('col-md-auto me-auto').addClass('col')
-            paging.removeClass('col-md-auto me-auto ms-auto').addClass('col-auto')
-
+            $('div.dt-length').parent().parent().removeClass('mt-2').addClass('row px-3 py-3')
+            $('div.dt-scroll').parent().parent().removeClass('mt-2')
+            $('div.dt-info').parent().parent().removeClass('mt-2').addClass('row card-footer')
+            $('div.dt-length').parent().removeClass('col-md-auto me-auto').addClass('col-auto')
+            $('div.dt-search').parent().removeClass('col-md-auto me-auto ms-auto').addClass('col-auto')
+            $('div.dt-info').parent().removeClass('col-md-auto me-auto').addClass('col')
+            $('div.dt-paging').parent().removeClass('col-md-auto me-auto ms-auto').addClass('col-auto')
             $('div.dt-scroll-body').css('border-bottom-style', 'none')
         },
         language: {

+ 3 - 2
resources/views/tabler/footer.tpl

@@ -49,6 +49,9 @@
 <script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 
 <script>
+    let successDialog = new tabler.bootstrap.Modal(document.getElementById('success-dialog'));
+    let failDialog = new tabler.bootstrap.Modal(document.getElementById('fail-dialog'));
+
     htmx.on("htmx:afterRequest", function(evt) {
         if (evt.detail.xhr.getResponseHeader('HX-Redirect'))
         {
@@ -56,8 +59,6 @@
         }
 
         let res = JSON.parse(evt.detail.xhr.response);
-        let successDialog = new bootstrap.Modal(document.getElementById('success-dialog'));
-        let failDialog = new bootstrap.Modal(document.getElementById('fail-dialog'));
 
         if (evt.detail.elt.id === 'send-verify-email') {
             document.getElementById('send-verify-email').disabled = true;

+ 4 - 8
resources/views/tabler/gateway/epay.tpl

@@ -8,48 +8,44 @@
         <button class="btn btn-flat waves-attach"
                 hx-post="/user/payment/purchase/epay" hx-swap="none"
                 hx-vals='js:{
-                    price: {$invoice->price},
                     invoice_id: {$invoice->id},
                     type: "alipay",
                     redir: window.location.href
                 }'>
-            <img src="/images/alipay.png" height="50px"/>
+            <img src="/images/alipay.svg" height="50px"/>
         </button>
         {/if}
         {if $public_setting['epay_wechat']}
         <button class="btn btn-flat waves-attach"
                 hx-post="/user/payment/purchase/epay" hx-swap="none"
                 hx-vals='js:{
-                    price: {$invoice->price},
                     invoice_id: {$invoice->id},
                     type: "wxpay",
                     redir: window.location.href
                 }'>
-            <img src="/images/wechat.png" height="50px"/>
+            <img src="/images/wechat.svg" height="50px"/>
         </button>
         {/if}
         {if $public_setting['epay_qq']}
         <button class="btn btn-flat waves-attach"
                 hx-post="/user/payment/purchase/epay" hx-swap="none"
                 hx-vals='js:{
-                    price: {$invoice->price},
                     invoice_id: {$invoice->id},
                     type: "qqpay",
                     redir: window.location.href
                 }'>
-            <img src="/images/qqpay.png" height="50px"/>
+            <img src="/images/qq.svg" height="50px"/>
         </button>
         {/if}
         {if $public_setting['epay_usdt']}
         <button class="btn btn-flat waves-attach"
                 hx-post="/user/payment/purchase/epay" hx-swap="none"
                 hx-vals='js:{
-                    price: {$invoice->price},
                     invoice_id: {$invoice->id},
                     type: "usdt",
                     redir: window.location.href
                 }'>
-            <img src="/images/usdt.png" height="50px"/>
+            <img src="/images/tether.svg" height="50px"/>
         </button>
         {/if}
     </form>

+ 3 - 4
resources/views/tabler/gateway/f2f.tpl

@@ -1,10 +1,10 @@
+<script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
+
 <div class="card-inner">
     <h4>
         支付宝当面付
     </h4>
     <p class="card-heading"></p>
-    <input hidden id="amount-f2fpay" name="amount-f2fpay" value="{$invoice->price}">
-    <input hidden id="invoice_id" name="invoice_id" value="{$invoice->id}">
     <div id="f2f-qrcode"></div>
     <button class="btn btn-flat waves-attach" id="f2fpay-button" type="button" onclick="f2fpay();">
         生成付款QR Code
@@ -20,8 +20,7 @@
             url: "/user/payment/purchase/f2f",
             dataType: "json",
             data: {
-                amount: $('#amount-f2fpay').val(),
-                invoice_id: $('#invoice_id').val(),
+                invoice_id: {$invoice->id},
             },
             success: (data) => {
                 if (data.ret === 1) {

+ 3 - 16
resources/views/tabler/gateway/paypal.tpl

@@ -17,28 +17,15 @@
                     "Content-Type": "application/json",
                 },
                 body: JSON.stringify({
-                    price: {$invoice->price},
                     invoice_id: {$invoice->id},
                 }),
             })
                 .then((response) => response.json())
                 .then((order) => order.id);
         },
-        onApprove(data) {
-            return fetch("/payment/notify/paypal", {
-                method: "POST",
-                headers: {
-                    "Content-Type": "application/json",
-                },
-                body: JSON.stringify({
-                    order_id: data.orderID,
-                }),
-            })
-                .then((response) => response.json())
-                .then(() => {
-                    window.setTimeout(location.href = '/user/invoice', {$config['jump_delay']});
-                });
+        onApprove() {
+            window.setTimeout(location.href = '/user/invoice', {$config['jump_delay']});
         }
     }).render('#paypal-button-container');
 
-</script>
+</script>

+ 10 - 8
resources/views/tabler/gateway/stripe.tpl

@@ -1,5 +1,5 @@
 <link rel="stylesheet"
-      href="https://{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler-payments.min.css">
+      href="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler-payments.min.css">
 
 <div class="card-inner">
     <h4>
@@ -11,11 +11,13 @@
         <span class="payment payment-xs payment-provider-mastercard me-auto"></span>
         <span class="payment payment-xs payment-provider-visa me-auto"></span>
         等标识的信用卡或借记卡</p>
-    <form action="/user/payment/purchase/stripe" method="post">
-        <div class="form-group form-group-label">
-            <input id="price" name="price" value="{$invoice->price}" hidden>
-            <input id="invoice_id" name="invoice_id" value="{$invoice->id}" hidden>
-            <button class="btn btn-flat waves-attach" type="submit"><i class="icon ti ti-credit-card"></i></button>
-        </div>
-    </form>
+    <div class="form-group form-group-label">
+        <button class="btn btn-flat waves-attach"
+            hx-post="/user/payment/purchase/stripe" hx-swap="none"
+            hx-vals='js:{
+                invoice_id: {$invoice->id},
+            }'>
+            <i class="icon ti ti-credit-card"></i>
+        </button>
+    </div>
 </div>

+ 18 - 2
resources/views/tabler/header.tpl

@@ -1,5 +1,5 @@
 <!doctype html>
-<html lang="zh">
+<html lang="{$config['locale']}" data-bs-theme="auto">
 
 <head>
     <meta charset="utf-8"/>
@@ -8,10 +8,26 @@
     <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
     <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
     <title>{$config['appName']}</title>
+    <!-- Auto dark mode -->
+    <script>
+        ;(function () {
+            const htmlElement = document.querySelector("html")
+            const theme = htmlElement.getAttribute("data-bs-theme");
+
+            if(theme === 'dark-auto' || theme === 'auto') {
+                function updateTheme() {
+                    htmlElement.setAttribute("data-bs-theme",
+                        window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
+                }
+                window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme)
+                updateTheme()
+            }
+        })()
+    </script>
     <!-- CSS files -->
     <link href="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet"/>
     <link href="//{$config['jsdelivr_url']}/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet"/>
     <!-- JS files -->
     <script src="/assets/js/fuck.min.js"></script>
-    <script src="//{$config['jsdelivr_url']}/npm/htmx.org@latest/dist/htmx.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/htmx.org@v2/dist/htmx.min.js"></script>
 </head>

+ 0 - 1
resources/views/tabler/live_chat.tpl

@@ -24,7 +24,6 @@
             ]);
     </script>
 {/if}
-
 {if $public_setting['live_chat'] === 'livechat'}
     <script>
         window.__lc = window.__lc ||

+ 2 - 2
resources/views/tabler/tinymce.tpl

@@ -1,8 +1,8 @@
-<script src="//cdnjs.cloudflare.com/ajax/libs/tinymce/7.0.1/tinymce.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/tinymce/7.2.0/tinymce.min.js"></script>
 
 <script>
     document.addEventListener("DOMContentLoaded", function () {
-        tinyMCE.baseURL = '//cdnjs.cloudflare.com/ajax/libs/tinymce/7.0.1/';
+        tinyMCE.baseURL = '//cdnjs.cloudflare.com/ajax/libs/tinymce/7.2.0';
         tinyMCE.suffix = '.min';
         tinyMCE.init({
             selector: '#tinymce',

+ 1 - 1
resources/views/tabler/user/docs/index.tpl

@@ -34,7 +34,7 @@
                                             </div>
                                         </div>
                                         <div class="col-auto">
-                                            <a class="btn btn-blue" href="/user/docs/{$doc->id}/view">
+                                            <a class="btn btn-primary" href="/user/docs/{$doc->id}/view">
                                                 查看
                                             </a>
                                         </div>

+ 133 - 104
resources/views/tabler/user/edit.tpl

@@ -67,38 +67,38 @@
                                                                {if ! $config['enable_change_email']}disabled=""{/if}>
                                                     </div>
                                                     {if $public_setting['reg_email_verify'] && $config['enable_change_email']}
-                                                        <div class="mb-3">
-                                                            <input id="email-code" type="text" class="form-control"
-                                                                   placeholder="验证码">
-                                                        </div>
+                                                    <div class="mb-3">
+                                                        <input id="email-code" type="text" class="form-control"
+                                                               placeholder="验证码">
+                                                    </div>
                                                     {/if}
                                                 </div>
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         {if $public_setting['reg_email_verify'] && $config['enable_change_email']}
-                                                            <button class="btn btn-link"
-                                                                    hx-post="/user/send" hx-swap="none"
-                                                                    hx-vals='js:{ email: document.getElementById("newemail").value }'>
-                                                                获取验证码
-                                                            </button>
-                                                            <button class="btn btn-primary ms-auto"
-                                                                    hx-post="/user/email" hx-swap="none"
-                                                                    hx-vals='js:{
-                                                                        newemail: document.getElementById("new-email").value,
-                                                                        emailcode: document.getElementById("email-code").value
-                                                                    }'>
-                                                                修改
-                                                            </button>
+                                                        <button class="btn btn-link"
+                                                                hx-post="/user/edit/send" hx-swap="none"
+                                                                hx-vals='js:{ email: document.getElementById("newemail").value }'>
+                                                            获取验证码
+                                                        </button>
+                                                        <button class="btn btn-primary ms-auto"
+                                                                hx-post="/user/edit/email" hx-swap="none"
+                                                                hx-vals='js:{
+                                                                    newemail: document.getElementById("new-email").value,
+                                                                    emailcode: document.getElementById("email-code").value
+                                                                }'>
+                                                            修改
+                                                        </button>
                                                         {elseif $config['enable_change_email']}
-                                                            <button class="btn btn-primary ms-auto"
-                                                                    hx-post="/user/email" hx-swap="none"
-                                                                    hx-vals='js:{ newemail: document.getElementById("new-email").value }'>
-                                                                修改
-                                                            </button>
+                                                        <button class="btn btn-primary ms-auto"
+                                                                hx-post="/user/edit/email" hx-swap="none"
+                                                                hx-vals='js:{ newemail: document.getElementById("new-email").value }'>
+                                                            修改
+                                                        </button>
                                                         {else}
-                                                            <button class="btn btn-primary ms-auto"
-                                                                    disabled>不允许修改
-                                                            </button>
+                                                        <button class="btn btn-primary ms-auto"
+                                                                disabled>不允许修改
+                                                        </button>
                                                         {/if}
                                                     </div>
                                                 </div>
@@ -117,7 +117,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                           hx-post="/user/username" hx-swap="none"
+                                                           hx-post="/user/edit/username" hx-swap="none"
                                                            hx-vals='js:{ newusername: document.getElementById("new-username").value }'>
                                                             修改
                                                         </button>
@@ -148,7 +148,7 @@
                                                     </div>
                                                     <div class="mb-3">
                                                         <input id="imvalue" type="text" class="form-control"
-                                                               disabled="" value="{$user->im_value}">
+                                                               value="{$user->im_value}" disabled>
                                                     </div>
                                                 </div>
                                                 <div class="card-footer">
@@ -162,24 +162,24 @@
                                                 <div class="card-body">
                                                     <h3 class="card-title">解绑 IM 账户</h3>
                                                     {if $user->im_type === 0}
-                                                        <p>你的账户当前没有绑定任何 IM 服务</p>
+                                                    <p>你的账户当前没有绑定任何 IM 服务</p>
                                                     {else}
-                                                        <p>
-                                                            当前绑定的 IM 服务:{$user->imType()}
-                                                            <br>
-                                                            账户 ID:<code>{$user->im_value}</code>
-                                                        </p>
+                                                    <p>
+                                                        当前绑定的 IM 服务:{$user->imType()}
+                                                        <br>
+                                                        账户 ID:<code>{$user->im_value}</code>
+                                                    </p>
                                                     {/if}
                                                 </div>
                                                 {if $user->im_type !== 0}
-                                                    <div class="card-footer">
-                                                        <div class="d-flex">
-                                                            <button class="btn btn-red ms-auto"
-                                                                    hx-post="/user/unbind_im" hx-swap="none">
-                                                                解绑
-                                                            </button>
-                                                        </div>
+                                                <div class="card-footer">
+                                                    <div class="d-flex">
+                                                        <button class="btn btn-red ms-auto"
+                                                                hx-post="/user/edit/unbind_im" hx-swap="none">
+                                                            解绑
+                                                        </button>
                                                     </div>
+                                                </div>
                                                 {/if}
                                             </div>
                                         </div>
@@ -228,7 +228,7 @@
                                                             </div>
                                                             <div class="col-md-12">
                                                                 <p>密钥:
-                                                                    <code id="ga-token">
+                                                                    <code id="ga-token" class="spoiler">
                                                                         {$user->ga_token}
                                                                     </code>
                                                                 </p>
@@ -284,7 +284,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                                hx-post="/user/password" hx-swap="none"
+                                                                hx-post="/user/edit/password" hx-swap="none"
                                                                 hx-vals='js:{
                                                                     new_password: document.getElementById("new_password").value,
                                                                     confirm_new_password: document.getElementById("confirm_new_password").value,
@@ -309,10 +309,10 @@
                                                     <div class="mb-3">
                                                         <select id="user-method" class="form-select">
                                                             {foreach $methods as $method}
-                                                                <option value="{$method}"
-                                                                        {if $user->method === $method}selected{/if}>
-                                                                    {$method}
-                                                                </option>
+                                                            <option value="{$method}"
+                                                                    {if $user->method === $method}selected{/if}>
+                                                                {$method}
+                                                            </option>
                                                             {/foreach}
                                                         </select>
                                                     </div>
@@ -320,7 +320,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                                hx-post="/user/method" hx-swap="none"
+                                                                hx-post="/user/edit/method" hx-swap="none"
                                                                 hx-vals='js:{ method: document.getElementById("user-method").value }'>
                                                             修改
                                                         </button>
@@ -338,7 +338,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto bg-red"
-                                                                hx-post="/user/url_reset" hx-swap="none">
+                                                                hx-post="/user/edit/url_reset" hx-swap="none">
                                                             重置
                                                         </button>
                                                     </div>
@@ -350,13 +350,13 @@
                                                 <div class="card-body">
                                                     <h3 class="card-title">重置连接密码</h3>
                                                     <p>重置连接密码与UUID ,重置后需更新订阅,才能继续使用</p>
-                                                    <p>当前连接密码:<code id="passwd">{$user->passwd}</code></p>
-                                                    <p>当前UUID:<code id="uuid">{$user->uuid}</code></p>
+                                                    <p>当前连接密码:<code id="passwd" class="spoiler">{$user->passwd}</code></p>
+                                                    <p>当前UUID:<code id="uuid" class="spoiler">{$user->uuid}</code></p>
                                                 </div>
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto bg-red"
-                                                                hx-post="/user/passwd_reset" hx-swap="none">
+                                                                hx-post="/user/edit/passwd_reset" hx-swap="none">
                                                             重置
                                                         </button>
                                                     </div>
@@ -391,7 +391,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                                hx-post="/user/daily_mail" hx-swap="none"
+                                                                hx-post="/user/edit/daily_mail" hx-swap="none"
                                                                 hx-vals='js:{ mail: document.getElementById("daily-mail").value }'>
                                                             修改
                                                         </button>
@@ -420,7 +420,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                                hx-post="/user/contact_method" hx-swap="none"
+                                                                hx-post="/user/edit/contact_method" hx-swap="none"
                                                                 hx-vals='js:{ contact: document.getElementById("contact-method").value }'>
                                                             修改
                                                         </button>
@@ -435,9 +435,9 @@
                                                     <div class="mb-3">
                                                         <select id="user-theme" class="form-select">
                                                             {foreach $themes as $theme}
-                                                                <option value="{$theme}"
-                                                                        {if $user->theme === $theme}selected{/if}>{$theme}
-                                                                </option>
+                                                            <option value="{$theme}"
+                                                                    {if $user->theme === $theme}selected{/if}>{$theme}
+                                                            </option>
                                                             {/foreach}
                                                         </select>
                                                     </div>
@@ -445,7 +445,7 @@
                                                 <div class="card-footer">
                                                     <div class="d-flex">
                                                         <button class="btn btn-primary ms-auto"
-                                                                hx-post="/user/theme" hx-swap="none"
+                                                                hx-post="/user/edit/theme" hx-swap="none"
                                                                 hx-vals='js:{ theme: document.getElementById("user-theme").value }'>
                                                             修改
                                                         </button>
@@ -453,26 +453,55 @@
                                                 </div>
                                             </div>
                                         </div>
-                                        {if $config['enable_kill']}
-                                            <div class="col-sm-12 col-md-6">
-                                                <div class="card">
-                                                    <div class="card-stamp">
-                                                        <div class="card-stamp-icon bg-red">
-                                                            <i class="ti ti-circle-x"></i>
-                                                        </div>
-                                                    </div>
-                                                    <div class="card-body">
-                                                        <h3 class="card-title">删除账户数据</h3>
+                                        <div class="col-sm-12 col-md-6">
+                                            <div class="card">
+                                                <div class="card-body">
+                                                    <h3 class="card-title">修改主题模式</h3>
+                                                    <div class="mb-3">
+                                                        <select id="theme-mode" class="form-select">
+                                                            <option value="2" {if $user->is_dark_mode === 2}selected{/if}>
+                                                                自动
+                                                            </option>
+                                                            <option value="0" {if $user->is_dark_mode === 0}selected{/if}>
+                                                                浅色
+                                                            </option>
+                                                            <option value="1" {if $user->is_dark_mode === 1}selected{/if}>
+                                                                深色
+                                                            </option>
+                                                        </select>
                                                     </div>
-                                                    <div class="card-footer">
-                                                        <button class="btn btn-red" data-bs-toggle="modal"
-                                                           data-bs-target="#destroy-account">
-                                                            <i class="ti ti-trash icon"></i>
-                                                            确认删除
+                                                </div>
+                                                <div class="card-footer">
+                                                    <div class="d-flex">
+                                                        <button class="btn btn-primary ms-auto"
+                                                                hx-post="/user/edit/theme_mode" hx-swap="none"
+                                                                hx-vals='js:{ theme_mode: document.getElementById("theme-mode").value }'>
+                                                            修改
                                                         </button>
                                                     </div>
                                                 </div>
                                             </div>
+                                        </div>
+                                        {if $config['enable_kill']}
+                                        <div class="col-sm-12 col-md-6">
+                                            <div class="card">
+                                                <div class="card-stamp">
+                                                    <div class="card-stamp-icon bg-red">
+                                                        <i class="ti ti-circle-x"></i>
+                                                    </div>
+                                                </div>
+                                                <div class="card-body">
+                                                    <h3 class="card-title">删除账户数据</h3>
+                                                </div>
+                                                <div class="card-footer">
+                                                    <button class="btn btn-red" data-bs-toggle="modal"
+                                                       data-bs-target="#destroy-account">
+                                                        <i class="ti ti-trash icon"></i>
+                                                        确认删除
+                                                    </button>
+                                                </div>
+                                            </div>
+                                        </div>
                                         {/if}
                                     </div>
                                 </div>
@@ -485,45 +514,45 @@
     </div>
 
     {if $config['enable_kill']}
-        <div class="modal modal-blur fade" id="destroy-account" tabindex="-1" role="dialog" aria-hidden="true">
-            <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
-                <div class="modal-content">
-                    <button class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
-                    <div class="modal-status bg-danger"></div>
-                    <div class="modal-body text-center py-4">
-                        <i class="ti ti-alert-circle icon mb-2 text-danger icon-lg" style="font-size:3.5rem;"></i>
-                        <h3>删除确认</h3>
-                        <div class="text-secondary">
-                            请确认是否真的要删除你的账户,此操作无法撤销,你的所有账户数据将会被从服务器上彻底删除
-                        </div>
-                        <div class="py-3">
-                            <form>
-                                <input id="confirm_kill_password" type="password" class="form-control"
-                                       placeholder="输入登录密码" autocomplete="off">
-                            </form>
-                        </div>
+    <div class="modal modal-blur fade" id="destroy-account" tabindex="-1" role="dialog" aria-hidden="true">
+        <div class="modal-dialog modal-sm modal-dialog-centered" role="document">
+            <div class="modal-content">
+                <button class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                <div class="modal-status bg-danger"></div>
+                <div class="modal-body text-center py-4">
+                    <i class="ti ti-alert-circle icon mb-2 text-danger icon-lg" style="font-size:3.5rem;"></i>
+                    <h3>删除确认</h3>
+                    <div class="text-secondary">
+                        请确认是否真的要删除你的账户,此操作无法撤销,你的所有账户数据将会被从服务器上彻底删除
                     </div>
-                    <div class="modal-footer">
-                        <div class="w-100">
-                            <div class="row">
-                                <div class="col">
-                                    <button class="btn w-100" data-bs-dismiss="modal">
-                                        取消
-                                    </button>
-                                </div>
-                                <div class="col">
-                                    <button href="#" class="btn btn-danger w-100" data-bs-dismiss="modal"
-                                            hx-post="/user/kill" hx-swap="none"
-                                            hx-vals='js:{ password: document.getElementById("confirm_kill_password").value }'>
-                                        确认
-                                    </button>
-                                </div>
+                    <div class="py-3">
+                        <form>
+                            <input id="confirm_kill_password" type="password" class="form-control"
+                                   placeholder="输入登录密码" autocomplete="off">
+                        </form>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <div class="w-100">
+                        <div class="row">
+                            <div class="col">
+                                <button class="btn w-100" data-bs-dismiss="modal">
+                                    取消
+                                </button>
+                            </div>
+                            <div class="col">
+                                <button href="#" class="btn btn-danger w-100" data-bs-dismiss="modal"
+                                        hx-post="/user/edit/kill" hx-swap="none"
+                                        hx-vals='js:{ password: document.getElementById("confirm_kill_password").value }'>
+                                    确认
+                                </button>
                             </div>
                         </div>
                     </div>
                 </div>
             </div>
         </div>
+    </div>
     {/if}
 
     <script>

+ 347 - 163
resources/views/tabler/user/index.tpl

@@ -110,7 +110,7 @@
                                                 速度限制
                                             </div>
                                             <div class="text-secondary">
-                                                {if $user->node_speedlimit !== 0.0}
+                                                {if $user->node_speedlimit !== 0}
                                                     <code>{$user->node_speedlimit}</code>
                                                     Mbps
                                                 {else}
@@ -181,44 +181,44 @@
                                 <div class="tab-pane active show" id="sub">
                                     <div>
                                         <p>
-                                            通用订阅(json):<code>{$UniversalSub}/json</code>
+                                            通用订阅(Json):<code class="spoiler">{$UniversalSub}/json</code>
                                         </p>
                                         <p>
-                                            通用订阅(clash):<code>{$UniversalSub}/clash</code>
+                                            通用订阅(Clash):<code class="spoiler">{$UniversalSub}/clash</code>
                                         </p>
                                         <p>
-                                            通用订阅(sing-box):<code>{$UniversalSub}/singbox</code>
+                                            通用订阅(SingBox):<code class="spoiler">{$UniversalSub}/singbox</code>
                                         </p>
                                         <p>
-                                            通用订阅(v2rayjson):<code>{$UniversalSub}/v2rayjson</code>
+                                            通用订阅(V2Ray Json):<code class="spoiler">{$UniversalSub}/v2rayjson</code>
                                         </p>
                                         {if $public_setting['enable_ss_sub']}
-                                            <p>
-                                                通用订阅(sip008):<code>{$UniversalSub}/sip008</code>
-                                            </p>
+                                        <p>
+                                            通用订阅(SIP008):<code class="spoiler">{$UniversalSub}/sip008</code>
+                                        </p>
                                         {/if}
                                         <div class="btn-list justify-content-start">
                                             <a data-clipboard-text="{$UniversalSub}/json"
                                                class="copy btn btn-primary">
-                                                复制通用订阅(json)
+                                                复制通用订阅(Json)
                                             </a>
                                             <a data-clipboard-text="{$UniversalSub}/clash"
                                                class="copy btn btn-primary">
-                                                复制通用订阅(clash)
+                                                复制通用订阅(Clash)
                                             </a>
                                             <a data-clipboard-text="{$UniversalSub}/singbox"
                                                class="copy btn btn-primary">
-                                                复制通用订阅(sing-box)
+                                                复制通用订阅(SingBox)
                                             </a>
                                             <a data-clipboard-text="{$UniversalSub}/v2rayjson"
                                                class="copy btn btn-primary">
-                                                复制通用订阅(v2rayjson)
+                                                复制通用订阅(V2Ray Json)
                                             </a>
                                             {if $public_setting['enable_ss_sub']}
-                                                <a data-clipboard-text="{$UniversalSub}/sip008"
-                                                   class="copy btn btn-primary">
-                                                    复制通用订阅(sip008)
-                                                </a>
+                                            <a data-clipboard-text="{$UniversalSub}/sip008"
+                                               class="copy btn btn-primary">
+                                                复制通用订阅(SIP008)
+                                            </a>
                                             {/if}
                                         </div>
                                     </div>
@@ -226,44 +226,44 @@
                                 <div class="tab-pane show" id="client-sub">
                                     <div>
                                         {if $public_setting['enable_ss_sub']}
-                                            <p>
-                                                客户端订阅(Shadowsocks):<code>{$UniversalSub}/ss</code></p>
-                                            <p>
-                                                客户端订阅(SIP002):<code>{$UniversalSub}/sip002</code>
-                                            </p>
+                                        <p>
+                                            客户端订阅(Shadowsocks):<code class="spoiler">{$UniversalSub}/ss</code></p>
+                                        <p>
+                                            客户端订阅(SIP002):<code class="spoiler">{$UniversalSub}/sip002</code>
+                                        </p>
                                         {/if}
                                         {if $public_setting['enable_v2_sub']}
-                                            <p>
-                                                客户端订阅(V2Ray):<code>{$UniversalSub}/v2ray</code>
-                                            </p>
+                                        <p>
+                                            客户端订阅(V2Ray):<code class="spoiler">{$UniversalSub}/v2ray</code>
+                                        </p>
                                         {/if}
                                         {if $public_setting['enable_trojan_sub']}
-                                            <p>
-                                                客户端订阅(Trojan):<code>{$UniversalSub}/trojan</code>
-                                            </p>
+                                        <p>
+                                            客户端订阅(Trojan):<code class="spoiler">{$UniversalSub}/trojan</code>
+                                        </p>
                                         {/if}
                                         <div class="btn-list justify-content-start">
                                             {if $public_setting['enable_ss_sub']}
-                                                <a data-clipboard-text="{$UniversalSub}/ss"
-                                                   class="copy btn btn-primary">
-                                                    复制客户端订阅(Shadowsocks)
-                                                </a>
-                                                <a data-clipboard-text="{$UniversalSub}/sip002"
-                                                   class="copy btn btn-primary">
-                                                    复制客户端订阅(SIP002)
-                                                </a>
+                                            <a data-clipboard-text="{$UniversalSub}/ss"
+                                               class="copy btn btn-primary">
+                                                复制客户端订阅(Shadowsocks)
+                                            </a>
+                                            <a data-clipboard-text="{$UniversalSub}/sip002"
+                                               class="copy btn btn-primary">
+                                                复制客户端订阅(SIP002)
+                                            </a>
                                             {/if}
                                             {if $public_setting['enable_v2_sub']}
-                                                <a data-clipboard-text="{$UniversalSub}/v2ray"
-                                                   class="copy btn btn-primary">
-                                                    复制客户端订阅(V2Ray)
-                                                </a>
+                                            <a data-clipboard-text="{$UniversalSub}/v2ray"
+                                               class="copy btn btn-primary">
+                                                复制客户端订阅(V2Ray)
+                                            </a>
                                             {/if}
                                             {if $public_setting['enable_trojan_sub']}
-                                                <a data-clipboard-text="{$UniversalSub}/trojan"
-                                                   class="copy btn btn-primary">
-                                                    复制客户端订阅(Trojan)
-                                                </a>
+                                            <a data-clipboard-text="{$UniversalSub}/trojan"
+                                               class="copy btn btn-primary">
+                                                复制客户端订阅(Trojan)
+                                            </a>
                                             {/if}
                                         </div>
                                     </div>
@@ -271,83 +271,119 @@
                                 <div class="tab-pane" id="windows">
                                     <div>
                                         <p>
-                                            适用于 Clash 的订阅:<code>{$UniversalSub}/clash</code>
+                                            适用于 Clash 的订阅:<code class="spoiler">{$UniversalSub}/clash</code>
+                                        </p>
+                                        <p>
+                                            适用于 SingBox 的订阅:<code class="spoiler">{$UniversalSub}/clash</code>
                                         </p>
                                         <div class="btn-list justify-content-start">
-                                            <a
-                                                {if $config['enable_r2_client_download']}
-                                                    href="/user/clients/Clash.Verge.exe"
+                                            <a  {if $config['enable_r2_client_download']}
+                                                href="/user/clients/Clash.Nyanpasu.exe"
                                                 {else}
-                                                    href="/clients/Clash.Verge.exe"
+                                                href="/clients/Clash.Nyanpasu.exe"
                                                 {/if} class="btn btn-azure">
-                                                下载 Clash Verge
+                                                下载 Clash Nyanpasu
                                             </a>
                                             <a data-clipboard-text="{$UniversalSub}/clash"
                                                class="copy btn btn-primary">
                                                 复制 Clash 订阅链接
                                             </a>
-                                            <a href="clash://install-config?url={$UniversalSub}/clash&name={$config['appName']}"
+                                            <a href="clash-nyanpasu://subscribe-remote-profile?url={$UniversalSub}&name={$config['appName']}"
+                                               class="btn btn-indigo">
+                                                导入 Clash Nyanpasu
+                                            </a>
+                                        </div>
+                                        <div class="btn-list justify-content-start my-2">
+                                            <a  {if $config['enable_r2_client_download']}
+                                                href="/user/clients/Hiddify.exe"
+                                                {else}
+                                                href="/clients/Hiddify.exe"
+                                                {/if} class="btn btn-azure">
+                                                下载 Hiddify
+                                            </a>
+                                            <a data-clipboard-text="{$UniversalSub}/singbox"
+                                               class="copy btn btn-primary">
+                                                复制 SingBox 订阅链接
+                                            </a>
+                                            <a href="hiddify://import/{$UniversalSub}#{$config['appName']}"
                                                class="btn btn-indigo">
-                                                导入 Clash
+                                                导入 Hiddify
                                             </a>
                                         </div>
                                     </div>
                                 </div>
                                 <div class="tab-pane" id="macos">
                                     <p>
-                                        适用于 Clash 的订阅:<code>{$UniversalSub}/clash</code>
+                                        适用于 Clash 的订阅:<code class="spoiler">{$UniversalSub}/clash</code>
                                     </p>
                                     <p>
-                                        适用于 sing-box 的订阅:<code>{$UniversalSub}/singbox</code>
+                                        适用于 SingBox 的订阅:<code class="spoiler">{$UniversalSub}/singbox</code>
                                     </p>
                                     <div class="btn-list justify-content-start">
                                         <a {if $config['enable_r2_client_download']}
-                                            href="/user/clients/Clash.Verge_aarch64.dmg"
-                                        {else}
-                                            href="/clients/Clash.Verge_aarch64.dmg"
-                                        {/if} class="btn btn-azure">
-                                            下载 Clash Verge (aarch64)
+                                            href="/user/clients/Clash.Nyanpasu_aarch64.dmg"
+                                            {else}
+                                            href="/clients/Clash.Nyanpasu_aarch64.dmg"
+                                            {/if} class="btn btn-azure">
+                                            下载 Clash Nyanpasu (aarch64)
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/clash"
                                            class="copy btn btn-primary">
                                             复制 Clash 订阅链接
                                         </a>
-                                        <a href="clash://install-config?url={$UniversalSub}/clash&name={$config['appName']}"
+                                        <a href="clash-nyanpasu://subscribe-remote-profile?url={$UniversalSub}&name={$config['appName']}"
                                            class="btn btn-indigo">
-                                            导入 Clash
+                                            导入 Clash Nyanpasu
                                         </a>
                                     </div>
                                     <div class="btn-list justify-content-start my-2">
                                         <a {if $config['enable_r2_client_download']}
                                             href="/user/clients/SFM.dmg"
-                                        {else}
+                                            {else}
                                             href="/clients/SFM.dmg"
-                                        {/if} class="btn btn-azure">
+                                            {/if} class="btn btn-azure">
                                             下载 SFM
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/singbox"
                                            class="copy btn btn-primary">
-                                            复制 sing-box 订阅链接
+                                            复制 SingBox 订阅链接
                                         </a>
                                         <a href="sing-box://import-remote-profile?url={$UniversalSub}/singbox#{$config['appName']}"
                                            class="btn btn-indigo">
                                             导入 SFM
                                         </a>
                                     </div>
+                                    <div class="btn-list justify-content-start my-2">
+                                        <a  {if $config['enable_r2_client_download']}
+                                            href="/user/clients/Hiddify.dmg"
+                                            {else}
+                                            href="/clients/Hiddify.dmg"
+                                            {/if} class="btn btn-azure">
+                                            下载 Hiddify
+                                        </a>
+                                        <a data-clipboard-text="{$UniversalSub}/singbox"
+                                           class="copy btn btn-primary">
+                                            复制 SingBox 订阅链接
+                                        </a>
+                                        <a href="hiddify://import/{$UniversalSub}#{$config['appName']}"
+                                           class="btn btn-indigo">
+                                            导入 Hiddify
+                                        </a>
+                                    </div>
                                 </div>
                                 <div class="tab-pane" id="android">
                                     <p>
-                                        适用于 Clash 的订阅:<code>{$UniversalSub}/clash</code>
+                                        适用于 Clash 的订阅:<code class="spoiler">{$UniversalSub}/clash</code>
                                     </p>
                                     <p>
-                                        适用于 sing-box 的订阅:<code>{$UniversalSub}/singbox</code>
+                                        适用于 SingBox 的订阅:<code class="spoiler">{$UniversalSub}/singbox</code>
                                     </p>
                                     <div class="btn-list justify-content-start">
                                         <a {if $config['enable_r2_client_download']}
                                             href="/user/clients/CMFA.apk"
-                                        {else}
+                                            {else}
                                             href="/clients/CMFA.apk"
-                                        {/if} class="btn btn-azure">
+                                            {/if} class="btn btn-azure">
                                             下载 Clash.Meta For Android
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/clash"
@@ -362,59 +398,96 @@
                                     <div class="btn-list justify-content-start my-2">
                                         <a {if $config['enable_r2_client_download']}
                                             href="/user/clients/SFA.apk"
-                                        {else}
+                                            {else}
                                             href="/clients/SFA.apk"
-                                        {/if} class="btn btn-azure">
+                                            {/if} class="btn btn-azure">
                                             下载 SFA
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/singbox"
                                            class="copy btn btn-primary">
-                                            复制 sing-box 订阅链接
+                                            复制 SingBox 订阅链接
                                         </a>
                                         <a href="sing-box://import-remote-profile?url={$UniversalSub}/singbox#{$config['appName']}"
                                            class="btn btn-indigo">
                                             导入 SFA
                                         </a>
                                     </div>
+                                    <div class="btn-list justify-content-start my-2">
+                                        <a  {if $config['enable_r2_client_download']}
+                                            href="/user/clients/Hiddify.apk"
+                                            {else}
+                                            href="/clients/Hiddify.apk"
+                                            {/if} class="btn btn-azure">
+                                            下载 Hiddify
+                                        </a>
+                                        <a data-clipboard-text="{$UniversalSub}/singbox"
+                                           class="copy btn btn-primary">
+                                            复制 SingBox 订阅链接
+                                        </a>
+                                        <a href="hiddify://import/{$UniversalSub}#{$config['appName']}"
+                                           class="btn btn-indigo">
+                                            导入 Hiddify
+                                        </a>
+                                    </div>
                                 </div>
                                 <div class="tab-pane" id="ios">
                                     <p>
-                                        适用于 sing-box 的订阅:<code>{$UniversalSub}/singbox</code>
+                                        适用于 SingBox 的订阅:<code class="spoiler">{$UniversalSub}/singbox</code>
                                     </p>
-                                    <div class="btn-list justify-content-start my-2">
+                                    <div class="btn-list justify-content-start">
                                         <a href="https://apps.apple.com/app/sing-box/id6451272673" target="_blank"
                                            class="btn btn-azure">
-                                            安裝 sing-box
+                                            安裝 SFI
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/singbox"
                                            class="copy btn btn-primary">
-                                            复制 sing-box 订阅链接
+                                            复制 SingBox 订阅链接
                                         </a>
                                         <a href="sing-box://import-remote-profile?url={$UniversalSub}/singbox#{$config['appName']}"
                                            class="btn btn-indigo">
-                                            导入 sing-box
+                                            导入 SFI
                                         </a>
                                     </div>
                                 </div>
                                 <div class="tab-pane" id="linux">
                                     <p>
-                                        适用于 Clash 的订阅:<code>{$UniversalSub}/clash</code>
+                                        适用于 Clash 的订阅:<code class="spoiler">{$UniversalSub}/clash</code>
+                                    </p>
+                                    <p>
+                                        适用于 SingBox 的订阅:<code class="spoiler">{$UniversalSub}/singbox</code>
                                     </p>
                                     <div class="btn-list justify-content-start">
                                         <a {if $config['enable_r2_client_download']}
-                                            href="/user/clients/Clash.Verge.AppImage.tar.gz"
-                                        {else}
-                                            href="/clients/Clash.Verge.AppImage.tar.gz"
-                                        {/if} class="btn btn-azure">
-                                            下载 Clash Verge
+                                            href="/user/clients/Clash.Nyanpasu.AppImage"
+                                            {else}
+                                            href="/clients/Clash.Nyanpasu.AppImage"
+                                            {/if} class="btn btn-azure">
+                                            下载 Clash Nyanpasu
                                         </a>
                                         <a data-clipboard-text="{$UniversalSub}/clash"
                                            class="copy btn btn-primary">
                                             复制 Clash 订阅链接
                                         </a>
-                                        <a href="clash://install-config?url={$UniversalSub}/clash&name={$config['appName']}"
+                                        <a href="clash-nyanpasu://subscribe-remote-profile?url={$UniversalSub}&name={$config['appName']}"
                                            class="btn btn-indigo">
-                                            导入 Clash
+                                            导入 Clash Nyanpasu
+                                        </a>
+                                    </div>
+                                    <div class="btn-list justify-content-start my-2">
+                                        <a  {if $config['enable_r2_client_download']}
+                                            href="/user/clients/Hiddify.AppImage"
+                                            {else}
+                                            href="/clients/Hiddify.AppImage"
+                                            {/if} class="btn btn-azure">
+                                            下载 Hiddify
+                                        </a>
+                                        <a data-clipboard-text="{$UniversalSub}/singbox"
+                                           class="copy btn btn-primary">
+                                            复制 SingBox 订阅链接
+                                        </a>
+                                        <a href="hiddify://import/{$UniversalSub}#{$config['appName']}"
+                                           class="btn btn-indigo">
+                                            导入 Hiddify
                                         </a>
                                     </div>
                                 </div>
@@ -429,11 +502,11 @@
                                             </tr>
                                             <tr>
                                                 <td><strong>连接密码</strong></td>
-                                                <td>{$user->passwd}</td>
+                                                <td><span class="spoiler">{$user->passwd}</span></td>
                                             </tr>
                                             <tr>
                                                 <td><strong>UUID</strong></td>
-                                                <td>{$user->uuid}</td>
+                                                <td><span class="spoiler">{$user->uuid}</span></td>
                                             </tr>
                                             <tr>
                                                 <td><strong>自定义加密</strong></td>
@@ -448,50 +521,108 @@
                     </div>
                 </div>
                 <div class="col-lg-6 col-sm-12">
-                    <div class="card">
-                        <div class="card-body">
-                            <h3 class="card-title">流量用量</h3>
-                            <div class="progress progress-separated mb-3">
-                                {if $user->LastusedTrafficPercent() < '1'}
+                    <div class="vstack">
+                        <div class="card">
+                            <div class="card-body">
+                                <h3 class="card-title">流量用量</h3>
+                                <div class="progress progress-separated mb-3">
+                                    {if $user->LastusedTrafficPercent() < '1'}
                                     <div class="progress-bar bg-primary" role="progressbar" style="width: 1%"></div>
-                                {else}
+                                    {else}
                                     <div class="progress-bar bg-primary" role="progressbar"
                                          style="width: {$user->LastusedTrafficPercent()}%">
                                     </div>
-                                {/if}
-                                {if $user->TodayusedTrafficPercent() < '1'}
+                                    {/if}
+                                    {if $user->TodayusedTrafficPercent() < '1'}
                                     <div class="progress-bar bg-success" role="progressbar" style="width: 1%"></div>
-                                {else}
+                                    {else}
                                     <div class="progress-bar bg-success" role="progressbar"
                                          style="width: {$user->TodayusedTrafficPercent()}%"></div>
-                                {/if}
-                            </div>
-                            <div class="row">
-                                <div class="col-auto d-flex align-items-center pe-2">
-                                    <span class="legend me-2 bg-primary"></span>
-                                    <span>过去用量 {$user->LastusedTraffic()}</span>
-                                </div>
-                                <div class="col-auto d-flex align-items-center px-2">
-                                    <span class="legend me-2 bg-success"></span>
-                                    <span>今日用量 {$user->TodayusedTraffic()}</span>
+                                    {/if}
                                 </div>
-                                <div class="col-auto d-flex align-items-center ps-2">
-                                    <span class="legend me-2"></span>
-                                    <span>剩余流量 {$user->unusedTraffic()}</span>
+                                <div class="row">
+                                    <div class="col-auto d-flex align-items-center pe-2">
+                                        <span class="legend me-2 bg-primary"></span>
+                                        <span>过去用量 {$user->LastusedTraffic()}</span>
+                                    </div>
+                                    <div class="col-auto d-flex align-items-center px-2">
+                                        <span class="legend me-2 bg-success"></span>
+                                        <span>今日用量 {$user->TodayusedTraffic()}</span>
+                                    </div>
+                                    <div class="col-auto d-flex align-items-center ps-2">
+                                        <span class="legend me-2"></span>
+                                        <span>剩余流量 {$user->unusedTraffic()}</span>
+                                    </div>
                                 </div>
-                            </div>
-                            <p class="my-3">
-                                {if $user->class === 0}
+                                <p class="my-3">
+                                    {if $user->class === 0}
                                     前往
                                     <a href="/user/product">商店</a>
                                     购买套餐
-                                {else}
+                                    {else}
                                     你的 LV. {$user->class} 账户会在 {$class_expire_days} 天后到期({$user->class_expire})
+                                    {/if}
+                                </p>
+                            </div>
+                        </div>
+                        {if $public_setting['traffic_log']}
+                        <div class="card my-3 mb-0">
+                            <div class="card-body">
+                                <h3 class="card-title">每小时用量</h3>
+                                <div id="traffic-log"></div>
+                            </div>
+                        </div>
+                        {/if}
+                    </div>
+                </div>
+                {if $public_setting['enable_checkin']}
+                <div class="col-lg-6 col-sm-12">
+                    <div class="card">
+                        <div class="card-stamp">
+                            <div class="card-stamp-icon bg-green">
+                                <i class="ti ti-check"></i>
+                            </div>
+                        </div>
+                        <div class="card-body">
+                            <h3 class="card-title">每日签到</h3>
+                            <p>
+                                签到可领取
+                                {if $public_setting['checkin_min'] !== $public_setting['checkin_max']}
+                                &nbsp;
+                                <code>{$public_setting['checkin_min']} MB</code>
+                                至
+                                <code>{$public_setting['checkin_max']} MB</code>
+                                范围内的流量
+                                {else}
+                                <code>{$public_setting['checkin_min']} MB</code>
                                 {/if}
                             </p>
+                            <p>
+                                上次签到时间:<code id="last-checkin-time">{$user->lastCheckInTime()}</code>
+                            </p>
+                        </div>
+                        <div class="card-footer">
+                            <div class="d-flex">
+                                {if ! $user->isAbleToCheckin()}
+                                <button id="check-in" class="btn btn-primary ms-auto" disabled>已签到</button>
+                                {else}
+                                {if $public_setting['enable_checkin_captcha']}
+                                {include file='captcha/div.tpl'}
+                                {/if}
+                                <button id="check-in" class="btn btn-primary ms-auto"
+                                    hx-post="/user/checkin" hx-swap="none" hx-vals='js:{
+                                    {if $public_setting['enable_checkin_captcha']}
+                                    {include file='captcha/ajax.tpl'}
+                                    {/if}
+                                    }'>
+                                    签到
+                                </button>
+                                {/if}
+                            </div>
                         </div>
                     </div>
                 </div>
+                {/if}
                 <div class="col-lg-6 col-sm-12">
                     <div class="card">
                         <div class="ribbon ribbon-top bg-yellow">
@@ -499,69 +630,21 @@
                         </div>
                         <div class="card-body">
                             <h3 class="card-title">
-                                最新公告
+                                置顶公告
                                 {if $ann !== null}
-                                    <span class="card-subtitle">{$ann->date}</span>
+                                <span class="card-subtitle">{$ann->date}</span>
                                 {/if}
                             </h3>
                             <p class="text-secondary">
                                 {if $ann !== null}
-                                    {$ann->content}
+                                {$ann->content}
                                 {else}
-                                    暂无公告
+                                暂无公告
                                 {/if}
                             </p>
                         </div>
                     </div>
                 </div>
-                {if $public_setting['enable_checkin']}
-                    <div class="col-lg-6 col-sm-12">
-                        <div class="card">
-                            <div class="card-stamp">
-                                <div class="card-stamp-icon bg-green">
-                                    <i class="ti ti-check"></i>
-                                </div>
-                            </div>
-                            <div class="card-body">
-                                <h3 class="card-title">每日签到</h3>
-                                <p>
-                                    签到可领取
-                                    {if $public_setting['checkin_min'] !== $public_setting['checkin_max']}
-                                        &nbsp;
-                                        <code>{$public_setting['checkin_min']} MB</code>
-                                        至
-                                        <code>{$public_setting['checkin_max']} MB</code>
-                                        范围内的流量
-                                    {else}
-                                        <code>{$public_setting['checkin_min']} MB</code>
-                                    {/if}
-                                </p>
-                                <p>
-                                    上次签到时间:<code id="last-checkin-time">{$user->lastCheckInTime()}</code>
-                                </p>
-                            </div>
-                            <div class="card-footer">
-                                <div class="d-flex">
-                                    {if ! $user->isAbleToCheckin()}
-                                        <button id="check-in" class="btn btn-primary ms-auto" disabled>已签到</button>
-                                    {else}
-                                        {if $public_setting['enable_checkin_captcha']}
-                                            {include file='captcha/div.tpl'}
-                                        {/if}
-                                        <button id="check-in" class="btn btn-primary ms-auto"
-                                                hx-post="/user/checkin" hx-swap="none" hx-vals='js:{
-                                                {if $public_setting['enable_checkin_captcha']}
-                                                    {include file='captcha/ajax.tpl'}
-                                                {/if}
-                                                }'>
-                                            签到
-                                        </button>
-                                    {/if}
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                {/if}
             </div>
         </div>
     </div>
@@ -570,4 +653,105 @@
         {include file='captcha/js.tpl'}
     {/if}
 
+    {if $public_setting['traffic_log']}
+    <script>
+        document.addEventListener("DOMContentLoaded", function () {
+            let chart = window.ApexCharts && new ApexCharts(document.getElementById('traffic-log'), {
+                chart: {
+                    type: "line",
+                    fontFamily: "inherit",
+                    height: '100%',
+                    parentHeightOffset: 0,
+                    toolbar: {
+                        show: false
+                    },
+                    animations: {
+                        enabled: false
+                    }
+                },
+                stroke: {
+                    curve: "smooth"
+                },
+                fill: {
+                    opacity: 1
+                },
+                series: [
+                    {
+                        name: "使用流量(MB)",
+                        data: {$traffic_logs}
+                    }
+                ],
+                tooltip: {
+                    theme: "dark"
+                },
+                grid: {
+                    padding: {
+                        top: -20,
+                        right: 0,
+                        left: 0,
+                        bottom: 0
+                    },
+                    strokeDashArray: 4
+                },
+                xaxis: {
+                    title: {
+                        text: "小时"
+                    },
+                    labels: {
+                        padding: 0
+                    },
+                    tooltip: {
+                        enabled: false
+                    },
+                    axisBorder: {
+                        show: false
+                    },
+                    categories: [
+                        "00",
+                        "01",
+                        "02",
+                        "03",
+                        "04",
+                        "05",
+                        "06",
+                        "07",
+                        "08",
+                        "09",
+                        "10",
+                        "11",
+                        "12",
+                        "13",
+                        "14",
+                        "15",
+                        "16",
+                        "17",
+                        "18",
+                        "19",
+                        "20",
+                        "21",
+                        "22",
+                        "23"
+                    ]
+                },
+                yaxis: {
+                    title: {
+                        text: "使用流量(MB)",
+                        rotate: -90
+                    },
+                    labels: {
+                        padding: 14
+                    }
+                },
+                colors: ["#FF4500"],
+                legend: {
+                    show: false
+                }
+            });
+            chart.render();
+        });
+    </script>
+
+    <script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/libs/apexcharts/dist/apexcharts.min.js"></script>
+    {/if}
+
     {include file='user/footer.tpl'}

+ 1 - 1
resources/views/tabler/user/invoice/index.tpl

@@ -25,7 +25,7 @@
                                 <thead>
                                 <tr>
                                     {foreach $details['field'] as $key => $value}
-                                        <th>{$value}</th>
+                                    <th>{$value}</th>
                                     {/foreach}
                                 </tr>
                                 </thead>

+ 109 - 113
resources/views/tabler/user/invoice/view.tpl

@@ -1,7 +1,5 @@
 {include file='user/header.tpl'}
 
-<script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
-
 <div class="page-wrapper">
     <div class="container-xl">
         <div class="page-header d-print-none text-white">
@@ -24,137 +22,135 @@
     <div class="page-body">
         <div class="container-xl">
             <div class="row row-cards">
-                {if $invoice->status === 'unpaid'}
+                {if $invoice->status === 'unpaid' || $invoice->status === 'partially_paid'}
                 <div class="col-sm-12 col-md-6 col-lg-9">
-                    {else}
-                    <div class="col-sm-12 col-md-12 col-lg-12">
-                        {/if}
-                        <div class="card">
-                            <div class="card-header">
-                                <h3 class="card-title">基本信息</h3>
-                            </div>
-                            <div class="card-body">
-                                <div class="datagrid">
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">订单ID</div>
-                                        <div class="datagrid-content">{$invoice->order_id}</div>
-                                    </div>
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">订单金额</div>
-                                        <div class="datagrid-content">{$invoice->price}</div>
-                                    </div>
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">订单状态</div>
-                                        <div class="datagrid-content">{$invoice->status_text}</div>
-                                    </div>
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">创建时间</div>
-                                        <div class="datagrid-content">{$invoice->create_time}</div>
-                                    </div>
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">更新时间</div>
-                                        <div class="datagrid-content">{$invoice->update_time}</div>
-                                    </div>
-                                    <div class="datagrid-item">
-                                        <div class="datagrid-title">支付时间</div>
-                                        <div class="datagrid-content">{$invoice->pay_time}</div>
-                                    </div>
-                                    {if $invoice->status === 'paid_gateway'}
-                                        <div class="datagrid-item">
-                                            <div class="datagrid-title">支付网关单号</div>
-                                            <div class="datagrid-content">{$paylist->tradeno}</div>
-                                        </div>
-                                    {/if}
+                {else}
+                <div class="col-md-12">
+                {/if}
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">基本信息</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="datagrid">
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">订单ID</div>
+                                    <div class="datagrid-content">{$invoice->order_id}</div>
+                                </div>
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">订单金额</div>
+                                    <div class="datagrid-content">{$invoice->price}</div>
                                 </div>
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">订单状态</div>
+                                    <div class="datagrid-content">{$invoice->status_text}</div>
+                                </div>
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">创建时间</div>
+                                    <div class="datagrid-content">{$invoice->create_time}</div>
+                                </div>
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">更新时间</div>
+                                    <div class="datagrid-content">{$invoice->update_time}</div>
+                                </div>
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">支付时间</div>
+                                    <div class="datagrid-content">{$invoice->pay_time}</div>
+                                </div>
+                                {if $invoice->status === 'paid_gateway'}
+                                <div class="datagrid-item">
+                                    <div class="datagrid-title">支付网关单号</div>
+                                    <div class="datagrid-content">{$paylist->tradeno}</div>
+                                </div>
+                                {/if}
                             </div>
                         </div>
-                        <div class="card my-3">
-                            <div class="card-header">
-                                <h3 class="card-title">账单详情</h3>
-                            </div>
-                            <div class="card-body">
-                                <div class="table-responsive">
-                                    <table id="invoice_content_table" class="table table-vcenter card-table">
-                                        <thead>
+                    </div>
+                    <div class="card my-3">
+                        <div class="card-header">
+                            <h3 class="card-title">账单详情</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="table-responsive">
+                                <table id="invoice_content_table" class="table table-vcenter card-table">
+                                    <thead>
+                                    <tr>
+                                        <th>名称</th>
+                                        <th>价格</th>
+                                    </tr>
+                                    </thead>
+                                    <tbody>
+                                        {foreach $invoice_content as $invoice_content_detail}
                                         <tr>
-                                            <th>名称</th>
-                                            <th>价格</th>
+                                            <td>{$invoice_content_detail->name}</td>
+                                            <td>{$invoice_content_detail->price}</td>
                                         </tr>
-                                        </thead>
-                                        <tbody>
-                                        {foreach $invoice_content as $invoice_content_detail}
-                                            <tr>
-                                                <td>{$invoice_content_detail->name}</td>
-                                                <td>{$invoice_content_detail->price}</td>
-                                            </tr>
                                         {/foreach}
-                                        </tbody>
-                                    </table>
-                                </div>
+                                    </tbody>
+                                </table>
                             </div>
                         </div>
                     </div>
-                    {if $invoice->status === 'unpaid'}
-                        <div class="col-sm-12 col-md-6 col-lg-3">
-                            <div class="card">
-                                <div class="card-header">
-                                    <h3 class="card-title">余额支付</h3>
-                                </div>
-                                <div class="card-body">
+                </div>
+                {if $invoice->status === 'unpaid' || $invoice->status === 'partially_paid'}
+                <div class="col-sm-12 col-md-6 col-lg-3">
+                    <div class="card">
+                        <ul class="nav nav-tabs nav-fill" data-bs-toggle="tabs">
+                            {if $invoice->type !== 'topup'}
+                            <li class="nav-item">
+                                <a href="#balance" class="nav-link active" data-bs-toggle="tab">
+                                    <i class="ti ti-coins icon"></i>
+                                    &nbsp;余额支付
+                                </a>
+                            </li>
+                            {/if}
+                            {if count($payments) > 0}
+                            <li class="nav-item">
+                                <a href="#gateway" class="nav-link" data-bs-toggle="tab">
+                                    <i class="ti ti-coin icon"></i>
+                                    &nbsp;网关支付
+                                </a>
+                            </li>
+                            {/if}
+                        </ul>
+                        <div class="card-body">
+                            <div class="tab-content">
+                                {if $invoice->type !== 'topup'}
+                                <div class="tab-pane active show" id="balance">
                                     <div class="mb-3">
                                         当前账户可用余额:<code>{$user->money}</code> 元
                                     </div>
-                                </div>
-                                <div class="card-footer">
                                     <div class="d-flex">
-                                        <button id="pay-balance" class="btn btn-blue" type="button">支付</button>
+                                        <button class="btn btn-primary" type="button"
+                                                hx-post="/user/invoice/pay_balance" hx-swap="none"
+                                                hx-vals='js:{
+                                                    invoice_id: {$invoice->id},
+                                                }'>
+                                            支付
+                                        </button>
                                     </div>
                                 </div>
-                            </div>
-                            {if count($payments) > 0}
-                                <div class="card my-3">
-                                    <div class="card-header">
-                                        <h3 class="card-title">网关支付</h3>
-                                    </div>
-                                    <div class="card-body">
-                                        {foreach from=$payments item=payment}
-                                            <div class="mb-3">
-                                                {$payment_name = $payment::_name()}
-                                                {include file="../../gateway/$payment_name.tpl"}
-                                            </div>
-                                        {/foreach}
+                                {/if}
+                                {if count($payments) > 0}
+                                <div class="tab-pane show" id="gateway">
+                                    {foreach from=$payments item=payment}
+                                    <div class="mb-3">
+                                        {$payment_name = $payment::_name()}
+                                        {include file="../../gateway/$payment_name.tpl"}
                                     </div>
+                                    {/foreach}
                                 </div>
-                            {/if}
+                                {/if}
+                                {if $invoice->type === 'topup' && count($payments) === 0}
+                                暂无可用支付方式
+                                {/if}
+                            </div>
                         </div>
-                    {/if}
+                    </div>
                 </div>
+                {/if}
             </div>
         </div>
+    </div>
 
-        <script>
-            $("#pay-balance").click(function () {
-                $.ajax({
-                    url: '/user/invoice/pay_balance',
-                    type: 'POST',
-                    dataType: "json",
-                    data: {
-                        invoice_id: {$invoice->id},
-                    },
-                    success: function (data) {
-                        if (data.ret === 1) {
-                            $('#success-message').text(data.msg);
-                            $('#success-dialog').modal('show');
-                            setTimeout(function () {
-                                $(location).attr('href', '/user/invoice');
-                            }, {$config['jump_delay']});
-                        } else {
-                            $('#fail-message').text(data.msg);
-                            $('#fail-dialog').modal('show');
-                        }
-                    }
-                })
-            });
-        </script>
-
-        {include file='user/footer.tpl'}
+    {include file='user/footer.tpl'}

+ 35 - 0
resources/views/tabler/user/money.tpl

@@ -14,6 +14,11 @@
                 </div>
                 <div class="col-auto">
                     <div class="btn-list">
+                        <a href="#" class="btn btn-primary" data-bs-toggle="modal"
+                           data-bs-target="#topup">
+                            <i class="icon ti ti-plus"></i>
+                            余额充值
+                        </a>
                         <a href="#" class="btn btn-primary" data-bs-toggle="modal"
                            data-bs-target="#apply-giftcard-dialog">
                             <i class="icon ti ti-cash-banknote"></i>
@@ -88,4 +93,34 @@
         </div>
     </div>
 
+    <div class="modal modal-blur fade" id="topup" tabindex="-1" role="dialog" aria-hidden="true">
+        <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">余额充值</h5>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group mb-3 row">
+                        <div class="col">
+                            <input id="topup_amount" type="number" step="10" class="form-control"
+                                   placeholder="请输入要充值的金额">
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn me-auto" data-bs-dismiss="modal">取消</button>
+                    <button id="apply-topup" class="btn btn-primary" data-bs-dismiss="modal"
+                            hx-post="/user/order/create" hx-swap="none"
+                            hx-vals='js:{
+                                amount: document.getElementById("topup_amount").value,
+                                type: "topup"
+                            }'>
+                        充值
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+
 {include file='user/footer.tpl'}

+ 16 - 53
resources/views/tabler/user/order/create.tpl

@@ -1,7 +1,5 @@
 {include file='user/header.tpl'}
 
-<script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
-
 <div class="page-wrapper">
     <div class="container-xl">
         <div class="page-header d-print-none text-white">
@@ -116,15 +114,28 @@
                                 <div class="input-group mb-2">
                                     <input id="coupon" type="text" class="form-control"
                                            placeholder="填写优惠码,没有请留空">
-                                    <button id="verify-coupon" class="btn" type="button">应用</button>
+                                    <button class="btn" type="button"
+                                            hx-post="/user/coupon" hx-swap="none"
+                                            hx-vals='js:{
+                                                coupon: document.getElementById("coupon").value,
+                                                product_id: {$product->id},
+                                            }'>
+                                        应用
+                                    </button>
                                 </div>
                             </div>
                         </div>
                     </div>
                     <div class="card my-3">
                         <div class="card-body">
-                            <button id="create-order" href=""
-                                    class="btn btn-primary w-100 my-3">创建订单
+                            <button class="btn btn-primary w-100 my-3"
+                                    hx-post="/user/order/create" hx-swap="none"
+                                    hx-vals='js:{
+                                        type: "product",
+                                        coupon: document.getElementById("coupon").value,
+                                        product_id: {$product->id},
+                                    }'>
+                                创建订单
                             </button>
                         </div>
                     </div>
@@ -133,52 +144,4 @@
         </div>
     </div>
 
-    <script>
-        $("#verify-coupon").click(function () {
-            $.ajax({
-                url: '/user/coupon',
-                type: 'POST',
-                dataType: "json",
-                data: {
-                    coupon: $('#coupon').val(),
-                    product_id: {$product->id},
-                },
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#coupon-code').text($('#coupon').val());
-                        $('#product-buy-discount').text(data.discount);
-                        $('#product-buy-total').text(data.buy_price);
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-
-        $("#create-order").click(function () {
-            $.ajax({
-                url: '/user/order/create',
-                type: 'POST',
-                dataType: "json",
-                data: {
-                    coupon: $('#coupon').val(),
-                    product_id: {$product->id},
-                },
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#success-message').text(data.msg);
-                        $('#success-dialog').modal('show');
-                        setTimeout(function () {
-                            $(location).attr('href', '/user/invoice/' + data.invoice_id + '/view');
-                        }, {$config['jump_delay']});
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-    </script>
-
     {include file='user/footer.tpl'}

+ 1 - 1
resources/views/tabler/user/order/index.tpl

@@ -25,7 +25,7 @@
                                 <thead>
                                 <tr>
                                     {foreach $details['field'] as $key => $value}
-                                        <th>{$value}</th>
+                                    <th>{$value}</th>
                                     {/foreach}
                                 </tr>
                                 </thead>

+ 2 - 0
resources/views/tabler/user/order/view.tpl

@@ -62,6 +62,7 @@
                     </div>
                 </div>
             </div>
+            {if $order->type === 'topup'}
             <div class="card my-3">
                 <div class="card-header">
                     <h3 class="card-title">商品内容</h3>
@@ -113,6 +114,7 @@
                     </div>
                 </div>
             </div>
+            {/if}
             <div class="card my-3">
                 <div class="card-header">
                     <h3 class="card-title">关联账单</h3>

+ 56 - 19
resources/views/tabler/user/profile.tpl

@@ -32,7 +32,7 @@
                     <div class="card">
                         <div class="card-body">
                             <div class="d-flex align-items-center">
-                                <div class="subheader">账户昵称</div>
+                                <div class="subheader">用户名</div>
                             </div>
                             <div class="h1 mb-3">{$user->user_name}</div>
                         </div>
@@ -59,8 +59,44 @@
                     </div>
                 </div>
             </div>
+            {if $public_setting['subscribe_log']}
             <div class="row row-deck my-3">
-                <div class="col-md-6 com-sm-12">
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">最近10次订阅记录</h3>
+                        </div>
+                        <div class="table-responsive">
+                            <table class="table table-vcenter text-nowrap card-table">
+                                <thead>
+                                    <tr>
+                                        <th>类型</th>
+                                        <th>UA</th>
+                                        <th>IP</th>
+                                        <th>IP归属地</th>
+                                        <th>时间</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    {foreach $subs as $sub}
+                                    <tr>
+                                        <td>{$sub->type}</td>
+                                        <td>{$sub->request_user_agent}</td>
+                                        <td>{$sub->request_ip}</td>
+                                        <td>{$sub->location}</td>
+                                        <td>{$sub->request_time}</td>
+                                    </tr>
+                                    {/foreach}
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            {/if}
+            <div class="row row-deck my-3">
+                {if $public_setting['login_log']}
+                <div class="col-md-6 col-sm-12">
                     <div class="card">
                         <div class="card-header">
                             <h3 class="card-title">最近10次成功登录记录</h3>
@@ -68,26 +104,27 @@
                         <div class="table-responsive">
                             <table class="table table-vcenter text-nowrap card-table">
                                 <thead>
-                                <tr>
-                                    <th>IP</th>
-                                    <th>时间</th>
-                                    <th>IP归属地</th>
-                                </tr>
+                                    <tr>
+                                        <th>IP</th>
+                                        <th>IP归属地</th>
+                                        <th>时间</th>
+                                    </tr>
                                 </thead>
                                 <tbody>
-                                {foreach $logins as $login}
+                                    {foreach $logins as $login}
                                     <tr>
                                         <td>{$login->ip}</td>
-                                        <td>{$login->datetime}</td>
                                         <td>{$login->location}</td>
+                                        <td>{$login->datetime}</td>
                                     </tr>
-                                {/foreach}
+                                    {/foreach}
                                 </tbody>
                             </table>
                         </div>
                     </div>
                 </div>
-                <div class="col-md-6 com-sm-12">
+                {/if}
+                <div class="col-md-6 col-sm-12">
                     <div class="card">
                         <div class="card-header">
                             <h3 class="card-title">当前在线IP</h3>
@@ -95,22 +132,22 @@
                         <div class="table-responsive">
                             <table class="table table-vcenter text-nowrap card-table">
                                 <thead>
-                                <tr>
-                                    <th>IP</th>
-                                    <th>IP归属地</th>
-                                    <th>节点名称</th>
-                                    <th>最后在线时间</th>
-                                </tr>
+                                    <tr>
+                                        <th>IP</th>
+                                        <th>IP归属地</th>
+                                        <th>节点名称</th>
+                                        <th>最后在线时间</th>
+                                    </tr>
                                 </thead>
                                 <tbody>
-                                {foreach $ips as $ip}
+                                    {foreach $ips as $ip}
                                     <tr>
                                         <td>{$ip->ip}</td>
                                         <td>{$ip->location}</td>
                                         <td>{$ip->node_name}</td>
                                         <td>{$ip->last_time}</td>
                                     </tr>
-                                {/foreach}
+                                    {/foreach}
                                 </tbody>
                             </table>
                         </div>

+ 1 - 1
resources/views/tabler/user/rate.tpl

@@ -120,7 +120,7 @@
                         padding: 4,
                     },
                 },
-                colors: [tabler.getColor("azure")],
+                colors: ["#FF0000"],
                 legend: {
                     show: false,
                 },

部分文件因为文件数量过多而无法显示