环境#
System:Windows 11
IDE:VSCode + Github Copilot Chat + Rust + Tauri 插件
Env:npm、Rust
已经通过npm create tauri-app@latest建立项目框架
以下标题均为在 VSCode 的 copilot chat 中输入的文本,然后对 Copilot 输出的代码进行更新替换。
将首页修改为:左侧为导航区域,以树状视图,显示不同功能的页面链接;点击左侧导航区域的功能菜单项后,右侧加载对应的功能页面#
Note
这里引用了 'App.tsx'
GitHub Copilot: 要实现这个功能,我们需要在App.tsx 中进行以下步骤:
- 创建一个导航栏组件,使用树状视图显示不同功能的页面链接。
- 使用 React Router 来处理页面导航。
- 在右侧区域加载对应的功能页面。
首先,安装 React Router:
npm install react-router-dom
然后,修改App.tsx 文件如下:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
function Home() {
  return <h2>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 组件提取到一个独立的文件中。以下是步骤:
- 创建一个新的文件 Home.tsx。
- 将 Home组件的代码移到Home.tsx文件中。
- 在 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 样式。具体步骤如下:
- 修改 App.css文件,确保导航栏占满页面高度。
- 确保容器的高度也占满页面。
以下是更新后的 App.css 文件:
/* App.css */
html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}
.container {
  display: flex;
  height: 100%;
}
.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
  height: 100%;
}
.content {
  flex-grow: 1;
  padding: 10px;
}
确保 App.tsx 文件中包含以下代码:
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
function Feature1() {
  return <h2>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 样式。具体步骤如下:
- 确保容器的高度占满页面,并且导航栏和内容区域都从顶部开始对齐。
- 更新 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 样式。具体步骤如下:
- 确保 .container使用flex布局,并且子元素按照左右排列。
- 移除重复的 .container样式定义。
以下是更新后的 App.css 文件:
/* App.css */
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}
.container {
  display: flex;
  height: 100%;
  align-items: flex-start; /* 确保子元素顶部对齐 */
}
.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
  height: 100%;
}
.content {
  flex-grow: 1;
  padding: 10px;
  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 子目录中。以下是步骤:
- 创建一个新的目录 Features。
- 在 Features目录中创建一个新的文件Feature1.tsx。
- 将 Feature1组件的代码移到Feature1.tsx文件中。
- 在 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 子目录中。以下是步骤:
- 在 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。以下是步骤:
- 重命名文件
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 条,我们需要以下步骤:
- 创建一个状态来存储剪贴板内容。
- 使用 useEffect钩子来模拟获取剪贴板内容。
- 按时间倒序排列剪贴板内容,并限制显示 100 条。
以下是更新后的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
interface ClipboardItem {
  content: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  useEffect(() => {
    // 模拟获取剪贴板内容
    const fetchClipboardItems = () => {
      const items = [
        { content: "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;
在这个实现中,我们:
- 定义了一个 ClipboardItem接口来表示剪贴板内容项。
- 使用 useState钩子来存储剪贴板内容。
- 使用 useEffect钩子来模拟获取剪贴板内容,并将其存储在状态中。
- 按时间倒序排列剪贴板内容,并限制显示 100 条。
- 在组件中渲染剪贴板内容列表。
你可以根据实际需求替换模拟数据部分,获取真实的剪贴板内容。
定时每隔 1s 读取一次剪贴板,如果存在新内容,则添加到剪贴板的内容列表中#
Note
这里需要手动在src-tauri/capabilities/default.json中添加剪贴板的读写权限,后续文件操作相同。同时 copilot 代码中的import还是 Tauri v1 的格式,需要手动调整为 v2 的格式。
GitHub Copilot: 为了实现定时每隔 1 秒读取一次剪贴板,并在存在新内容时将其添加到剪贴板的内容列表中,我们可以使用 setInterval 来定时读取剪贴板内容,并在
useEffect
中进行处理。
以下是更新后的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";
interface ClipboardItem {
  content: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        setClipboardItems((prevItems) => [
          { content, timestamp: Date.now() },
          ...prevItems,
        ]);
        setLastContent(content);
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [lastContent]);
  // 按时间倒序排列,并限制显示100条
  const sortedItems = clipboardItems.slice(0, 100);
  return (
    <div>
      <h2>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;
在这个实现中,我们:
- 使用
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: 为了实现每项内容提供 “复制” 和 “删除” 的文本链接,我们需要以下步骤:
- 添加 “复制” 和 “删除” 按钮。
- 实现复制功能,将内容重新复制到剪贴板。
- 实现删除功能,从列表中移除该项。
以下是更新后的
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;
在这个实现中,我们:
- 添加了 “复制” 和 “删除” 按钮。
- 实现了 handleCopy函数,将内容重新复制到剪贴板。
- 实现了 handleDelete函数,从列表中移除该项。
- 在每个列表项中添加了 “复制” 和 “删除” 链接,并绑定相应的事件处理函数。
这样,每项内容将提供 “复制” 和 “删除” 的文本链接,点击复制时,重新复制到剪贴板,点击删除时,从列表中移除该项。
在列表中同时保存内容文本和内容的 md5 值,当一个新的内容,md5 值和已有的内容文本一致时,仅仅更新时间,而不是新增一条记录#
Note
复制后会产生新的一条记录,因此通过 md5 来区分,确保唯一性显示。
GitHub Copilot: 为了在列表中同时保存内容文本和内容的 MD5 值,并在新内容的 MD5 值与已有内容文本一致时仅更新时间而不是新增一条记录,我们需要以下步骤:
- 安装 crypto-js库来计算 MD5 值。
- 修改 ClipboardItem接口以包含 MD5 值。
- 在读取剪贴板内容时计算 MD5 值,并检查是否已有相同的 MD5 值。
首先,安装 crypto-js 库:
npm install crypto-js
然后,更新 ClipBoard.tsx 文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent
)
 {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            return prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
          } else {
            return [{ content, md5, timestamp: Date.now() }, ...prevItems];
          }
        });
        setLastContent(content);
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [lastContent]);
  const handleCopy = async (content: string) => {
    await writeText(content);
  };
  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };
  // 按时间倒序排列,并限制显示100条
  const sortedItems = clipboardItems.slice(0, 100);
  return (
    <div>
      <h2>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;
在这个实现中,我们:
- 安装并导入了 crypto-js库来计算 MD5 值。
- 更新了 ClipboardItem接口以包含md5属性。
- 在读取剪贴板内容时计算 MD5 值,并检查是否已有相同的 MD5 值。如果有相同的 MD5 值,则仅更新时间;否则,新增一条记录。
相同内容更新时间后,列表要重新按时间排序#
GitHub Copilot: 为了确保在相同内容更新时间后,列表重新按时间排序,我们需要在更新时间戳后对列表进行排序。以下是更新后的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const 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;
在这个实现中,我们:
- 在更新时间戳后,对列表进行排序。
- 确保在添加新内容时,列表也进行排序并限制显示 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 类 (even 和 odd)。
3. 在
ClipBoard.css
文件中定义了 even 和 odd 类的背景颜色。
这样,相邻的两行将以不同的背景颜色进行区分。
不是相邻的两行,而是相邻的两个剪贴板内容以不同颜色区分,包括文本内容、时间和操作按钮#
GitHub Copilot: 为了确保相邻的两个剪贴板内容以不同颜色区分,包括文本内容、时间和操作按钮,我们可以使用 CSS 样式来实现条纹效果。以下是更新后的
ClipBoard.tsx
文件和相应的 CSS 样式:
首先,更新
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 导入 CSS 文件
interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5
 =
 CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [lastContent]);
  const handleCopy = async (content: string) => {
    await writeText(content);
  };
  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };
  return (
    <div>
      <h2>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 类 (even 和 odd)。
3. 在
ClipBoard.css
文件中定义了 even 和 odd 类的背景颜色。
这样,相邻的两个剪贴板内容将以不同的背景颜色进行区分,包括文本内容、时间和操作按钮。
内容的背景色调调整为暗色调,文本调整为对比度高的颜色#
GitHub Copilot: 为了将内容的背景色调调整为暗色调,并将文本调整为对比度高的颜色,我们可以更新
ClipBoard.css
文件中的样式。以下是更新后的
ClipBoard.css
文件:
/* ClipBoard.css */
ul {
  list-style-type: none;
  padding: 0;
}
li.even {
  background-color: #333; /* 暗色调背景 */
  color: #fff; /* 高对比度文本颜色 */
}
li.odd {
  background-color: #444; /* 暗色调背景 */
  color: #fff; /* 高对比度文本颜色 */
}
li {
  padding: 10px;
  margin: 5px 0;
}
a {
  margin-right: 10px;
  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 文件:
// 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;
}
在这个实现中,我们:
- 在 ClipBoard.tsx文件中将时间和操作按钮放在一个div容器中,并添加item-header类。
- 在
ClipBoard.css
文件中定义了 item-header 类,使用 Flexbox 布局将时间和操作按钮合并到一行,并在两端对齐。
这样,时间和操作按钮将合并到一行,并且内容的背景色调调整为暗色调,文本调整为对比度高的颜色。
以 1 分钟的间隔,定时将当前内容列表以及时间信息保存到 appdata 下的 history.json 文件中#
Note
持久化保存历史记录。这里的读写 API 同样都是输出了 v1 的格式,因此需要手动调整为 v2 的 API 调用方式,通过 VSCode 的 tauri 插件,可以 Ctrl + 单击函数名称打开 API 的源码,其注释中给出了调用参考。
GitHub Copilot: 为了以 1 分钟的间隔定时将当前内容列表以及时间信息保存到 appdata 下的 history.json 文件中,我们需要以下步骤:
- 使用 fs模块来写入文件。
- 使用 setInterval来定时保存内容列表。
- 确保在组件卸载时清除定时器。
以下是更新后的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { writeTextFile, BaseDirectory } from "@tauri-apps/api/fs";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 导入 CSS 文件
interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const 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;
在这个实现中,我们:
- 使用 @tauri-apps/api/fs模块中的writeTextFile函数来写入文件。
- 使用 useEffect钩子设置一个定时器,每隔 1 分钟将当前内容列表保存到appdata下的history.json文件中。
- 确保在组件卸载时清除定时器。
这样,每隔 1 分钟,当前内容列表以及时间信息将被保存到 appdata 下的 history.json 文件中。
在首次进入剪贴板页面时,从 AppData 目录下读取 history.json 文件,并将内容解析为剪贴板的历史内容,显示在界面上#
GitHub Copilot: 为了在首次进入剪贴板页面时从 AppData 目录下读取 history.json 文件,并将内容解析为剪贴板的历史内容显示在界面上,我们需要以下步骤:
- 使用 fs模块来读取文件。
- 在组件挂载时读取 history.json文件并解析内容。
- 将解析后的内容设置为剪贴板的历史内容。
以下是更新后的
ClipBoard.tsx
文件:
// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { readTextFile, writeTextFile, BaseDirectory } from "@tauri-apps/plugin-fs";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // 导入 CSS 文件
interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}
function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");
  useEffect(() => {
    const loadHistory = async () => {
      try {
        const data = await readTextFile("history.json", { dir: BaseDirectory.AppData });
        const items: ClipboardItem[] = JSON.parse(data);
        setClipboardItems(items);
      } catch (error) {
        console.error("Failed to load history:", error);
      }
    };
    loadHistory();
  }, []);
  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = 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;
在这个实现中,我们:
- 使用 @tauri-apps/plugin-fs模块中的readTextFile函数来读取文件。
- 在组件挂载时(
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;
在这个实现中,我们:
- 在 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映射到界面元素的异常处理,忽略无效的数据。