hap-view-plugin

此技能提供创建和开发明道云 HAP 自定义视图插件的完整工作流程和开发规范。

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hap-view-plugin" with this command: npx skills add garfield-bb/hap-skills-collection/garfield-bb-hap-skills-collection-hap-view-plugin

HAP 自定义视图插件开发技能

此技能提供创建和开发明道云 HAP 自定义视图插件的完整工作流程和开发规范。

关于此技能

此技能专门用于开发明道云 HAP(High-performance Application Platform)自定义视图插件。通过集成的脚手架工具,可以快速创建 React 基础示例模板项目,安装依赖并启动开发环境。

前置条件

在使用此技能前,确保:

  • 已安装 16.20 或更高版本的 Node.js

  • 拥有明道云开发者账号和插件开发权限

  • 了解基本的 React 开发知识

开发环境配置

Cursor 编辑器配置

下载 mdye-cursorrules.md 文件并复制其中内容到视图开发项目根目录下的 .cursorrules 文件中,即可在 Cursor 编辑器中获得明道云视图插件开发的智能提示和代码规范检查。

教学 DEMO

请下载明道云视图插件开发教学 DEMO,此插件为开发者提供直观、可交互的 API 使用实例。

核心功能

  1. 安装 mdye-cli 工具
  • 全局安装插件开发专用的命令行工具

  • 验证工具安装是否成功

  1. 初始化本地项目
  • 创建唯一的插件项目文件夹

  • 使用 React 基础示例模板

  • 生成项目配置文件

  1. 安装项目依赖
  • 安装项目所需的 npm 依赖包

  • 配置开发环境

  1. 启动开发环境
  • 启动本地开发服务器

  • 支持热重载和实时预览

  • 提供线上调试能力

开发工作流程

步骤 1:检查并安装 mdye-cli 工具

首先检查是否已安装:

mdye --version

如果显示版本号,说明已安装,可以跳过安装步骤。

如果未安装,根据系统安装:

Mac OS 用户:

sudo npm install -g mdye-cli

Windows/Linux 用户:

npm install -g mdye-cli

验证安装:

mdye --version

步骤 2:初始化本地项目

创建项目命令:

mdye init view --id 693d2fed8474b99be3d3c12e-69563e5df03728c888c04f05 --template React

参数说明:

  • --id : 插件 ID(示例 ID,实际使用时需要替换)

  • --template React : 使用 React 基础示例模板

项目结构:

mdye_view_69563e5df03728c888c04f05/ ├── package.json ├── mdye.json ├── src/ │ ├── index.jsx │ ├── App.jsx │ └── styles.less └── .gitignore

步骤 3:进入项目目录并安装依赖

进入项目目录:

cd mdye_view_69563e5df03728c888c04f05

安装依赖:

npm i

步骤 4:启动开发环境

启动命令:

mdye start

启动后:

API 使用指南

  1. 环境变量及配置获取

1.1 获取 env 环境变量

// 使用辅助函数安全获取env中的配置项 function getEnvValue(env, key, defaultValue = null) { if (!env || !key) return defaultValue;

const value = env[key];

// 处理数组类型(字段选择器) if (Array.isArray(value)) { return value.length > 0 ? value[0] : defaultValue; }

// 处理普通值 return value !== undefined ? value : defaultValue; }

// 使用示例 const titleFieldId = getEnvValue(env, 'title'); const maxRecords = getEnvValue(env, 'maxRecords', '50');

1.2 获取 config 配置

import { config } from "mdye";

// 获取应用、工作表、视图的ID const { appId, worksheetId, viewId, controls } = config;

// 获取字段控件信息 const fieldControl = _.find(controls, { controlId: fieldId });

  1. 数据获取 API

2.1 获取工作表数据 (getFilterRows)

import { api } from "mdye";

