使用Copilot引导开发Tauri

环境

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:

Copy
npm install react-router-dom

然后，修改App.tsx 文件如下：

Copy
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 Home Page ;
}

function Feature1() {
return Feature 1 Page ;
}

function Feature2() {
return Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

同时，更新 App.css 文件以适应新的布局：

Copy
.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 文件，并添加以下代码：

Copy
// Home.tsx
import React from "react";

function Home() {
return Home Page ;
}

export default Home;

然后，修改 App.tsx 文件，导入 Home 组件：

Copy
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 Feature 1 Page ;
}

function Feature2() {
return Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，Home 组件就被提取到一个独立的文件中了。

Similar code found with 1 license type

左侧导航栏垂直拉伸到占满页面

Note

左侧导航栏只占据了包含文本的区域，因此希望能全部占满。

GitHub Copilot: 为了使左侧导航栏垂直拉伸到占满页面，我们需要调整 CSS 样式。具体步骤如下：

修改 App.css 文件，确保导航栏占满页面高度。

确保容器的高度也占满页面。

以下是更新后的 App.css 文件：

Copy
/* 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 文件中包含以下代码：

Copy
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 Feature 1 Page ;
}

function Feature2() {
return Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</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 文件：

Copy
/* 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;
height: 100%;
}

确保 App.tsx 文件中包含以下代码：

Copy
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 Feature 1 Page ;
}

function Feature2() {
return Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，右侧页面和左侧导航栏将会顶部对齐。

Similar code found with 1 license type

.container 内的子元素按照左右布局排列

GitHub Copilot: 为了确保 .container 内的子元素按照左右布局排列，我们需要调整 CSS 样式。具体步骤如下：

确保 .container 使用 flex 布局，并且子元素按照左右排列。

移除重复的 .container 样式定义。

以下是更新后的 App.css 文件：

Copy
/* 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;
height: 100%;
}

确保 App.tsx 文件中包含以下代码：

Copy
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 Feature 1 Page ;
}

function Feature2() {
return Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，.container 内的子元素将按照左右布局排列，并且顶部对齐。

Similar code found with 1 license type

导航栏的背景颜色设置为系统背景色

Note

系统主题是暗色，但默认背景色为白色，且这个代码未实际生效，最后手动删除了.sidebar的背景色配置

GitHub Copilot: 为了将导航栏的背景颜色设置为系统背景色，可以使用 CSS 中的 background-color 属性，并将其值设置为 background 系统颜色。以下是更新后的 App.css 文件：

Copy
/* 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;
height: 100%;
}

.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 文件，并添加以下代码：

Copy
// Features/Feature1.tsx
import React from "react";

function Feature1() {
return Feature 1 Page ;
}

export default Feature1;

然后，修改

App.tsx

文件，导入

Feature1

组件：

Copy
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 Feature 2 Page ;
}

function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");

async function greet() {
setGreetMsg(await invoke("greet", { name }));
}

return (
<Router>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</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 文件，并添加以下代码：

Copy
// Features/Feature2.tsx
import React from "react";

function Feature2() {
return Feature 2 Page ;
}

export default Feature2;

然后，修改

App.tsx

文件，导入

Feature2

组件：

Copy
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>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，

Feature2

组件就被提取到 Features 子目录中的独立模块中了。

Similar code found with 1 license type

页面默认包含滚动条，将页面高度调整到不出现滚动条

Note

默认内容区出现了滚动条

GitHub Copilot: 为了确保页面高度调整到不出现滚动条，我们需要确保容器的高度和内容的高度都正确设置。我们可以使用 overflow: hidden 来防止滚动条的出现，并确保内容区域的高度不会超过视口高度。

以下是更新后的

App.css

文件：

Copy
/* 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 文件中包含以下代码：

Copy
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>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，页面的高度将调整到不出现滚动条，并且内容区域允许滚动。

Similar code found with 1 license type

greetMsg 的段落显示在新的一行

Note

这里原因是为了保留示例中的 greet 交互，手动将代码从 App.tsx 移到了 Home.tsc 中，但是 标签不生效，和文本输出栏显示在同一行。通过下一 Promt，以 标签来解决了问题。

GitHub Copilot: 为了确保

greetMsg

显示在新的一行，我们可以将其放在一个段落 ( ) 标签中。以下是更新后的

Home.tsx

文件：

Copy
// Home.tsx
import React from "react";

function Home({ greetMsg }: { greetMsg: string }) {
return (

Home Page
{greetMsg}

);
}

export default Home;

同时，确保在 App.tsx 中传递

greetMsg

作为 Home 组件的属性：

Copy
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>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home greetMsg={greetMsg} />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，

greetMsg

将显示在 Home 组件中的新的一行。

Similar code found with 1 license type

greetMsg 无须从 App 中传入

GitHub Copilot: 好的，我们可以在

Home

组件中直接定义 greetMsg，而不需要从

App

中传入。以下是更新后的

Home.tsx

文件：

Copy
// 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 (

Home Page
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
Greet
{greetMsg}

);
}

export default Home;

同时，

App.tsx

文件不需要任何修改，因为 greetMsg 不再需要从

App

中传入：

Copy
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>




Home


Feature 1


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/feature1" element={<Feature1 />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</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，并更新文件内容如下：

Copy
// Features/ClipBoard.tsx
import React from "react";

function ClipBoard() {
return ClipBoard Page ;
}

export default ClipBoard;

然后，修改 App.tsx 文件，导入 ClipBoard 组件：

Copy
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>




Home


ClipBoard


Feature 2




<Routes>
<Route path="/" element={<Home />} />
<Route path="/clipboard" element={<ClipBoard />} />
<Route path="/feature2" element={<Feature2 />} />
</Routes>


</Router>
);
}

export default App;

这样，

Feature1

组件就被重命名为 ClipBoard 并且在 App.tsx 中正确导入和使用了。

剪贴板页面，显示最近的剪贴板内容，按照时间倒序排列，最多显示 100 条

GitHub Copilot: 为了实现剪贴板页面显示最近的剪贴板内容，并按照时间倒序排列，最多显示 100 条，我们需要以下步骤：

创建一个状态来存储剪贴板内容。

使用 useEffect 钩子来模拟获取剪贴板内容。

按时间倒序排列剪贴板内容，并限制显示 100 条。

以下是更新后的

ClipBoard.tsx

文件：

Copy
// 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: "Item 1", timestamp: Date.now() - 1000 },
{ content: "Item 2", timestamp: Date.now() - 2000 },
// 添加更多模拟数据
];
setClipboardItems(items);
};

fetchClipboardItems();
}, []);

// 按时间倒序排列，并限制显示100条
const sortedItems = clipboardItems
.sort((a, b) => b.timestamp - a.timestamp)
.slice(0, 100);

return (

ClipBoard Page

{sortedItems.map((item, index) => (

{item.content}
{new Date(item.timestamp).toLocaleString()}

))}


);
}

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

文件：

Copy
// 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 (

ClipBoard Page

{sortedItems.map((item, index) => (

{item.content}
{new Date(item.timestamp).toLocaleString()}

))}


);
}

export default ClipBoard;

在这个实现中，我们：

使用

useState

钩子来存储剪贴板内容和最后一次读取的内容。
2. 使用

useEffect

钩子来设置一个定时器，每隔 1 秒读取一次剪贴板内容。
3. 如果读取到的新内容与最后一次读取的内容不同，则将其添加到剪贴板内容列表中，并更新最后一次读取的内容。
4. 按时间倒序排列剪贴板内容，并限制显示 100 条。
5. 在组件中渲染剪贴板内容列表。

这样，每隔 1 秒读取一次剪贴板内容，并在存在新内容时将其添加到剪贴板的内容列表中。

将时间显示在内容的上方

GitHub Copilot: 为了将时间显示在内容的上方，我们需要调整 ClipBoard.tsx 文件中的渲染顺序。以下是更新后的代码：

Copy
// 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 (

ClipBoard Page

{sortedItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}

))}


);
}

export default ClipBoard;

在这个实现中，我们将时间 ({new Date(item.timestamp).toLocaleString()}) 放在内容 ( {item.content} ) 的上方。这样，时间将显示在内容的上方。

每项内容，提供 “复制” 和 "删除" 的文本链接，点击复制时，重新复制到剪贴板，点击删除时，从列表中移除该项

GitHub Copilot: 为了实现每项内容提供 “复制” 和 “删除” 的文本链接，我们需要以下步骤：

添加 “复制” 和 “删除” 按钮。

实现复制功能，将内容重新复制到剪贴板。

实现删除功能，从列表中移除该项。

以下是更新后的

ClipBoard.tsx

文件：

Copy
// 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 (

ClipBoard Page

{sortedItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}
handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除

))}


);
}

export default ClipBoard;

在这个实现中，我们：

添加了 “复制” 和 “删除” 按钮。

实现了 handleCopy 函数，将内容重新复制到剪贴板。

实现了 handleDelete 函数，从列表中移除该项。

在每个列表项中添加了 “复制” 和 “删除” 链接，并绑定相应的事件处理函数。

这样，每项内容将提供 “复制” 和 “删除” 的文本链接，点击复制时，重新复制到剪贴板，点击删除时，从列表中移除该项。

在列表中同时保存内容文本和内容的 md5 值，当一个新的内容，md5 值和已有的内容文本一致时，仅仅更新时间，而不是新增一条记录

Note

复制后会产生新的一条记录，因此通过 md5 来区分，确保唯一性显示。

GitHub Copilot: 为了在列表中同时保存内容文本和内容的 MD5 值，并在新内容的 MD5 值与已有内容文本一致时仅更新时间而不是新增一条记录，我们需要以下步骤：

安装 crypto-js 库来计算 MD5 值。

修改 ClipboardItem 接口以包含 MD5 值。

在读取剪贴板内容时计算 MD5 值，并检查是否已有相同的 MD5 值。

首先，安装 crypto-js 库：

Copy
npm install crypto-js

然后，更新 ClipBoard.tsx 文件：

Copy
// 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 (

ClipBoard Page

{sortedItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}
handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除

))}


);
}

export default ClipBoard;

在这个实现中，我们：

安装并导入了 crypto-js 库来计算 MD5 值。

更新了 ClipboardItem 接口以包含 md5 属性。

在读取剪贴板内容时计算 MD5 值，并检查是否已有相同的 MD5 值。如果有相同的 MD5 值，则仅更新时间；否则，新增一条记录。

相同内容更新时间后，列表要重新按时间排序

GitHub Copilot: 为了确保在相同内容更新时间后，列表重新按时间排序，我们需要在更新时间戳后对列表进行排序。以下是更新后的

ClipBoard.tsx

文件：

Copy
// 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 md

5

= 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}
handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除

))}


);
}

export default ClipBoard;

在这个实现中，我们：

在更新时间戳后，对列表进行排序。

确保在添加新内容时，列表也进行排序并限制显示 100 条。

这样，当相同内容更新时间后，列表将重新按时间排序。

重新排序后，页面也要按照新的顺序重新刷新显示

Note

排序后，页面并未刷新，因此要求强制刷新。

GitHub Copilot: 为了确保在重新排序后页面按照新的顺序重新刷新显示，我们需要确保在 setClipboardItems 中对列表进行排序，并且在渲染时使用排序后的列表。

以下是更新后的

ClipBoard.tsx

文件：

Copy
// 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 !== last

Content

) {
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 (

ClipBoard Page

{clipboardItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}
handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除

))}


);
}

export default ClipBoard;

在这个实现中，我们确保在 setClipboardItems 中对列表进行排序，并且在渲染时使用排序后的 clipboardItems 列表。这样，当列表重新排序后，页面将按照新的顺序重新刷新显示。

相邻的两行以不同的背景颜色进行区分

Note

UI 优化。

GitHub Copilot: 为了实现相邻的两行以不同的背景颜色进行区分，我们可以使用 CSS 样式来实现条纹效果。以下是更新后的

ClipBoard.tsx

文件和相应的 CSS 样式：

首先，更新

ClipBoard.tsx

文件：

Copy
// 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}
handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除

))}


);
}

export default ClipBoard;

然后，创建

ClipBoard.css

文件，并添加以下样式：

Copy
/* 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

文件：

Copy
// 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (

{new Date(item.timestamp).toLocaleString()}
{item.content}

handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除


))}


);
}

export default ClipBoard;

然后，创建或更新

ClipBoard.css

文件，并添加以下样式：

Copy
/* 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

文件：

Copy
/* 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;
color: #1e90ff; /* 高对比度链接颜色 */
}

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 文件：

