_layout.blade.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <!DOCTYPE html>
  2. <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta name="description"
  8. content="御宅云加速器,采用开源加速引擎,顶级IDC集群,全线高端刀片服务器!为网游用户解决延迟、掉线、卡机等问题,让你游戏更爽快!告别高延迟!外服加速72小时免费试用。海外直连专线,外服游戏加速效果业界顶尖!支持加速绝地求生、H1Z1、GTA5、CS:GO,以及LOL英雄联盟、DNF地下城与勇士、CF穿越火线、CSGO等上百款热门中外网游。">
  9. <meta name="keywords" content="御宅云 绅士世界 加速器 网游加速器 外服加速器 超快感 远征军 海外游戏加速 steam加速 免费加速器 游戏加速 H1Z1加速器 绝地求生加速器 大逃杀加速 绝地加速 GTA加速 CSGO加速">
  10. <meta name="author" content="绅士世界御宅云">
  11. <meta name="copyright" content="御宅云">
  12. <title>@yield('title')</title>
  13. <link href="{{ asset('favicon.ico') }}" rel="shortcut icon apple-touch-icon">
  14. <!-- 样式表/Stylesheets -->
  15. <link href="/assets/bundle/app.min.css" rel="stylesheet">
  16. <link href="https://fonts.loli.net" rel="preconnect" crossorigin>
  17. <link href="https://gstatic.loli.net" rel="preconnect" crossorigin>
  18. <link href="https://cdn.jsdelivr.net" rel="preconnect" crossorigin>
  19. <link href="https://cdn.jsdelivr.net/npm/flag-icons@7/css/flag-icons.min.css" rel="stylesheet">
  20. @yield('layout_css')
  21. <!-- 字体/Fonts -->
  22. <link href="/assets/global/fonts/web-icons/web-icons.min.css" rel="stylesheet">
  23. <link href="https://fonts.loli.net/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
  24. rel="stylesheet">
  25. <!-- Scripts -->
  26. <script src="/assets/global/vendor/breakpoints/breakpoints.min.js"></script>
  27. <script>
  28. Breakpoints();
  29. </script>
  30. @if (config('theme.skin'))
  31. <link id="skinStyle" href="/assets/css/skins/{{ config('theme.skin') }}.min.css" rel="stylesheet">
  32. @endif
  33. </head>
  34. <body class="animsition @yield('body_class')">
  35. @yield('layout_content')
  36. <!-- 核心/Core -->
  37. <script src="/assets/global/vendor/babel-external-helpers/babel-external-helpers.js"></script>
  38. <script src="/assets/global/vendor/jquery/jquery.min.js"></script>
  39. <script src="/assets/global/vendor/popper-js/umd/popper.min.js"></script>
  40. <script src="/assets/global/vendor/bootstrap/bootstrap.min.js"></script>
  41. <script src="/assets/global/vendor/animsition/animsition.min.js"></script>
  42. <script src="/assets/global/vendor/mousewheel/jquery.mousewheel.min.js"></script>
  43. <script src="/assets/global/vendor/asscrollbar/jquery-asScrollbar.min.js"></script>
  44. <script src="/assets/global/vendor/asscrollable/jquery-asScrollable.min.js"></script>
  45. <script src="/assets/global/vendor/ashoverscroll/jquery-asHoverScroll.min.js"></script>
  46. <!-- 插件/Plugins -->
  47. <script src="/assets/global/vendor/screenfull/screenfull.min.js"></script>
  48. <script src="/assets/global/vendor/slidepanel/jquery-slidePanel.min.js"></script>
  49. <!-- 脚本/Scripts -->
  50. <script src="/assets/global/js/Component.js"></script>
  51. <script src="/assets/global/js/Plugin.js"></script>
  52. <script src="/assets/global/js/Base.js"></script>
  53. <script src="/assets/global/js/Config.js"></script>
  54. <script src="/assets/js/Section/Menubar.js"></script>
  55. <script src="/assets/js/Section/Sidebar.js"></script>
  56. <script src="/assets/js/Section/PageAside.js"></script>
  57. <script src="/assets/js/Plugin/menu.js"></script>
  58. <!-- 设置/Config -->
  59. <script src="/assets/global/js/config/colors.js"></script>
  60. <script>
  61. Config.set("assets", "/assets");
  62. </script>
  63. <!-- 页面/Page -->
  64. <script src="/assets/js/Site.js"></script>
  65. <script src="/assets/global/js/Plugin/asscrollable.js"></script>
  66. <script src="/assets/global/js/Plugin/slidepanel.js"></script>
  67. <script>
  68. // 初始化国际化管理器
  69. window.i18n = function(key, fallback) {
  70. const keys = key.split('.');
  71. let value = window.i18n.translations || {};
  72. for (let i = 0; i < keys.length; i++) {
  73. if (value && typeof value === 'object' && value.hasOwnProperty(keys[i])) {
  74. value = value[keys[i]];
  75. } else {
  76. return fallback || key;
  77. }
  78. }
  79. return value || fallback || key;
  80. };
  81. // 初始化空的翻译对象
  82. window.i18n.translations = {};
  83. // 扩展翻译文本的方法
  84. window.i18n.extend = function(additionalTranslations) {
  85. window.i18n.translations = Object.assign({}, window.i18n.translations, additionalTranslations);
  86. };
  87. // Create and append link element to load the font CSS asynchronously
  88. const link = document.createElement("link");
  89. link.rel = 'stylesheet';
  90. link.href = 'https://fonts.loli.net/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap';
  91. document.head.appendChild(link);
  92. // Apply font to body after font has loaded
  93. link.onload = function() {
  94. document.body.style.fontFamily = 'Roboto, system-ui, sans-serif';
  95. };
  96. (function(document, window, $) {
  97. "use strict";
  98. const Site = window.Site;
  99. $(document).ready(function() {
  100. Site.run();
  101. });
  102. })(document, window, jQuery);
  103. @auth
  104. document.addEventListener("DOMContentLoaded", async function() {
  105. const avatarElements = Array.from(document.querySelectorAll("img[data-uid]"));
  106. let avatarData = JSON.parse(localStorage.getItem("avatarData")) || {};
  107. const fetchPromises = {};
  108. // Group img elements by uid
  109. const uidToElementsMap = groupElementsByUid(avatarElements);
  110. for (const [uid, elements] of Object.entries(uidToElementsMap)) {
  111. if (avatarData[uid]) {
  112. updateElementsSrc(elements, avatarData[uid]);
  113. } else if (!fetchPromises[uid]) {
  114. const {
  115. username,
  116. qq
  117. } = elements[0].dataset;
  118. fetchPromises[uid] = fetchAndCacheAvatar(uid, username, qq).then(imgUrl => {
  119. avatarData[uid] = imgUrl;
  120. localStorage.setItem("avatarData", JSON.stringify(avatarData));
  121. updateElementsSrc(elements, imgUrl);
  122. }).catch(error => {
  123. console.error(`Error fetching avatar for uid ${uid}:`, error);
  124. updateElementsSrc(elements, ""); // Or set to a default URL
  125. });
  126. }
  127. }
  128. });
  129. function groupElementsByUid(elements) {
  130. return elements.reduce((acc, el) => {
  131. const uid = el.dataset.uid;
  132. if (!acc[uid]) acc[uid] = [];
  133. acc[uid].push(el);
  134. return acc;
  135. }, {});
  136. }
  137. function updateElementsSrc(elements, src) {
  138. elements.forEach(el => el.src = src);
  139. }
  140. async function fetchAndCacheAvatar(uid, username, qq) {
  141. const response = await fetch(`{{ route('getAvatar') }}?username=${username}&qq=${qq}`);
  142. const url = await response.json();
  143. if (/@qq\.com/.test(username) || qq) {
  144. return url;
  145. }
  146. const imgResponse = await fetch(url);
  147. if (url === imgResponse.url) {
  148. const type = imgResponse.headers.get("Content-Type");
  149. const imgBlob = await imgResponse.blob();
  150. const base64String = await blobToBase64(imgBlob);
  151. return `data:${type};base64,${base64String}`;
  152. } else {
  153. return imgResponse.url;
  154. }
  155. }
  156. async function blobToBase64(blob) {
  157. return new Promise((resolve, reject) => {
  158. const reader = new FileReader();
  159. reader.onload = () => resolve(reader.result.split(",")[1]);
  160. reader.onerror = () => reject("Error converting blob to base64");
  161. reader.readAsDataURL(blob);
  162. });
  163. }
  164. @endauth
  165. function adjustPagination() {
  166. const paginations = document.querySelectorAll('.pagination');
  167. paginations.forEach(pagination => {
  168. const allItems = Array.from(pagination.querySelectorAll('.page-item'));
  169. // 1. 清理动态省略号并恢复显示以进行测量
  170. pagination.querySelectorAll('.dynamic-dot').forEach(el => el.remove());
  171. allItems.forEach(item => item.style.display = '');
  172. const totalWidthNeeded = pagination.scrollWidth;
  173. // 2. 寻找第一个宽度限制容器
  174. let parent = pagination.parentElement;
  175. while (parent && parent.tagName !== 'HTML' && parent.clientWidth === totalWidthNeeded) {
  176. // 如果父容器 clientWidth 明显小于当前 pagination 宽度,说明它就是那个限制框
  177. parent = parent.parentElement;
  178. }
  179. let limitWidth = parent.clientWidth;
  180. // 如果宽度足够(给 60px 缓冲),不需要调整
  181. if (totalWidthNeeded + 60 < limitWidth) return;
  182. // 3. 更加精准的估算容量
  183. // 取最后一页的宽度作为基准(通常三位数页码最宽,最具代表性)
  184. const itemWidth = (allItems[allItems.length - 2] || allItems[0]).offsetWidth || 40;
  185. // 计算最大可用槽位:(容器宽 / 单个宽) - 预留给省略号的 2 个位置
  186. let maxSlots = Math.max(5, Math.floor(limitWidth / itemWidth) - 2);
  187. // 4. 筛选页码(排除 Prev/Next)
  188. const numItems = allItems.filter(item => {
  189. const text = item.textContent.trim();
  190. // 排除上一页/下一页图标,只留数字
  191. return !isNaN(text) && text !== '' && !item.querySelector('[rel="prev"]') && !item.querySelector('[rel="next"]');
  192. });
  193. const activeItem = pagination.querySelector('.active');
  194. const firstPage = numItems[0];
  195. const lastPage = numItems[numItems.length - 1];
  196. const prevBtn = allItems[0];
  197. const nextBtn = allItems[allItems.length - 1];
  198. // 5. 构建必须显示的权重集合 (Set)
  199. let visibleSet = new Set([prevBtn, nextBtn, firstPage, lastPage, activeItem]);
  200. // 6. 权重填充:按距离 Active 远近填充剩余槽位
  201. let remaining = maxSlots - visibleSet.size;
  202. if (remaining > 0) {
  203. const activeIdx = allItems.indexOf(activeItem);
  204. const sortedNums = [...numItems]
  205. .filter(item => !visibleSet.has(item))
  206. .sort((a, b) => {
  207. const distA = Math.abs(allItems.indexOf(a) - activeIdx);
  208. const distB = Math.abs(allItems.indexOf(b) - activeIdx);
  209. if (distA === distB) return allItems.indexOf(a) - allItems.indexOf(b); // 距离相等时,左侧优先
  210. return distA - distB;
  211. });
  212. for (let i = 0; i < remaining && i < sortedNums.length; i++) {
  213. visibleSet.add(sortedNums[i]);
  214. }
  215. }
  216. // 7. 执行显示/隐藏
  217. allItems.forEach(item => {
  218. item.style.display = visibleSet.has(item) ? '' : 'none';
  219. });
  220. // 8. 补齐省略号 (检查索引断层)
  221. const finalVisible = allItems.filter(item => item.style.display !== 'none');
  222. for (let i = 0; i < finalVisible.length - 1; i++) {
  223. const currIdx = allItems.indexOf(finalVisible[i]);
  224. const nextIdx = allItems.indexOf(finalVisible[i + 1]);
  225. if (nextIdx - currIdx > 1) {
  226. let nativeDot = null;
  227. for (let j = currIdx + 1; j < nextIdx; j++) {
  228. if (allItems[j].textContent.includes('...')) {
  229. nativeDot = allItems[j];
  230. break;
  231. }
  232. }
  233. if (nativeDot) {
  234. nativeDot.style.display = '';
  235. } else {
  236. // 正确插入 HTML 节点的方法
  237. finalVisible[i + 1].insertAdjacentHTML('beforebegin',
  238. `<li class="page-item disabled dynamic-dot" aria-disabled="true"><span class="page-link">...</span></li>`);
  239. }
  240. }
  241. }
  242. });
  243. }
  244. let resizeTimer;
  245. window.addEventListener('resize', () => {
  246. clearTimeout(resizeTimer);
  247. resizeTimer = setTimeout(adjustPagination, 100);
  248. });
  249. // 在 DOM 加载完成后执行一次
  250. document.addEventListener('DOMContentLoaded', adjustPagination);
  251. </script>
  252. @yield('layout_javascript')
  253. </body>
  254. </html>