ArcGIS JS 基础教程(46):编辑要素属性 FeatureForm

ArcGIS JS 基础教程(46):编辑要素属性 FeatureForm

零、写在前面

📌 本系列教程完整目录 :ArcGIS JS 系列基础教程(100个项目常用热门功能)

💡 在线示例 :完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验

🗂️ 专栏导航 :收藏 + 关注,专栏文章第一时间送达

❤️ 一键三连:点赞 + 评论 + 收藏


一、功能介绍

<arcgis-feature-form> 是 FeatureForm 的 Web Component 形态 ,用于渲染一个要素的属性编辑表单。它根据 FormTemplate 自动生成输入框、下拉选择等控件,配合 applyEdits 即可把修改写回图层(客户端图层写回内存)。

本课要点:

  • 用 FormTemplate 定义可编辑字段,下拉字段用 input: { type: "combo-box" } 声明;
  • 把要素赋给 featureForm.feature,组件自动渲染表单;
  • 监听 arcgisSubmit 事件,用 featureForm.getValues() 取回修改后的字段,再 applyEdits({ updateFeatures }) 写回;
  • 点击场景要素 → hitTest 拾取 → 载入表单,实现「点哪改哪」。

参考:官方 FeatureForm | arcgis-feature-form 组件

💡 三维要点 :本例图层为客户端 FeatureLayer,applyEdits 直接作用于内存 source,无需后端服务即可完成「编辑闭环」。


二、功能实现

2.1 FormTemplate 定义可编辑字段

javascript 复制代码
const formTemplate = new FormTemplate({
    title: "编辑监测点属性",
    elements: [
        { type: "field", fieldName: "NAME", label: "名称" },
        {
            type: "field", fieldName: "CATEGORY", label: "类别",
            input: { type: "combo-box", options: [
                { label: "A类", value: "A类" },
                { label: "B类", value: "B类" },
                { label: "C类", value: "C类" }
            ]}
        },
        {
            type: "field", fieldName: "STATUS", label: "运行状态",
            input: { type: "combo-box", options: [
                { label: "正常", value: "正常" },
                { label: "预警", value: "预警" },
                { label: "故障", value: "故障" }
            ]}
        },
        { type: "field", fieldName: "VALUE", label: "实时数值" }
    ]
});

const featureForm = document.getElementById("featureForm");
featureForm.formTemplate = formTemplate;

2.2 载入要素 / 提交保存

javascript 复制代码
// 点击要素 → 载入表单
view.on("click", async (event) => {
    const hit = await view.hitTest(event);
    const graphic = (hit.results || []).find(r => r.type === "graphic" && r.layer === monitorLayer);
    if (graphic) featureForm.feature = graphic.graphic;
});

// 提交:arcgisSubmit → 取回值 → applyEdits 写回
featureForm.addEventListener("arcgisSubmit", async () => {
    const values = await featureForm.getValues();
    const feature = featureForm.feature;
    feature.attributes = { ...feature.attributes, ...values };
    await monitorLayer.applyEdits({ updateFeatures: [feature] });
});

三、功能应用

应用场景 说明
属性编辑 表单化修改要素字段
下拉约束 input: { type: "combo-box", options } 限定取值
点选编辑 拾取即载入,所见即所改
客户端闭环 applyEdits 写回内存 source

四、核心代码

