wingsfly

wingsfly

Observer

使用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 中进行以下步骤:

  1. 创建一个导航栏组件,使用树状视图显示不同功能的页面链接。
  2. 使用 React Router 来处理页面导航。
  3. 在右侧区域加载对应的功能页面。

首先,安装 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>Home Page</h2>;
}

function Feature1() {
  return <h2>Feature 1 Page</h2>;
}

function Feature2() {
  return <h2>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 组件提取到一个独立的文件中。以下是步骤:

  1. 创建一个新的文件 Home.tsx
  2. Home 组件的代码移到 Home.tsx 文件中。
  3. App.tsx 中导入 Home 组件。

首先,创建 Home.tsx 文件,并添加以下代码:

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

function Home() {
  return <h2>Home Page</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>Feature 1 Page</h2>;
}

function Feature2() {
  return <h2>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 样式。具体步骤如下:

  1. 修改 App.css 文件,确保导航栏占满页面高度。
  2. 确保容器的高度也占满页面。

以下是更新后的 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>Feature 1 Page</h2>;
}

function Feature2() {
  return <h2>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 样式。具体步骤如下:

  1. 确保容器的高度占满页面,并且导航栏和内容区域都从顶部开始对齐。
  2. 更新 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;
  height: 100%;
}

确保 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>Feature 1 Page</h2>;
}

function Feature2() {
  return <h2>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 样式。具体步骤如下:

  1. 确保 .container 使用 flex 布局,并且子元素按照左右排列。
  2. 移除重复的 .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;
  height: 100%;
}

确保 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>Feature 1 Page</h2>;
}

function Feature2() {
  return <h2>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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;
  height: 100%;
}

.logo {
  height: 6em;
}

