搜索
首页web前端css教程嘿,让我们使用Jamstack创建功能日历应用程序

嘿,让我们使用Jamstack创建功能日历应用程序

嘿,让我们使用Jamstack创建功能日历应用程序

我一直想知道动态安排是如何工作的,所以我决定进行广泛的研究,学习新事物并撰写有关旅程的技术部分。警告您是很公平的:我涵盖的所有内容都是三个星期的研究凝结成一篇文章。即使对初学者友好,但它还是很健康的阅读量。因此,请拉起椅子,坐下来,让我们冒险。

我的计划是构建看起来像Google日历的东西,但仅展示了三个核心功能:

  1. 在日历上列出所有现有事件
  2. 创建新事件
  3. 根据创建期间选择的日期计划和电子邮件通知。时间表应运行一些代码,以在时间合适时通过电子邮件发送给用户。

漂亮,对吗?到本文的结尾,因为这就是我们将要做的。

我要求我的代码在以后或延期时间运行的唯一知识是Cron Jobs。使用CRON作业的最简单方法是在您的代码中静态定义作业。这是临时的 -从静态上讲,我不能简单地安排像Google日历这样的事件,并可以轻松地更新我的Cron代码。如果您在写Cron Triggers方面经验丰富,您会感到我的痛苦。如果不是,您很幸运,您可能永远不必以这种方式使用Cron。

为了详细说明我的挫败感,我需要根据HTTP请求的有效载荷触发时间表。有关此时间表的日期和信息将通过HTTP请求传递。这意味着没有办法事先知道预定日期之类的事情。

我们(我和我的同事)想出了一种做出这项工作的方法,并且在莎拉·德拉斯纳(Sarah Drasner)关于持久功能的文章的帮助下,我理解了我需要学习的东西(并且在此方面不理)。您将了解我在本文中工作的所有内容,从活动创建到电子邮件计划再到日历列表。这是该应用程序中的视频:

https://www.youtube.com/watch?v=SIMAM4FXPOO&

您可能会注意到微妙的延迟。这与时间表的执行时间或运行代码无关。我正在使用一个免费的SendGrid帐户进行测试,我怀疑该帐户具有某种形式的延迟。您可以通过在不发送电子邮件的情况下测试无服务器函数来确认这一点。您会注意到该代码在计划的时间正好运行。

工具和架构

这是该项目的三个基本单元:

  1. React Frontend :日历UI,包括用于创建,更新或删除事件的UI。
  2. 8Base GraphQl :应用程序的后端数据库层。这是我们将存储,阅读和更新日期的地方。有趣的部分是您不会为此后端编写任何代码。
  3. 耐用功能:耐用功能是一种无服务器功能,具有从先前执行中记住其状态的功能。这就是替代Cron作业的原因,并解决了我们前面描述的临时问题。

本文的其余部分将根据我们在上面看到的三个单元进行三个主要部分。我们将彼此接一个,将它们构建,对其进行测试,甚至部署工作。在进行此操作之前,让我们使用我为我们开始的开头项目进行设置。

项目回购

入门

您可以以不同的方式设置此项目 - 要么是一个全堆栈项目,其中一个项目中的三个单元,要么是一个独立项目,每个单元都生活在其根本上。好吧,我选择了第一个,因为它更简洁,更易于教学,并且可以管理,因为它是一个项目。

该应用程序将是一个创建反应项目,我为我们降低设置的障碍而做了一个开始。它带有补充代码和逻辑,我们不需要解释,因为它们不超出文章的范围。为我们设置以下内容:

  1. 日历组件
  2. 表示事件表格的模态和弹出组件
  3. 事件形式组件
  4. 一些GraphQl逻辑查询和突变数据
  5. 耐用的无服务器功能脚手架,我们将编写调度程序

提示:我们关心的每个现有文件都在文档顶部都有一个评论块。该注释块告诉您代码文件中当前正在发生的事情和待办事项部分,该部分描述了我们接下来需要做的事情。

首先将起动器形式github克隆出来:

 git克隆-b启动器-Single-Branch https://github.com/christiannwamba/calendar-app.git

安装root package.json文件以及无服务器软件包中描述的NPM依赖项。

 NPM安装

精心策划的耐用功能用于调度

