跳到正文

数字和日期 ​

机器翻译

本页由英文原文机器翻译而来,尚未经过母语审校,欢迎在 GitHub 上提出修改。内容如有出入,以英文原文为准。

任务: 接收一个金额、一个数量和一个日期,并用它们计算,而不必手动转换任何东西。

yaml
# Recipe: numbers and dates arrive checked and converted, not as text.
paths:
  components: ./components
  migrations: ./migrations

datasources:
  db:
    driver: sqlite
    database: ./data/expenses.db
sql
CREATE TABLE expenses (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    amount REAL NOT NULL,
    people INTEGER NOT NULL,
    spent_on DATE NOT NULL
);

表单发送的是文本。type="decimal" 和 type="integer" 在动作运行之前把它变成数字, 所以 amount / people 可以计算。min 和 max 针对这个数字检查。type="date" 接受 YYYY-MM-DD 格式且真实存在的日期,并把它保留为文本,可以直接存入数据库。

xml
<q:component name="Expenses">
  <q:action name="split" method="POST">
    <!-- decimal and integer arrive as numbers, date as a checked date:
         the arithmetic below needs no conversion. -->
    <q:param name="amount" type="decimal" required="true" min="0.01" max="10000" />
    <q:param name="people" type="integer" required="true" min="1" max="50" />
    <q:param name="spent_on" type="date" required="true" />
    <q:query name="saved" datasource="db">
      INSERT INTO expenses (amount, people, spent_on) VALUES (:amount, :people, :spent_on)
      <q:param name="amount" value="{amount}" type="decimal" />
      <q:param name="people" value="{people}" type="integer" />
      <q:param name="spent_on" value="{spent_on}" type="string" />
    </q:query>
    <q:redirect url="/" flash="{amount} on {spent_on}: {round(amount / people, 2)} each." />
  </q:action>

  <ui:window title="Split an expense">
    <q:if condition="flash">
      <ui:alert variant="{flashType == 'error' and 'danger' or 'success'}">{flash}</ui:alert>
    </q:if>
    <!-- The form gets type="number" (with min and max) and type="date". -->
    <ui:form on-submit="split" submit="Split">
      <ui:formitem label="Amount"><ui:input bind="amount" /></ui:formitem>
      <ui:formitem label="People"><ui:input bind="people" /></ui:formitem>
      <ui:formitem label="Date"><ui:input bind="spent_on" /></ui:formitem>
    </ui:form>
  </ui:window>
</q:component>

表单得到带同样 min 和 max 的 type="number" 以及 type="date",所以浏览器先检查。 测试像脚本一样直接向服务器提交:

xml
<q:test name="the numbers arrive as numbers" page="/">
  <test:submit action="split" amount="100" people="3" spent_on="2026-09-20" />
  <test:expect redirect="/" flash="100.0 on 2026-09-20: 33.33 each." />
  <test:expect table="expenses" count="1" where="amount = 100 AND people = 3 AND spent_on = '2026-09-20'" />
</q:test>

<q:test name="text where a number goes is refused" page="/">
  <test:submit action="split" amount="ten" people="3" spent_on="2026-09-20" />
  <test:expect error="amount" message="Must be a number, got 'ten'" />
</q:test>

<q:test name="min and max hold" page="/">
  <test:submit action="split" amount="0" people="51" spent_on="2026-09-20" />
  <test:expect error="amount" message="Must be at least 0.01 (got 0.0)" />
  <test:expect error="people" message="Must be at most 50 (got 51)" />
</q:test>

<q:test name="a date that does not exist is refused" page="/">
  <test:submit action="split" amount="10" people="2" spent_on="2026-02-30" />
  <test:expect error="spent_on" message="Must be a date (YYYY-MM-DD), got '2026-02-30'" />
  <test:expect table="expenses" count="0" />
</q:test>
text
tests/split.test.q
  PASS  the numbers arrive as numbers
  PASS  text where a number goes is refused
  PASS  min and max hold
  PASS  a date that does not exist is refused
4 passed, 0 failed

参见 ACT-2 和 UI-9。

MIT 许可证 · 使用 VitePress 构建