Skip to content

Shared Foundation Implementation Plan ​

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为后续 14 个模块打下同一套快照模型、HostAdapter 合同、模块开关和 Vitest 基建,且默认不改变现有设计器行为。

Architecture: 纯逻辑放在 designer3.0/src/modules/shared/。FcDesigner.vue 只增加 getSnapshot() 和把 config.adapter 往下传。测试用 Vitest,只覆盖新目录,不试图给 3700 行的设计器补历史单测。

Tech Stack: Vue3、现有 src/utils/sql.js、Vitest + jsdom。

Spec: docs/superpowers/specs/2026-08-23-innovation-requirements.md §3


File map ​

  • Create: designer3.0/vitest.config.js
  • Create: designer3.0/src/modules/shared/types.js
  • Create: designer3.0/src/modules/shared/snapshot.js
  • Create: designer3.0/src/modules/shared/adapter.js
  • Create: designer3.0/src/modules/shared/modePreset.js
  • Create: designer3.0/tests/modules/shared/snapshot.spec.js
  • Create: designer3.0/tests/modules/shared/adapter.spec.js
  • Modify: designer3.0/package.json — 增加 test script 与 devDependency vitest
  • Modify: designer3.0/src/utils/sql.js — 导出 buildSqlTables(现为文件内私有)
  • Modify: designer3.0/src/designer/FcDesigner.vue — getSnapshot()
  • Modify: designer3.0/types/index.d.ts — FormSnapshot、HostAdapter、getSnapshot、config.adapter

Task 1: 加上 Vitest ​

Files:

  • Create: designer3.0/vitest.config.js

  • Modify: designer3.0/package.json

  • [ ] Step 1: 增加测试脚本与依赖

在 designer3.0/package.json 的 scripts 中增加:

json
"test": "vitest run",
"test:watch": "vitest"

在 devDependencies 增加 "vitest": "^2.1.9"。

  • [ ] Step 2: 写 vitest 配置
js
import { defineConfig } from 'vitest/config';

export default defineConfig({
    test: {
        environment: 'node',
        include: ['tests/**/*.spec.js'],
    },
});
  • [ ] Step 3: 安装并确认空跑通过
bash
cd /Users/xaboy/Documents/git/form-create-package/designer3.0
pnpm add -D vitest@^2.1.9
pnpm test

Expected: No test files found 或 0 files,exit 0 / 1 均可,但命令必须存在。随后 Task 2 会补上第一个测试。

  • [ ] Step 4: Commit
bash
git add package.json vitest.config.js
git commit -m "chore: add vitest for new platform modules"

Task 2: 导出 buildSqlTables ​

Files:

  • Modify: designer3.0/src/utils/sql.js:485

  • Create: designer3.0/tests/modules/shared/snapshot.spec.js(先写对 sql 的冒烟,下一步再扩)

  • [ ] Step 1: 写失败测试

js
import { describe, it, expect } from 'vitest';
import { buildSqlTables } from '../../../src/utils/sql.js';

describe('buildSqlTables', () => {
    it('builds a main table from input fields', () => {
        const tables = buildSqlTables(
            [{ type: 'input', field: 'name', title: '姓名', _fc_id: 'id_name', _fc_drag_tag: 'input' }],
            { input: { sqlType: 'string' } },
            { formName: 'leave_form' }
        );
        expect(tables[0].name).toBe('leave_form');
        expect(tables[0].columns.some((c) => c.field === 'name')).toBe(true);
    });
});
  • [ ] Step 2: 跑测试,确认因未导出而失败
bash
pnpm test tests/modules/shared/snapshot.spec.js

Expected: FAIL buildSqlTables is not a function 或 import error。

  • [ ] Step 3: 导出函数

把 sql.js 中的 function buildSqlTables 改为 export function buildSqlTables。不要改算法。

  • [ ] Step 4: 再跑测试
bash
pnpm test tests/modules/shared/snapshot.spec.js

