star.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. class PugePanel {
  2. constructor() {
  3. this.loadedCSS = new Set();
  4. this.init();
  5. }
  6. // 初始化方法
  7. init() {
  8. this.removeExistingStartBox();
  9. this.createPanel();
  10. this.createTriggerButton();
  11. }
  12. // 加载CSS,避免重复加载
  13. loadCSS(url) {
  14. if (this.loadedCSS.has(url)) return;
  15. this.loadedCSS.add(url);
  16. const link = document.createElement('link');
  17. link.rel = 'stylesheet';
  18. link.type = 'text/css';
  19. link.href = url;
  20. (document.head || document.documentElement).appendChild(link);
  21. }
  22. // 移除已存在的start-box
  23. removeExistingStartBox() {
  24. const existingBox = document.querySelector('.start-box');
  25. if (existingBox) {
  26. existingBox.remove();
  27. }
  28. }
  29. // 创建关闭按钮
  30. createCloseButton() {
  31. const closeBtn = document.createElement('div');
  32. closeBtn.className = 'puge-close-btn';
  33. closeBtn.innerHTML = '×';
  34. closeBtn.setAttribute('aria-label', '关闭面板');
  35. // 添加关闭按钮事件
  36. closeBtn.addEventListener('click', () => this.togglePanel(false));
  37. closeBtn.addEventListener('mouseenter', () => {
  38. closeBtn.style.color = '#ff4444';
  39. });
  40. closeBtn.addEventListener('mouseleave', () => {
  41. closeBtn.style.color = '#666';
  42. });
  43. return closeBtn;
  44. }
  45. // 创建标题栏
  46. createTitleBar() {
  47. const titleBar = document.createElement('div');
  48. titleBar.className = 'puge-title-bar';
  49. const titleText = document.createElement('span');
  50. titleText.className = 'puge-title';
  51. titleText.textContent = '功能面板';
  52. titleBar.appendChild(titleText);
  53. titleBar.appendChild(this.createCloseButton());
  54. return titleBar;
  55. }
  56. // 创建内容区域
  57. createContentArea() {
  58. const contentArea = document.createElement('div');
  59. contentArea.className = 'puge-box puge-content-area';
  60. // 根据屏幕方向设置高度
  61. const isLandscape = window.innerWidth > window.innerHeight;
  62. if (isLandscape) {
  63. contentArea.style.maxHeight = '60%';
  64. } else {
  65. contentArea.style.height = 'calc(100% - 40px)';
  66. }
  67. return contentArea;
  68. }
  69. // 创建主面板
  70. createPanel() {
  71. setTimeout(() => {
  72. if (document.querySelector('.start-box')) return;
  73. const panel = document.createElement('div');
  74. panel.className = 'start-box owo puge-panel';
  75. panel.setAttribute('aria-hidden', 'true');
  76. // 设置面板样式
  77. const isLandscape = window.innerWidth > window.innerHeight;
  78. Object.assign(panel.style, {
  79. display: 'none',
  80. zIndex: '9665',
  81. position: 'fixed',
  82. right: '0',
  83. bottom: '0',
  84. boxShadow: '1px 1px 9px rgba(204, 204, 204, 0.5)',
  85. backgroundColor: 'rgba(255, 255, 255, 0.95)',
  86. backdropFilter: 'blur(10px)',
  87. transition: 'opacity 0.3s ease, transform 0.3s ease'
  88. });
  89. if (isLandscape) {
  90. panel.style.width = '320px';
  91. panel.style.maxHeight = '60%';
  92. } else {
  93. panel.style.width = '100%';
  94. panel.style.height = '100%';
  95. }
  96. const contentArea = this.createContentArea()
  97. // 组装面板
  98. panel.appendChild(this.createTitleBar());
  99. panel.appendChild(contentArea);
  100. setTimeout(() => {
  101. document.body.appendChild(panel);
  102. // 触发创建回调
  103. if (window.pugeCreated) {
  104. window.pugeCreated(contentArea);
  105. window.pugeCreated = null;
  106. }
  107. }, 1000);
  108. }, 1000);
  109. }
  110. // 星星图标SVG
  111. get starIconSVG() {
  112. return `<svg class="puge-star-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="40" height="40">
  113. <path d="M622.7968 89.7024l60.1088 124.1088c15.9744 36.0448 52.0192 60.1088 92.16 66.048l134.144 20.0704c100.1472 14.0288 138.1376 136.192 68.096 208.2816l-98.0992 94.1056c-28.0576 28.0576-42.0864 68.096-36.0448 106.0864l22.016 134.144c15.9744 100.1472-86.1184 174.1824-176.2304 128.2048l-120.1152-62.0544c-36.0448-18.0224-76.0832-18.0224-112.128 0L334.4384 972.8c-90.112 46.08-192.2048-28.0576-176.2304-128.2048l22.016-134.144c7.9872-40.0384-6.0416-80.0768-36.0448-106.0864L46.08 508.2112c-72.0896-72.0896-32.0512-192.2048 68.096-208.2816l134.144-20.0704c40.0384-6.0416 74.1376-30.0032 92.16-66.048l60.1088-124.1088c50.0736-90.112 176.2304-90.112 222.208 0" fill="#FED44A"></path>
  114. <path d="M404.5824 89.7024l-60.1088 124.1088c-15.9744 36.0448-52.0192 60.1088-92.16 66.048l-134.144 20.0704c-62.0544 10.0352-102.0928 60.1088-104.1408 112.128 18.0224-98.0992 220.2624-22.016 312.4224-14.0288 94.1056 10.0352 120.1152-74.1376 120.1152-74.1376s20.0704-100.1472 44.032-226.304c18.0224-102.0928 86.1184-58.0608 110.1824-40.0384C544.768-0.4096 444.6208 11.6736 404.5824 89.7024" fill="#FFDE73"></path>
  115. <path d="M574.7712 784.5888c-74.1376-50.0736-140.1856 0-140.1856 0S362.496 848.6912 278.4256 928.768c-86.1184 80.0768-118.1696-33.9968-118.1696-33.9968 18.0224 72.0896 102.0928 116.1216 174.1824 76.0832l122.1632-62.0544c36.0448-18.0224 76.0832-18.0224 112.128 0L690.8928 972.8c56.1152 30.0032 118.1696 11.9808 152.1664-32.0512 2.048-7.9872 3.9936-14.0288 7.9872-20.0704-67.9936 66.1504-200.192-86.016-276.2752-136.0896" fill="#FEC54A"></path>
  116. <path d="M466.6368 103.7312c15.9744 3.9936 26.0096 18.0224 22.016 36.0448l-28.0576 126.1568c-3.9936 15.9744-18.0224 26.0096-36.0448 22.016-15.9744-3.9936-26.0096-18.0224-22.016-36.0448l28.0576-128.2048c3.9936-15.9744 20.0704-23.9616 36.0448-19.968" fill="#FFF2CA"></path>
  117. <path d="M375.3984 530.8416c-29.2864 0-53.0432-18.7392-53.0432-41.984 0-23.1424 23.7568-41.984 53.0432-41.984s53.0432 18.7392 53.0432 41.984c0 23.1424-23.7568 41.984-53.0432 41.984z m260.9152 0c-29.2864 0-53.0432-18.7392-53.0432-41.984 0-23.1424 23.7568-41.984 53.0432-41.984s53.0432 18.7392 53.0432 41.984c0 23.1424-23.7568 41.984-53.0432 41.984z m-190.464 74.6496c-2.56-3.6864-3.3792-8.192-2.56-12.4928 0.9216-4.3008 3.584-8.0896 7.2704-10.4448 7.8848-5.12 18.432-3.072 23.8592 4.608 12.288 17.7152 26.2144 25.8048 43.1104 25.8048s30.8224-8.0896 43.1104-25.8048c5.4272-7.68 15.9744-9.728 23.8592-4.608 3.7888 2.3552 6.4512 6.144 7.2704 10.4448 0.9216 4.3008 0 8.9088-2.56 12.4928-18.3296 26.5216-42.7008 40.6528-71.7824 40.6528-28.8768-0.1024-53.248-14.1312-71.5776-40.6528z" fill="#92410E"></path>
  118. </svg>`;
  119. }
  120. // 创建触发按钮
  121. createTriggerButton() {
  122. if (document.querySelector('.puge-menu')) return;
  123. const triggerBtn = document.createElement('div');
  124. triggerBtn.className = 'puge-menu puge-trigger-btn';
  125. triggerBtn.setAttribute('aria-label', '打开功能面板');
  126. triggerBtn.setAttribute('role', 'button');
  127. triggerBtn.tabIndex = 0;
  128. Object.assign(triggerBtn.style, {
  129. position: 'fixed',
  130. width: '40px',
  131. height: '40px',
  132. right: '10px',
  133. bottom: '120px',
  134. zIndex: '9666',
  135. cursor: 'pointer',
  136. transition: 'transform 0.3s ease'
  137. });
  138. triggerBtn.innerHTML = this.starIconSVG;
  139. // 添加点击事件
  140. triggerBtn.addEventListener('click', () => this.togglePanel(true));
  141. triggerBtn.addEventListener('keydown', (e) => {
  142. if (e.key === 'Enter' || e.key === ' ') {
  143. e.preventDefault();
  144. this.togglePanel(true);
  145. }
  146. });
  147. // 添加悬停效果
  148. triggerBtn.addEventListener('mouseenter', () => {
  149. triggerBtn.style.transform = 'scale(1.1)';
  150. });
  151. triggerBtn.addEventListener('mouseleave', () => {
  152. triggerBtn.style.transform = 'scale(1)';
  153. });
  154. setTimeout(() => {
  155. document.body.appendChild(triggerBtn);
  156. }, 1000);
  157. }
  158. // 切换面板显示/隐藏
  159. togglePanel(show) {
  160. const panel = document.querySelector('.start-box');
  161. const triggerBtn = document.querySelector('.puge-menu');
  162. if (!panel) return;
  163. const shouldShow = show ?? panel.style.display === 'none';
  164. if (shouldShow) {
  165. panel.style.display = 'block';
  166. panel.setAttribute('aria-hidden', 'false');
  167. panel.style.opacity = '0';
  168. panel.style.transform = 'translateY(20px)';
  169. // 动画显示
  170. requestAnimationFrame(() => {
  171. panel.style.opacity = '1';
  172. panel.style.transform = 'translateY(0)';
  173. });
  174. if (triggerBtn) {
  175. triggerBtn.style.display = 'none';
  176. }
  177. // 触发回调
  178. if (window.pugeCreated) {
  179. window.pugeCreated(panel.querySelector('.puge-content-area'));
  180. window.pugeCreated = null;
  181. }
  182. } else {
  183. panel.style.opacity = '0';
  184. panel.style.transform = 'translateY(20px)';
  185. setTimeout(() => {
  186. panel.style.display = 'none';
  187. panel.setAttribute('aria-hidden', 'true');
  188. if (triggerBtn) {
  189. triggerBtn.style.display = 'block';
  190. triggerBtn.style.opacity = '0';
  191. triggerBtn.style.transform = 'scale(0.8)';
  192. requestAnimationFrame(() => {
  193. triggerBtn.style.opacity = '1';
  194. triggerBtn.style.transform = 'scale(1)';
  195. });
  196. }
  197. }, 300);
  198. }
  199. }
  200. // 全局方法挂载
  201. static mountGlobalMethods() {
  202. window.owostart = () => {
  203. const instance = window.pugePanelInstance;
  204. if (instance) instance.togglePanel();
  205. };
  206. window.loadingDialog = () => {
  207. // 保留原接口,空实现
  208. return;
  209. };
  210. window.alertDialog = (text) => {
  211. if (window.logBox && window.logBox.add) {
  212. window.logBox.add(text);
  213. } else {
  214. console.log('Alert:', text);
  215. }
  216. };
  217. }
  218. }
  219. // 创建样式(可以移到CSS文件中)
  220. function addStyles() {
  221. const style = document.createElement('style');
  222. style.textContent = `
  223. .puge-title-bar {
  224. position: relative;
  225. height: 40px;
  226. line-height: 40px;
  227. background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
  228. border-bottom: 1px solid #e0e0e0;
  229. padding: 0 15px;
  230. font-weight: 600;
  231. font-size: 16px;
  232. color: #333;
  233. user-select: none;
  234. display: flex;
  235. justify-content: space-between;
  236. align-items: center;
  237. }
  238. .puge-title {
  239. font-size: 16px;
  240. color: #333;
  241. }
  242. .puge-close-btn {
  243. width: 30px;
  244. height: 40px;
  245. line-height: 40px;
  246. text-align: center;
  247. font-size: 24px;
  248. color: #666;
  249. cursor: pointer;
  250. transition: color 0.3s ease, transform 0.2s ease;
  251. border-radius: 4px;
  252. }
  253. .puge-close-btn:hover {
  254. color: #ff4444;
  255. transform: scale(1.1);
  256. }
  257. .puge-close-btn:active {
  258. transform: scale(0.95);
  259. }
  260. .puge-box {
  261. padding: 0 10px;
  262. overflow: auto;
  263. box-sizing: border-box;
  264. background-color: #fff;
  265. }
  266. .puge-trigger-btn {
  267. display: flex;
  268. align-items: center;
  269. justify-content: center;
  270. transition: all 0.3s ease !important;
  271. }
  272. .puge-star-icon {
  273. display: block;
  274. filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
  275. }
  276. .puge-login-box, .puge-box.puge-content-area {
  277. position: relative;
  278. padding-bottom: 10px;
  279. }
  280. @media (prefers-reduced-motion: reduce) {
  281. .puge-panel,
  282. .puge-trigger-btn,
  283. .puge-close-btn {
  284. transition: none !important;
  285. }
  286. }
  287. `;
  288. (document.head || document.documentElement).appendChild(style);
  289. }
  290. // 初始化
  291. (function() {
  292. // 防止重复初始化
  293. if (window.pugePanelInstance) return;
  294. // 添加样式
  295. addStyles();
  296. // 创建实例
  297. window.pugePanelInstance = new PugePanel();
  298. // 挂载全局方法
  299. PugePanel.mountGlobalMethods();
  300. })();