Copy
// 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (


{new Date(item.timestamp).toLocaleString()}

handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除


{item.content}

))}


);
}

export default ClipBoard;

然后，更新

ClipBoard.css

文件，并添加以下样式：

Copy
/* 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;
color: #1e90ff; /* 高对比度链接颜色 */
}

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

文件：

Copy
// 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 interval = setInterval(async () => {
const content = await readText();
if (content && content !== lastContent) {
const md

5

= 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (


{new Date(item.timestamp).toLocaleString()}

handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除


{item.content}

))}


);
}

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

文件：

Copy
// 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 = 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 (

ClipBoard Page

{clipboardItems.map((item, index) => (


{new Date(item.timestamp).toLocaleString()}

handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除


{item.content}

))}


);
}

export default ClipBoard;

在这个实现中，我们：

使用 @tauri-apps/plugin-fs 模块中的 readTextFile 函数来读取文件。

在组件挂载时（

useEffect

钩子）读取 history.json 文件并解析内容。
3. 将解析后的内容设置为剪贴板的历史内容，并显示在界面上。

这样，在首次进入剪贴板页面时，将从 AppData 目录下读取 history.json 文件，并将内容解析为剪贴板的历史内容显示在界面上。

增加 ClipboardItems map 到界面元素的异常处理，忽略无效的数据