在我们了解这个术语是什么之前,我们需要首先摆脱两个词:编排耐用

编排最初用于描述一个协调良好的事件,动作等的组装。在计算中,它大量借用了计算机系统的平滑协调。关键词是坐标。我们需要以协调的方式将两个或多个系统单元放在一起。

耐用的用来描述具有持久更长的出色功能的任何事物。

将系统协调和持久放在一起,您将获得持久的功能。如果Azure的无服务器功能,这是最强大的功能。耐用的功能基于我们现在知道的这两个功能:

  1. 它们可用于组装两个或多个功能的执行并协调它们,以免发生种族条件(编排)。
  2. 耐用的功能记住事情。这就是使它如此强大的原因。它打破了HTTP:无状态的第一规则。耐用的功能使其状态保持完整,无论他们必须等待多长时间。创建未来1,000年的时间表,持久功能将在一百万年后执行,同时记住在触发之日传递给它的参数。这意味着耐用的功能是有状态的

这些耐用性功能可以解锁无服务器功能的新机会,这就是为什么我们今天探索这些功能之一的原因。我强烈建议莎拉(Sarah)的文章又一次,用于可视化的某些耐用功能的可能用例的可视化版本。

我还对我们今天要写的耐用功能的行为进行了视觉表示。以动画架构图为动画:

来自外部系统(8Base)的数据突变通过调用HTTP触发器触发编排。然后触发器调用安排事件的编排功能。当到期执行时间时,编排功能再次调用,但是这次跳过了编排并调用活动功能。活动函数是动作表演者。这是发生的实际事情,例如“发送电子邮件通知”。

创建精心策划的耐用功能

让我通过使用VS代码来引导您通过创建功能。您需要两件事:

  1. 一个Azure帐户
  2. VS代码

两者都设置后,您需要将它们绑在一起。您可以使用VS代码扩展名和节点CLI工具来执行此操作。从安装CLI工具开始:

NPM安装-g azure-functions核心工具

# 或者

酿造淡淡的Azure/功能
酿造安装Azure-funnctions核心工具

接下来,安装Azure函数扩展程序以使VS代码与Azure上的函数相关。您可以从我上一篇文章中阅读有关设置Azure功能的更多信息。

现在您已经完成了所有设置,让我们开始创建这些功能。我们将创建的功能将映射到以下文件夹。

文件夹 功能
日程 耐用的HTTP触发器
SchooporeSterator 耐用的编排
sendemail 耐用的活动

从扳机开始。

  1. 单击Azure扩展图标,然后按照下图创建时间表功能
  2. 由于这是第一个功能,因此我们选择文件夹图标来创建一个功能项目。之后的图标创建一个单个函数(不是项目)。
  3. 单击“浏览”,然后在项目内部创建无服务器文件夹。选择新的无服务器文件夹。
  4. 选择JavaScript作为语言。如果您的果酱是打字稿(或任何其他语言),请自由。
  5. 选择耐用功能HTTP启动器。这是触发器。
  6. 将第一个功能命名为时间表

接下来,创建编排者。而不是创建功能项目,而是创建功能。

  1. 单击功能图标:
  2. 选择耐用功能编排。
  3. 给它一个名字,scheworestertor,然后命中Enter
  4. 您将被要求选择一个存储帐户。乐队使用存储来保留在过程中的功能状态。
  5. 在您的Azure帐户中选择订阅。就我而言,我选择了免费的试用订阅。
  6. 请按照剩余的步骤创建存储帐户。

最后,重复上一步以创建活动。这次,以下内容应不同:

  • 选择耐用的功能活动。
  • 命名它。
  • 不需要存储帐户。

使用耐用的HTTP触发器进行调度

无需触摸的无服务器/附表/index.js中的代码。这是最初使用VS代码或CLI工具脚手架的函数时的样子。

 const df = require(“耐用功能”);