async function loadRecords() { const result = await api.getFilterRows({ worksheetId, // 必填-工作表ID viewId, // 必填-视图ID pageIndex: 1, // 可选-页码 pageSize: 50, // 可选-每页记录数 sortId: "fieldId", // 可选-排序字段 isAsc: true, // 可选-升序排序 // 获取关联字段数据 requestParams: { plugin_detail_control: relationFieldId } });

return result.data; // 记录数组 }

2.2 获取记录详情 (getRowDetail)

async function getRecordDetail(rowId) { const result = await api.getRowDetail({ appId, worksheetId, viewId, rowId });

return result.data; }

2.3 获取关联记录 (getRowRelationRows)

async function loadRelationRows({ controlId, rowId }) { const result = await api.getRowRelationRows({ worksheetId, controlId, // 关联字段ID rowId, // 主记录ID pageIndex: 1, pageSize: 10 });

return result.data; }

  1. 数据操作 API

3.1 新增记录 (addWorksheetRow)

async function addRecord(fieldsData) { const response = await api.addWorksheetRow({ appId, worksheetId, receiveControls: [ { controlId: "fieldId1", type: 2, value: "测试文本" } ] }); return response; }

3.2 更新记录 (updateWorksheetRow)

async function updateRecord(rowId, fieldId, newValue) { const response = await api.updateWorksheetRow({ appId, worksheetId, rowId, newOldControl: [ { controlId: fieldId, type: 2, value: newValue } ] }); return response; }

3.3 删除记录 (deleteWorksheetRow)

async function deleteRecord(rowId) { const response = await api.deleteWorksheetRow({ appId, worksheetId, rowIds: [rowId] }); return response; }

  1. 工具函数 (utils)

4.1 打开记录详情(推荐使用!)

使用 utils.openRecordInfo 打开明道云原生行记录组件是最佳实践:

优势:

  • ✅ 原生体验,与明道云界面一致

  • ✅ 功能完整:支持编辑、删除、讨论、日志、附件等所有功能

  • ✅ 自动处理权限验证

  • ✅ 无需自己开发弹窗 UI

  • ✅ 返回操作结果,方便进行数据同步

基础用法:

import { utils } from "mdye";

// 打开记录详情 const handleRecordClick = async (recordId) => { try { const result = await utils.openRecordInfo({ appId, worksheetId, viewId, recordId });

// 处理返回结果
if (result) {
  console.log('操作结果:', result);

  // 根据操作类型处理
  switch (result.action) {
    case 'update':
      // 记录被更新,刷新数据
      console.log('记录已更新:', result.value);
      loadRecords(); // 重新加载数据
      break;
    case 'delete':
      // 记录被删除,刷新列表
      console.log('记录已删除');
      loadRecords(); // 重新加载数据
      break;
    case 'close':
      // 用户关闭弹窗(无修改)
      console.log('用户关闭了弹窗');
      break;
  }
}

} catch (error) { console.error('打开记录详情失败:', error); } };

返回值说明:

{ action: 'update' | 'delete' | 'close', // 操作类型 value: object | null // 更新后的记录数据(仅 action='update' 时) }

完整的 React Hook 示例(包含自动刷新):

import React, { useEffect, useState } from 'react'; import { config, api, utils } from 'mdye';

function RecordsList() { const { appId, worksheetId, viewId } = config; const [records, setRecords] = useState([]); const [loading, setLoading] = useState(false);

// 加载记录列表 const loadRecords = async () => { try { setLoading(true); const result = await api.getFilterRows({ worksheetId, viewId, pageSize: 100, pageIndex: 1 }); setRecords(result.data || []); } catch (error) { console.error('加载记录失败:', error); } finally { setLoading(false); } };

// 打开记录详情 const handleRecordClick = async (recordId) => { try { const result = await utils.openRecordInfo({ appId, worksheetId, viewId, recordId });

  // 自动刷新列表
  if (result?.action === 'update' || result?.action === 'delete') {
    loadRecords(); // 刷新数据
  }
} catch (error) {
  console.error('打开记录详情失败:', error);
}

};

// 初始加载 useEffect(() => { loadRecords(); }, []);

return ( <div> {loading ? ( <div>加载中...</div> ) : ( <div> {records.map(record => ( <div key={record.rowid} onClick={() => handleRecordClick(record.rowid)} style={{ cursor: 'pointer' }} > {record.title} </div> ))} </div> )} </div> ); }

性能优化建议:

// 1. 使用 useCallback 避免重复创建函数 const handleRecordClick = useCallback(async (recordId) => { const result = await utils.openRecordInfo({ appId, worksheetId, viewId, recordId }); if (result?.action === 'update' || result?.action === 'delete') { loadRecords(); } }, [appId, worksheetId, viewId]);

// 2. 只在需要时刷新 const handleRecordClick = async (recordId) => { const result = await utils.openRecordInfo({ appId, worksheetId, viewId, recordId });

// 根据具体操作决定是否刷新 if (result?.action === 'update') { // 局部更新(性能更好) setRecords(prev => prev.map(r => r.rowid === recordId ? result.value : r) ); } else if (result?.action === 'delete') { // 从列表中移除 setRecords(prev => prev.filter(r => r.rowid !== recordId)); } };

4.2 打开新建记录窗口

utils.openNewRecord({ appId, worksheetId }).then(newRecord => { if (newRecord) { addLocalRecord(newRecord); } });

4.3 选择用户

const users = await utils.selectUsers({ projectId: "orgId1", unique: false // 是否单选 });

4.4 选择部门

const departments = await utils.selectDepartments({ projectId: "orgId1", unique: false });

4.5 选择位置

const location = await utils.selectLocation({ distance: 1000, defaultPosition: { lat: 39.915, lng: 116.404 }, multiple: false });

4.6 选择记录

const records = await utils.selectRecord({ projectId: "orgId1", relateSheetId: "worksheetId1", multiple: true });

  1. 事件监听

5.1 筛选条件变更事件

import { md_emitter } from "mdye";

useEffect(() => { const handleFiltersUpdate = (newFilters) => { console.log('筛选条件已更新:', newFilters); // 重新获取数据 };

md_emitter.addListener('filters-update', handleFiltersUpdate);

return () => { md_emitter.removeListener('filters-update', handleFiltersUpdate); }; }, []);

5.2 新增记录事件

useEffect(() => { const handleNewRecord = (newRecord) => { console.log('新增记录:', newRecord); setRecords(prev => [...prev, newRecord]); };

md_emitter.addListener('new-record', handleNewRecord);

return () => { md_emitter.removeListener('new-record', handleNewRecord); }; }, []);

特殊字段类型处理

⚠️ 重要提示:字段类型编号

明道云字段类型编号与文档中的枚举值不完全一致,开发时务必注意:

根据明道云 API V3 版本的实际字段类型定义:

  • Type 9 = 单选 (SingleSelect) ⚠️ 注意不是 type 11

  • Type 10 = 多选 (MultipleSelect)

  • Type 11 = 下拉 (Dropdown)

完整字段类型对照表(V3 实用版)

类型编号 枚举名称 字段类型 API 创建 API 返回

2 Text 文本框 ✅ ✅

3 PhoneNumber 手机 ❌ ✅

4 LandlinePhone 座机 ❌ ✅

5 Email 邮箱 ❌ ✅

6 Number 数值 ✅ ✅

7 Certificate 证件 ❌ ✅

8 Currency 金额 ❌ ✅

9 SingleSelect 单选 ✅ ✅

10 MultipleSelect 多选 ✅ ✅

11 Dropdown 下拉 ❌ ✅

14 Attachment 附件 ✅ ✅

15 Date 日期 ✅ ✅

16 DateTime 时间 ✅ ✅

19/23/24 Region 地区 ❌ ✅

21 DynamicLink 自由链接 ❌ ✅

22 Divider 分段 ❌ ❌

25 AmountInWords 大写金额 ❌ ✅

26 Collaborator 成员 ✅ ✅

27 Department 部门 ❌ ✅

28 Rating 等级 ❌ ✅

29 Relation 连接他表 ✅ ✅

30 Lookup 他表字段 ❌ ✅

31 Formula 公式 ❌ ✅

32 Concatenate 文本拼接 ❌ ✅

33 AutoNumber 自动编号 ❌ ✅

34 SubTable 子表 ❌ ✅

35 CascadingSelect 级联选择 ❌ ✅

36 Checkbox 检查框 ❌ ✅

37 Rollup 汇总 ❌ ✅

38 DateFormula 公式(日期) ❌ ✅

39 CodeScan 扫码 ❌ ✅

40 Location 定位 ❌ ✅

41 RichText 富文本 ❌ ✅

42 Signature 签名 ❌ ✅

43 OCR 文字识别 ❌ ✅

44 Role 角色 ❌ ✅

45 Embed 嵌入 ❌ ❌

46 Time 时间 ✅ ✅

47 Barcode 条码 ❌ ✅

48 OrgRole 组织角色 ❌ ✅

常见错误示例

❌ 错误写法:

// 只查找 type 10 和 11,会遗漏 type 9 的单选字段 const selectField = controls?.find(ctrl => ctrl.controlName?.includes('状态') && (ctrl.type === 10 || ctrl.type === 11) );

✅ 正确写法:

// 包含 type 9, 10, 11 所有选项字段类型 const selectField = controls?.find(ctrl => ctrl.controlName?.includes('状态') && (ctrl.type === 9 || ctrl.type === 10 || ctrl.type === 11) );

字段解析函数

单选字段

function parseSingleSelect(value, control) { try { if (!value) return { key: "", text: "" };

const keys = typeof value === 'string'
  ? JSON.parse(value)
  : (Array.isArray(value) ? value : []);

const selectedKey = keys[0] || "";

let selectedText = "";
if (control &#x26;&#x26; control.options) {
  const option = control.options.find(opt => opt.key === selectedKey);
  selectedText = option ? option.value : "";
}

return { key: selectedKey, text: selectedText };

} catch (err) { console.error("解析单选字段失败:", err); return { key: "", text: "" }; } }

多选字段

function parseMultiSelect(value, control) { try { if (!value) return [];

const keys = typeof value === 'string'
  ? JSON.parse(value)
  : (Array.isArray(value) ? value : []);

const result = [];
if (control &#x26;&#x26; control.options) {
  keys.forEach(key => {
    const option = control.options.find(opt => opt.key === key);
    if (option) {
      result.push({ key: key, text: option.value });
    }
  });
}

return result;

} catch (err) { console.error("解析多选字段失败:", err); return []; } }

成员字段

function parseMembers(value) { try { if (!value) return []; return typeof value === 'string' ? JSON.parse(value) : value; } catch (err) { return []; } }

附件字段

function parseAttachments(value) { try { if (!value) return []; return typeof value === 'string' ? JSON.parse(value) : value; } catch (err) { return []; } }

定位字段

function parseLocation(value) { try { if (!value) return { title: "", address: "", x: 0, y: 0 }; return typeof value === 'string' ? JSON.parse(value) : value; } catch (err) { return { title: "", address: "", x: 0, y: 0 }; } }

关联记录字段(⚠️ 重要!)

关联字段 (type 29) 的特殊处理规则:

关联字段根据 enumDefault 或 subType 属性分为两种类型,返回数据格式完全不同:

单条关联 (enumDefault=1 或 subType=1)

  • 返回格式: JSON 数组字符串

  • 示例: "[{"sid":"...","name":"客户名称","sourcevalue":"..."}]"

  • 处理方式: 直接解析 JSON 字符串即可

多条关联 (enumDefault=2 或 subType=2)

  • 返回格式: 数字(表示关联记录的数量)

  • 示例: 2 (表示关联了 2 条记录)

  • 处理方式: 必须调用 getRowRelationRows API 才能获取实际数据

完整处理示例:

// 1. 判断是否为多条关联 function isMultipleRelation(value) { return typeof value === 'number' || (!isNaN(value) && value !== ''); }

// 2. 解析单条关联数据 function parseRelationData(value) { try { if (!value) return [];

const relations = typeof value === 'string' ? JSON.parse(value) : value;
if (!Array.isArray(relations)) return [];

return relations.map(item => {
  let sourceValue = {};
  if (item.sourcevalue) {
    try {
      sourceValue = typeof item.sourcevalue === 'string'
        ? JSON.parse(item.sourcevalue)
        : item.sourcevalue;
    } catch (e) {
      console.error("解析sourcevalue失败:", e);
    }
  }

  return {
    sid: item.sid || '',
    name: item.name || '',
    rowid: sourceValue.rowid || '',
    ...item
  };
});

} catch (err) { console.error("解析关联记录字段失败:", err); return []; } }

// 3. 完整使用示例(包含单条和多条处理) async function loadOrdersWithProducts() { const result = await api.getFilterRows({ worksheetId, viewId, pageSize: 1000, pageIndex: 1 });

// 使用 Promise.all 并行处理所有订单 const ordersData = await Promise.all( result.data.map(async (row) => { // 获取关联产品字段值 const productsValue = row['relationFieldId']; let products = [];

  // 判断是单条还是多条关联
  if (isMultipleRelation(productsValue)) {
    // 多条关联:调用 API 获取详情
    try {
      const relationResult = await api.getRowRelationRows({
        worksheetId,
        controlId: 'relationFieldId',  // 关联字段ID
        rowId: row.rowid,
        pageSize: 100,
        pageIndex: 1
      });

      if (relationResult &#x26;&#x26; relationResult.data) {
        products = relationResult.data.map(item => ({
          name: item['productNameFieldId'],    // 产品名称字段ID
          code: item['productCodeFieldId'],    // 产品编码字段ID
          price: item['productPriceFieldId'],  // 产品单价字段ID
          rowid: item.rowid
        }));
      }
    } catch (error) {
      console.error('获取多条关联失败:', error);
    }
  } else {
    // 单条关联:直接解析
    products = parseRelationData(productsValue);
  }

  return {
    id: row.rowid,
    products: products,
    productsCount: isMultipleRelation(productsValue)
      ? Number(productsValue)
      : products.length
  };
})

);

