”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 我是如何构建 PeerSplit:一款免费的点对点费用分摊应用程序 — 从构思到发布仅需数周时间

我是如何构建 PeerSplit:一款免费的点对点费用分摊应用程序 — 从构思到发布仅需数周时间

发布于2024-11-07
浏览:400

我构建了 PeerSplit——一个免费的、点对点的 Splitwise 替代品——从想法到发布仅用了两周时间!

PeerSplit 是一款本地优先的应用程序,用于分配团体费用。它可以离线工作,100% 免费且私密,不需要注册或任何个人数据。

以下是我如何构建它以及我在此过程中学到的一切。

为何选择 PeerSplit?

我多年来一直依靠 Splitwise 来管理与朋友和室友的开支。但由于最近的每日交易限制和侵入性广告,它的使用变得令人沮丧。

我想要一个免费的、隐私优先的替代方案,不需要服务器来存储或同步数据。我不会相信第三方服务器的费用。

在完成了点对点、本地优先的项目(例如锻炼追踪器和无干扰写作应用程序)后,我意识到我可以应用相同的方法来分配费用。

PeerSplit 就这样诞生了。我开始设计应用程序。


使用 Nuxt 构建 UI Nuxt UI

我不擅长设计 UI。

几个月前,我不会想到我可以构建一个像 PeerSplit 一样精美的 UI(有些人甚至说它比 Splitwise 具有更好的用户体验)。

那么,我是如何做到的呢? Nuxt UI。

Nuxt UI 非常华丽,并且拥有令人惊叹的开发者体验 (DX)。

它还附带其他有用的 Nuxt 模块,如 @nuxt/icon、@nuxtjs/tailwindcss 和 @nuxtjs/colormode。

我所要做的就是选择一种主色,并且我拥有了将 PeerSplit 的 UI 整合在一起所需的所有组件(图标、深色模式和其他所有内容)。


cr-sqlite 用于本地同步?

对于本地数据存储和同步,我选择了 cr-sqlite,它基于 wa-sqlite 构建并使用 CRDT(无冲突复制数据类型)。

CRDT 非常适合点对点系统,因为它们会自动处理冲突,因此用户可以离线工作,并且当他们重新连接时,更改会无缝合并。

但是,cr-sqlite 本身不会通过网络同步更改。它仅提供用于导出和合并更改的 API。您需要在设备之间手动发送这些更改。


Gun.js 用于点对点同步?

为了处理安全的点对点同步,我使用了 Gun.js,它提供了点对点分布式图形数据库。

Gun 的gun.user API 让我可以为每个组创建加密节点。组的所有更改都存储在该节点上,并且仅与组成员同步,从而保持所有内容的私密性。

当用户执行操作时,我将从 cr-sqlite 导出的更改并将它们推送到节点。当用户重新上线时,Gun 会同步新的更改,让每个人都了解最新情况。

以高性能的方式实现这一点很棘手。欲了解更多详细信息,您可以在这里查看源代码。


简化债务?

Splitwise(现在是 PeerSplit)的一个很酷的功能是“简化债务”。

具体原理是这样的:如果A欠B,B欠C,A可以直接向C付款,以减少还款次数。

在PeerSplit中,我首先计算每个人的净余额。然后我对这些余额进行排序,并建议一项一项付款,每次至少使一个人的余额为零。

这种排序可确保每个人在其设备上看到相同的还款。

这不是 100% 最优(某些群体可能仍然有最多 n-1 笔付款),但在大多数情况下效果很好。

最佳解决方案的计算结果是指数级的,并且只能节省少量费用。所以这是简单性和速度的最佳权衡!

export const groupGetPayments = (group) => {
  const payments = [];
  const balances = Object.entries(groupGetBalances(group)).map(([a, b]) => [
    b,
    a,
  ]);
  balances.sort();
  let i = 0,
    j = balances.length - 1;
  while (i  balances[j][0]) {
      payments.push({
        from: balances[i][1],
        to: balances[j][1],
        value: round(balances[j][0]),
      });
      balances[i][0]  = balances[j][0];
      balances[j][0] = 0;
    } else {
      payments.push({
        from: balances[i][1],
        to: balances[j][1],
        value: round(-balances[i][0]),
      });
      balances[j][0]  = balances[i][0];
      balances[i][0] = 0;
    }
  }
  return payments;
};