模块。Exports= async函数(context,req){
  const client = df.getClient(context);
  const instanceID =等待client.startnew(req.params.functionName,undefined,req.body);
  context.log(``启动以id ='$ {instanceid}'。
  返回client.createcheckstatusresponse(context.bindingdata.req,instanceid);
};

这里发生了什么?

  1. 我们正在基于请求上下文的客户端创建一个耐用的功能。
  2. 我们使用客户端的startNew()函数来调用编排器。乐队函数名称被作为第一个通过params对象作为startNew()的参数传递。 Req.body也将传递给StartNew()作为第三个参数,该论点转发给了编排者。
  3. 最后,我们返回一组数据,可用于检查编目功能的状态,甚至可以在完成之前取消该数据的状态。

调用上述函数的URL看起来像这样:

 http:// localhost:7071/api/排请求/{functionName}

where functionName是传递给startnew的名称。在我们的情况下,应该是:

 // LOCALHOST:7071/API/编排/Scheperorchestrator

也很高兴知道您可以更改此URL的外观。

用耐用的编排编排

HTTP Trigger Startnew调用呼叫调用函数,该函数基于我们传递给它的名称。该名称对应于保存编排逻辑的功能和文件夹的名称。无服务器/scheperorchestrator/index.js文件导出耐用功能。用以下内容替换内容:

 const df = require(“耐用功能”);
Module.exports = df.orchestrator(function*(context){
  const input = context.df.getInput()
  // todo- 1
  
  // todo- 2
});

编目函数使用Context.df.getInput()从HTTP触发器检索请求主体。

替换todo -1用以下代码行替换,这可能是整个演示中最重要的事情:

收益上下文。df.createTimer(新日期(input.startat))

该线路确实使用耐用函数来根据通过HTTP触发器从请求主体传递的日期创建一个计时器。

当此功能执行并到达此处时,它将触发计时器并临时保释。时间表到期时,它将返回,跳过此行,并拨打以下行,您应该使用该行代替TODO -2。

返回收益率上下文。df.callactivity('sendemail',输入);

该功能将调用活动函数发送电子邮件。我们还将有效载荷作为第二个参数。

这就是完整的功能的样子:

 const df = require(“耐用功能”);

Module.exports = df.orchestrator(function*(context){
  const input = context.df.getInput()
    
  收益上下文。df.createTimer(新日期(input.startat))
    
  返回收益率上下文。df.callactivity('sendemail',输入);
});

发送带有持久活动的电子邮件

当定期时间表时,编排者会回来调用活动。活动文件属于无服务器/sendemail/index.js。用以下内容更换其中的内容:

 const sgmail = require('@sendgrid/mail');
sgmail.setapikey(process.env ['sendgrid_api_key']);

模块。exports= async函数(上下文){
  // todo- 1
  const msg = {}
  // todo- 2
  返回msg;
};

当前,它导入SendGrid的邮件并设置API密钥。您可以按照以下说明获取API密钥。

我正在设置环境变量中的钥匙,以确保我的凭证安全。您可以通过在serverless/local.settings.json中使用sendgrid键在serverless/local.settings.json中使用sendgrid_api_key键来安全地存储自己的方式:

 {
  “ isencrypted”:false,
  “值”:{
    “ azurewebjobsstorage”:“ ”,
    “ functions_worker_runtime”:“ node”,
    “ sendgrid_api_key”:“ ”
  }
}

替换todo -1用以下行:

 const {电子邮件,title,startat,description} = context.bindings.payload;

这从编目函数的输入中汲取了事件信息。输入连接到上下文。结合。有效载荷可以是您命名的任何东西,因此请转到无服务器/sendemail/function.json并将名称值更改为有效负载:

 {
  “绑定”:[
    {
      “名称”:“有效载荷”,
      “ type”:“ ActivityTrigger”,
      “方向”:“在”
    }
  这是给出的
}

接下来,更新todo -2带有以下块发送电子邮件:

 const msg = {
  到:电子邮件,
  来自:{电子邮件:'[[电子邮件保护]',名称:'codebeast日历'},
  主题:`事件:$ {title}`,
  html:`<h4 id="title-startat"> $ {title} @ $ {startat} </h4> <p> $ {description} </p>``
};
sgmail.send(msg);

返回msg;

这是完整版本:

 const sgmail = require('@sendgrid/mail');
sgmail.setapikey(process.env ['sendgrid_api_key']);

模块。exports= async函数(上下文){
  const {电子邮件,title,startat,description} = context.bindings.payload;
  const msg = {
    到:电子邮件,
    来自:{电子邮件:'[[电子邮件保护]',名称:'codebeast日历'},
    主题:`事件:$ {title}`,
    html:`<h4 id="title-startat"> $ {title} @ $ {startat} </h4> <p> $ {description} </p>``
  };
  sgmail.send(msg);

  返回msg;
};

将功能部署到Azure

将功能部署到Azure很容易。这仅仅是从VS代码编辑器中单击。单击循环图标以部署并获得部署URL:

仍然和我在一起吗?您正在取得巨大进步!在这里休息一下,午睡,伸展或休息是完全可以的。我在写这篇文章时肯定做到了。

带有8Base的数据和GraphQl层

我对8base的最简单描述和理解是“ GraphQl的firebase”。 8base是您可以想到的任何类型的应用程序的数据库层,最有趣的方面是基于GraphQl。

描述8个键在堆栈中的最佳方法是绘制场景的图片。

想象一下,您是一家自由职业者开发人员,拥有小型到中等规模的合同,为客户建立电子商务商店。您的核心技能在网络上,因此您的后端不太舒适。虽然您可以写一些节点。

不幸的是,电子商务需要管理库存,订单管理,管理购买,管理身份验证和身份等。在基本层面上“管理”只是意味着数据CRUD和数据访问。

与其在后端代码中创建,阅读,更新,删除和管理访问访问的多余和无聊的过程,如果我们可以在UI中描述这些业务需求,该怎么办?如果我们可以创建允许我们配置CRUD操作,AUTH和访问的表,该怎么办?如果我们有这样的帮助,只专注于构建前端代码和编写查询怎么办?我们刚刚描述的一切都通过8base解决

这是一个无后端应用程序的架构,它依赖于8个键的数据层:

创建一个用于事件存储和检索的8base表

在创建表格之前,我们需要做的第一件事是创建一个帐户。有一个帐户后,创建一个工作空间,该工作空间可以保留给定项目的所有表和逻辑。

接下来,创建一个表,命名表事件并填写表字段。

我们需要配置访问级别。目前,每个用户都没有什么可隐藏的,因此我们可以打开对我们创建的事件表的所有访问:

设置auth具有8个键,因为它与auth0集成在一起。如果您的实体需要受到保护或想扩展我们的示例以使用AUTH,请疯狂。

最后,抓住您的端点URL以供在React应用中使用:

测试操场上的GraphQl查询和突变

只是为了确保我们准备将URL带到野外并开始构建客户端,让我们首先使用GraphQL操场测试API,然后看看设置是否还不错。单击探险家。

将以下查询粘贴到编辑器中。

询问 {
  eventslist {
    数数
    项目 {
      ID
      标题
      Startat
      Endat
      描述
      Allday
      电子邮件
    }
  }
}

我通过8Base UI创建了一些测试数据,并且在运行时会收回结果:

您可以使用探索页面右端的架构文档探索整个数据库。

日历和事件形式接口

我们项目的第三个(也是最后一个)单元是构建用户界面的React应用程序。有四个主要组件构成了UI,其中包括:

  1. 日历:列出所有现有事件的日历UI
  2. 事件模态:一种反应模式,它渲染事件形式组件创建一个组件
  3. 事件popover: popover UI读取单个事件,使用EventForm或Delete Event更新事件
  4. 事件表格:用于创建新事件的HTML表格

在我们直接深入日历组件之前,我们需要设置React React Apollo客户端。 React Apollo提供商为您提供了使用React模式查询GraphQL数据源的工具。原始提供商允许您使用高阶组件或渲染道具来查询和突变数据。我们将向原始提供商使用包装器,该包装器允许您使用React钩查询和突变。

在src/index.js中,导入React Apollo Hooks和Todo中的8base客户端-1:

从“ react-apollo-hooks”中导入{apolloprovider};
从'@8base/apollo-client'导入{八baseapolloclient};

在todo -2,用端点URL配置客户端,我们在8base设置阶段中获得:

 const uri ='https://api.8base.com/cjvuk51i0000701ss0hvvcbnxg';

const apolloclient =新的八baseapolloclient({{
  Uri:Uri,
  withauth:false
});

使用此客户端将整个应用程序树包裹在TODO上的提供商-3:

 Reactdom.render(
  <apolloprovider client="{apolloclient}">
    <app></app>
  </apolloprovider>,
  document.getElementById('root')
);

在日历上显示事件

日历组件在应用程序组件内渲染,并从NPM渲染bigcalendar组件。然后 :

  1. 我们渲染日历,其中包括事件列表。
  2. 我们为日历提供了一个自定义的弹出式(EventPopover)组件,该组件将用于编辑事件。
  3. 我们渲染将用于创建新事件的模态(事件模式)。

我们唯一需要更新的是事件列表。我们不使用静态事件,而是要查询所有商店事件的8base。

替换todo -1用以下行:

 const {数据,错误,加载} = usequery(events_query);

从NPM导入USEQUERY库和文件开头的Events_query:

从'react-apollo-hooks'导入{usequery};
从'../ ../ queries'导入{events_query};

events_query与我们在8Base Explorer中测试的查询完全相同。它生活在SRC/查询中,看起来像这样:

导出const events_query = gql`
  询问 {
    eventslist {
      数数
      项目 {
        ID
        ...
      }
    }
  }
`;

让我们添加一个简单的错误,并在todo上加载处理程序-2:

 if(error)return console.log(error);
  如果(加载)
    返回 (
      <div classname="“" calendar>
        <p>加载... </p>
      </div>
    );

请注意,日历组件使用EventPopover组件渲染自定义事件。您还可以观察到日历组件文件也呈现EventModal。这两个组件均已为您设置,它们的唯一责任是渲染事件形式。

使用事件表单组件创建,更新和删除事件

src/组件/event/eventform.js中的组件呈现一个表单。该表格用于创建,编辑或删除事件。在Todo -1,导入UsecReateupDatemutt和usedeletemnout:

导入{usecreateupdatemuont,undereletemnount}从'./eventmunthooks'
  • USECREATEUPDATEMUNT:此突变要么根据事件已经存在,因此会创建或更新事件。
  • USED​​ERETEMENT:此突变删除了现有事件。

对这些功能的任何一个呼叫都会返回另一个功能。返回的功能可以用作均匀处理程序。

现在,继续替换todo -2呼叫两个功能:

 const createUpdateEvent = usecreateupdatemunt(
  有效载荷,
  事件,
  EventExists,
  ()=> clocemodal()
);
const deleteevent = undereletemontoution(event,()=> cockemodal());

这些是我写的自定义钩子,以包装React Apollo钩子所揭示的用户。每个钩子都会产生一个突变,并将突变变量传递到用户符号查询。在SRC/组件/事件/EventMunthooks.js中看起来如下的块是最重要的部分:

 USEMUNT(MutationType,{
  变量:{
    数据
  },,
  更新:( cache,{data})=> {
    const {eventList} = cache.readquery({{
      查询:events_query
    });
    cache.writequery({
      查询:events_query,
      数据: {
        eventslist:transformcacheupdatedata(eventlist,数据)
      }
    });
    // ..
  }
});

调用来自8Base的耐用功能HTTP触发器

我们花了很多时间来构建日历应用程序的无服务器结构,数据存储和UI层。为了回顾一下,UI将数据发送到8base进行存储, 8Base保存数据并触发耐用的功能HTTP触发器,HTTP触发器在编排中踢球,其余就是历史记录。当前,我们正在使用突变保存数据,但我们没有在8base中任何地方调用无服务器功能。

8base允许您编写自定义逻辑,这就是使其非常强大且可扩展的原因。自定义逻辑是基于在8Base数据库上执行的操作调用的简单函数。例如,我们可以设置一个逻辑,每次在表上发生突变时都被调用。让我们创建创建事件时称为的。

首先安装8base CLI:

 NPM安装-G 8Base

在日历上,应用程序项目运行以下命令以创建一个入门逻辑:

 8base Init 8base

8base Init命令创建一个新的8base逻辑项目。您可以将其传递一个目录名称,在这种情况下,我们将其命名为8base逻辑文件夹8base - 不要扭曲它。

触发调度逻辑

删除8base/src中的所有内容,然后在SRC文件夹中创建一个triggerschedule.js文件。完成此操作后,将以下内容放入文件中:

 const fetch = require('node-fetch');

模块。Exports= async event => {
  const res =等待fetch('<http>',{
    方法:“帖子”,
    正文:json.stringify(event.data),
    标题:{'content-type':'application/json'}
  }))
  const json =等待res.json();
  console.log(event,json)
  返回JSON;
};</http>

有关GraphQL突变的信息可作为数据可用。

部署功能后,将替换为您获得的URL。您可以通过转到Azure URL中的功能来获取URL,然后单击“复制URL”。

您还需要安装Node-fetch模块,该模块将从API中获取数据:

 npm安装 - 保存节点fetch

8base逻辑配置

接下来要做的是告诉8base触发此逻辑需要什么确切的突变或查询。在我们的情况下,在事件表上创建突变。您可以在8base.yml文件中描述此信息:

功能:
  triggerschedule:
    处理者:
      代码:src/triggerschedule.js
    类型:触发器
    操作:events.greate

从某种意义上说,这就是说,当事件表上发生创建突变时,请在突变发生后致电src/triggerschedule.js。

我们想部署所有的东西

在部署任何内容之前,我们需要登录到8Base帐户,我们可以通过命令行进行:

 8base登录

然后,让我们运行部署命令以在您的工作区实例中发送和设置APP逻辑。

 8base部署

测试整个流程

要在其所有荣耀中查看该应用程序,请单击日历的日子之一。您应该获得包含表单的事件模式。填写并放置未来的开始日期,以便我们触发通知。尝试与当前时间相距超过2-5分钟的日期,因为我无法更快地触发通知。

https://www.youtube.com/watch?v=SIMAM4FXPOO&

是的,去检查您的电子邮件!由于SendGrid,该电子邮件应该到达。现在,我们拥有一个应用程序,该应用程序允许我们创建事件并通过事件提交的详细信息通知。

以上是嘿,让我们使用Jamstack创建功能日历应用程序的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
光标的下一个CSS样式光标的下一个CSS样式Apr 23, 2025 am 11:04 AM

具有CSS的自定义光标很棒,但是我们可以将JavaScript提升到一个新的水平。使用JavaScript,我们可以在光标状态之间过渡,将动态文本放置在光标中,应用复杂的动画并应用过滤器。

世界碰撞:使用样式查询的钥匙帧碰撞检测世界碰撞:使用样式查询的钥匙帧碰撞检测Apr 23, 2025 am 10:42 AM

互动CSS动画和元素相互启动的元素在2025年似乎更合理。虽然不需要在CSS中实施乒乓球,但CSS的灵活性和力量的增加,可以怀疑Lee&Aver Lee&Aver Lee有一天将是一场

使用CSS背景过滤器进行UI效果使用CSS背景过滤器进行UI效果Apr 23, 2025 am 10:20 AM

有关利用CSS背景滤波器属性来样式用户界面的提示和技巧。您将学习如何在多个元素之间进行背景过滤器,并将它们与其他CSS图形效果集成在一起以创建精心设计的设计。

微笑吗?微笑吗?Apr 23, 2025 am 09:57 AM

好吧,事实证明,SVG的内置动画功能从未按计划进行弃用。当然,CSS和JavaScript具有承载负载的能力,但是很高兴知道Smil并没有像以前那样死在水中

'漂亮”在情人眼中'漂亮”在情人眼中Apr 23, 2025 am 09:40 AM

是的,让#039;跳上文字包装:Safari Technology Preview In Pretty Landing!但是请注意,它与在铬浏览器中的工作方式不同。

CSS-tricks编年史XLIIICSS-tricks编年史XLIIIApr 23, 2025 am 09:35 AM

此CSS-tricks更新了,重点介绍了年鉴,最近的播客出现,新的CSS计数器指南以及增加了几位新作者,这些新作者贡献了有价值的内容。

tailwind的@Apply功能比听起来更好tailwind的@Apply功能比听起来更好Apr 23, 2025 am 09:23 AM

在大多数情况下,人们展示了@Apply的@Apply功能,其中包括Tailwind的单个property实用程序之一(会改变单个CSS声明)。当以这种方式展示时,@Apply听起来似乎很有希望。如此明显

感觉就像我没有释放:走向理智的旅程感觉就像我没有释放:走向理智的旅程Apr 23, 2025 am 09:19 AM

像白痴一样部署的部署归结为您部署的工具与降低复杂性与添加的复杂性之间的奖励之间的不匹配。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!