subscribe.blade.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. @extends('_layout')
  2. @section('title', '订 阅')
  3. @section('layout_css')
  4. <link href="/assets/global/fonts/font-awesome/css/all.min.css" rel="stylesheet">
  5. <link href="/assets/global/vendor/toastr/toastr.min.css" rel="stylesheet">
  6. <link href="/assets/css/subscribe.css" rel="stylesheet">
  7. @endsection
  8. @section('body_class', 'layout-full position-relative')
  9. @section('layout_content')
  10. <div class="container pt-15 pt-md-45 px-1 px-md-15">
  11. <div class="panel m-0">
  12. <!-- Top bar -->
  13. <div class="d-flex align-items-center justify-content-between mb-15">
  14. <h3 class="font-weight-bold text-white">{{ trans('model.subscribe.attribute') }}</h3>
  15. <div>
  16. <button class="btn btn-outline-info btn-sm mt-clipboard btn-round-sm" data-clipboard-action="copy"><i
  17. class="fas fa-copy mr-1"></i>{{ trans('user.subscribe.page.get_link') }}</button>
  18. <!-- 语言选择下拉 -->
  19. <div class="dropdown d-inline">
  20. <button class="btn btn-outline-primary btn-sm dropdown-toggle btn-round-sm" id="langDropdown" data-toggle="dropdown" type="button"
  21. aria-haspopup="true" aria-expanded="false">
  22. <i class="fi fi-{{ config('common.language.' . app()->getLocale())[1] }}"></i>
  23. </button>
  24. <div class="dropdown-menu dropdown-menu-right" aria-labelledby="langDropdown">
  25. @foreach (config('common.language') as $key => $value)
  26. @if ($key !== app()->getLocale())
  27. <a class="dropdown-item" href="{{ route('lang', ['locale' => $key]) }}">
  28. <i class="fi fi-{{ $value[1] }} mr-2" aria-hidden="true"></i>
  29. {{ $value[0] }}
  30. </a>
  31. @endif
  32. @endforeach
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. <!-- 用户信息卡片 -->
  38. <div class="info-card mb-3">
  39. <div class="row align-items-center no-gutters">
  40. <div class="col-auto text-center pr-0">
  41. <img class="avatar-big d-block mx-auto rounded-circle" src="{{ $user->avatar }}" alt="avatar" />
  42. </div>
  43. <div class="col">
  44. <div class="row text-center text-md-left pt-2 pt-md-0">
  45. <div class="col-6 col-md-3 mb-2">
  46. <div class="info-label"><i class="fa fa-user"></i> {{ trans('model.user.nickname') }}</div>
  47. <div class="info-value">{{ $user->nickname }}</div>
  48. </div>
  49. <div class="col-6 col-md-3 mb-2">
  50. <div class="info-label"><i class="fa fa-circle-dot text-success"></i> {{ trans('common.status.attribute') }}</div>
  51. <div class="info-value text-success">
  52. @if ($user->enable)
  53. <div class="info-value text-success">
  54. <i class="fa fa-check mr-1"></i> {{ ucfirst(trans('common.status.enabled')) }}
  55. </div>
  56. @else
  57. <div class="info-value text-warning">
  58. <i class="fa fa-times mr-1"></i> {{ ucfirst(trans('common.status.disabled')) }}
  59. </div>
  60. @endif
  61. </div>
  62. </div>
  63. <div class="col-6 col-md-3 mb-2">
  64. <div class="info-label"><i class="fa-regular fa-clock text-warning"></i> {{ trans('model.user.expired_date') }}</div>
  65. <div class="info-value">{{ localized_date($user->expiration_date) }}</div>
  66. </div>
  67. <div class="col-6 col-md-3 mb-2">
  68. <div class="info-label"><i class="fa fa-gauge-high text-primary"></i> {{ trans('user.attribute.data') }}</div>
  69. <div class="info-value text-info">{{ number_format($user->used_traffic / GiB, 2) }} / {{ $user->transfer_enable_formatted }}</div>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- Installation -->
  76. <div class="d-flex align-items-center justify-content-between mb-2">
  77. <h4 class="font-weight-semibold mb-0 text-white"><i class="fa fa-download text-primary mr-1"></i>{{ trans('user.tutorials') }}</h4>
  78. <ul class="nav nav-pills nav-platforms p-1 mb-0">
  79. <li class="nav-item"><a class="nav-link" data-platform="pc" href="#"><i class="fa-solid fa-desktop mr-1"></i>PC</a></li>
  80. <li class="nav-item"><a class="nav-link" data-platform="ios" href="#"><i class="fa-brands fa-apple mr-1"></i>iOS</a></li>
  81. <li class="nav-item"><a class="nav-link" data-platform="android" href="#"><i class="fa-brands fa-android mr-1"></i>Android</a></li>
  82. </ul>
  83. </div>
  84. <div class="mb-3" id="apps-tab"></div>
  85. <!-- Step -->
  86. <div class="stepper" id="steps-container"></div>
  87. </div>
  88. </div>
  89. @endsection
  90. @section('layout_javascript')
  91. <script src="/assets/global/vendor/toastr/toastr.min.js"></script>
  92. <script src="/assets/global/js/Plugin/toastr.js"></script>
  93. <script src="/assets/custom/clipboardjs/clipboard.min.js"></script>
  94. <script>
  95. const createLocalizedGetter = (lang = "en") =>
  96. obj => obj?.[lang] ?? obj?.en;
  97. const trans = createLocalizedGetter(@json(app()->getLocale()));
  98. const sublink = @json($user->sub_url);
  99. const sublink_base64 = @json(base64url_encode($user->sub_url));
  100. let clients = {};
  101. const clipboard = new ClipboardJS(".mt-clipboard", {
  102. text: function() {
  103. return sublink;
  104. }
  105. });
  106. clipboard.on("success", function() {
  107. toastr.success('{{ trans('common.copy.success') }}');
  108. });
  109. clipboard.on("error", function() {
  110. toastr.error('{{ trans('common.copy.failed') }}');
  111. });
  112. async function loadClients() {
  113. try {
  114. const response = await fetch("/clients/app.json");
  115. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  116. return await response.json();
  117. } catch (error) {
  118. console.error("{{ trans('errors.report') }}", error);
  119. return {};
  120. }
  121. }
  122. loadClients().then(data => {
  123. clients = data;
  124. renderAppTabs();
  125. renderSteps();
  126. });
  127. let currentPlatform = "pc",
  128. currentAppIdx = 0;
  129. document.querySelectorAll(".nav-platforms .nav-link").forEach(function(tab, idx) {
  130. tab.onclick = function(e) {
  131. e.preventDefault();
  132. document.querySelectorAll(".nav-platforms .nav-link").forEach((el) => el.classList.remove("active"));
  133. tab.classList.add("active");
  134. currentPlatform = tab.dataset.platform;
  135. currentAppIdx = 0;
  136. renderAppTabs();
  137. renderSteps();
  138. };
  139. if (idx === 0) tab.classList.add("active");
  140. });
  141. function renderAppTabs() {
  142. const el = document.getElementById("apps-tab");
  143. el.innerHTML = "";
  144. const apps = clients[currentPlatform] || [];
  145. if (apps.length === 0) {
  146. el.innerHTML = "<span class=\"text-secondary\">{{ trans('user.subscribe.page.error.no_app') }}</span>";
  147. document.getElementById("steps-container").innerHTML = "";
  148. return;
  149. }
  150. apps.forEach(function(app, i) {
  151. const b = document.createElement("button");
  152. b.className = "btn btn-app btn-sm mr-1 mb-1" + (i === currentAppIdx ? " selected" : "");
  153. b.innerHTML = (app.isFeatured ? "<i class=\"fa fa-star text-warning\"></i> " : "") + app.name;
  154. b.onclick = function() {
  155. currentAppIdx = i;
  156. renderAppTabs();
  157. renderSteps();
  158. };
  159. el.appendChild(b);
  160. });
  161. }
  162. function renderSteps() {
  163. const app = (clients[currentPlatform] || [])[currentAppIdx];
  164. if (!app) {
  165. document.getElementById("steps-container").innerHTML = "";
  166. return;
  167. }
  168. const steps = [];
  169. if (app.installationStep) {
  170. steps.push({
  171. dot: "install",
  172. title: `{{ trans('common.download_item', ['attribute' => '${app.name}']) }}`,
  173. desc: trans(app.installationStep.description),
  174. btns: app.installationStep.buttons?.map((btn) => ({
  175. url: btn.buttonLink,
  176. txt: trans(btn.buttonText)
  177. })),
  178. ic: "fa fa-download"
  179. });
  180. }
  181. if (app.additionalBeforeAddSubscriptionStep) {
  182. steps.push({
  183. dot: "simple",
  184. title: trans(app.additionalBeforeAddSubscriptionStep.title),
  185. desc: trans(app.additionalBeforeAddSubscriptionStep.description),
  186. ic: "fa fa-language"
  187. });
  188. }
  189. if (app.addSubscriptionStep) {
  190. steps.push({
  191. dot: "sub",
  192. title: "{{ trans('admin.action.add_item', ['attribute' => trans('model.subscribe.attribute')]) }}",
  193. desc: trans(app.addSubscriptionStep.description),
  194. addSubBtn: app.urlScheme + (app.isNeedBase64Encoding ? sublink_base64 : sublink),
  195. ic: "fa fa-plus"
  196. });
  197. }
  198. if (app.additionalAfterAddSubscriptionStep) {
  199. steps.push({
  200. dot: "simple",
  201. title: trans(app.additionalAfterAddSubscriptionStep.title),
  202. desc: trans(app.additionalAfterAddSubscriptionStep.description),
  203. ic: "fa fa-comment"
  204. });
  205. }
  206. if (app.connectAndUseStep) {
  207. steps.push({
  208. dot: "last",
  209. title: "{{ trans('user.subscribe.page.connect') }}",
  210. desc: trans(app.connectAndUseStep.description),
  211. ic: "fa fa-bolt"
  212. });
  213. }
  214. let html = "";
  215. steps.forEach((step, idx) => {
  216. html += `
  217. <div class="step-item step-${step.dot}">
  218. <div class="step-dot"><i class="${step.ic || "fa fa-circle"}"></i></div>
  219. <div class="step-content">
  220. ${step.title ? `<div class="step-title text-white">${step.title || ""}</div>` : ""}
  221. ${step.desc ? `<div class="step-desc">${step.desc}</div>` : ""}
  222. ${step.btns && step.btns.length ? `<div class="btn-group flex-wrap mb-2 app-download">${step.btns.map((btn) => `<a href="${btn.url}" target="_blank" class="btn btn-outline-info btn-sm mr-2 mb-1">${btn.txt}</a>`).join("")}</div>` : ""}
  223. ${step.addSubBtn ? `<a class="btn addsub-btn" href="${step.addSubBtn}"><i class="fa fa-plus mr-2"></i>{{ trans('admin.action.add_item', ['attribute' => trans('model.subscribe.attribute')]) }}</a>` : ""}
  224. </div>
  225. </div>
  226. `;
  227. });
  228. document.getElementById("steps-container").innerHTML = html;
  229. }
  230. </script>
  231. @endsection