環境#
System:Windows 11
IDE:VSCode + Github Copilot Chat + Rust + Tauri 插件
Env:npm、Rust
已經通過npm create tauri-app@latest
建立項目框架
以下標題均為在 VSCode 的 copilot chat 中輸入的文本,然後對 Copilot 輸出的代碼進行更新替換。
將首頁修改為:左側為導航區域,以樹狀視圖,顯示不同功能的頁面鏈接;點擊左側導航區域的功能菜單項後,右側加載對應的功能頁面#
Note
這裡引用了 'App.tsx'
GitHub Copilot: 要實現這個功能,我們需要在App.tsx
中進行以下步驟:
- 創建一個導航欄組件,使用樹狀視圖顯示不同功能的頁面鏈接。
- 使用 React Router 來處理頁面導航。
- 在右側區域加載對應的功能頁面。
首先,安裝 React Router:
npm install react-router-dom
然後,修改App.tsx
文件如下:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
function Home() {
return <h2>首頁</h2>;
}
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
同時,更新 App.css
文件以適應新的佈局:
.container {
display: flex;
}
.sidebar {
width: 200px;
background-color: #f0f0f0;
padding: 10px;
}
.content {
flex-grow: 1;
padding: 10px;
}
這樣,左側將顯示導航欄,右側將根據導航欄的選擇加載相應的功能頁面。
Similar code found with 1 license type
創建獨立的 Home 組件#
GitHub Copilot: 好的,我們可以將 Home
組件提取到一個獨立的文件中。以下是步驟:
- 創建一個新的文件
Home.tsx
。 - 將
Home
組件的代碼移到Home.tsx
文件中。 - 在
App.tsx
中導入Home
組件。
首先,創建 Home.tsx
文件,並添加以下代碼:
// Home.tsx
import React from "react";
function Home() {
return <h2>首頁</h2>;
}
export default Home;
然後,修改 App.tsx
文件,導入 Home
組件:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,Home
組件就被提取到一個獨立的文件中了。
Similar code found with 1 license type
左側導航欄垂直拉伸到佔滿頁面#
Note
左側導航欄只佔據了包含文本的區域,因此希望能全部佔滿。
GitHub Copilot: 為了使左側導航欄垂直拉伸到佔滿頁面,我們需要調整 CSS 樣式。具體步驟如下:
- 修改
App.css
文件,確保導航欄佔滿頁面高度。 - 確保容器的高度也佔滿頁面。
以下是更新後的 App.css
文件:
/* App.css */
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
}
.container {
display: flex;
height: 100%;
}
.sidebar {
width: 200px;
background-color: #f0f0f0;
padding: 10px;
height: 100%;
}
.content {
flex-grow: 1;
padding: 10px;
}
確保 App.tsx
文件中包含以下代碼:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,左側導航欄將垂直拉伸佔滿頁面高度。
Similar code found with 1 license type
右側頁面和左側導航欄應該頂部對齊#
Note
出現右側內容區顯示在導航區的下方的情形,4,5,6 均為嘗試修正的 Promt,後來發現是 css 中.container 的樣式沒有完全替換,重新替換後正常顯示
GitHub Copilot: 為了確保右側頁面和左側導航欄頂部對齊,我們需要調整 CSS 樣式。具體步驟如下:
- 確保容器的高度佔滿頁面,並且導航欄和內容區域都從頂部開始對齊。
- 更新
App.css
文件以確保佈局正確。
以下是更新後的 App.css
文件:
/* App.css */
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
}
.container {
display: flex;
height: 100%;
align-items: flex-start; /* 確保子元素頂部對齊 */
}
.sidebar {
width: 200px;
background-color: #f0f0f0;
padding: 10px;
height: 100%;
}
.content {
flex-grow: 1;
padding: 10px;
}
確保 App.tsx
文件中包含以下代碼:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,右側頁面和左側導航欄將會頂部對齊。
Similar code found with 1 license type
.container 內的子元素按照左右佈局排列#
GitHub Copilot: 為了確保 .container
內的子元素按照左右佈局排列,我們需要調整 CSS 樣式。具體步驟如下:
- 確保
.container
使用flex
佈局,並且子元素按照左右排列。 - 移除重複的
.container
樣式定義。
以下是更新後的 App.css
文件:
/* App.css */
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
}
.container {
display: flex;
height: 100%;
align-items: flex-start; /* 確保子元素頂部對齊 */
}
.sidebar {
width: 200px;
background-color: #f0f0f0;
padding: 10px;
height: 100%;
}
.content {
flex-grow: 1;
padding: 10px;
}
確保 App.tsx
文件中包含以下代碼:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,.container
內的子元素將按照左右佈局排列,並且頂部對齊。
Similar code found with 1 license type
導航欄的背景顏色設置為系統背景色#
Note
系統主題是暗色,但默認背景色為白色,且這個代碼未實際生效,最後手動刪除了.sidebar
的背景色配置
GitHub Copilot: 為了將導航欄的背景顏色設置為系統背景色,可以使用 CSS 中的 background-color
屬性,並將其值設置為 background
系統顏色。以下是更新後的 App.css
文件:
/* App.css */
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
}
.container {
display: flex;
height: 100%;
align-items: flex-start; /* 確保子元素頂部對齊 */
}
.sidebar {
width: 200px;
background-color: var(--system-background-color, #f0f0f0); /* 使用系統背景色 */
padding: 10px;
height: 100%;
}
.content {
flex-grow: 1;
padding: 10px;
}
.logo {
height: 6em;
}
在上面的代碼中,var(--system-background-color, #f0f0f0)
使用了 CSS 變量來設置背景顏色。如果系統背景色未定義,則使用默認顏色 #f0f0f0
。你可以根據需要調整默認顏色。
將 Feature1 單獨放入 Features 子目錄下的獨立模塊中#
GitHub Copilot: 好的,我們可以將 Feature1
組件提取到一個獨立的文件中,並將其放入 Features
子目錄中。以下是步驟:
- 創建一個新的目錄
Features
。 - 在
Features
目錄中創建一個新的文件Feature1.tsx
。 - 將
Feature1
組件的代碼移到Feature1.tsx
文件中。 - 在
App.tsx
中導入Feature1
組件。
首先,創建 Features/Feature1.tsx
文件,並添加以下代碼:
// Features/Feature1.tsx
import React from "react";
function Feature1() {
return <h2>功能 1 頁面</h2>;
}
export default Feature1;
然後,修改
App.tsx
文件,導入
Feature1
組件:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,
Feature1
組件就被提取到 Features
子目錄中的獨立模塊中了。
Similar code found with 1 license type
同樣將 Feature2 移入獨立文件#
GitHub Copilot: 好的,我們可以將
Feature2
組件提取到一個獨立的文件中,並將其放入 Features
子目錄中。以下是步驟:
- 在
Features
目錄中創建一個新的文件
Feature2.tsx
。
2. 將
Feature2
組件的代碼移到
Feature2.tsx
文件中。
3. 在
App.tsx
中導入
Feature2
組件。
首先,創建 Features/Feature2.tsx
文件,並添加以下代碼:
// Features/Feature2.tsx
import React from "react";
function Feature2() {
return <h2>功能 2 頁面</h2>;
}
export default Feature2;
然後,修改
App.tsx
文件,導入
Feature2
組件:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,
Feature2
組件就被提取到 Features
子目錄中的獨立模塊中了。
Similar code found with 1 license type
頁面默認包含滾動條,將頁面高度調整到不出現滾動條#
Note
默認內容區出現了滾動條
GitHub Copilot: 為了確保頁面高度調整到不出現滾動條,我們需要確保容器的高度和內容的高度都正確設置。我們可以使用 overflow: hidden
來防止滾動條的出現,並確保內容區域的高度不會超過視口高度。
以下是更新後的
App.css
文件:
/* App.css */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden; /* 防止滾動條出現 */
}
.container {
display: flex;
height: 100%;
align-items: flex-start; /* 確保子元素頂部對齊 */
}
.sidebar {
width: 200px;
padding: 10px;
height: 100%;
background-color: var(--system-background-color, #f0f0f0); /* 使用系統背景色 */
}
.content {
flex-grow: 1;
padding: 10px;
height: 100%;
overflow: auto; /* 允許內容區域滾動 */
}
確保 App.tsx
文件中包含以下代碼:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,頁面的高度將調整到不出現滾動條,並且內容區域允許滾動。
Similar code found with 1 license type
greetMsg 的段落顯示在新的一行#
Note
這裡原因是為了保留示例中的 greet 交互,手動將代碼從 App.tsx 移到了 Home.tsc 中,但是<p>
標籤不生效,和文本輸出欄顯示在同一行。通過下一 Promt,以<div>
標籤來解決了問題。
GitHub Copilot: 為了確保
greetMsg
顯示在新的一行,我們可以將其放在一個段落 (<p>
) 標籤中。以下是更新後的
Home.tsx
文件:
// Home.tsx
import React from "react";
function Home({ greetMsg }: { greetMsg: string }) {
return (
<div>
<h2>首頁</h2>
<p>{greetMsg}</p>
</div>
);
}
export default Home;
同時,確保在 App.tsx
中傳遞
greetMsg
作為 Home
組件的屬性:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home greetMsg={greetMsg} />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,
greetMsg
將顯示在 Home
組件中的新的一行。
Similar code found with 1 license type
greetMsg 無須從 App 中傳入#
GitHub Copilot: 好的,我們可以在
Home
組件中直接定義 greetMsg
,而不需要從
App
中傳入。以下是更新後的
Home.tsx
文件:
// Home.tsx
import React, { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
function Home() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<div>
<h2>首頁</h2>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="輸入你的名字"
/>
<button onClick={greet}>問候</button>
<p>{greetMsg}</p>
</div>
);
}
export default Home;
同時,
App.tsx
文件不需要任何修改,因為 greetMsg
不再需要從
App
中傳入:
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";
function App() {
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/feature1">功能 1</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,greetMsg
將在
Home
組件中定義和使用。
Similar code found with 2 license types
將 Feature1 重命名為 ClipBoard#
GitHub Copilot: 好的,我們可以將
Feature1
組件重命名為 ClipBoard
。以下是步驟:
- 重命名文件
Feature1.tsx
為 Features/ClipBoard.tsx
。
2. 更新組件名稱為 ClipBoard
。
3. 在 App.tsx
中導入 ClipBoard
組件。
首先,重命名文件
Feature1.tsx
為 Features/ClipBoard.tsx
,並更新文件內容如下:
// Features/ClipBoard.tsx
import React from "react";
function ClipBoard() {
return <h2>剪貼板頁面</h2>;
}
export default ClipBoard;
然後,修改 App.tsx
文件,導入 ClipBoard
組件:
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import "./App.css";
import Home from "./Home";
import ClipBoard from "./Features/ClipBoard";
import Feature2 from "./Features/Feature2";
function App() {
return (
<Router>
<div className="container">
<nav className="sidebar">
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/clipboard">剪貼板</Link>
</li>
<li>
<Link to="/feature2">功能 2</Link>
</li>
</ul>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/clipboard" element={<ClipBoard />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>
</div>
</div>
</Router>
);
}
export default App;
這樣,
Feature1
組件就被重命名為 ClipBoard
並且在 App.tsx
中正確導入和使用了。
剪貼板頁面,顯示最近的剪貼板內容,按照時間倒序排列,最多顯示 100 條#
GitHub Copilot: 為了實現剪貼板頁面顯示最近的剪貼板內容,並按照時間倒序排列,最多顯示 100 條,我們需要以下步驟:
- 創建一個狀態來存儲剪貼板內容。
- 使用
useEffect
鉤子來模擬獲取剪貼板內容。 - 按時間倒序排列剪貼板內容,並限制顯示 100 條。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
interface ClipboardItem {
content: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
useEffect(() => {
// 模擬獲取剪貼板內容
const fetchClipboardItems = () => {
const items = [
{ content: "項目 1", timestamp: Date.now() - 1000 },
{ content: "項目 2", timestamp: Date.now() - 2000 },
// 添加更多模擬數據
];
setClipboardItems(items);
};
fetchClipboardItems();
}, []);
// 按時間倒序排列,並限制顯示100條
const sortedItems = clipboardItems
.sort((a, b) => b.timestamp - a.timestamp)
.slice(0, 100);
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{sortedItems.map((item, index) => (
<li key={index}>
<p>{item.content}</p>
<small>{new Date(item.timestamp).toLocaleString()}</small>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 定義了一個
ClipboardItem
接口來表示剪貼板內容項。 - 使用
useState
鉤子來存儲剪貼板內容。 - 使用
useEffect
鉤子來模擬獲取剪貼板內容,並將其存儲在狀態中。 - 按時間倒序排列剪貼板內容,並限制顯示 100 條。
- 在組件中渲染剪貼板內容列表。
你可以根據實際需求替換模擬數據部分,獲取真實的剪貼板內容。
定時每隔 1s 讀取一次剪貼板,如果存在新內容,則添加到剪貼板的內容列表中#
Note
這裡需要手動在src-tauri/capabilities/default.json
中添加剪貼板的讀寫權限,後續文件操作相同。同時 copilot 代碼中的import
還是 Tauri v1 的格式,需要手動調整為 v2 的格式。
GitHub Copilot: 為了實現定時每隔 1 秒讀取一次剪貼板,並在存在新內容時將其添加到剪貼板的內容列表中,我們可以使用 setInterval
來定時讀取剪貼板內容,並在
useEffect
中進行處理。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";
interface ClipboardItem {
content: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
setClipboardItems((prevItems) => [
{ content, timestamp: Date.now() },
...prevItems,
]);
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
// 按時間倒序排列,並限制顯示100條
const sortedItems = clipboardItems.slice(0, 100);
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{sortedItems.map((item, index) => (
<li key={index}>
<p>{item.content}</p>
<small>{new Date(item.timestamp).toLocaleString()}</small>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 使用
useState
鉤子來存儲剪貼板內容和最後一次讀取的內容。
2. 使用
useEffect
鉤子來設置一個定時器,每隔 1 秒讀取一次剪貼板內容。
3. 如果讀取到的新內容與最後一次讀取的內容不同,則將其添加到剪貼板內容列表中,並更新最後一次讀取的內容。
4. 按時間倒序排列剪貼板內容,並限制顯示 100 條。
5. 在組件中渲染剪貼板內容列表。
這樣,每隔 1 秒讀取一次剪貼板內容,並在存在新內容時將其添加到剪貼板的內容列表中。
將時間顯示在內容的上方#
GitHub Copilot: 為了將時間顯示在內容的上方,我們需要調整 ClipBoard.tsx
文件中的渲染順序。以下是更新後的代碼:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";
interface ClipboardItem {
content: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
setClipboardItems((prevItems) => [
{ content, timestamp: Date.now() },
...prevItems,
]);
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
// 按時間倒序排列,並限制顯示100條
const sortedItems = clipboardItems.slice(0, 100);
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{sortedItems.map((item, index) => (
<li key={index}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們將時間 (<small>{new Date(item.timestamp).toLocaleString()}</small>
) 放在內容 (<p>{item.content}</p>
) 的上方。這樣,時間將顯示在內容的上方。
每項內容,提供 “複製” 和 "刪除" 的文本鏈接,點擊複製時,重新複製到剪貼板,點擊刪除時,從列表中移除該項#
GitHub Copilot: 為了實現每項內容提供 “複製” 和 “刪除” 的文本鏈接,我們需要以下步驟:
- 添加 “複製” 和 “刪除” 按鈕。
- 實現複製功能,將內容重新複製到剪貼板。
- 實現刪除功能,從列表中移除該項。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
interface ClipboardItem {
content: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
setClipboardItems((prevItems) => [
{ content, timestamp: Date.now() },
...prevItems,
]);
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
// 按時間倒序排列,並限制顯示100條
const sortedItems = clipboardItems.slice(0, 100);
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{sortedItems.map((item, index) => (
<li key={index}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 添加了 “複製” 和 “刪除” 按鈕。
- 實現了
handleCopy
函數,將內容重新複製到剪貼板。 - 實現了
handleDelete
函數,從列表中移除該項。 - 在每個列表項中添加了 “複製” 和 “刪除” 鏈接,並綁定相應的事件處理函數。
這樣,每項內容將提供 “複製” 和 “刪除” 的文本鏈接,點擊複製時,重新複製到剪貼板,點擊刪除時,從列表中移除該項。
在列表中同時保存內容文本和內容的 md5 值,當一個新的內容,md5 值和已有的內容文本一致時,僅僅更新时间,而不是新增一條記錄#
Note
複製後會產生新的一條記錄,因此通過 md5 來區分,確保唯一性顯示。
GitHub Copilot: 為了在列表中同時保存內容文本和內容的 MD5 值,並在新內容的 MD5 值與已有內容文本一致時僅更新时间而不是新增一條記錄,我們需要以下步驟:
- 安裝
crypto-js
庫來計算 MD5 值。 - 修改
ClipboardItem
接口以包含 MD5 值。 - 在讀取剪貼板內容時計算 MD5 值,並檢查是否已有相同的 MD5 值。
首先,安裝 crypto-js
庫:
npm install crypto-js
然後,更新 ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
return prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
} else {
return [{ content, md5, timestamp: Date.now() }, ...prevItems];
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
// 按時間倒序排列,並限制顯示100條
const sortedItems = clipboardItems.slice(0, 100);
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{sortedItems.map((item, index) => (
<li key={index}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 安裝並導入了
crypto-js
庫來計算 MD5 值。 - 更新了
ClipboardItem
接口以包含md5
屬性。 - 在讀取剪貼板內容時計算 MD5 值,並檢查是否已有相同的 MD5 值。如果有相同的 MD5 值,則僅更新时间;否則,新增一條記錄。
相同內容更新时间後,列表要重新按時間排序#
GitHub Copilot: 為了確保在相同內容更新时间後,列表重新按時間排序,我們需要在更新时间戳後對列表進行排序。以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 在更新时间戳後,對列表進行排序。
- 確保在添加新內容時,列表也進行排序並限制顯示 100 條。
這樣,當相同內容更新时间後,列表將重新按時間排序。
重新排序後,頁面也要按照新的順序重新刷新顯示#
Note
排序後,頁面並未刷新,因此要求強制刷新。
GitHub Copilot: 為了確保在重新排序後頁面按照新的順序重新刷新顯示,我們需要確保在 setClipboardItems
中對列表進行排序,並且在渲染時使用排序後的列表。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們確保在 setClipboardItems
中對列表進行排序,並且在渲染時使用排序後的 clipboardItems
列表。這樣,當列表重新排序後,頁面將按照新的順序重新刷新顯示。
相鄰的兩行以不同的背景顏色進行區分#
Note
UI 優化。
GitHub Copilot: 為了實現相鄰的兩行以不同的背景顏色進行區分,我們可以使用 CSS 樣式來實現條紋效果。以下是更新後的
ClipBoard.tsx
文件和相應的 CSS 樣式:
首先,更新
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 導入 CSS 文件
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index} className={index % 2 === 0 ? "even" : "odd"}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
然後,創建
ClipBoard.css
文件,並添加以下樣式:
/* ClipBoard.css */
ul {
list-style-type: none;
padding: 0;
}
li.even {
background-color: #f9f9f9;
}
li.odd {
background-color: #e9e9e9;
}
li {
padding: 10px;
margin: 5px 0;
}
a {
margin-right: 10px;
}
在這個實現中,我們:
- 在
ClipBoard.tsx
文件中導入了
ClipBoard.css
文件。
2. 使用 className
屬性為相鄰的兩行添加不同的 CSS 類 (even
和 odd
)。
3. 在
ClipBoard.css
文件中定義了 even
和 odd
類的背景顏色。
這樣,相鄰的兩行將以不同的背景顏色進行區分。
不是相鄰的兩行,而是相鄰的兩個剪貼板內容以不同顏色區分,包括文本內容、時間和操作按鈕#
GitHub Copilot: 為了確保相鄰的兩個剪貼板內容以不同顏色區分,包括文本內容、時間和操作按鈕,我們可以使用 CSS 樣式來實現條紋效果。以下是更新後的
ClipBoard.tsx
文件和相應的 CSS 樣式:
首先,更新
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 導入 CSS 文件
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index} className={index % 2 === 0 ? "even" : "odd"}>
<small>{new Date(item.timestamp).toLocaleString()}</small>
<p>{item.content}</p>
<div>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</div>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
然後,創建或更新
ClipBoard.css
文件,並添加以下樣式:
/* ClipBoard.css */
ul {
list-style-type: none;
padding: 0;
}
li.even {
background-color: #f9f9f9;
}
li.odd {
background-color: #e9e9e9;
}
li {
padding: 10px;
margin: 5px 0;
}
a {
margin-right: 10px;
}
在這個實現中,我們:
- 在
ClipBoard.tsx
文件中導入了
ClipBoard.css
文件。
2. 使用
className
屬性為相鄰的兩個剪貼板內容添加不同的 CSS 類 (even
和 odd
)。
3. 在
ClipBoard.css
文件中定義了 even
和 odd
類的背景顏色。
這樣,相鄰的兩個剪貼板內容將以不同的背景顏色進行區分,包括文本內容、時間和操作按鈕。
內容的背景色調調整為暗色調,文本調整為對比度高的顏色#
GitHub Copilot: 為了將內容的背景色調調整為暗色調,並將文本調整為對比度高的顏色,我們可以更新
ClipBoard.css
文件中的樣式。以下是更新後的
ClipBoard.css
文件:
/* ClipBoard.css */
ul {
list-style-type: none;
padding: 0;
}
li.even {
background-color: #333; /* 暗色調背景 */
color: #fff; /* 高對比度文本顏色 */
}
li.odd {
background-color: #444; /* 暗色調背景 */
color: #fff; /* 高對比度文本顏色 */
}
li {
padding: 10px;
margin: 5px 0;
}
a {
margin-right: 10px; /* 高對比度鏈接顏色 */
}
a:hover {
text-decoration: underline;
}
在這個實現中,我們:
- 將
li.even
和li.odd
的背景顏色分別設置為#333
和#444
,以實現暗色調背景。 - 將
li.even
和li.odd
的文本顏色設置為#fff
,以實現高對比度文本顏色。 - 將鏈接的顏色設置為
#1e90ff
,以實現高對比度鏈接顏色,並在懸停時添加下划線。
這樣,內容的背景色調將調整為暗色調,文本將調整為對比度高的顏色。
將時間和操作按鈕合併到一行#
GitHub Copilot: 為了將時間和操作按鈕合併到一行,我們可以使用 CSS Flexbox 佈局來實現。以下是更新後的 ClipBoard.tsx
文件和相應的 CSS 樣式:
首先,更新 ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 導入 CSS 文件
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index} className={index % 2 === 0 ? "even" : "odd"}>
<div className="item-header">
<small>{new Date(item.timestamp).toLocaleString()}</small>
<div>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</div>
</div>
<p>{item.content}</p>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
然後,更新
ClipBoard.css
文件,並添加以下樣式:
/* ClipBoard.css */
ul {
list-style-type: none;
padding: 0;
}
li.even {
background-color: #333; /* 暗色調背景 */
color: #fff; /* 高對比度文本顏色 */
}
li.odd {
background-color: #444; /* 暗色調背景 */
color: #fff; /* 高對比度文本顏色 */
}
li {
padding: 10px;
margin: 5px 0;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
}
a {
margin-right: 10px; /* 高對比度鏈接顏色 */
}
a:hover {
text-decoration: underline;
}
在這個實現中,我們:
- 在
ClipBoard.tsx
文件中將時間和操作按鈕放在一個div
容器中,並添加item-header
類。 - 在
ClipBoard.css
文件中定義了 item-header
類,使用 Flexbox 佈局將時間和操作按鈕合併到一行,並在兩端對齊。
這樣,時間和操作按鈕將合併到一行,並且內容的背景色調調整為暗色調,文本調整為對比度高的顏色。
以 1 分鐘的間隔,定時將當前內容列表以及時間信息保存到 appdata 下的 history.json 文件中#
Note
持久化保存歷史記錄。這裡的讀寫 API 同樣都是輸出了 v1 的格式,因此需要手動調整為 v2 的 API 調用方式,通過 VSCode 的 tauri 插件,可以 Ctrl + 單擊函數名稱打開 API 的源碼,其註釋中給出了調用參考。
GitHub Copilot: 為了以 1 分鐘的間隔定時將當前內容列表以及時間信息保存到 appdata
下的 history.json
文件中,我們需要以下步驟:
- 使用
fs
模塊來寫入文件。 - 使用
setInterval
來定時保存內容列表。 - 確保在組件卸載時清除定時器。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { writeTextFile, BaseDirectory } from "@tauri-apps/api/fs";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 導入 CSS 文件
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const loadHistory = async () => {
try {
const data = await readTextFile("history.json", { dir: BaseDirectory.AppData });
const items: ClipboardItem[] = JSON.parse(data);
setClipboardItems(items);
} catch (error) {
console.error("Failed to load history:", error);
}
};
loadHistory();
}, []);
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prevItems.find(item => item.md5 === md5);
if (existingItem) {
const updatedItems = prevItems.map(item =>
item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
);
return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
} else {
const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
}
});
setLastContent(content);
}
}, 1000);
return () => clearInterval(interval);
}, [lastContent]);
useEffect(() => {
const saveInterval = setInterval(async () => {
const data = JSON.stringify(clipboardItems, null, 2);
await writeTextFile({ path: "history.json", contents: data }, { dir: BaseDirectory.AppData });
}, 60000); // 1分鐘間隔
return () => clearInterval(saveInterval);
}, [clipboardItems]);
const handleCopy = async (content: string) => {
await writeText(content);
};
const handleDelete = (timestamp: number) => {
setClipboardItems((prevItems) =>
prevItems.filter((item) => item.timestamp !== timestamp)
);
};
return (
<div>
<h2>剪貼板頁面</h2>
<ul>
{clipboardItems.map((item, index) => (
<li key={index} className={index % 2 === 0 ? "even" : "odd"}>
<div className="item-header">
<small>{new Date(item.timestamp).toLocaleString()}</small>
<div>
<a href="#" onClick={() => handleCopy(item.content)}>複製</a> |
<a href="#" onClick={() => handleDelete(item.timestamp)}>刪除</a>
</div>
</div>
<p>{item.content}</p>
</li>
))}
</ul>
</div>
);
}
export default ClipBoard;
在這個實現中,我們:
- 使用
@tauri-apps/api/fs
模塊中的writeTextFile
函數來寫入文件。 - 使用
useEffect
鉤子設置一個定時器,每隔 1 分鐘將當前內容列表保存到appdata
下的history.json
文件中。 - 確保在組件卸載時清除定時器。
這樣,每隔 1 分鐘,當前內容列表以及時間信息將被保存到 appdata
下的 history.json
文件中。
在首次進入剪貼板頁面時,從 AppData 目錄下讀取 history.json 文件,並將內容解析為剪貼板的歷史內容,顯示在界面上#
GitHub Copilot: 為了在首次進入剪貼板頁面時從 AppData
目錄下讀取 history.json
文件,並將內容解析為剪貼板的歷史內容顯示在界面上,我們需要以下步驟:
- 使用
fs
模塊來讀取文件。 - 在組件掛載時讀取
history.json
文件並解析內容。 - 將解析後的內容設置為剪貼板的歷史內容。
以下是更新後的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { readTextFile, writeTextFile, BaseDirectory } from "@tauri-apps/plugin-fs";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 導入 CSS 文件
interface ClipboardItem {
content: string;
md5: string;
timestamp: number;
}
function ClipBoard() {
const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
const [lastContent, setLastContent] = useState<string>("");
useEffect(() => {
const loadHistory = async () => {
try {
const data = await readTextFile("history.json", { dir: BaseDirectory.AppData });
const items: ClipboardItem[] = JSON.parse(data);
setClipboardItems(items);
} catch (error) {
console.error("Failed to load history:", error);
}
};
loadHistory();
}, []);
useEffect(() => {
const interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md5 = CryptoJS.MD5(content).toString();
setClipboardItems((prevItems) => {
const existingItem = prev