mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.0.3
This commit is contained in:
37
README.md
37
README.md
@@ -1,26 +1,47 @@
|
|||||||
# desktop
|
# LanMontainDesktop
|
||||||
|
|
||||||
An Electron application with React and TypeScript
|
一个使用 Electron 打包的桌面应用:前端采用 Vue 3(Renderer),主进程内置 Elysia.js 作为本地后端服务(Main)。
|
||||||
|
|
||||||
## Recommended IDE Setup
|
## 技术栈
|
||||||
|
|
||||||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
- Electron + electron-vite(主进程/构建/开发)
|
||||||
|
- Vue 3 + Vite + TypeScript(渲染进程 UI)
|
||||||
|
- Elysia.js + @elysiajs/node(主进程内的本地后端 API)
|
||||||
|
|
||||||
## Project Setup
|
## 架构说明
|
||||||
|
|
||||||
### Install
|
这个项目不是传统意义上“浏览器前端 + 远程后端”的部署形态,而是:
|
||||||
|
|
||||||
|
- 主进程(Electron Main)负责创建窗口,并启动 Elysia.js(HTTP Server 绑定到 127.0.0.1 的随机端口)。
|
||||||
|
- 预加载(Preload)通过 `ipcRenderer.invoke('eiysia:request', ...)` 把“类 HTTP 请求”转发到主进程里的 Elysia 路由。
|
||||||
|
- 渲染进程(Vue 3 Renderer)通过 `window.api.call({ method, path, body })` 调用后端接口(例如 `/apps/list`、`/apps/launch`、`/open/external`)。
|
||||||
|
|
||||||
|
## 目录结构(关键)
|
||||||
|
|
||||||
|
- `src/main/`:Electron 主进程入口(创建窗口、启动 Elysia 服务)
|
||||||
|
- `src/preload/`:Preload 桥接层(暴露 `window.api`)
|
||||||
|
- `src/renderer/`:Vue 3 渲染进程(UI 与交互)
|
||||||
|
- `src/eiysia/`:Elysia.js “后端”路由与启动逻辑
|
||||||
|
|
||||||
|
## 推荐 IDE
|
||||||
|
|
||||||
|
- VSCode + Volar(Vue Language Features)+ ESLint + Prettier
|
||||||
|
|
||||||
|
## 开发与构建
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ pnpm install
|
$ pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ pnpm dev
|
$ pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build
|
### 构建
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For windows
|
# For windows
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
class="deskTile touchButton"
|
class="deskTile touchButton"
|
||||||
:class="[
|
:class="[
|
||||||
tile.variant ? `deskTile--${tile.variant}` : '',
|
tile.variant ? `deskTile--${tile.variant}` : '',
|
||||||
tile.id === 'writingPanel' ? 'deskTile--panel' : ''
|
(tile.id === 'writingPanel' || tile.id === 'notesPanel') ? 'deskTile--panel' : '',
|
||||||
|
tile.id === 'notesPanel' ? 'deskTile--notes' : ''
|
||||||
]"
|
]"
|
||||||
:style="{
|
:style="{
|
||||||
gridColumn: `${tile.col} / span ${tile.colSpan}`,
|
gridColumn: `${tile.col} / span ${tile.colSpan}`,
|
||||||
@@ -54,6 +55,21 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="tile.id === 'notesPanel'">
|
||||||
|
<div class="notesWidget">
|
||||||
|
<div class="notesHeader">
|
||||||
|
<div class="notesHeaderTitle">作业版</div>
|
||||||
|
<button class="notesAddButton touchButton" type="button" @pointerup="addNote">
|
||||||
|
新建
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="notesGrid">
|
||||||
|
<div v-for="note in notes" :key="note.id" class="noteCard">
|
||||||
|
<textarea v-model="note.text" class="noteTextarea" placeholder="写点什么..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="deskTileRow">
|
<div class="deskTileRow">
|
||||||
<svg
|
<svg
|
||||||
@@ -73,18 +89,6 @@
|
|||||||
</component>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="notesPanel">
|
|
||||||
<div class="notesHeader">
|
|
||||||
<div class="notesHeaderTitle">作业版</div>
|
|
||||||
<button class="notesAddButton touchButton" type="button" @pointerup="addNote">新建</button>
|
|
||||||
</div>
|
|
||||||
<div class="notesGrid">
|
|
||||||
<div v-for="note in notes" :key="note.id" class="noteCard">
|
|
||||||
<textarea v-model="note.text" class="noteTextarea" placeholder="写点什么..." />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -243,7 +247,7 @@
|
|||||||
<path fill="currentColor" :d="iconPath(w.icon)" />
|
<path fill="currentColor" :d="iconPath(w.icon)" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="homeWidgetCardTitle">{{ w.title }}</div>
|
<div class="homeWidgetCardTitle">{{ w.title }}</div>
|
||||||
<div class="homeWidgetCardMeta">{{ w.size === '2x4' ? '2×4' : w.size === '4x2' ? '4×2' : '2×2' }}</div>
|
<div class="homeWidgetCardMeta">{{ w.size }}</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -271,8 +275,8 @@
|
|||||||
<div class="settingsSection">
|
<div class="settingsSection">
|
||||||
<div class="settingsSectionTitle">详细设置:{{ widgetTitle(homeEditSelectedId) }}</div>
|
<div class="settingsSectionTitle">详细设置:{{ widgetTitle(homeEditSelectedId) }}</div>
|
||||||
<div class="settingsConfigArea">
|
<div class="settingsConfigArea">
|
||||||
<template v-if="homeEditSelectedId === 'writingPanel'">
|
<template v-if="homeEditSelectedId === 'writingPanel' || homeEditSelectedId === 'notesPanel'">
|
||||||
<div v-for="a in writingActions" :key="a.id" class="settingsSubAction">
|
<div v-if="homeEditSelectedId === 'writingPanel'" v-for="a in writingActions" :key="a.id" class="settingsSubAction">
|
||||||
<div class="settingsSubActionTitle">{{ a.label }}</div>
|
<div class="settingsSubActionTitle">{{ a.label }}</div>
|
||||||
<select
|
<select
|
||||||
class="settingsSelect touchButton"
|
class="settingsSelect touchButton"
|
||||||
@@ -293,6 +297,9 @@
|
|||||||
清空
|
清空
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="settingsSingleActionRow">
|
||||||
|
<div class="settingsActionHint">作业版目前不支持自定义点击行为。</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="settingsSingleActionRow">
|
<div class="settingsSingleActionRow">
|
||||||
@@ -392,7 +399,7 @@ const notes = ref<Note[]>([])
|
|||||||
|
|
||||||
type ActionKind = 'app' | 'url'
|
type ActionKind = 'app' | 'url'
|
||||||
type ActionConfig = { kind: ActionKind; target: string }
|
type ActionConfig = { kind: ActionKind; target: string }
|
||||||
type WidgetSize = '2x2' | '4x2' | '2x4'
|
type WidgetSize = '1x1' | '2x1' | '1x2' | '2x2' | '4x2' | '2x4' | '4x4' | '8x4'
|
||||||
type TileIcon = 'pen' | 'folder' | 'camera' | 'globe' | 'apps' | 'note' | 'doc' | 'comment'
|
type TileIcon = 'pen' | 'folder' | 'camera' | 'globe' | 'apps' | 'note' | 'doc' | 'comment'
|
||||||
type DesktopTile = {
|
type DesktopTile = {
|
||||||
id: string
|
id: string
|
||||||
@@ -456,19 +463,20 @@ let pageSwipeStartMainPage: MainPage = 'home'
|
|||||||
let suppressTapUntil = 0
|
let suppressTapUntil = 0
|
||||||
|
|
||||||
const availableWidgets: DesktopTile[] = [
|
const availableWidgets: DesktopTile[] = [
|
||||||
{ id: 'writingPanel', title: '书写', size: '2x4', icon: 'pen', clickable: false, variant: 'soft' },
|
{ id: 'writingPanel', title: '书写', size: '2x4', icon: 'pen', clickable: false, variant: 'accent' },
|
||||||
|
{ id: 'notesPanel', title: '作业版', size: '4x4', icon: 'note', clickable: false, variant: 'soft' },
|
||||||
{
|
{
|
||||||
id: 'whiteboard',
|
id: 'whiteboard',
|
||||||
title: '白板书写',
|
title: '白板',
|
||||||
size: '2x2',
|
size: '2x2',
|
||||||
icon: 'pen',
|
icon: 'doc',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
onActivate: () => void activateAction('whiteboard'),
|
onActivate: () => void activateAction('whiteboard'),
|
||||||
variant: 'soft'
|
variant: 'soft'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fileManager',
|
id: 'fileManager',
|
||||||
title: '文件管理',
|
title: '文件',
|
||||||
size: '2x2',
|
size: '2x2',
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
@@ -476,7 +484,7 @@ const availableWidgets: DesktopTile[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'visualPresenter',
|
id: 'visualPresenter',
|
||||||
title: '视频展台',
|
title: '展台',
|
||||||
size: '2x2',
|
size: '2x2',
|
||||||
icon: 'camera',
|
icon: 'camera',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
@@ -492,18 +500,59 @@ const availableWidgets: DesktopTile[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'moreApps',
|
id: 'moreApps',
|
||||||
title: '更多应用',
|
title: '全部应用',
|
||||||
size: '2x2',
|
size: '2x2',
|
||||||
icon: 'apps',
|
icon: 'apps',
|
||||||
clickable: true,
|
clickable: true,
|
||||||
onActivate: () => void activateAction('moreApps', () => void openApps()),
|
onActivate: () => void activateAction('moreApps', () => void openApps()),
|
||||||
variant: 'accent'
|
variant: 'accent'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'calculator',
|
||||||
|
title: '计算器',
|
||||||
|
size: '1x1',
|
||||||
|
icon: 'apps',
|
||||||
|
clickable: true,
|
||||||
|
onActivate: () => void activateAction('calculator')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'calendar',
|
||||||
|
title: '日历',
|
||||||
|
size: '2x1',
|
||||||
|
icon: 'apps',
|
||||||
|
clickable: true,
|
||||||
|
onActivate: () => void activateAction('calendar')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'timer',
|
||||||
|
title: '计时器',
|
||||||
|
size: '1x1',
|
||||||
|
icon: 'apps',
|
||||||
|
clickable: true,
|
||||||
|
onActivate: () => void activateAction('timer')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'camera',
|
||||||
|
title: '相机',
|
||||||
|
size: '1x1',
|
||||||
|
icon: 'camera',
|
||||||
|
clickable: true,
|
||||||
|
onActivate: () => void activateAction('camera')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const widgetsOrder = ref<string[]>(['writingPanel', 'whiteboard', 'fileManager', 'visualPresenter', 'browser', 'moreApps'])
|
const widgetsOrder = ref<string[]>([
|
||||||
|
'writingPanel',
|
||||||
|
'notesPanel',
|
||||||
|
'whiteboard',
|
||||||
|
'fileManager',
|
||||||
|
'visualPresenter',
|
||||||
|
'browser',
|
||||||
|
'moreApps'
|
||||||
|
])
|
||||||
const widgetsEnabled = ref<Record<string, boolean>>({
|
const widgetsEnabled = ref<Record<string, boolean>>({
|
||||||
writingPanel: true,
|
writingPanel: true,
|
||||||
|
notesPanel: true,
|
||||||
whiteboard: true,
|
whiteboard: true,
|
||||||
fileManager: true,
|
fileManager: true,
|
||||||
visualPresenter: true,
|
visualPresenter: true,
|
||||||
@@ -536,13 +585,36 @@ const enabledWidgets = computed<DesktopTile[]>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const placedDesktopTiles = computed<PlacedDesktopTile[]>(() => {
|
const placedDesktopTiles = computed<PlacedDesktopTile[]>(() => {
|
||||||
const cols = 4
|
const cols = 8
|
||||||
const rows = 4
|
const rows = 5
|
||||||
const occupied = Array.from({ length: rows }, () => Array.from({ length: cols }, () => false))
|
const occupied = Array.from({ length: rows }, () => Array.from({ length: cols }, () => false))
|
||||||
|
|
||||||
const tryPlace = (tile: DesktopTile): PlacedDesktopTile | null => {
|
const tryPlace = (tile: DesktopTile): PlacedDesktopTile | null => {
|
||||||
const rowSpan = tile.size === '2x4' ? 4 : 2
|
let rowSpan = 1
|
||||||
const colSpan = tile.size === '4x2' ? 4 : 2
|
let colSpan = 1
|
||||||
|
if (tile.size === '8x4') {
|
||||||
|
rowSpan = 4
|
||||||
|
colSpan = 8
|
||||||
|
} else if (tile.size === '4x4') {
|
||||||
|
rowSpan = 4
|
||||||
|
colSpan = 4
|
||||||
|
} else if (tile.size === '2x4') {
|
||||||
|
rowSpan = 4
|
||||||
|
colSpan = 2
|
||||||
|
} else if (tile.size === '4x2') {
|
||||||
|
rowSpan = 2
|
||||||
|
colSpan = 4
|
||||||
|
} else if (tile.size === '2x2') {
|
||||||
|
rowSpan = 2
|
||||||
|
colSpan = 2
|
||||||
|
} else if (tile.size === '2x1') {
|
||||||
|
rowSpan = 1
|
||||||
|
colSpan = 2
|
||||||
|
} else if (tile.size === '1x2') {
|
||||||
|
rowSpan = 2
|
||||||
|
colSpan = 1
|
||||||
|
}
|
||||||
|
|
||||||
for (let r = 1; r <= rows - rowSpan + 1; r += 1) {
|
for (let r = 1; r <= rows - rowSpan + 1; r += 1) {
|
||||||
for (let c = 1; c <= cols - colSpan + 1; c += 1) {
|
for (let c = 1; c <= cols - colSpan + 1; c += 1) {
|
||||||
let ok = true
|
let ok = true
|
||||||
@@ -579,7 +651,7 @@ const placedWidgetIds = computed(() => new Set(placedDesktopTiles.value.map((t)
|
|||||||
|
|
||||||
const settingsWidgets = computed(() => {
|
const settingsWidgets = computed(() => {
|
||||||
return widgetsInOrder.value.map((w) => {
|
return widgetsInOrder.value.map((w) => {
|
||||||
const sizeLabel = w.size === '2x4' ? '2×4' : w.size === '4x2' ? '4×2' : '2×2'
|
const sizeLabel = w.size
|
||||||
const enabled = widgetsEnabled.value[w.id] !== false
|
const enabled = widgetsEnabled.value[w.id] !== false
|
||||||
const visible = placedWidgetIds.value.has(w.id)
|
const visible = placedWidgetIds.value.has(w.id)
|
||||||
return { id: w.id, title: w.title, sizeLabel, enabled, visible }
|
return { id: w.id, title: w.title, sizeLabel, enabled, visible }
|
||||||
@@ -931,9 +1003,18 @@ const handlePagePointerCancel = (event: PointerEvent): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const restoreDefaultWidgets = (): void => {
|
const restoreDefaultWidgets = (): void => {
|
||||||
widgetsOrder.value = ['writingPanel', 'whiteboard', 'fileManager', 'visualPresenter', 'browser', 'moreApps']
|
widgetsOrder.value = [
|
||||||
|
'writingPanel',
|
||||||
|
'notesPanel',
|
||||||
|
'whiteboard',
|
||||||
|
'fileManager',
|
||||||
|
'visualPresenter',
|
||||||
|
'browser',
|
||||||
|
'moreApps'
|
||||||
|
]
|
||||||
widgetsEnabled.value = {
|
widgetsEnabled.value = {
|
||||||
writingPanel: true,
|
writingPanel: true,
|
||||||
|
notesPanel: true,
|
||||||
whiteboard: true,
|
whiteboard: true,
|
||||||
fileManager: true,
|
fileManager: true,
|
||||||
visualPresenter: true,
|
visualPresenter: true,
|
||||||
|
|||||||
@@ -55,19 +55,7 @@ body {
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcher {
|
/* 移除旧的 .notesPanel 和 .launcher 相关样式,它们已不再使用 */
|
||||||
position: fixed;
|
|
||||||
left: 16px;
|
|
||||||
top: 84px;
|
|
||||||
width: 520px;
|
|
||||||
height: calc(100vh - 168px);
|
|
||||||
padding: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.28);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
border-radius: 14px;
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home {
|
.home {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -81,20 +69,21 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desktopGridShell {
|
.desktopGridShell {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
top: 88px;
|
top: 88px;
|
||||||
bottom: 96px;
|
bottom: 96px;
|
||||||
right: calc(50vw + 8px);
|
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktopGrid {
|
.desktopGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
grid-template-columns: repeat(8, minmax(100px, 1fr));
|
||||||
grid-template-rows: repeat(4, minmax(120px, 1fr));
|
grid-template-rows: repeat(5, minmax(100px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +93,7 @@ body {
|
|||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
background: rgba(0, 0, 0, 0.25);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
color: var(--ev-c-text-1);
|
color: var(--ev-c-text-1);
|
||||||
padding: 14px;
|
padding: 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -112,6 +101,50 @@ body {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deskTile--notes {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 48px 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget .notesHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget .notesHeaderTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget .notesAddButton {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget .notesGrid {
|
||||||
|
padding: 10px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
|
grid-auto-rows: 140px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesWidget .noteCard {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.deskTile {
|
button.deskTile {
|
||||||
@@ -143,22 +176,75 @@ button.deskTile {
|
|||||||
|
|
||||||
.deskTileRow {
|
.deskTileRow {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deskTileIcon {
|
.deskTileIcon {
|
||||||
width: 34px;
|
width: 28px;
|
||||||
height: 34px;
|
height: 28px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deskTile:active .deskTileIcon {
|
||||||
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.deskTileText {
|
.deskTileText {
|
||||||
font-size: 20px;
|
font-size: 13px;
|
||||||
line-height: 24px;
|
line-height: 1.2;
|
||||||
font-weight: 900;
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 针对较宽组件的特殊布局 (如 2x1, 4x2) */
|
||||||
|
.deskTile[style*="span 2"] .deskTileRow,
|
||||||
|
.deskTile[style*="span 4"] .deskTileRow,
|
||||||
|
.deskTile[style*="span 8"] .deskTileRow {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 16px;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deskTile[style*="span 2"] .deskTileIcon,
|
||||||
|
.deskTile[style*="span 4"] .deskTileIcon,
|
||||||
|
.deskTile[style*="span 8"] .deskTileIcon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deskTile[style*="span 2"] .deskTileText,
|
||||||
|
.deskTile[style*="span 4"] .deskTileText,
|
||||||
|
.deskTile[style*="span 8"] .deskTileText {
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: left;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 针对较高组件的特殊布局 (如 1x2, 2x4) */
|
||||||
|
.deskTile[style*="grid-row: span 2"] .deskTileRow,
|
||||||
|
.deskTile[style*="grid-row: span 4"] .deskTileRow {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 针对 1x1 极小组件的微调 */
|
||||||
|
.deskTile[style*="span 1 / span 1"] .deskTileText {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deskTile--panel {
|
.deskTile--panel {
|
||||||
@@ -176,8 +262,14 @@ button.deskTile {
|
|||||||
.writingPanelButtons {
|
.writingPanelButtons {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writingPanelButtons::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.writingActionButton {
|
.writingActionButton {
|
||||||
@@ -226,107 +318,77 @@ button.deskTile {
|
|||||||
background: rgba(0, 0, 0, 0.18);
|
background: rgba(0, 0, 0, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesPanel {
|
/* 移除旧的绝对定位 notesPanel */
|
||||||
position: fixed;
|
/* .notesPanel {...} 已被集成到 grid 中的 .deskTile--notes */
|
||||||
top: 88px;
|
|
||||||
right: 16px;
|
.notesWidget {
|
||||||
bottom: 96px;
|
width: 100%;
|
||||||
left: calc(50vw + 8px);
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.28);
|
display: flex;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
flex-direction: column;
|
||||||
border-radius: 14px;
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: grid;
|
background: rgba(0, 0, 0, 0.1);
|
||||||
grid-template-rows: 56px 1fr;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesHeader {
|
.notesWidget .notesHeader {
|
||||||
|
flex: 0 0 48px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 14px;
|
padding: 0 12px;
|
||||||
font-size: 18px;
|
background: rgba(255, 255, 255, 0.05);
|
||||||
line-height: 22px;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
font-weight: 800;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesHeaderTitle {
|
.notesWidget .notesHeaderTitle {
|
||||||
font-size: 18px;
|
|
||||||
line-height: 22px;
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notesAddButton {
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
color: var(--ev-c-text-1);
|
|
||||||
background: rgba(0, 0, 0, 0.18);
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 18px;
|
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
height: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesAddButton:hover {
|
.notesWidget .notesAddButton {
|
||||||
border-color: rgba(255, 255, 255, 0.22);
|
height: 32px;
|
||||||
background: rgba(0, 0, 0, 0.32);
|
padding: 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesGrid {
|
.notesWidget .notesGrid {
|
||||||
height: 100%;
|
flex: 1;
|
||||||
overflow: auto;
|
padding: 10px;
|
||||||
padding: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
grid-auto-rows: 200px;
|
grid-auto-rows: 120px;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesGrid::-webkit-scrollbar {
|
.notesWidget .notesGrid::-webkit-scrollbar {
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteCard {
|
.notesWidget .noteCard {
|
||||||
border-radius: 14px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
background: rgba(0, 0, 0, 0.22);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteTextarea {
|
.notesWidget .noteTextarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
resize: none;
|
padding: 8px;
|
||||||
border: none;
|
font-size: 14px;
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--ev-c-text-1);
|
border: none;
|
||||||
padding: 12px;
|
color: white;
|
||||||
box-sizing: border-box;
|
resize: none;
|
||||||
font-size: 16px;
|
outline: none;
|
||||||
line-height: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
user-select: text;
|
|
||||||
scrollbar-width: none;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noteTextarea::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.openAppsButton {
|
.openAppsButton {
|
||||||
|
|||||||
Reference in New Issue
Block a user