easychuan

在局域网内互传纯文本和文件的云剪贴板
即开即用的网页版,无须安装 APP

参考了TransparentLC的cloud-clipboard项目

由于这个项目是针对个人在连接到同一局域网(比如家里的路由器)的设备之间使用而设计的,因此并没有额外考虑在公开的服务器上使用时可能面对的技术和安全问题。

技术栈

前端:Ant Design Vue + Vite + Vue3;
后端:koa
适配:主要使用flex布局

easychuan.zip

解压后,双击easychuan.exe,即可启动,访问ip + 9501,如 http://192.168.0.100:9501/#/
目录结构

|   config.json    配置文件
|   easychuan.exe
|   history.json   历史记录
|   package.json
|
+---build
+---static
\---vendor

说明

  1. 传输文件:存储在操作系统默认的临时文件的目录
    C:\Users\ADMINI~1\AppData\Local\Temp\.cloud-clipboard-storage
  2. 历史记录存储在 history.json
  3. 配置文件说明
{
    "server": {
        // 监听的 IP 地址,省略或设为null则会监听所有网卡的IP地址
        "host": [],
        "port": 9501, // 端口号
        "key": null, // HTTPS 私钥路径
        "cert": null, // HTTPS 证书路径
        "history": 10, // 消息历史记录的数量
        "auth": false // 是否在连接时要求使用密码认证,falsy 值表示不使用
    },
    "text": {
        "limit": 20000, // 文本的长度限制,最大支持2万个字符
    },
    "file": {
        "expire": 86400, // 24小时有效期,上传文件的有效期,超过有效期后自动删除,单位为秒,60*60*24
        "chunk": 2097152,  // 上传文件的分片大小,不能超过 2 MB,单位为 byte,1024*1024*2
        "limit": 268435456, // 上传文件的大小限制,不能超过 256 MBMB,单位为 byte,1024*1024*256
    }
}

截图

easychuan_1
easychuan_2

应用打包工具 Nexe

  1. 先使用 @vercel/ncc 可以将 Node.js 项目编译为单个文件,包括项目的依赖与 gcc-style。
  2. 再将上面生产的文件用Nexe 打包为可执行文件。
    Nexe 是一款小巧却非常实用的,它可以为NodeJS应用创建单一可执行的文件,并且无需安装运行时,这样,一些非技术终端的用户就无需变动 NodeJS应用的所有依赖程序。
npm install -g nexe
nexe -i index.js -o dist/easychuan.exe -t x64-14.15.3
其中-i指定输入,-o指定输出,-t指定目标。

代码修改

参考:vue3中在<script setup>中获取全局内容(三种方式)

前端

新建.env.development, 设置VITE_API_HOST='/api'
配置文件web_vite_vue3\vite.config.js

import Components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
import viteCompression from "vite-plugin-compression";
...
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [
        AntDesignVueResolver({
          importStyle: false, // css in js
          resolveIcons: true, // 自动按需引入图标
        }),
      ],
    }),
    viteCompression({
      filename: "[path][base].gz",
      algorithm: "gzip",
      test: /.js$|.css$|.html$/,
      threshold: 10240, // 对超过10k的数据压缩
      minRatio: 0.8, // 压缩率小于0.8才会压缩
    }),
  ],
...
  server: {
    port: 3001,
    host: "0.0.0.0",
    hmr: true,
    proxy: {
      "/api": {
        target: "http://localhost:9501",
        ws: false, // 这里把ws代理给关闭
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
      "/file": {
        target: "http://localhost:9501",
        ws: false, // 这里把ws代理给关闭
        changeOrigin: true,
      },
    },
  },
  build: {
    rollupOptions: {
      output: {
        chunkFileNames: "js/[name]-[hash].js", // 引入文件名的名称
        entryFileNames: "js/[name]-[hash].js", // 包的入口文件名称
        assetFileNames: "[ext]/[name]-[hash].[ext]", // 资源文件像 字体,图片等
      },
    },
  },
});

服务端 app\http-router.js

电脑上页面传输可以,但是手机上连不上,因为请求 '/server',返回的是ws://localhost:9501/push
而手机上无法请求localhost

//获取本机ip地址
function getIPAdress() {
    let interfaces = os.networkInterfaces();
    for (let devName in interfaces) {    
        let iface = interfaces[devName];      
        for (let i = 0; i < iface.length; i++) {
            let alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                return alias.address;
            }
        }  
    }
}
const ip = getIPAdress();
router.get('/server', async ctx => {
  ctx.body = {
    // server: `ws://${ctx.request.host}/push`,
    server: `ws://${ip}:${config.server.port}/push`,
    auth: !!config.server.auth,
  };
});