return ordersData; }

字段配置示例:

// 在 config.controls 中查看关联字段配置 const relationControl = controls.find(ctrl => ctrl.controlId === 'relationFieldId');

// 单条关联配置 { "controlId": "692ed1d0f34d7ea4df717c67", "type": 29, "controlName": "关联客户", "enumDefault": 1, // 或 subType: 1 // ... 其他属性 }

// 多条关联配置 { "controlId": "692ed1d0f34d7ea4df717c6e", "type": 29, "controlName": "关联产品", "enumDefault": 2, // 或 subType: 2 // ... 其他属性 }

自动获取字段值的工具函数

function getFieldValue(fieldId, record, controls) { if (!fieldId || !record) return null;

const rawValue = record[fieldId]; if (rawValue === undefined) return null;

const control = controls.find(ctrl => ctrl.controlId === fieldId); if (!control) return rawValue;

const fieldType = getFieldTypeByControlType(control.type);

switch (fieldType) { case 'text': case 'email': case 'phone': return rawValue;

case 'number':
  return parseFloat(rawValue) || 0;

case 'select':
  return parseSingleSelect(rawValue, control);

case 'multiselect':
  return parseMultiSelect(rawValue, control);

case 'user':
  return parseMembers(rawValue);

case 'department':
  return parseDepartments(rawValue);

case 'attachment':
  return parseAttachments(rawValue);

case 'location':
  return parseLocation(rawValue);

case 'boolean':
  return rawValue === "1" || rawValue === 1 || rawValue === true;

case 'relation':
  return parseRelationData(rawValue);

default:
  return rawValue;

} }

