hap-view-plugin
npx skills add https://github.com/garfield-bb/hap-skills-collection --skill hap-view-plugin
Agent 安装分布
Skill 文档
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 å·¥å ·
- å ¨å±å®è£ æä»¶å¼åä¸ç¨çå½ä»¤è¡å·¥å ·
- éªè¯å·¥å ·å®è£ æ¯å¦æå
2. åå§åæ¬å°é¡¹ç®
- å建å¯ä¸çæä»¶é¡¹ç®æä»¶å¤¹
- ä½¿ç¨ React åºç¡ç¤ºä¾æ¨¡æ¿
- çæé¡¹ç®é ç½®æä»¶
3. å®è£ 项ç®ä¾èµ
- å®è£ é¡¹ç®æéç npm ä¾èµå
- é ç½®å¼åç¯å¢
4. å¯å¨å¼åç¯å¢
- å¯å¨æ¬å°å¼åæå¡å¨
- æ¯æçéè½½å宿¶é¢è§
- æä¾çº¿ä¸è°è¯è½å
å¼å工使µç¨
æ¥éª¤ 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
å¯å¨åï¼
- å¼åæå¡å¨å°å¨
http://localhost:3000/å¯å¨ - å°è°è¯å°å
http://localhost:3000/bundle.jsç²è´´å°æéäºè§å¾é ç½®å¼åè°è¯è¾å ¥æ¡ - æ¯æå®æ¶ç¼è¾åçéè½½
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 });
2. æ°æ®è·å 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;
}
3. æ°æ®æä½ 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;
}
4. å·¥å ·å½æ° (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
});
5. äºä»¶çå¬
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 | é®ç®± | â | â | |
| 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 && 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 && 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 æ¡è®°å½) - å¤çæ¹å¼: å¿
é¡»è°ç¨
getRowRelationRowsAPI æè½è·åå®é æ°æ®
宿´å¤ç示ä¾:
// 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 && 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. 项ç®ç»ç»
- ä¿æé¡¹ç®ç»ææ¸ æ°
- åçååç»ä»¶å模å
- ä½¿ç¨ææä¹çæä»¶å½å
2. 代ç è´¨é
- éµå¾ª React æä½³å®è·µ
- ä½¿ç¨ ESLint å Prettier
- ç¼åæ¸ æ°ç注éåææ¡£
3. æ§è½ä¼å
- ä½¿ç¨ React.memo ä¼å渲æ
- é¿å ä¸å¿ è¦ç鿏²æ
- ä¼åç¶æç®¡ç
- 使ç¨ä»£ç åå²
4. å®å ¨æ³¨æäºé¡¹
- é¿å 硬ç¼ç ææä¿¡æ¯
- 使ç¨ç¯å¢åé管çé ç½®
- éªè¯ç¨æ·è¾å ¥
- 鲿¢ 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 && 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 æ°ç»å符串
- å¿
é¡»è°ç¨
getRowRelationRowsAPI æè½è·åå®é çå ³èè®°å½æ°æ®
è§£å³æ¹æ¡:
// 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 && 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 å®è£ 失败
- æ£æ¥ç½ç»è¿æ¥
- æ¸
ç npm ç¼åï¼
npm cache clean --force - ä½¿ç¨æ·å®éåï¼
npm config set registry https://registry.npmmirror.com
é®é¢ 2ï¼mdye å½ä»¤ä¸åå¨
- éæ°å®è£ mdye-cli
- æ£æ¥ PATH ç¯å¢åé
- 使ç¨
which mdyeæ£æ¥å®è£ ä½ç½®
é®é¢ 3ï¼é¡¹ç®å¯å¨å¤±è´¥
- æ£æ¥ç«¯å£æ¯å¦è¢«å ç¨
- æ£æ¥ä¾èµæ¯å¦å®æ´å®è£
- æ¥çé误æ¥å¿ä¿¡æ¯
é®é¢ 4ï¼æä»¶ ID å²çª
- ä½¿ç¨æ°çå¯ä¸åç¼
- å 餿§çå²çªé¡¹ç®
- éæ°åå§å项ç®
ð¤ AI 婿æ§è¡æå
å½ç¨æ·è¯·æ±å¼åæéäºè§å¾æä»¶æ¶,AI å¿ é¡»æç §ä»¥ä¸æµç¨æ§è¡:
1. åç½®ç¯å¢æ£æ¥(å¿ é¡»æ§è¡)
Step 1.1: æ£æ¥ Node.js çæ¬
node --version
- â å¦æçæ¬ >= 16.20: ç»§ç»ä¸ä¸æ¥
- â å¦æçæ¬ < 16.20 ææªå®è£
:
- åç¥ç¨æ·éè¦å®è£ Node.js 16.20 ææ´é«çæ¬
- æä¾å®è£ 龿¥: https://nodejs.org/
- çå¾ ç¨æ·å®è£ 宿ååç»§ç»
Step 1.2: æ£æ¥ mdye-cli æ¯å¦å·²å®è£
mdye --version
- â 妿æ¾ç¤ºçæ¬å·: mdye-cli å·²å®è£ ,è·³è¿å®è£ æ¥éª¤
- â 妿å½ä»¤ä¸åå¨: èªå¨å¸®ç¨æ·å®è£ mdye-cli
Step 1.3: èªå¨å®è£ mdye-cli(妿æªå®è£ )
Mac OS ç¨æ·:
sudo npm install -g mdye-cli
Windows/Linux ç¨æ·:
npm install -g mdye-cli
å®è£ åéªè¯:
mdye --version
åç¥ç¨æ·:
â
mdye-cli å·¥å
·å·²å®è£
ð å®è£
ä¿¡æ¯:
- å·¥å
·åç§°: mdye-cli
- çæ¬: [æ¾ç¤ºçæ¬å·]
- ç¨é: æéäºè§å¾æä»¶å¼åä¸ç¨å½ä»¤è¡å·¥å
·
ð¡ ä¸ä¸æ¥:
- ç°å¨å¯ä»¥å¼å§å建è§å¾æä»¶é¡¹ç®äº
2. 模æ¿éæ©(æ ¹æ®éæ±èªå¨éæ©)
æ ¹æ®ç¨æ·éæ±éæ©åéçæ¨¡æ¿:
| ç¨æ·éæ± | æ¨èæ¨¡æ¿ | 说æ |
|---|---|---|
| ç®åå±ç¤ºãå¦ä¹ ç¤ºä¾ | --template React |
React åºç¡æ¨¡æ¿,éåå¿«é䏿 |
| éè¦ UI ç»ä»¶åº | --template React-Tailwind |
å å« Tailwind CSS,éåå¿«éæå»ºçé¢ |
| 夿ä¸å¡é»è¾ | --template React |
åºç¡æ¨¡æ¿,å¯èªè¡æ·»å éè¦çåº |
| Vue ææ¯æ | --template Vue |
Vue 模æ¿(妿å¯ç¨) |
é»è®¤æ¨è: --template React-Tailwind(éå大夿°åºæ¯)
3. 项ç®åå§å(èªå¨æ§è¡)
Step 3.1: è¯¢é®æçææä»¶ ID
- å¦æç¨æ·æä¾äºæä»¶ ID: ç´æ¥ä½¿ç¨
- å¦æç¨æ·æªæä¾: 使ç¨ç¤ºä¾ ID æè¯¢é®ç¨æ·
Step 3.2: åå§å项ç®
mdye init view --id [æä»¶ID] --template React-Tailwind
Step 3.3: è¿å ¥é¡¹ç®ç®å½å¹¶å®è£ ä¾èµ
cd mdye_view_[æä»¶åç¼]/
npm i
妿 npm å®è£ 失败:
- å»ºè®®ä½¿ç¨æ·å®éå:
npm config set registry https://registry.npmmirror.com - æ¸
çç¼å:
npm cache clean --force - éæ°å®è£
:
npm i
4. å¯å¨å¼åç¯å¢(èªå¨æ§è¡)
mdye start
å¯å¨ååç¥ç¨æ·:
â
è§å¾æä»¶å¼åç¯å¢å·²å¯å¨ï¼
ð 项ç®ä¿¡æ¯:
- 项ç®ç®å½: mdye_view_[æä»¶åç¼]/
- å¼åæå¡å¨: http://localhost:3000/
- è°è¯å°å: http://localhost:3000/bundle.js
ð¡ ä¸ä¸æ¥:
1. å°è°è¯å°åç²è´´å°æéäºè§å¾é
ç½®çå¼åè°è¯è¾å
¥æ¡
2. å¨é¡¹ç®ä¸ç¼è¾ä»£ç ,æ¯æçéè½½
3. å¼å宿åè¿è¡ `npm run build` çæåå¸å
ð å¼åæå:
- 主å
¥å£æä»¶: src/index.jsx
- æ ·å¼æä»¶: src/styles.less
- API 使ç¨: åèæè½ææ¡£ä¸ç API ä½¿ç¨æå
5. å¼åè¿ç¨ä¸çè¾ å©
ç¨æ·è¯·æ±æ°æ®æ¥è¯¢æ¶:
- 使ç¨
api.getFilterRows()è·åå·¥ä½è¡¨æ°æ® - æ£ç¡®å¤çå ³èåæ®µ(åè”常è§é®é¢”é¨å)
- å¤çé项忮µ(ä½¿ç¨ key å¼èé value)
ç¨æ·è¯·æ±æ°æ®æä½æ¶:
- 使ç¨
api.addWorksheetRow()æ°å¢è®°å½ - 使ç¨
api.updateWorksheetRow()æ´æ°è®°å½ - 使ç¨
api.deleteWorksheetRows()å é¤è®°å½
ç¨æ·éå°é®é¢æ¶:
- åè”常è§é®é¢ä¸è§£å³æ¹æ¡”é¨å
- æä¾è¯¦ç»çè¯ææ¥éª¤åè§£å³æ¹æ¡
6. AI æ§è¡åå
- â 䏻卿£æ¥: å¼ååå¿ é¡»æ£æ¥ Node.js å mdye-cli
- â èªå¨å®è£ : æ£æµå°å·¥å ·æªå®è£ æ¶,èªå¨å¸®ç¨æ·å®è£
- â éæ©æ¨¡æ¿: æ ¹æ®ç¨æ·éæ±èªå¨éæ©åéçæ¨¡æ¿
- â 宿´æµç¨: ä»ç¯å¢æ£æ¥å°å¯å¨å¼åç¯å¢,䏿°åµæ
- â é误å¤ç: éå°éè¯¯æ¶æä¾è¯¦ç»è¯æåè§£å³æ¹æ¡
- â ä¸è¦è·³è¿: ä¸è¦è·³è¿åç½®ç¯å¢æ£æ¥æ¥éª¤
- â ä¸è¦å设: ä¸è¦åè®¾ç¨æ·å·²å®è£ ææå·¥å ·
åèèµæº
- æéäºå¼åè ææ¡£
- React 宿¹ææ¡£
- Node.js 宿¹ææ¡£
- æéäºå¼åè 社åº
注æï¼ æ¤æè½æä¾çæ¯å¼å工使µç¨æå¯¼å API 使ç¨è§èï¼å®é å¼åä¸è¯·æ ¹æ®å ·ä½éæ±è°æ´é ç½®å代ç ã