渐进式网页应用

我希望 PeerSplit 能够作为离线应用程序运行,但我不想经历构建多个本机应用程序或处理在应用程序商店上发布它们的漫长过程的麻烦。因此,选择渐进式 Web 应用程序 (PWA) 是明智的选择。

PWA 结合了网络和移动应用程序的优点,允许用户将其安装在自己的设备上,同时仍然享受离线功能。

为了将我的 Nuxt 应用程序转换为 PWA,我使用了 vite-pwa。
我在 Figma 中设计了一个 SVG 徽标,并使用它通过 vite-pwa 的资产生成器生成所有必需的 PWA 资产。

之后,我配置了 PWA 清单,vite-pwa 会自动为我设置 Service Worker。

我将 Nuxt 配置为预渲染所有路线,以便我的应用程序可以完全离线运行。


这就结束了。感谢您的阅读!

产品搜索发布

PeerSplit 刚刚在 Product Hunt 上推出!这是我的第一次发布,希望得到您的支持和反馈。

查看 Product Hunt 上的 PeerSplit

PeerSplit 是公平来源的,因此请随意在 GitHub 上贡献或提交功能请求。

How I built PeerSplit: A free, peer-to-peer expense-splitting app—from idea to launch in just eeks 塔奈维克 / 同行分裂

PeerSplit 是一款免费、本地优先的点对点应用程序,可帮助您轻松、私密地分配和跟踪团体费用。

对等分裂​​

PeerSplit 是一款免费、本地优先的点对点应用程序,可帮助您轻松、私密地分配和跟踪团体费用。

特征

  • 100% 免费 — 无需注册
  • 本地优先 — 完全离线工作
  • 跨平台 PWA — 在移动设备、台式机或笔记本电脑上使用
  • 点对点 — 与朋友协作,同时保持数据私密性
  • 简单的用户体验 — 流畅且简约的界面,不妨碍您
  • 深色和浅色模式 — 在主题之间切换以符合您的喜好
  • 导入/导出 — 从 Splitwise 导入并将数据导出到 CSV

计划的功能

  • 高级账单拆分 — 添加明细账单作为单一费用。
  • 账单扫描 — 通过拍照自动扫描和拆分账单。
  • 多币种支持 — 通过实时汇率处理不同币种的费用。
  • 交易备注和评论 — 为每笔交易添加备注和评论以保留…


在 GitHub 上查看