function getFieldTypeByControlType(controlType) { const typeMap = { 2: 'text', // 文本框 3: 'phone', // 手机 4: 'phone', // 座机 5: 'email', // 邮箱 6: 'number', // 数值 7: 'certificate', // 证件 8: 'number', // 金额 9: 'select', // 单选 ⚠️ 重要:type 9 是单选 10: 'multiselect', // 多选 11: 'select', // 下拉 14: 'attachment', // 附件 15: 'date', // 日期 16: 'datetime', // 时间 19: 'region', // 地区 23: 'region', // 地区 24: 'region', // 地区 26: 'user', // 成员 27: 'department', // 部门 28: 'rating', // 等级 29: 'relation', // 连接他表 36: 'boolean', // 检查框 40: 'location', // 定位 41: 'richtext', // 富文本 42: 'signature', // 签名 46: 'time', // 时间 48: 'role', // 组织角色 }; return typeMap[controlType] || 'unknown'; }

mdye 命令行工具

基本命令

查看版本

mdye --version

授权登录

mdye auth

初始化项目

mdye init view --id <id> --template <template-name>

启动开发

mdye start

构建项目

mdye build

提交插件

mdye push -m "提交说明"

查看当前用户

mdye whoami

注销

mdye logout

同步插件参数配置

mdye sync-params -f <file-path>

插件发布流程(重要!)

插件开发完成后,需要按以下步骤提交发布到明道云平台。发布成功后,本插件在组织下所有应用均可使用。

第1步:构建项目

执行以下命令将本地项目打包:

cd your_plugin_project mdye build

构建过程说明:

  • Webpack 会编译并打包所有源代码

  • 生成优化后的 bundle.js 文件

  • 通常需要 1-2 秒完成编译

  • 成功后会显示 "构建代码完成" 和 bundle 文件大小

构建输出示例:

[21:20:33] 开始构建代码 ℹ Compiling Webpack ✔ Webpack: Compiled successfully in 1.94s asset bundle.js 228 KiB [emitted] [minimized] (name: main) webpack 5.98.0 compiled successfully in 1947 ms [21:20:35] 构建代码完成

第2步:提交并发布

执行以下命令将本地项目提交并推送到线上待发布插件列表:

mdye push -m "提交说明"

提交说明编写建议:

建议在提交信息中包含以下内容:

  • 功能特性:列出插件的主要功能

  • 技术实现:说明关键技术点和优化

  • 版本说明:首次发布/功能更新/Bug修复

完整示例:

mdye push -m "订单状态视图插件首次发布

功能特性:

  • 按订单状态分类展示(待付款/已付款/已发货/已完成/已取消)
  • 完整订单信息展示(订单编号/客户/联系人/日期/金额/负责人)
  • 多条关联产品信息展示(产品名称/编号/分类/单价)
  • 点击订单卡片打开原生行记录弹窗
  • 支持编辑/删除订单并自动刷新列表
  • 响应式网格布局和流畅动画效果

技术实现:

  • 正确处理单选字段(type 9)和关联记录字段(type 29)
  • 使用 getRowRelationRows API 处理多条关联
  • 使用 utils.openRecordInfo 实现原生交互
  • Promise.all 并行加载提升性能"

第3步:登录认证

提交时需要登录账户,按提示输入:

  • 用户名(手机号或邮箱地址)

  • 密码

如果已登录,可以通过 mdye whoami 查看当前登录用户。

第4步:确认发布成功

发布成功后会显示插件信息:

[21:20:54] 文件上传成功 [21:20:55] push成功 ┌──────────────────────────────────────────────────────────┐ │ ---- 插件信息 ---- │ │ │ │ 插件名称: 自定义视图 │ │ 视图名称: 自定义视图 │ │ 视图地址: https://www.mingdao.com/worksheet/... │ │ 提交信息: 订单状态视图插件首次发布 │ │ 提交人: 用户名 │ └──────────────────────────────────────────────────────────┘

发布后的状态

✅ 插件已发布 - 可以在组织内所有应用中使用 ✅ 视图地址 - 可以通过返回的 URL 直接访问插件 ✅ 组织共享 - 组织内其他成员可以使用该插件

常见问题

问题1: 构建失败

  • 检查代码语法错误

  • 确保所有依赖已正确安装 (npm install )

  • 查看错误日志定位问题

问题2: 推送失败

  • 确认已登录:mdye whoami

  • 检查网络连接

  • 验证账号权限是否支持插件开发

问题3: 登录超时

  • 重新登录:mdye auth

  • 输入正确的手机号/邮箱和密码

本地项目结构

plugin_project/ ├── .config/ # 配置文件目录 ├── src/ # 源代码目录 │ ├── components/ # 组件目录 │ ├── utils/ # 工具函数目录 │ ├── App.js # 主应用组件 │ ├── index.js # 入口文件 │ └── style.less # 样式文件 ├── mdye.json # 插件配置文件 └── package.json # 项目依赖配置

最佳实践

  1. 项目组织
  • 保持项目结构清晰

  • 合理划分组件和模块

  • 使用有意义的文件命名

  1. 代码质量
  • 遵循 React 最佳实践

  • 使用 ESLint 和 Prettier

  • 编写清晰的注释和文档

  1. 性能优化
  • 使用 React.memo 优化渲染

  • 避免不必要的重渲染

  • 优化状态管理

  • 使用代码分割

  1. 安全注意事项
  • 避免硬编码敏感信息

  • 使用环境变量管理配置

  • 验证用户输入

  • 防止 XSS 攻击

常见问题解决

问题 1:选项字段显示 key 而不是文本

问题描述: 单选或多选字段显示的是 UUID 格式的 key (如 42ad38bf-d3e6-441f-a960-670e704abe4a ),而不是选项的显示文本。

原因分析:

  • 明道云选项字段返回的原始值是 JSON 格式的 key 数组,如 "["42ad38bf-d3e6-441f-a960-670e704abe4a"]"

  • 需要从 config.controls 中找到对应字段的 options ,然后根据 key 匹配出 value

解决方案:

// 1. 获取字段控件定义(包含options) const control = config.controls.find(ctrl => ctrl.controlId === fieldId);

// 2. 解析选项字段 function parseSingleSelect(value, control) { try { if (!value) return { key: "", text: "" };

// 解析 JSON 字符串得到 key 数组
let keys = [];
if (typeof value === 'string') {
  try {
    keys = JSON.parse(value); // ["42ad38bf-..."]
  } catch {
    keys = [value];
  }
} else if (Array.isArray(value)) {
  keys = value;
}

const selectedKey = keys[0] || "";

// 从 options 中查找对应的显示文本
let selectedText = "";
if (control &#x26;&#x26; control.options) {
  const option = control.options.find(opt => opt.key === selectedKey);
  selectedText = option ? option.value : selectedKey;
}

return { key: selectedKey, text: selectedText };

} catch (err) { console.error("解析单选字段失败:", err, value); return { key: "", text: "" }; } }

