AppML 案例 Customers

客户管理案例:必填校验、数字范围、搜索过滤。

Intermediate
Key Points客户案例的重点是校验与搜索:必填、额度范围、按城市过滤。

数据

JSON
[
  { "id": 1, "company": "北京云图科技", "contact": "张伟", "city": "北京", "credit": 50000 },
  { "id": 2, "company": "上海海联贸易", "contact": "李娜", "city": "上海", "credit": 80000 },
  { "id": 3, "company": "广州南方物流", "contact": "王强", "city": "广州", "credit": 35000 },
  { "id": 4, "company": "北京中科数据", "contact": "赵敏", "city": "北京", "credit": 120000 }
]

模型

XML
<appml>
  <datasource>
    <query>data/customers.json</query>
  </datasource>
  <html>
    <title>客户管理</title>
  </html>
  <items>
    <item name="id"      label="编号"   type="number" primarykey="true" readonly="true"/>
    <item name="company" label="公司"   required="true" maxlength="60"/>
    <item name="contact" label="联系人" required="true" maxlength="20"/>
    <item name="city"    label="城市"   default="北京"/>
    <item name="credit"  label="额度"   type="number" min="0" max="1000000" step="1000" default="0"/>
  </items>
</appml>

搜索

HTML
<appml-app src="models/customers.xml" id="cust">
  <appml-view view="search"></appml-view>
  <appml-view view="list"></appml-view>
  <appml-view view="form"></appml-view>
</appml-app>

<div style="margin-top:1rem">
  <button onclick="showCity('北京')">只看北京</button>
  <button onclick="showCity('')">显示全部</button>
</div>

<script>
  function showCity(city) {
    const app = document.getElementById('cust').appml;
    const rows = city ? app.data.filter(r => r.city === city) : app.data;
    renderTable(rows);
  }

  function renderTable(rows) {
    const tbody = document.querySelector('table tbody');
    if (!tbody) return;
    tbody.innerHTML = rows.map(r => `
      <tr>
        <td>${r.id}</td>
        <td>${escapeHtml(r.company)}</td>
        <td>${escapeHtml(r.contact)}</td>
        <td>${escapeHtml(r.city)}</td>
        <td>${r.credit.toLocaleString()}</td>
      </tr>`).join('');
  }

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, c =>
      ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
  }
</script>
Output
编号  公司            联系人  城市  额度
1     北京云图科技    张伟    北京  50,000
4     北京中科数据    赵敏    北京  120,000

Filtering app.data and redrawing gives the most control. Redrawing drops event bindings the framework attached, so row buttons stop working — rebind or use event delegation.

额度格式化

JavaScript
function money(n) {
  return '¥' + Number(n).toLocaleString('zh-CN', { minimumFractionDigits: 2 });
}

money(50000);      // ¥50,000.00

toLocaleString handles grouping better than manual string work, and minimumFractionDigits: 2 keeps 50000 as 50,000.00 rather than 50,000.

必填与范围校验

RuleWhereEffect
公司必填required="true"空值时表单不提交
额度 0–100 万min="0" max="1000000"超出范围标红
额度步进 1000step="1000"上下箭头按千递增
公司名长度maxlength="60"输入框限长
Frontend validation stops typos, not attackers: min, max and required can all be removed in devtools. Enforce NOT NULL and CHECK in the database and validate again in the API.

搜索的两种做法

JavaScript
// 做法一:前端过滤(数据量小,几百条以内)
const hits = app.data.filter(r =>
  r.company.includes(kw) || r.contact.includes(kw));

// 做法二:后端查询(数据量大)
fetch(`/api/customers?kw=${encodeURIComponent(kw)}`)
  .then(r => r.json())
  .then(rows => renderTable(rows));

Frontend filtering ships every row to the browser: fine for thousands, sluggish for tens of thousands. Beyond that, query the backend and keep only the current page in app.data.

Friends & Partners