Expected: PASS。若 formName 清理规则与预期不符,断言改成与 sanitizeName 一致(只允许字母数字下划线)。

  • [ ] Step 5: Commit
bash
git add src/utils/sql.js tests/modules/shared/snapshot.spec.js
git commit -m "feat: export buildSqlTables for snapshot module"

Task 3: FormSnapshot 与 getSnapshot ​

Files:

  • Create: designer3.0/src/modules/shared/snapshot.js

  • Modify: designer3.0/tests/modules/shared/snapshot.spec.js

  • Modify: designer3.0/src/designer/FcDesigner.vue(methods 中 getSQL 附近,约 1985 行)

  • Modify: designer3.0/types/index.d.ts

  • [ ] Step 1: 追加失败测试

js
import { buildSnapshot } from '../../../src/modules/shared/snapshot.js';

describe('buildSnapshot', () => {
    it('embeds rule, options and schema', () => {
        const snap = buildSnapshot({
            formId: 'f1',
            rule: [{ type: 'input', field: 'name', title: '姓名', _fc_id: 'id_name', _fc_drag_tag: 'input' }],
            options: { formName: 'leave_form' },
            dragRuleList: { input: { sqlType: 'string' } },
        });
        expect(snap.format).toBe('1.0.0');
        expect(snap.formId).toBe('f1');
        expect(snap.rule[0].field).toBe('name');
        expect(Array.isArray(snap.schema)).toBe(true);
        expect(snap.schema[0].name).toBe('leave_form');
    });
});
  • [ ] Step 2: 跑测试确认失败
bash
pnpm test tests/modules/shared/snapshot.spec.js

Expected: FAIL cannot find module snapshot.js

  • [ ] Step 3: 实现 buildSnapshot
js
import { buildSqlTables } from '../../utils/sql.js';

export const SNAPSHOT_FORMAT = '1.0.0';

export function buildSnapshot({ formId, rule, options, dragRuleList, now = () => new Date().toISOString() }) {
    const name = options.formCreateFormName || options.formName || 'fc_form';
    return {
        format: SNAPSHOT_FORMAT,
        formId: formId || name,
        name,
        description: options.description || '',
        rule,
        options,
        schema: buildSqlTables(rule, dragRuleList, options),
        createdAt: now(),
    };
}
  • [ ] Step 4: 在 FcDesigner 暴露 getSnapshot

在 getSQL() 旁增加:

js
getSnapshot() {
    const options = designerForm.parseJson(methods.getOptionsJson());
    return buildSnapshot({
        formId: options.formId || options.formName,
        rule: methods.getRule(),
        options,
        dragRuleList: data.dragRuleList || data.hiddenItem,
    });
}

dragRuleList 的实际字段以 getSQL() 内部用法为准:打开 getSQL 实现,把同一份 dragRuleList 传入,禁止猜一个不存在的 data 字段。

types/index.d.ts 的 expose 增加:

ts
getSnapshot: () => FormSnapshot;

并在文件中声明 FormSnapshot / SchemaTable / SchemaColumn(字段与 buildSqlTables 返回值对齐:name、comment、columns)。

  • [ ] Step 5: 跑测试
bash
pnpm test tests/modules/shared/snapshot.spec.js

Expected: PASS

  • [ ] Step 6: Commit
bash
git add src/modules/shared/snapshot.js src/designer/FcDesigner.vue types/index.d.ts tests/modules/shared/snapshot.spec.js
git commit -m "feat: add FormSnapshot and getSnapshot"

Task 4: HostAdapter 合同 ​

Files:

  • Create: designer3.0/src/modules/shared/adapter.js

  • Create: designer3.0/tests/modules/shared/adapter.spec.js

  • Modify: designer3.0/types/index.d.ts — Config.adapter?: HostAdapter

  • [ ] Step 1: 写失败测试

js
import { describe, it, expect } from 'vitest';
import { createMemoryAdapter, requireAdapter } from '../../../src/modules/shared/adapter.js';