问题 2:找不到单选字段

问题描述: 使用 controls.find() 查找单选字段时,返回 undefined 。

原因分析:

  • 单选字段的 type 是 9 而不是 11

  • type 10 是多选,type 11 是下拉

  • 如果只检查 ctrl.type === 11 ,会遗漏 type 9 的单选字段

解决方案:

// ✅ 正确:包含所有选项字段类型 const selectField = controls?.find(ctrl => ctrl.controlName?.includes('状态') && (ctrl.type === 9 || ctrl.type === 10 || ctrl.type === 11) );

// ❌ 错误:会遗漏 type 9 const selectField = controls?.find(ctrl => ctrl.controlName?.includes('状态') && (ctrl.type === 10 || ctrl.type === 11) );

问题 3:多条关联字段只显示数字

问题描述: 关联字段显示的是数字(如 2 、3 ),而不是实际的关联记录信息。

原因分析:

  • 多条关联字段 (enumDefault=2 或 subType=2) 返回的原始值是数字,表示关联记录的数量

  • 与单条关联不同,多条关联不会直接返回 JSON 数组字符串

  • 必须调用 getRowRelationRows API 才能获取实际的关联记录数据

解决方案:

// 1. 判断是否为多条关联 function isMultipleRelation(value) { return typeof value === 'number' || (!isNaN(value) && value !== ''); }