在上面的代码中,var(--system-background-color, #f0f0f0) 使用了 CSS 变量来设置背景颜色。如果系统背景色未定义,则使用默认颜色 #f0f0f0。你可以根据需要调整默认颜色。


将 Feature1 单独放入 Features 子目录下的独立模块中#


GitHub Copilot: 好的,我们可以将 Feature1
组件提取到一个独立的文件中,并将其放入 Features 子目录中。以下是步骤:

  1. 创建一个新的目录 Features
  2. Features 目录中创建一个新的文件 Feature1.tsx
  3. Feature1 组件的代码移到 Feature1.tsx 文件中。
  4. App.tsx 中导入 Feature1 组件。

首先,创建 Features/Feature1.tsx 文件,并添加以下代码:

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

function Feature1() {
  return <h2>Feature 1 Page</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>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 子目录中。以下是步骤:

  1. 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>Feature 2 Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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>Home Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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>Home Page</h2>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <button onClick={greet}>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="/">Home</Link>
            </li>
            <li>
              <Link to="/feature1">Feature 1</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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。以下是步骤:

  1. 重命名文件

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>ClipBoard Page</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="/">Home</Link>
            </li>
            <li>
              <Link to="/clipboard">ClipBoard</Link>
            </li>
            <li>
              <Link to="/feature2">Feature 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 条,我们需要以下步骤:

  1. 创建一个状态来存储剪贴板内容。
  2. 使用 useEffect 钩子来模拟获取剪贴板内容。
  3. 按时间倒序排列剪贴板内容,并限制显示 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: "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 (
    <div>
      <h2>ClipBoard Page</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;

在这个实现中,我们:

  1. 定义了一个 ClipboardItem 接口来表示剪贴板内容项。
  2. 使用 useState 钩子来存储剪贴板内容。
  3. 使用 useEffect 钩子来模拟获取剪贴板内容,并将其存储在状态中。
  4. 按时间倒序排列剪贴板内容,并限制显示 100 条。
  5. 在组件中渲染剪贴板内容列表。

你可以根据实际需求替换模拟数据部分,获取真实的剪贴板内容。


定时每隔 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>ClipBoard Page</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;

在这个实现中,我们:

  1. 使用

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>ClipBoard Page</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: 为了实现每项内容提供 “复制” 和 “删除” 的文本链接,我们需要以下步骤:

  1. 添加 “复制” 和 “删除” 按钮。
  2. 实现复制功能,将内容重新复制到剪贴板。
  3. 实现删除功能,从列表中移除该项。

以下是更新后的

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>ClipBoard Page</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;

在这个实现中,我们:

  1. 添加了 “复制” 和 “删除” 按钮。
  2. 实现了 handleCopy 函数,将内容重新复制到剪贴板。
  3. 实现了 handleDelete 函数,从列表中移除该项。
  4. 在每个列表项中添加了 “复制” 和 “删除” 链接,并绑定相应的事件处理函数。

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


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

Note

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


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

  1. 安装 crypto-js 库来计算 MD5 值。
  2. 修改 ClipboardItem 接口以包含 MD5 值。
  3. 在读取剪贴板内容时计算 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>ClipBoard Page</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;

在这个实现中,我们:

  1. 安装并导入了 crypto-js 库来计算 MD5 值。
  2. 更新了 ClipboardItem 接口以包含 md5 属性。
  3. 在读取剪贴板内容时计算 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 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 (
    <div>
      <h2>ClipBoard Page</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;

在这个实现中,我们:

  1. 在更新时间戳后,对列表进行排序。
  2. 确保在添加新内容时,列表也进行排序并限制显示 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 !== 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 (
    <div>
      <h2>ClipBoard Page</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>ClipBoard Page</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 类 (evenodd)。
3. 在

ClipBoard.css

文件中定义了 evenodd 类的背景颜色。

这样,相邻的两行将以不同的背景颜色进行区分。


不是相邻的两行,而是相邻的两个剪贴板内容以不同颜色区分,包括文本内容、时间和操作按钮#


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>ClipBoard Page</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 类 (evenodd)。
3. 在

ClipBoard.css

文件中定义了 evenodd 类的背景颜色。

这样,相邻的两个剪贴板内容将以不同的背景颜色进行区分,包括文本内容、时间和操作按钮。


内容的背景色调调整为暗色调,文本调整为对比度高的颜色#


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

a:hover {
  text-decoration: underline;
}

在这个实现中,我们:

  1. li.evenli.odd 的背景颜色分别设置为 #333#444,以实现暗色调背景。
  2. li.evenli.odd 的文本颜色设置为 #fff,以实现高对比度文本颜色。
  3. 将链接的颜色设置为 #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>ClipBoard Page</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;
  color: #1e90ff; /* 高对比度链接颜色 */
}

a:hover {
  text-decoration: underline;
}

在这个实现中,我们:

  1. 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 文件中,我们需要以下步骤:

  1. 使用 fs 模块来写入文件。
  2. 使用 setInterval 来定时保存内容列表。
  3. 确保在组件卸载时清除定时器。

以下是更新后的

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 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 (
    <div>
      <h2>ClipBoard Page</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;

在这个实现中,我们:

  1. 使用 @tauri-apps/api/fs 模块中的 writeTextFile 函数来写入文件。
  2. 使用 useEffect 钩子设置一个定时器,每隔 1 分钟将当前内容列表保存到 appdata 下的 history.json 文件中。
  3. 确保在组件卸载时清除定时器。

这样,每隔 1 分钟,当前内容列表以及时间信息将被保存到 appdata 下的 history.json 文件中。


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


GitHub Copilot: 为了在首次进入剪贴板页面时从 AppData 目录下读取 history.json 文件,并将内容解析为剪贴板的历史内容显示在界面上,我们需要以下步骤:

  1. 使用 fs 模块来读取文件。
  2. 在组件挂载时读取 history.json 文件并解析内容。
  3. 将解析后的内容设置为剪贴板的历史内容。

以下是更新后的

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 = 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>ClipBoard Page</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;

在这个实现中,我们:

  1. 使用 @tauri-apps/plugin-fs 模块中的 readTextFile 函数来读取文件。
  2. 在组件挂载时(

useEffect

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

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


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

Note

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


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

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 = 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>ClipBoard Page</h2>
      <ul>
        {clipboardItems.map((item, index) => {
          try {
            if (!item.content || !item.timestamp) {
              throw new Error("Invalid item");
            }
            return (
              <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>
            );
          } catch (error) {
            console.error("Invalid clipboard item:", error);
            return null;
          }
        })}
      </ul>
    </div>
  );
}

export default ClipBoard;

在这个实现中,我们:

  1. clipboardItems.map 中添加了错误处理逻辑。
  2. 检查每个 item 是否包含有效的 contenttimestamp 属性。
  3. 如果 item 无效,则抛出错误并在控制台中记录错误信息,同时返回 null 以忽略无效的数据。

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


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

Note

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


GitHub Copilot:

  1. 创建独立的 Home 组件。
  2. Feature1 移入 Features 子目录中的独立模块。
  3. Feature1 重命名为 ClipBoard
  4. 显示最近的剪贴板内容,按照时间倒序排列,最多显示 100 条。
  5. greetMsg 显示在新的一行。
  6. 定时每隔 1 秒读取一次剪贴板,如果存在新内容,则添加到剪贴板的内容列表中。
  7. 在列表中同时保存内容文本和内容的 MD5 值,当一个新的内容,MD5 值和已有的内容文本一致时,仅更新时间,而不是新增一条记录。
  8. 将时间和操作按钮合并到一行。
  9. 在首次进入剪贴板页面时,从 AppData 目录下读取 history.json 文件,并将内容解析为剪贴板的历史内容,显示在界面上。
  10. 增加 ClipboardItems 映射到界面元素的异常处理,忽略无效的数据。

最终效果图#

image


加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。