Main.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <script setup lang="ts">
  2. import { ref, onMounted } from 'vue'
  3. import ContextMenu from '@imengyu/vue3-context-menu'
  4. import { HtmlUtils } from '@imengyu/imengyu-utils'
  5. import { AppItem } from '../model/App'
  6. import AppList from '../components/AppList.vue'
  7. import AppListItem from '../components/AppListItem.vue'
  8. import UpdateStatus from '../components/UpdateStatus.vue'
  9. import { APP_TITLE_KEY, DEFAULT_TITLE } from '../composeable/AppTitle.ts'
  10. // 状态管理
  11. const apps = ref<Record<string, AppItem[]>>({})
  12. const selectedApp = ref<AppItem | null>(null)
  13. const isFullScreen = ref(false)
  14. const isLoading = ref(false)
  15. const currentAspectRatio = ref('')
  16. const isLeftPanelOpen = ref(true)
  17. const menuBtn = ref<HTMLButtonElement>()
  18. // 切换左侧面板
  19. function toggleLeftPanel() {
  20. isLeftPanelOpen.value = !isLeftPanelOpen.value
  21. window.electronAPI.toggleChildSide(isLeftPanelOpen.value)
  22. }
  23. // 从本地加载应用数据
  24. async function loadApps() {
  25. try {
  26. const data = await window.electronAPI.loadAppsJson()
  27. if (Array.isArray(data)) {
  28. apps.value = {
  29. "未分组": data as AppItem[]
  30. };
  31. } else {
  32. apps.value = data
  33. }
  34. let id = 0;
  35. for (const group in apps.value) {
  36. for (const app of apps.value[group])
  37. app.id = ++id;
  38. }
  39. } catch (error) {
  40. console.error('Failed to load apps:', error)
  41. }
  42. }
  43. // 选择应用
  44. function selectApp(app: AppItem) {
  45. isLoading.value = true
  46. //if (app.openType === 'window') {
  47. // window.electronAPI.openWindow(app.url)
  48. //} else {
  49. selectedApp.value = app
  50. function parseAspectRatio(aspectRatio: string) {
  51. if (aspectRatio && aspectRatio.includes('/')) {
  52. const [width, height] = aspectRatio.split('/').map(Number)
  53. if (width > 0 && height > 0) {
  54. currentAspectRatio.value = `${width} : ${height}`
  55. return width / height
  56. }
  57. }
  58. return 0
  59. }
  60. document.title = app.title;
  61. window.electronAPI.loadChildUrl(app.url, app.keepScreenSize ? (
  62. parseAspectRatio(app.aspectRatio || '16:9')
  63. ) : 0, app.inputPassword ? {
  64. username: app.inputPassword.username,
  65. password: app.inputPassword.password,
  66. } : undefined)
  67. //}
  68. setTimeout(() => {
  69. isLoading.value = false
  70. }, 2000);
  71. }
  72. // 打开应用
  73. function openInSystem(app: AppItem) {
  74. window.electronAPI.openInSystem(app.url)
  75. }
  76. // 刷新应用
  77. function refreshApp(app: AppItem) {
  78. if (app.id === selectedApp.value?.id) {
  79. selectApp(app)
  80. } else {
  81. window.electronAPI.refresh()
  82. }
  83. }
  84. // 退出应用
  85. function exitApp() {
  86. window.electronAPI.exit()
  87. }
  88. // 切换全屏
  89. function toggleFullScreen() {
  90. isFullScreen.value = !isFullScreen.value
  91. window.electronAPI.toggleFullScreen(isFullScreen.value)
  92. }
  93. // 切换开发者工具
  94. function toggleDevTools() {
  95. window.electronAPI.toggleDevTools()
  96. }
  97. // 显示配置窗口
  98. function showConfig() {
  99. window.electronAPI.showConfig()
  100. }
  101. // 显示关于窗口
  102. function showAbout() {
  103. window.electronAPI.showAbout()
  104. }
  105. // 清除缓存
  106. async function clearCache() {
  107. await window.electronAPI.clearCache()
  108. }
  109. // 显示菜单
  110. function showMenu() {
  111. if (ContextMenu.isAnyContextMenuOpen())
  112. return
  113. ContextMenu.showContextMenu({
  114. x: HtmlUtils.getLeft(menuBtn.value!) + menuBtn.value!.offsetWidth,
  115. y: HtmlUtils.getTop(menuBtn.value!) + menuBtn.value!.offsetHeight + 8,
  116. direction: 'bl',
  117. items: [
  118. {
  119. label: '清除缓存',
  120. onClick: clearCache,
  121. },
  122. {
  123. label: '列表配置',
  124. onClick: showConfig,
  125. },
  126. {
  127. label: '全屏',
  128. shortcut: 'F11',
  129. onClick: toggleFullScreen,
  130. },
  131. {
  132. label: '开发者工具',
  133. shortcut: 'F12',
  134. onClick: toggleDevTools,
  135. },
  136. {
  137. label: '检查更新',
  138. divided: 'up',
  139. onClick: () => window.ipcRenderer.send('updater-check'),
  140. },
  141. {
  142. label: '关于',
  143. onClick: showAbout,
  144. },
  145. {
  146. label: '退出',
  147. shortcut: 'Alt+F4',
  148. onClick: exitApp,
  149. },
  150. ],
  151. })
  152. }
  153. // 初始化加载数据
  154. onMounted(() => {
  155. document.title = localStorage.getItem(APP_TITLE_KEY) || DEFAULT_TITLE
  156. loadApps()
  157. window.ipcRenderer.on('main-side-state-changed', (_event, isOpen) => {
  158. isLeftPanelOpen.value = isOpen
  159. });
  160. window.ipcRenderer.on('main-config-changed', (_event) => {
  161. loadApps();
  162. });
  163. })
  164. </script>
  165. <template>
  166. <div class="app-container">
  167. <!-- 左侧应用列表 -->
  168. <div v-if="isLeftPanelOpen" class="left-panel">
  169. <h2 class="panel-title">
  170. <div>
  171. <img src="/icon.svg" alt="应用" />
  172. 应用列表
  173. </div>
  174. <button ref="menuBtn" class="control-btn" @click="showMenu">
  175. <img src="../assets/menu.svg" alt="菜单" />
  176. </button>
  177. </h2>
  178. <UpdateStatus />
  179. <AppList>
  180. <div v-for="(group, title) in apps" :key="title">
  181. <h5>{{ title }}</h5>
  182. <AppListItem
  183. v-for="app in group"
  184. :key="app.id"
  185. :app="app"
  186. :activeAppId="selectedApp?.id"
  187. @selectApp="selectApp(app)"
  188. @openInSystem="openInSystem(app)"
  189. @refreshApp="refreshApp(app)"
  190. />
  191. </div>
  192. </AppList>
  193. <!-- 底部控制按钮 -->
  194. <div class="bottom-panel">
  195. <button class="control-btn" @click="toggleLeftPanel">
  196. <img src="../assets/left.svg" alt="折叠" />
  197. </button>
  198. </div>
  199. </div>
  200. <!-- 右侧iframe区域 -->
  201. <div class="right-panel">
  202. <div v-if="currentAspectRatio" class="aspect-ratio-info">
  203. 显示比例:{{ currentAspectRatio }}
  204. </div>
  205. </div>
  206. </div>
  207. </template>
  208. <style lang="scss">
  209. @use '../assets/colors.scss' as *;
  210. .app-container {
  211. display: flex;
  212. flex-direction: row;
  213. height: 100vh;
  214. width: 100vw;
  215. overflow: hidden;
  216. font-family: Arial, sans-serif;
  217. color: $text-color;
  218. background-color: #000;
  219. // 左侧面板
  220. .left-panel {
  221. position: relative;
  222. width: $panel-width;
  223. background: $bg-light;
  224. display: flex;
  225. flex-direction: column;
  226. border-right: 1px solid $bg-lighter;
  227. border-top-right-radius: 10px;
  228. border-bottom-right-radius: 10px;
  229. .panel-title {
  230. display: flex;
  231. flex-direction: row;
  232. align-items: center;
  233. justify-content: space-between;
  234. padding: 20px;
  235. margin: 0;
  236. font-size: 18px;
  237. color: $primary-color;
  238. font-weight: bold;
  239. > div {
  240. display: flex;
  241. flex-direction: row;
  242. align-items: center;
  243. align-content: center;
  244. img {
  245. width: 28px;
  246. height: 28px;
  247. margin-right: 8px;
  248. }
  249. }
  250. }
  251. }
  252. // 右侧面板
  253. .right-panel {
  254. position: relative;
  255. flex: 1;
  256. padding: 0;
  257. .aspect-ratio-info {
  258. position: absolute;
  259. right: 10px;
  260. top: 10px;
  261. padding: 10px;
  262. font-size: 14px;
  263. border-radius: 5px;
  264. color: $text-color;
  265. background-color: $bg-light;
  266. }
  267. }
  268. // 底部面板
  269. .bottom-panel {
  270. flex-shrink: 0;
  271. padding: 15px;
  272. display: flex;
  273. justify-content: flex-start;
  274. gap: 10px;
  275. }
  276. }
  277. .control-btn {
  278. position: relative;
  279. padding: 10px;
  280. border: none;;
  281. cursor: pointer;
  282. font-size: 14px;
  283. font-weight: bold;
  284. transition: all 0.3s ease;
  285. color: #000;
  286. border-radius: 6px;
  287. background-color: transparent;
  288. background-color: #efefef;
  289. img {
  290. width: 16px;
  291. height: 16px;
  292. }
  293. &:hover {
  294. background-color: #ddd;
  295. }
  296. }
  297. </style>