// 2. 处理关联字段(支持单条和多条) async function handleRelationField(worksheetId, controlId, rowId, fieldValue) { let relationData = [];

if (isMultipleRelation(fieldValue)) { // 多条关联:调用 API 获取详情 try { const result = await api.getRowRelationRows({ worksheetId, controlId, rowId, pageSize: 100, pageIndex: 1 });

  if (result &#x26;&#x26; result.data) {
    relationData = result.data.map(item => ({
      rowid: item.rowid,
      name: item['titleFieldId'],  // 使用实际的标题字段ID
      // 解析其他需要的字段
    }));
  }
} catch (error) {
  console.error('获取多条关联失败:', error);
}

} else { // 单条关联:直接解析 relationData = parseRelationData(fieldValue); }

return relationData; }

// 3. 完整使用示例 async function loadRecordsWithRelations() { const result = await api.getFilterRows({ worksheetId, viewId, pageSize: 100, pageIndex: 1 });

// 使用 Promise.all 并行处理 const records = await Promise.all( result.data.map(async (row) => { const relationValue = row['relationFieldId'];

  const relations = await handleRelationField(
    worksheetId,
    'relationFieldId',
    row.rowid,
    relationValue
  );

  return {
    ...row,
    relations: relations
  };
})

);

return records; }