版本声明 本文转载于:https://dev.to/tanay/how-i-built-peersplit-a-free-peer-to-peer-expense-splitting-app-from-idea-to-launch-in-just-2-weeks-386m?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何使用node-mysql在单个查询中执行多个SQL语句?
    如何使用node-mysql在单个查询中执行多个SQL语句?
    在node-mysql node-mysql文档最初出于安全原因最初禁用多个语句支持,因为它可能导致SQL注入攻击。要启用此功能,您需要在创建连接时将倍增设置设置为true: var connection = mysql.createconnection({{multipleStatement:...
    编程 发布于2025-05-17
  • 在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8表中将latin1字符转换为utf8 ,您遇到了一个问题,其中含义的字符(例如,“jáuòiñe”)在utf8 table tabled tablesset中被extect(例如,“致电。The recommended approach to correct the data is t...
    编程 发布于2025-05-17
  • 在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    For Each Loop vs. Iterator: Efficiency in Collection TraversalIntroductionWhen traversing a collection in Java, the choice arises between using a for-...
    编程 发布于2025-05-17
  • 如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    Transform Pandas DataFrame Column to DateTime FormatScenario:Data within a Pandas DataFrame often exists in various formats, including strings.使用时间数据时...
    编程 发布于2025-05-17
  • 您如何在Laravel Blade模板中定义变量?
    您如何在Laravel Blade模板中定义变量?
    在Laravel Blade模板中使用Elegance 在blade模板中如何分配变量对于存储以后使用的数据至关重要。在使用“ {{}}”分配变量的同时,它可能并不总是最优雅的解决方案。幸运的是,Blade通过@php Directive提供了更优雅的方法: $ old_section =“...
    编程 发布于2025-05-17
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案: 在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。编译器: perl: 1 输入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    编程 发布于2025-05-17
  • 如何检查对象是否具有Python中的特定属性?
    如何检查对象是否具有Python中的特定属性?
    方法来确定对象属性存在寻求一种方法来验证对象中特定属性的存在。考虑以下示例,其中尝试访问不确定属性会引起错误: >>> a = someClass() >>> A.property Trackback(最近的最新电话): 文件“ ”,第1行, attributeError:SomeClass实...
    编程 发布于2025-05-17
  • Spark DataFrame添加常量列的妙招
    Spark DataFrame添加常量列的妙招
    在Spark Dataframe ,将常数列添加到Spark DataFrame,该列具有适用于所有行的任意值的Spark DataFrame,可以通过多种方式实现。使用文字值(SPARK 1.3)在尝试提供直接值时,用于此问题时,旨在为此目的的使用column方法可能会导致错误。 df.with...
    编程 发布于2025-05-17
  • C++中如何将独占指针作为函数或构造函数参数传递?
    C++中如何将独占指针作为函数或构造函数参数传递?
    在构造函数和函数中将唯一的指数管理为参数 unique pointers( unique_ptr [2启示。通过值: base(std :: simelor_ptr n) :next(std :: move(n)){} 此方法将唯一指针的所有权转移到函数/对象。指针的内容被移至功能中,在操作...
    编程 发布于2025-05-17
  • 在Oracle SQL中如何提取下划线前的子字符串?
    在Oracle SQL中如何提取下划线前的子字符串?
    [ 在oracle sql 解决方案: Explanation:SUBSTR function extracts a substring starting from the specified position (0) and continuing for a specified length.IN...
    编程 发布于2025-05-17
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义函数。 runkit_function_renction_re...
    编程 发布于2025-05-17
  • 如何使用不同数量列的联合数据库表?
    如何使用不同数量列的联合数据库表?
    合并列数不同的表 当尝试合并列数不同的数据库表时,可能会遇到挑战。一种直接的方法是在列数较少的表中,为缺失的列追加空值。 例如,考虑两个表,表 A 和表 B,其中表 A 的列数多于表 B。为了合并这些表,同时处理表 B 中缺失的列,请按照以下步骤操作: 确定表 B 中缺失的列,并将它们添加到表的末...
    编程 发布于2025-05-17
  • Android如何向PHP服务器发送POST数据?
    Android如何向PHP服务器发送POST数据?
    在android apache httpclient(已弃用) httpclient httpclient = new defaulthttpclient(); httppost httppost = new httppost(“ http://www.yoursite.com/script.p...
    编程 发布于2025-05-17
  • 如何处理PHP文件系统功能中的UTF-8文件名?
    如何处理PHP文件系统功能中的UTF-8文件名?
    在PHP的Filesystem functions中处理UTF-8 FileNames 在使用PHP的MKDIR函数中含有UTF-8字符的文件很多flusf-8字符时,您可能会在Windows Explorer中遇到comploreer grounder grounder grounder gro...
    编程 发布于2025-05-17
  • 将图片浮动到底部右侧并环绕文字的技巧
    将图片浮动到底部右侧并环绕文字的技巧
    在Web设计中围绕在Web设计中,有时可以将图像浮动到页面右下角,从而使文本围绕它缠绕。这可以在有效地展示图像的同时创建一个吸引人的视觉效果。 css位置在右下角,使用css float and clear properties: img { 浮点:对; ...
    编程 发布于2025-05-17

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3