describe('createMemoryAdapter', () => {
    it('saves and lists versions in memory', async () => {
        const adapter = createMemoryAdapter();
        const saved = await adapter.saveForm({
            snapshot: { format: '1.0.0', formId: 'f1', name: '请假', rule: [], options: {}, schema: [], createdAt: '2026-01-01T00:00:00.000Z' },
            rule: '[]',
            options: '{}',
        });
        expect(saved.id).toBeTruthy();
        const versions = await adapter.listVersions(saved.id);
        expect(versions).toHaveLength(1);
        expect(versions[0].published).toBe(false);
    });
});

describe('requireAdapter', () => {
    it('throws a stable error when method missing', async () => {
        await expect(requireAdapter({}, 'listRecords')({ page: 1 })).rejects.toThrow('[fc-adapter] listRecords is not implemented');
    });
});
  • [ ] Step 2: 跑测试确认失败
bash
pnpm test tests/modules/shared/adapter.spec.js

Expected: FAIL cannot find module

  • [ ] Step 3: 实现 adapter

createMemoryAdapter 用闭包 Map 存 forms 与 versions。saveForm 若无 formId 则生成 form_ + 自增。listVersions 按 version 倒序。publishVersion 把对应项 published=true,并把同 form 其他项保持不变(允许多个 published,灰度解释权在宿主)。

requireAdapter(adapter, method) 返回:

js
export function requireAdapter(adapter, method) {
    return (...args) => {
        const fn = adapter && adapter[method];
        if (typeof fn !== 'function') {
            return Promise.reject(new Error(`[fc-adapter] ${method} is not implemented`));
        }
        return fn(...args);
    };
}
  • [ ] Step 4: 跑测试
bash
pnpm test tests/modules/shared/adapter.spec.js

Expected: PASS

  • [ ] Step 5: Commit
bash
git add src/modules/shared/adapter.js tests/modules/shared/adapter.spec.js types/index.d.ts
git commit -m "feat: add HostAdapter contract and memory adapter"

Task 5: 业务模式预设(给 P1-5 复用) ​

Files:

  • Create: designer3.0/src/modules/shared/modePreset.js

  • Create: designer3.0/tests/modules/shared/modePreset.spec.js

  • [ ] Step 1: 写失败测试

js
import { describe, it, expect } from 'vitest';
import { resolveDesignerConfig } from '../../../src/modules/shared/modePreset.js';

describe('resolveDesignerConfig', () => {
    it('hides developer surfaces in business mode', () => {
        const cfg = resolveDesignerConfig({ mode: 'business' });
        expect(cfg.showJsonPreview).toBe(false);
        expect(cfg.showEventForm).toBe(false);
        expect(cfg.showPreviewSfc).toBe(false);
        expect(cfg.showAi).not.toBe(false);
        expect(cfg.showPreviewForm).not.toBe(false);
    });

    it('keeps explicit overrides', () => {
        const cfg = resolveDesignerConfig({ mode: 'business', showEventForm: true });
        expect(cfg.showEventForm).toBe(true);
    });
});
  • [ ] Step 2: 实现
js
const BUSINESS_DEFAULTS = {
    showJsonPreview: false,
    showEventForm: false,
    showPreviewSfc: false,
    showPreviewHtml: false,
    showPreviewSql: false,
    showPreviewComponent: false,
    showCustomProps: false,
    showShortcut: false,
};

export function resolveDesignerConfig(config = {}) {
    if (config.mode !== 'business') {
        return { ...config };
    }
    return { ...BUSINESS_DEFAULTS, ...config };
}

调用点放到 P1-5 计划,本任务只交付纯函数。

  • [ ] Step 3: 跑测试并提交
bash
pnpm test tests/modules/shared/modePreset.spec.js

Expected: PASS

bash
git add src/modules/shared/modePreset.js tests/modules/shared/modePreset.spec.js
git commit -m "feat: add business-mode config preset"