如何判断字段是单条还是多条关联:

// 方法1: 查看字段配置 const control = config.controls.find(ctrl => ctrl.controlId === 'relationFieldId'); if (control) { const isSingle = control.enumDefault === 1 || control.subType === 1; const isMultiple = control.enumDefault === 2 || control.subType === 2; console.log('单条关联:', isSingle, '多条关联:', isMultiple); }

// 方法2: 根据返回值类型判断 const value = row['relationFieldId']; if (typeof value === 'number' || !isNaN(value)) { console.log('这是多条关联,需要调用 getRowRelationRows'); } else if (typeof value === 'string') { console.log('这是单条关联,可以直接解析 JSON'); }

问题 4:npm 安装失败

问题 2:mdye 命令不存在

  • 重新安装 mdye-cli

  • 检查 PATH 环境变量

  • 使用 which mdye 检查安装位置

问题 3:项目启动失败

  • 检查端口是否被占用

  • 检查依赖是否完整安装

  • 查看错误日志信息

问题 4:插件 ID 冲突

  • 使用新的唯一后缀

  • 删除旧的冲突项目

  • 重新初始化项目

参考资源

  • 明道云开发者文档

  • React 官方文档

  • Node.js 官方文档

  • 明道云开发者社区

注意: 此技能提供的是开发工作流程指导和 API 使用规范,实际开发中请根据具体需求调整配置和代码。

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

hap-v3-api

No summary provided by upstream source.

Repository SourceNeeds Review
General

hap-mcp-usage

No summary provided by upstream source.

Repository SourceNeeds Review
General

hap-skills-updater

No summary provided by upstream source.

Repository SourceNeeds Review