Note

首次打开时没有 history.json 文件，手动创建并构建一个空 json 后抛出异常，最后手动加入检查 items 为有效数组时才赋值的代码后通过。

GitHub Copilot: 为了增加 clipboardItems 映射到界面元素的异常处理，并忽略无效的数据，我们可以在渲染列表项时添加错误处理逻辑。以下是更新后的

ClipBoard.tsx

文件：

Copy
// 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 = 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 (

ClipBoard Page

{clipboardItems.map((item, index) => {
try {
if (!item.content || !item.timestamp) {
throw new Error("Invalid item");
}
return (


{new Date(item.timestamp).toLocaleString()}

handleCopy(item.content)}>复制 |
handleDelete(item.timestamp)}>删除


{item.content}

);
} catch (error) {
console.error("Invalid clipboard item:", error);
return null;
}
})}


);
}

export default ClipBoard;

在这个实现中，我们：

在 clipboardItems.map 中添加了错误处理逻辑。

检查每个 item 是否包含有效的 content 和 timestamp 属性。

如果 item 无效，则抛出错误并在控制台中记录错误信息，同时返回 null 以忽略无效的数据。

这样，在映射 clipboardItems 到界面元素时，将会忽略无效的数据，并在控制台中记录错误信息。

只列出之前发出的所有指令，不显示代码更新内容

Note

这里的总结只给出了功能调整的指令，项目结构和 UI 优化的指令都被忽略了。

GitHub Copilot:

创建独立的 Home 组件。

将 Feature1 移入 Features 子目录中的独立模块。

将 Feature1 重命名为 ClipBoard。

显示最近的剪贴板内容，按照时间倒序排列，最多显示 100 条。

将 greetMsg 显示在新的一行。

定时每隔 1 秒读取一次剪贴板，如果存在新内容，则添加到剪贴板的内容列表中。

在列表中同时保存内容文本和内容的 MD5 值，当一个新的内容，MD5 值和已有的内容文本一致时，仅更新时间，而不是新增一条记录。

将时间和操作按钮合并到一行。

在首次进入剪贴板页面时，从 AppData 目录下读取 history.json 文件，并将内容解析为剪贴板的历史内容，显示在界面上。

增加 ClipboardItems 映射到界面元素的异常处理，忽略无效的数据。

最终效果图