diff --git a/.env.development b/.env.development index f6fb6f0..f460cc1 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ # 接口 -VITE_API=https://api.imyeyu.dev \ No newline at end of file +VITE_API=https://api.imyeyu.dev diff --git a/.gitignore b/.gitignore index 970c1c2..a30009a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ +/.env.production +/.eslintrc-auto-import.json +/.claude +/CLAUDE.md +/AGENTS.md +/components.d.ts +/src/auto-imports.d.ts + + # Logs logs *.log @@ -22,10 +31,3 @@ dist-ssr *.njsproj *.sln *.sw? - -/.env.production -/.eslintrc-auto-import.json -/.claude -/CLAUDE.md -/components.d.ts -/src/auto-imports.d.ts diff --git a/src/api/ClipboardAPI.ts b/src/api/ClipboardAPI.ts new file mode 100644 index 0000000..278f931 --- /dev/null +++ b/src/api/ClipboardAPI.ts @@ -0,0 +1,68 @@ +import { axios } from "timi-web"; + +/** 共享剪切板请求 */ +interface ClipboardRequest { + content: string; +} + +/** 构建共享剪切板 SSE 地址 */ +function buildStreamUrl(id: string): string { + const baseUrl = axios.defaults.baseURL || ""; + const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; + return `${normalizedBaseUrl}/clipboard/stream/${id}`; +} + +/** + * 获取共享剪切板内容 + * + * @param id 会话 ID + */ +async function getContent(id: string): Promise { + // timi-web 响应拦截器已自动解包,直接返回内容 + return axios.get(`/clipboard/${id}`); +} + +/** + * 设置共享剪切板内容 + * + * @param id 会话 ID + * @param content 剪切板内容 + */ +async function setContent(id: string, content: string): Promise { + const payload: ClipboardRequest = { content }; + await axios.post(`/clipboard/${id}`, payload); +} + +/** + * 订阅共享剪切板实时更新 + * + * @param id 会话 ID + * @param onMessage 收到内容回调 + * @param onError 连接错误回调 + */ +function openStream(id: string, onMessage: (content: string) => void, onError?: () => void): EventSource { + const url = buildStreamUrl(id); + // 跨域 SSE 需要携带凭证 + const source = new EventSource(url, { withCredentials: true }); + source.addEventListener("open", () => { + console.debug("[Clipboard] SSE 连接已建立"); + }); + source.addEventListener("clipboard", (event) => { + const message = event as MessageEvent; + onMessage(String(message.data ?? "")); + }); + source.addEventListener("error", (event) => { + console.debug("[Clipboard] SSE 连接错误", event); + source.close(); + if (onError) { + onError(); + } + }); + return source; +} + +export default { + getContent, + setContent, + openStream +}; diff --git a/src/components/SharedClipboard.vue b/src/components/SharedClipboard.vue new file mode 100644 index 0000000..8ce8033 --- /dev/null +++ b/src/components/SharedClipboard.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/views/Home.vue b/src/views/Home.vue index 7816569..739d1d8 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -15,12 +15,15 @@
-
- -
-
- - + +
+
+ +
+
+ + +
@@ -30,6 +33,7 @@ import FileUpload from "@/components/FileUpload.vue"; import FileDownload from "@/components/FileDownload.vue"; import FileHistoryList from "@/components/FileHistoryList.vue"; +import SharedClipboard from "@/components/SharedClipboard.vue"; import { useFileHistoryStore } from "@/store/fileHistory.ts"; import { SettingMapper, Time } from "../../../timi-web"; @@ -63,19 +67,25 @@ onMounted(() => setInterval(fileHistoryStore.cleanExpiredFiles, Time.S)); display: flex; border-top: var(--tui-border); border-bottom: var(--tui-border); + flex-direction: column; - .panel { - flex: 1; + .file { + display: flex; + border-top: var(--tui-border); - &:first-child { - border-right: var(--tui-border); + .panel { + flex: 1; + + &:first-child { + border-right: var(--tui-border); + } } } } } @media (max-width: 960px) { - .home .content { + .home .content .file { flex-direction: column; .panel:first-child {