📦 完整代码 已保存至 sample/lesson46_feature_form.html,可直接在浏览器打开。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第46课:编辑要素属性 FeatureForm</title>
    <link rel="stylesheet" href="https://js.arcgis.com/5.0/esri/themes/light/main.css">
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft YaHei", sans-serif; }
        #mapContainer { width: 100vw; height: 100vh; }
        .page-title {
            position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(255,255,255,0.95); padding: 10px 24px; border-radius: 6px;
            font-size: 18px; font-weight: bold; z-index: 100;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        }
        /* 左侧:arcgis-feature-form 组件面板 */
        .form-panel {
            position: absolute; top: 80px; left: 20px;
            width: 300px; max-height: 75vh; overflow: auto;
            background: rgba(255,255,255,0.97); padding: 12px; border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            z-index: 100;
        }
        .form-panel h3 { margin: 0 0 8px 0; font-size: 14px; color: #333; }
        #featureForm { width: 100%; }
        .form-actions { margin-top: 10px; display: flex; gap: 8px; }
        .form-actions button {
            flex: 1; padding: 8px 0;
            border: 1px solid #1890ff; border-radius: 4px;
            background: #1890ff; color: #fff; cursor: pointer; font-size: 13px;
        }
        .form-actions button.ghost {
            background: white; color: #1890ff;
        }
        .form-actions button:hover { opacity: 0.9; }

        .control-panel {
            position: absolute; top: 80px; right: 20px;
            background: rgba(255,255,255,0.95); padding: 16px; border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            z-index: 100; min-width: 240px;
        }
        .control-panel h3 { margin: 0 0 8px 0; font-size: 14px; color: #333; }
        .btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
        .btn-row button {
            flex: 1; min-width: 100px; padding: 6px 0;
            border: 1px solid #d9d9d9; border-radius: 4px;
            background: white; cursor: pointer; font-size: 12px;
        }
        .btn-row button:hover { border-color: #1890ff; color: #1890ff; }
        .btn-row button.on { background: #1890ff; color: white; border-color: #1890ff; }
        .info-card {
            margin-top: 10px; padding: 10px 12px;
            background: #f0f5ff; border-radius: 6px;
            border-left: 3px solid #1890ff; font-size: 12px; line-height: 1.8;
        }
        .info-card .val { font-weight: bold; color: #1890ff; }
        .status-text {
            position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(0,0,0,0.7); color: white; padding: 8px 20px;
            border-radius: 20px; font-size: 13px; z-index: 100; pointer-events: none;
            white-space: nowrap;
        }
    </style>
</head>
<body>
<h1 class="page-title">第46课:编辑要素属性 FeatureForm</h1>

<div class="form-panel">
    <h3>✏️ 要素属性编辑(arcgis-feature-form)</h3>
    <arcgis-feature-form id="featureForm"></arcgis-feature-form>
    <div class="form-actions">
        <button id="btnSave">💾 保存修改</button>
        <button id="btnClear" class="ghost">清空</button>
    </div>
</div>

<div class="control-panel">
    <h3>🖱️ 操作</h3>
    <div class="btn-row">
        <button id="btnLoad">📥 载入首个要素</button>
        <button id="btnPick" class="on">🎯 点击拾取编辑</button>
    </div>
    <div class="info-card">
        <div>当前编辑:<span class="val" id="editName">未载入</span></div>
        <div>保存次数:<span class="val" id="saveCount">0</span></div>
    </div>
</div>

<div class="status-text" id="statusText">FeatureForm | 编辑要素属性并写回图层</div>

<div id="mapContainer"></div>

<script type="module">
    const [Map, SceneView, FeatureLayer, FormTemplate, Graphic] = await $arcgis.import([
        "@arcgis/core/Map.js",
        "@arcgis/core/views/SceneView.js",
        "@arcgis/core/layers/FeatureLayer.js",
        "@arcgis/core/form/FormTemplate.js",
        "@arcgis/core/Graphic.js"
    ]);
    const getTianditu = await $arcgis.import("https://openlayers.vip/examples/resources/tianditu.js");

    const vecLayers = getTianditu.default({ type: "vec_w" });
    const map = new Map({ basemap: { baseLayers: [vecLayers.base, vecLayers.anno] } });

    const view = new SceneView({
        container: "mapContainer", map: map,
        camera: {
            position: { longitude: 121.498, latitude: 31.205, z: 4000 },
            heading: 20, tilt: 60
        }
    });

    // ===== 监测点图层(可编辑客户端 FeatureLayer) =====
    const monitorLayer = (() => {
        const cats = ["A类", "B类", "C类"];
        const colors = {
            "A类": [227, 74, 51],
            "B类": [56, 135, 220],
            "C类": [60, 180, 110]
        };
        const statuses = ["正常", "预警", "故障"];
        const graphics = [];
        for (let i = 0; i < 90; i++) {
            graphics.push(new Graphic({
                geometry: {
                    type: "point",
                    x: 121.46 + Math.random() * 0.08,
                    y: 31.18 + Math.random() * 0.06,
                    z: 80 + Math.random() * 400,
                    spatialReference: { wkid: 4326 }
                },
                attributes: {
                    OBJECTID: i + 1,
                    NAME: "监测点" + (i + 1),
                    CATEGORY: cats[i % 3],
                    STATUS: statuses[Math.floor(Math.random() * 3)],
                    VALUE: (Math.random() * 100).toFixed(1)
                }
            }));
        }
        return new FeatureLayer({
            source: graphics,
            objectIdField: "OBJECTID",
            fields: [
                { name: "OBJECTID", type: "oid" },
                { name: "NAME", type: "string" },
                { name: "CATEGORY", type: "string" },
                { name: "STATUS", type: "string" },
                { name: "VALUE", type: "double" }
            ],
            geometryType: "point",
            spatialReference: { wkid: 4326 },
            title: "实时监测点",
            elevationInfo: { mode: "relative-to-ground" },
            renderer: {
                type: "unique-value",
                field: "CATEGORY",
                uniqueValueInfos: cats.map(cat => ({
                    value: cat,
                    symbol: {
                        type: "point-3d",
                        symbolLayers: [{
                            type: "object",
                            resource: { primitive: "sphere" },
                            width: 220, height: 220, depth: 220,
                            material: { color: colors[cat] }
                        }]
                    }
                }))
            },
            popupTemplate: {
                title: "{NAME}",
                content: [
                    { type: "fields", fieldInfos: [
                        { fieldName: "CATEGORY", label: "类别" },
                        { fieldName: "STATUS", label: "运行状态" },
                        { fieldName: "VALUE", label: "实时数值" }
                    ]}
                ]
            }
        });
    })();
    map.add(monitorLayer);

    // ===== FormTemplate:定义可编辑字段 =====
    const formTemplate = new FormTemplate({
        title: "编辑监测点属性",
        elements: [
            { type: "field", fieldName: "NAME", label: "名称" },
            {
                type: "field", fieldName: "CATEGORY", label: "类别",
                input: { type: "combo-box", options: [
                    { label: "A类", value: "A类" },
                    { label: "B类", value: "B类" },
                    { label: "C类", value: "C类" }
                ]}
            },
            {
                type: "field", fieldName: "STATUS", label: "运行状态",
                input: { type: "combo-box", options: [
                    { label: "正常", value: "正常" },
                    { label: "预警", value: "预警" },
                    { label: "故障", value: "故障" }
                ]}
            },
            { type: "field", fieldName: "VALUE", label: "实时数值" }
        ]
    });

    // ===== arcgis-feature-form 组件 =====
    const featureForm = document.getElementById("featureForm");
    featureForm.formTemplate = formTemplate;

    let pickMode = true;
    let saveCount = 0;

    function loadFeature(feature) {
        featureForm.feature = feature;   // 组件渲染表单
        document.getElementById("editName").textContent =
            feature.attributes.NAME || "要素";
        setStatus("已载入:" + (feature.attributes.NAME || "要素"));
    }

    // 点击拾取要素载入表单
    view.on("click", async (event) => {
        if (!pickMode) return;
        const hit = await view.hitTest(event);
        const graphic = (hit.results || []).find(r => r.type === "graphic" && r.layer === monitorLayer);
        if (graphic) loadFeature(graphic.graphic);
        else setStatus("请点击监测点要素");
    });

    // ===== 提交保存(arcgisSubmit → applyEdits) =====
    featureForm.addEventListener("arcgisSubmit", async () => {
        try {
            const values = await featureForm.getValues();
            const feature = featureForm.feature;
            if (!feature) return;
            feature.attributes = { ...feature.attributes, ...values };
            await monitorLayer.applyEdits({ updateFeatures: [feature] });
            saveCount += 1;
            document.getElementById("saveCount").textContent = saveCount;
            setStatus("已保存:" + feature.attributes.NAME +
                "(类别=" + feature.attributes.CATEGORY + ")");
        } catch (e) {
            console.warn("保存失败:", e.message);
            setStatus("保存失败:" + e.message);
        }
    });

    await view.when();
    await monitorLayer.when();
    view.goTo(monitorLayer.fullExtent, { duration: 1500 }).catch(() => {});
    setStatus("FeatureForm 就绪 | 点击要素或载入首个要素进行编辑");

    function setStatus(msg) { document.getElementById("statusText").textContent = msg; }

    // 载入首个要素
    document.getElementById("btnLoad").addEventListener("click", async () => {
        const res = await monitorLayer.queryFeatures({
            where: "1=1", outFields: ["*"], returnGeometry: false, num: 1
        });
        if (res.features[0]) loadFeature(res.features[0]);
    });
    // 保存(触发组件 submit,进而触发 arcgisSubmit)
    document.getElementById("btnSave").addEventListener("click", () => {
        if (!featureForm.feature) { setStatus("请先载入要素"); return; }
        featureForm.submit();
    });
    // 清空
    document.getElementById("btnClear").addEventListener("click", () => {
        featureForm.feature = null;
        document.getElementById("editName").textContent = "未载入";
        setStatus("已清空表单");
    });
    // 拾取模式开关
    document.getElementById("btnPick").addEventListener("click", function () {
        pickMode = !pickMode;
        this.classList.toggle("on", pickMode);
        setStatus(pickMode ? "点击拾取已开启" : "点击拾取已关闭");
    });
</script>
</body>
</html>

五、在线示例

🔗 在线体验 :https://southjor.github.io/arcgis-examples/lessons/lesson46.html

操作说明:

  1. 左侧为 <arcgis-feature-form> 组件,按 FormTemplate 渲染名称、类别(下拉)、状态(下拉)、数值字段。
  2. 「载入首个要素」或「点击场景中的监测点」把要素载入表单。
  3. 修改后点「保存修改」→ arcgisSubmit → getValues() → applyEdits({updateFeatures}) 写回图层。

六、关键 API 说明

API 说明
FormTemplate 定义表单字段与编辑器类型
input: { type: "combo-box" } 下拉选择编辑器(options 限定取值)
arcgis-feature-form.feature 赋要素即渲染表单
arcgisSubmit 事件 表单提交触发
featureForm.getValues() 取回修改后的字段值
layer.applyEdits({updateFeatures}) 写回图层(客户端写内存)

参考链接: FeatureForm API


七、系列导航

⬅️ 上一篇 :ArcGIS JS 基础教程(45):鼠标悬停显示信息

➡️ 下一篇 :ArcGIS JS 基础教程(47):内置组件集成


💡 小贴士 :<arcgis-feature-form> 把「属性编辑」变成了声明式表单,你只需定义 FormTemplate,下拉、校验、提交全部由组件接管。配合客户端 FeatureLayer 的 applyEdits,无需任何后端即可跑通完整的编辑闭环。

相关推荐
梅如你1 天前
ISPRS | 1990-2020年间中国人工林与天然林的空间分布数据
arcgis·gis·遥感
limit00754 天前
公开 WMTS / XYZ 瓦片接入 Cesium:6 类常见问题与排查顺序
chrome·低代码·arcgis·web3·旅游
limit00755 天前
三维模型悬空与切片偏移的排查顺序
低代码·3d·arcgis·web3·安卓·webgl
最强小杰5 天前
Continue + minimax-m3 接入教程:协议、模型 ID、404 与工具调用排错
arcgis·ai
2501_934304409 天前
新增2026年! 2012-2026年全国逐月夜间灯光数据
arcgis·gis数据
梅如你10 天前
中国2026年路网数据
arcgis·gis·遥感
梦想的初衷~10 天前
GPT × ArcGIS 强强联合:AI助力数据处理、空间分析、可视化及多案例综合应用实战教程
人工智能·gpt·arcgis
时间的情敌11 天前
《WebGIS EPSG:4326 与 EPSG:3857 墨卡托投影转换》
arcgis·mars2d
时间的情敌11 天前
《Mars2D 快速上手,从零搭建三维 GIS 项目》
arcgis·mars2d