Compare commits
No commits in common. "develop" and "master" have entirely different histories.
|
|
@ -1,168 +0,0 @@
|
|||
---
|
||||
name: tinyengine-dsl-generator
|
||||
description: Use when creating or modifying TinyEngine low-code applications - generating page, block, or app DSL (JSON schemas), converting designs/screenshots to DSL, or debugging generated TinyEngine JSON.
|
||||
---
|
||||
|
||||
# TinyEngine DSL Generator
|
||||
|
||||
Generate conformant DSL (JSON) for the TinyEngine low-code platform: **pages**, **blocks**, and **apps**. This file is a router — load the reference files on demand for detail instead of reading everything up front.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Do |
|
||||
| ------------------ | ----------------------------------------------------- |
|
||||
| Generate page DSL | Describe components, layout, interactions → §Workflow |
|
||||
| Generate block DSL | Describe reusable functionality + configurable props |
|
||||
| Generate app DSL | Describe multi-page structure + shared componentsMap |
|
||||
| From screenshot | Describe layout → map to components (§Design-to-DSL) |
|
||||
| Lookup a component | `node scripts/query_components.mjs props <Name>` |
|
||||
| Validate output | `bash scripts/validate_all.sh <file>` (required) |
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Understand the goal** — Page (components / state / methods / lifeCycles), Block (reusable, exposes a props `schema`), or App (pages + `componentsMap` + `meta`).
|
||||
2. **Gather requirements** — name / route / title; component hierarchy; state; event handlers; data sources. Blocks additionally: exposed props, emitted events. Apps additionally: all pages, shared `componentsMap`.
|
||||
3. **Load only the reference you need:**
|
||||
|
||||
| Need | File |
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Schema structure, TS interfaces, reserved names, prop types | [protocol.md](references/protocol.md) |
|
||||
| Component props/events, or a component not listed here | [components.md](references/components.md) · `query_components.mjs` |
|
||||
| List page / form page / layout / interaction templates | [patterns.md](references/patterns.md) |
|
||||
|
||||
⚠️ **Before generating any page with interactions**, read the event-binding section of [protocol.md](references/protocol.md). Event handlers are the #1 error source; the compact Critical Rules table below is a reminder, not a substitute for the full ❌/✅ example.
|
||||
|
||||
4. **Generate** — follow the Page skeleton + property types below. Full Page/Block/Component interfaces are in protocol.md.
|
||||
5. **Validate** (required) — see §Validate.
|
||||
6. **Run the checklist** before handing off — see §Pre-Generation Checklist.
|
||||
|
||||
### Page skeleton (anchor)
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "Page",
|
||||
"fileName": "PageName",
|
||||
"meta": { "id": 1, "title": "...", "router": "...", "creator": "...", "isHome": false, "parentId": "0", "rootElement": "div", "group": "staticPages" },
|
||||
"state": {},
|
||||
"methods": {},
|
||||
"lifeCycles": {},
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Property value types
|
||||
|
||||
- **Literal**: `"text"`, `123`, `true`
|
||||
- **JSExpression**: `{"type":"JSExpression","value":"this.state.count"}` — bindings, conditions, **event handlers**
|
||||
- **JSFunction**: `{"type":"JSFunction","value":"function(){}"}` — **only** inside `methods` / `lifeCycles`
|
||||
- **i18n**: `{"type":"i18n","key":"app.title"}`
|
||||
- **JSResource**: `{"type":"JSResource","value":"this.utils.format()"}`
|
||||
|
||||
### Referencing a block
|
||||
|
||||
```json
|
||||
{ "componentName": "BlockFileName", "componentType": "block", "id": "block-001", "props": { "title": "value" } }
|
||||
```
|
||||
|
||||
Inside the block: read `this.props.xxx`, emit via `this.emit('eventName', data)`.
|
||||
|
||||
## Critical Rules (common pitfalls)
|
||||
|
||||
These cause silent failures. Full ❌/✅ JSON examples live in [protocol.md](references/protocol.md); the checklist below enforces them.
|
||||
|
||||
| Rule | ❌ Wrong | ✅ Right |
|
||||
| ------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| **Event bindings** | `"onClick":{"type":"JSFunction",...}`; or `JSExpression.value` = `"function…"` | `"onClick":{"type":"JSExpression","value":"this.handleX"}` — put the body in `methods` as `JSFunction` |
|
||||
| **Method params** | `function(filter){...}` | `function(event, filter){...}`; binding `"params":["'all'"]` → call `handleX(event,'all')` |
|
||||
| **Lifecycle name** | `"mounted":{...}` | `"onMounted":{"type":"JSFunction","value":"function onMounted(){...}"}` |
|
||||
| **Two-way binding**| `modelValue` with **no** `model` | `"model":true` (v-model) or `"model":{"prop":"x"}` (v-model:x) |
|
||||
| **Page editable** | `"occupier": {...}` | `"occupier": null` |
|
||||
| **CSS class** | `props.class` | `props.className` |
|
||||
|
||||
### Event bindings — the full pattern (highest-frequency error)
|
||||
|
||||
The function body lives in `methods` (`JSFunction`); the event only **references** it (`JSExpression`). `event` is always the first arg; `params` append after.
|
||||
|
||||
```json
|
||||
"methods": {
|
||||
"handleDelete": {
|
||||
"type": "JSFunction",
|
||||
"value": "function(event, id) { this.state.list = this.state.list.filter(x => x.id !== id); }"
|
||||
}
|
||||
},
|
||||
"children": [{
|
||||
"componentName": "TinyButton",
|
||||
"props": {
|
||||
"text": "删除",
|
||||
"onClick": { "type": "JSExpression", "value": "this.handleDelete", "params": ["123"] }
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
The binding above calls `handleDelete(event, 123)`. ❌ Never put a `JSFunction` on an event, and never put a `function(){}` body inside a `JSExpression.value` — both silently break the handler.
|
||||
|
||||
**Memory aid:** `JSExpression` = reference (`this.fn`) · `JSFunction` = definition (`function(){}`). Events use references; methods use definitions.
|
||||
|
||||
## Validate (required)
|
||||
|
||||
```bash
|
||||
bash .agents/skills/tinyengine-dsl-generator/scripts/validate_all.sh <output-file>
|
||||
```
|
||||
|
||||
`validate_all.sh` chains three checks — do **not** rely on `validate_dsl.mjs` alone (it misses event-binding errors). Fix and re-run until all three pass; never hand off unvalidated output.
|
||||
|
||||
| Stage | Script | Catches |
|
||||
| -------------- | ------------------------ | ------------------------------------------------------------------------------- |
|
||||
| Structure | validate_dsl.mjs | Required fields, Page/Block `componentName`, meta, `class` vs `className`, app/page id types |
|
||||
| Event bindings | check_event_bindings.mjs | `JSFunction` on an event, or a function body in `JSExpression.value` |
|
||||
| CSS | check_css.mjs | Malformed `css` strings |
|
||||
|
||||
## Pre-Generation Checklist
|
||||
|
||||
- [ ] Event bindings use `JSExpression`; no `value` starts with `"function"`; function bodies live in `methods` / `lifeCycles`
|
||||
- [ ] Event methods take `event` as the first parameter; `params` append after it
|
||||
- [ ] Lifecycle names start with `on` (`onMounted`, …); `setup` is the only exception
|
||||
- [ ] `modelValue` declares `model` (`true` for standard v-model)
|
||||
- [ ] `occupier` is `null`
|
||||
- [ ] All `id`s are unique; CSS classes use `className`, not `class`
|
||||
- [ ] **App schema** `id` and `meta.appId` are integers (`918`, not `"918"`) — apps.js persists `meta.appId` as string internally, keep the DSL integer
|
||||
- [ ] **Page** `app` reference is a string (`"918"`, not `918`) — pages.js queries with `appId.toString()`; a numeric `app` won't be found by `list()`. Page's own `id` is a NanoID string assigned by the server
|
||||
|
||||
## Component lookup
|
||||
|
||||
Don't load `bundle.json` (≈1 MB) by hand. Query it:
|
||||
|
||||
```bash
|
||||
node scripts/query_components.mjs list # all components
|
||||
node scripts/query_components.mjs props TinyButton # one component's props (fuzzy match)
|
||||
node scripts/query_components.mjs cat 表单 # components in a category
|
||||
node scripts/query_components.mjs search 表格 # full-text search
|
||||
```
|
||||
|
||||
## File output
|
||||
|
||||
- **Apps** → `mockServer/data/apps/<app-name>.json`
|
||||
- **Pages** → `mockServer/data/pages/<PageName>.json`
|
||||
- **Blocks** → `mockServer/data/blocks/<BlockName>.json`
|
||||
|
||||
Pages and blocks are saved with an **outer wrapper** around the DSL: page files wrap the Page DSL in `page_content` (plus `name`, `id`, `app`, `route`, `tenant`, `parentId`, `group`, `isPage`, `isHome`); block files wrap the Block DSL in `content` (plus `id`, `label`, `framework`, `path`, `public`, `is_published`). The validators auto-unwrap both, so you can validate either the wrapper or the inner DSL directly.
|
||||
|
||||
## Design-to-DSL
|
||||
|
||||
From a description or screenshot: identify layout regions → map visuals to components → extract interactions → define state + handlers → apply `className` / `style`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Check |
|
||||
| ----------------- | ---------------------------------------------------------------------- |
|
||||
| Input not working | `modelValue` declares `model` (`true` for standard v-model) |
|
||||
| Event not firing | `JSExpression` (not `JSFunction`); method exists in `methods` |
|
||||
| Page not editable | `occupier` is `null` |
|
||||
| Wrong params | first param is always `event`; `params` append after |
|
||||
|
||||
## Resources
|
||||
|
||||
- [protocol.md](references/protocol.md) — schema spec, TS interfaces, reserved names, property types, slots, full ❌/✅ examples
|
||||
- [components.md](references/components.md) — component catalog (props/events); supplement with `query_components.mjs`
|
||||
- [patterns.md](references/patterns.md) — list/form page, layout, interaction templates
|
||||
- `scripts/` — `validate_all.sh` (run this), `validate_dsl.mjs`, `check_event_bindings.mjs`, `check_css.mjs`, `validate_page.mjs`, `query_components.mjs`
|
||||
|
|
@ -1,474 +0,0 @@
|
|||
# TinyEngine Components Reference
|
||||
|
||||
本文档包含 TinyEngine 可用组件的快速参考。
|
||||
|
||||
## 组件来源
|
||||
|
||||
组件清单位于项目根目录的 `designer-demo/public/mock/bundle.json` 文件中。
|
||||
|
||||
## 组件分类
|
||||
|
||||
| 分类 | 组件数量 | 说明 |
|
||||
| ------------ | -------- | ----------------- |
|
||||
| general | 1+ | 通用基础组件 |
|
||||
| html | 10 | HTML 原生元素 |
|
||||
| 容器组件 | 2 | 布局容器 |
|
||||
| 图表组件 | 12 | 数据可视化 |
|
||||
| 组件 | 8 | 业务组件 |
|
||||
| 评分组件 | 1 | 评分输入 |
|
||||
| 进度条 | 1 | 进度显示 |
|
||||
| 骨架屏 | 1 | 加载占位 |
|
||||
| 滑块组件 | 1 | 滑块输入 |
|
||||
| 步骤条 | 1 | 步骤导航 |
|
||||
| element-plus | 7 | Element Plus 组件 |
|
||||
| Other | 33+ | 其他组件 |
|
||||
|
||||
## 常用基础组件
|
||||
|
||||
### 按钮 (TinyButton)
|
||||
|
||||
```typescript
|
||||
interface TinyButtonProps {
|
||||
text: string // 按钮文字
|
||||
type: 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
||||
size: 'medium' | 'small' | 'mini'
|
||||
disabled: boolean
|
||||
plain: boolean // 朴素按钮
|
||||
round: boolean // 圆角
|
||||
circle: boolean // 圆形
|
||||
loading: boolean // 加载中
|
||||
icon: string // 图标类名
|
||||
onClick: JSFunction // 点击事件
|
||||
}
|
||||
```
|
||||
|
||||
### 输入框 (TinyInput)
|
||||
|
||||
```typescript
|
||||
interface TinyInputProps {
|
||||
modelValue: string | number;
|
||||
type: 'text' | 'number' | 'password' | 'textarea';
|
||||
placeholder: string;
|
||||
disabled: boolean;
|
||||
readonly: boolean;
|
||||
clearable: boolean; // 可清空
|
||||
size: 'medium' | 'small' | 'mini';
|
||||
maxlength: number;
|
||||
onChange: JSExpression; // ⚠️ 使用 JSExpression 引用方法
|
||||
onFocus: JSExpression;
|
||||
onBlur: JSExpression;
|
||||
onKeyup: JSExpression; // 键盘事件 (如 Enter 键处理)
|
||||
}
|
||||
|
||||
// ⚠️ 双向绑定示例:
|
||||
"modelValue": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.inputText",
|
||||
"model": true // 标准双向绑定 (v-model)
|
||||
}
|
||||
|
||||
// ⚠️ 事件绑定示例:
|
||||
"onChange": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.handleInputChange"
|
||||
}
|
||||
|
||||
// methods 中定义:
|
||||
"handleInputChange": {
|
||||
"type": "JSFunction",
|
||||
"value": "function(event) { this.state.inputText = event; }"
|
||||
}
|
||||
|
||||
// Enter 键处理示例:
|
||||
"onKeyup": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.handleInputKeyup"
|
||||
}
|
||||
|
||||
// methods 中定义:
|
||||
"handleInputKeyup": {
|
||||
"type": "JSFunction",
|
||||
"value": "function(event) { if (event.keyCode === 13) { this.submitForm(event); } }"
|
||||
}
|
||||
```
|
||||
|
||||
### 表格 (TinyGrid)
|
||||
|
||||
```typescript
|
||||
interface TinyGridProps {
|
||||
data: Array<any> // 表格数据
|
||||
columns: Array<{
|
||||
// 列配置
|
||||
field: string
|
||||
title: string
|
||||
width?: number
|
||||
fixed?: 'left' | 'right'
|
||||
align?: 'left' | 'center' | 'right'
|
||||
editor?: {
|
||||
component: string
|
||||
type?: 'visible' | 'default'
|
||||
}
|
||||
}>
|
||||
border: boolean
|
||||
stripe: boolean // 斑马纹
|
||||
height: string | number
|
||||
autoResize: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### 对话框 (TinyDialogBox)
|
||||
|
||||
```typescript
|
||||
interface TinyDialogBoxProps {
|
||||
visible: boolean // 是否显示
|
||||
title: string
|
||||
width: string
|
||||
fullscreen: boolean
|
||||
top: string
|
||||
modal: boolean
|
||||
lockScroll: boolean
|
||||
beforeClose: JSFunction
|
||||
onClose: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
### 选择器 (TinySelect)
|
||||
|
||||
```typescript
|
||||
interface TinySelectProps {
|
||||
modelValue: string | number | Array<any>
|
||||
multiple: boolean
|
||||
disabled: boolean
|
||||
clearable: boolean
|
||||
placeholder: string
|
||||
options: Array<{
|
||||
label: string
|
||||
value: any
|
||||
disabled?: boolean
|
||||
}>
|
||||
remote: boolean // 远程搜索
|
||||
remoteMethod: JSFunction
|
||||
onChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
### 标签页 (TinyTabs)
|
||||
|
||||
```typescript
|
||||
interface TinyTabsProps {
|
||||
activeName: string
|
||||
type: '' | 'card' | 'border-card'
|
||||
tabPosition: 'top' | 'right' | 'bottom' | 'left'
|
||||
stretch: boolean
|
||||
onTabClick: JSFunction
|
||||
}
|
||||
|
||||
// 子项 TinyTabItem
|
||||
interface TinyTabItemProps {
|
||||
title: string
|
||||
name: string
|
||||
disabled: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### 表单 (TinyForm)
|
||||
|
||||
```typescript
|
||||
interface TinyFormProps {
|
||||
modelValue: Record<string, any>;
|
||||
rules: Record<string, any>; // 校验规则
|
||||
labelWidth: string;
|
||||
labelPosition: 'left' | 'right' | 'top';
|
||||
inline: boolean;
|
||||
disabled: boolean;
|
||||
validate: JSExpression;
|
||||
resetFields: JSExpression;
|
||||
}
|
||||
|
||||
// ⚠️ 表单双向绑定示例:
|
||||
"modelValue": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.formData",
|
||||
"model": true // 标准双向绑定 (v-model)
|
||||
}
|
||||
|
||||
// ⚠️ 表单内输入框双向绑定:
|
||||
"modelValue": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.formData.name",
|
||||
"model": true // 标准双向绑定用 true;{prop} 仅用于具名 v-model (v-model:xxx)
|
||||
}
|
||||
|
||||
// 表单验证示例:
|
||||
"methods": {
|
||||
"handleSubmit": {
|
||||
"type": "JSFunction",
|
||||
"value": "async function(event) { const valid = await this.$refs.formRef.validate(); if (valid) { /* 提交表单 */ } }"
|
||||
}
|
||||
}
|
||||
|
||||
// 表单项 TinyFormItem
|
||||
interface TinyFormItemProps {
|
||||
label: string;
|
||||
prop: string;
|
||||
required: boolean;
|
||||
rules: Array<any>;
|
||||
}
|
||||
```
|
||||
|
||||
### 布局组件
|
||||
|
||||
#### 容器 (div)
|
||||
|
||||
```typescript
|
||||
interface DivProps {
|
||||
className: string
|
||||
style: string
|
||||
}
|
||||
```
|
||||
|
||||
#### Span (span)
|
||||
|
||||
```typescript
|
||||
interface SpanProps {
|
||||
className: string
|
||||
style: string
|
||||
}
|
||||
```
|
||||
|
||||
#### 图标 (Icon)
|
||||
|
||||
```typescript
|
||||
interface IconProps {
|
||||
name: string // 图标名称,如 "IconChevronLeft"
|
||||
style: string
|
||||
className: string
|
||||
}
|
||||
```
|
||||
|
||||
#### 文本 (Text)
|
||||
|
||||
```typescript
|
||||
interface TextProps {
|
||||
text: string | II18n // 支持i18n
|
||||
style: string
|
||||
className: string
|
||||
}
|
||||
```
|
||||
|
||||
### 数据展示
|
||||
|
||||
#### 树形控件 (TinyTree)
|
||||
|
||||
```typescript
|
||||
interface TinyTreeProps {
|
||||
data: Array<{
|
||||
label: string
|
||||
children?: Array<any>
|
||||
id: string | number
|
||||
}>
|
||||
showCheckbox: boolean
|
||||
checkOnClickNode: boolean
|
||||
defaultExpandAll: boolean
|
||||
filterNodeMethod: JSFunction
|
||||
onCheckChange: JSFunction
|
||||
onNodeClick: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
#### 分页 (TinyPager)
|
||||
|
||||
```typescript
|
||||
interface TinyPagerProps {
|
||||
currentPage: number
|
||||
pageSizes: Array<number>
|
||||
pageSize: number
|
||||
total: number
|
||||
layout: string // 如 "total, sizes, prev, pager, next, jumper"
|
||||
onCurrentChange: JSFunction
|
||||
onSizeChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
#### 进度条 (TinyProgress)
|
||||
|
||||
```typescript
|
||||
interface TinyProgressProps {
|
||||
percentage: number // 0-100
|
||||
type: 'line' | 'circle' | 'dashboard'
|
||||
status: 'success' | 'exception' | 'warning'
|
||||
strokeWidth: number
|
||||
color: string | string[]
|
||||
}
|
||||
```
|
||||
|
||||
### 开关和选择
|
||||
|
||||
#### 开关 (TinySwitch)
|
||||
|
||||
```typescript
|
||||
interface TinySwitchProps {
|
||||
modelValue: boolean
|
||||
disabled: boolean
|
||||
width: number
|
||||
activeText: string
|
||||
inactiveText: string
|
||||
onChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
#### 单选框 (TinyRadio)
|
||||
|
||||
```typescript
|
||||
interface TinyRadioProps {
|
||||
modelValue: string | number | boolean
|
||||
label: string | number
|
||||
disabled: boolean
|
||||
border: boolean
|
||||
onChange: JSFunction
|
||||
}
|
||||
|
||||
// 单选组 TinyRadioGroup
|
||||
interface TinyRadioGroupProps {
|
||||
modelValue: any
|
||||
size: 'medium' | 'small' | 'mini'
|
||||
fill: string
|
||||
textColor: string
|
||||
onChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
#### 复选框 (TinyCheckbox)
|
||||
|
||||
```typescript
|
||||
interface TinyCheckboxProps {
|
||||
modelValue: boolean | string | number
|
||||
label: string
|
||||
trueLabel: string
|
||||
falseLabel: string
|
||||
disabled: boolean
|
||||
border: boolean
|
||||
onChange: JSFunction
|
||||
}
|
||||
|
||||
// 复选组 TinyCheckboxGroup
|
||||
interface TinyCheckboxGroupProps {
|
||||
modelValue: Array<any>
|
||||
size: string
|
||||
min: number
|
||||
max: number
|
||||
onChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
#### 日期选择 (TinyDatePicker)
|
||||
|
||||
```typescript
|
||||
interface TinyDatePickerProps {
|
||||
modelValue: string | Date
|
||||
type: 'year' | 'month' | 'date' | 'dates' | 'week' | 'datetime' | 'datetimerange' | 'daterange'
|
||||
placeholder: string
|
||||
startPlaceholder: string
|
||||
endPlaceholder: string
|
||||
format: string
|
||||
disabled: boolean
|
||||
clearable: boolean
|
||||
onChange: JSFunction
|
||||
}
|
||||
```
|
||||
|
||||
### 消息提示
|
||||
|
||||
#### 警告 (TinyAlert)
|
||||
|
||||
```typescript
|
||||
interface TinyAlertProps {
|
||||
title: string
|
||||
type: 'success' | 'warning' | 'info' | 'error'
|
||||
description: string
|
||||
closable: boolean
|
||||
center: boolean
|
||||
closeText: string
|
||||
showIcon: boolean
|
||||
}
|
||||
```
|
||||
|
||||
#### 消息 (TinyModal)
|
||||
|
||||
```typescript
|
||||
// 使用方法
|
||||
{
|
||||
"type": "JSResource",
|
||||
"value": "this.$modal.message({ message: '操作成功', status: 'success' })"
|
||||
}
|
||||
```
|
||||
|
||||
#### 确认框 (TinyConfirm)
|
||||
|
||||
```typescript
|
||||
// 使用方法
|
||||
{
|
||||
"type": "JSResource",
|
||||
"value": "this.$modal.confirm({ message: '确定删除?' }).then(() => {})"
|
||||
}
|
||||
```
|
||||
|
||||
## 图表组件
|
||||
|
||||
### 折线图 (TinyLineChart)
|
||||
|
||||
```typescript
|
||||
interface TinyLineChartProps {
|
||||
data: Array<any>
|
||||
settings: {
|
||||
dimensions?: string[]
|
||||
metrics?: string[]
|
||||
xAxisType?: 'category' | 'value' | 'time'
|
||||
yAxisType?: 'category' | 'value' | 'time'
|
||||
yAxisName?: string[]
|
||||
area?: boolean
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 柱状图 (TinyBarChart)
|
||||
|
||||
```typescript
|
||||
interface TinyBarChartProps {
|
||||
data: Array<any>
|
||||
settings: {
|
||||
dimensions?: string[]
|
||||
metrics?: string[]
|
||||
axisSite?: { top?: string[] }
|
||||
label?: { show?: boolean }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 饼图 (TinyPieChart)
|
||||
|
||||
```typescript
|
||||
interface TinyPieChartProps {
|
||||
data: Array<any>
|
||||
settings: {
|
||||
dimension?: string
|
||||
metrics?: string[]
|
||||
radius?: number | number[]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 组件查询
|
||||
|
||||
不要手动 grep bundle.json(≈1MB)。用查询脚本:
|
||||
|
||||
```bash
|
||||
node scripts/query_components.mjs props TinyButton # 某组件的属性表(模糊匹配)
|
||||
node scripts/query_components.mjs list # 全部组件
|
||||
node scripts/query_components.mjs cat 表单 # 某分类下的组件
|
||||
node scripts/query_components.mjs search 表格 # 全字段搜索
|
||||
```
|
||||
|
||||
## 常见问题与规则
|
||||
|
||||
通用规则(事件绑定、双向绑定 `model`、`occupier`、`className`、生命周期、参数传递)的精简表见 [SKILL.md](../SKILL.md)「Critical Rules / Troubleshooting」;完整 ❌/✅ 示例与 TS 接口见 [protocol.md](protocol.md);条件渲染 / 循环渲染等模式见 [patterns.md](patterns.md)。本文件专注于各组件的 props/events 参考。
|
||||
|
|
@ -1,595 +0,0 @@
|
|||
# TinyEngine DSL Protocol Reference
|
||||
|
||||
本文档包含 TinyEngine 低代码平台 DSL 协议的完整参考,用于生成符合协议规范的 JSON 数据。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [应用协议](#应用协议)
|
||||
2. [页面结构](#页面结构)
|
||||
3. [组件结构](#组件结构)
|
||||
4. [区块结构](#区块结构)
|
||||
5. [保留字](#保留字)
|
||||
6. [插槽语法](#插槽语法)
|
||||
7. [数据源](#数据源)
|
||||
8. [国际化](#国际化)
|
||||
|
||||
---
|
||||
|
||||
## 应用协议
|
||||
|
||||
### 应用结构
|
||||
|
||||
```typescript
|
||||
interface IAppSchema {
|
||||
version: string // 协议版本号,如 "1.0.0"
|
||||
componentsMap: IComponentMap[] // 组件映射关系
|
||||
componentsTree: IPageSchema[] // 应用包含的页面列表
|
||||
bridge: IBridge[] // 桥接源(工具函数、依赖)
|
||||
meta: IAppMeta // 应用基础信息
|
||||
dataSource?: IDataSource // 应用级数据源
|
||||
i18n?: II18n // 应用级国际化
|
||||
utils?: any[] // 工具类
|
||||
constants?: Record<string, any> // 常量
|
||||
css?: string // 全局CSS
|
||||
config?: IAppConfig // 应用配置
|
||||
}
|
||||
|
||||
interface IComponentMap {
|
||||
componentName: string // 渲染时使用的组件名
|
||||
package: string // npm包名
|
||||
version: string // 版本号
|
||||
destructuring: boolean // 是否解构
|
||||
exportName: string // 导出名
|
||||
subName?: string // 子导出名
|
||||
}
|
||||
|
||||
interface IAppMeta {
|
||||
appId: string | number // App Schema 中建议使用整数 (e.g., 918);服务端持久化时会转成字符串
|
||||
name: string
|
||||
description: string
|
||||
creator: string
|
||||
git_group?: string
|
||||
project_name?: string
|
||||
gmt_create: string
|
||||
gmt_modified: string
|
||||
}
|
||||
|
||||
/**
|
||||
* App ID 格式建议:
|
||||
* - App Schema 文件中的 `id` 字段: 整数类型 (e.g., 918)
|
||||
* - App Schema 文件中的 `meta.appId` 字段: 整数类型 (e.g., 918)
|
||||
* - App Metadata 文件中的 `id` 字段: 整数类型 (e.g., 918)
|
||||
* - Page 文件中的 `app` 字段: 字符串类型 (e.g., "918")
|
||||
*
|
||||
* 注意: pages.js 使用 appId.toString() 查询页面,直接落盘的 Page 文件必须用字符串 app 引用。
|
||||
*/
|
||||
|
||||
interface IAppConfig {
|
||||
sdkVersion: string
|
||||
historyMode: 'hash' | 'browser'
|
||||
targetRootID: string
|
||||
}
|
||||
|
||||
interface IBridge {
|
||||
name: string
|
||||
type: 'npm' | 'function'
|
||||
content?: {
|
||||
package?: string
|
||||
version?: string
|
||||
exportName?: string
|
||||
subName?: string
|
||||
destructuring?: boolean
|
||||
main?: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 页面 Schema
|
||||
|
||||
```typescript
|
||||
interface IPageSchema {
|
||||
componentName: 'Page' // 固定值
|
||||
fileName: string // 页面文件名
|
||||
meta: IPageMeta // 页面元信息
|
||||
props?: IProps // 页面属性
|
||||
state?: Record<string, any> // 页面状态
|
||||
methods?: Record<string, IJSFunction> // 页面方法
|
||||
lifeCycles?: Record<string, IJSFunction> // 生命周期
|
||||
children?: IComponentSchema[] | string // 子组件
|
||||
css?: string // 页面CSS (换行必须转义为 \n)
|
||||
dataSource?: IDataSource // 页面数据源
|
||||
utils?: any[] // 页面工具函数
|
||||
bridge?: IBridge[] // 页面桥接源
|
||||
occupier?: null | IOccupier // ⚠️ 必须为 null 才能编辑页面
|
||||
}
|
||||
|
||||
interface IPageMeta {
|
||||
id: number
|
||||
title: string
|
||||
description?: string
|
||||
router: string // 不能以 / 开头,不支持路由参数 xxx/:id
|
||||
creator: string
|
||||
isHome: boolean
|
||||
parentId: string // 顶层时为 "0"
|
||||
rootElement: string // 如 "div"
|
||||
group: string // 如 "staticPages"
|
||||
gmt_create: string
|
||||
gmt_modified: string
|
||||
}
|
||||
|
||||
interface IOccupier {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
is_admin: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### 页面示例
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "Page",
|
||||
"fileName": "HomePage",
|
||||
"meta": {
|
||||
"id": 1,
|
||||
"title": "首页",
|
||||
"router": "home",
|
||||
"creator": "admin",
|
||||
"isHome": true,
|
||||
"parentId": "0",
|
||||
"rootElement": "div",
|
||||
"group": "staticPages",
|
||||
"description": "应用首页",
|
||||
"gmt_create": "2024-01-01 00:00:00",
|
||||
"gmt_modified": "2024-01-01 00:00:00"
|
||||
},
|
||||
"props": {},
|
||||
"state": {
|
||||
"count": 0,
|
||||
"message": "Hello"
|
||||
},
|
||||
"methods": {
|
||||
"handleClick": {
|
||||
"type": "JSFunction",
|
||||
"value": "function(event) { this.state.count++ }"
|
||||
}
|
||||
},
|
||||
"lifeCycles": {
|
||||
"onMounted": {
|
||||
"type": "JSFunction",
|
||||
"value": "function onMounted() { console.log('Page mounted'); }"
|
||||
}
|
||||
},
|
||||
"css": ".container { padding: 20px; }",
|
||||
"occupier": null,
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### 生命周期钩子
|
||||
|
||||
可用的生命周期名称(必须以 `on` 开头):
|
||||
|
||||
| 生命周期 | 说明 | Vue 等价 |
|
||||
| ----------------- | ------------------- | ----------------- |
|
||||
| `setup` | 组合式 API 设置入口 | setup() |
|
||||
| `onBeforeMount` | 挂载前 | onBeforeMount() |
|
||||
| `onMounted` | 挂载后 | onMounted() |
|
||||
| `onBeforeUpdate` | 更新前 | onBeforeUpdate() |
|
||||
| `onUpdated` | 更新后 | onUpdated() |
|
||||
| `onBeforeUnmount` | 卸载前 | onBeforeUnmount() |
|
||||
| `onUnmounted` | 卸载后 | onUnmounted() |
|
||||
|
||||
**setup 生命周期特殊参数**:
|
||||
|
||||
```json
|
||||
{
|
||||
"lifeCycles": {
|
||||
"setup": {
|
||||
"type": "JSFunction",
|
||||
"value": "function setup({ props, state, watch, onMounted, onUpdated }) {\n // 使用这些参数进行响应式编程\n watch(() => props.data, (newVal) => { console.log('Data changed:', newVal); });\n}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 组件结构
|
||||
|
||||
### 组件 Schema
|
||||
|
||||
```typescript
|
||||
interface IComponentSchema {
|
||||
componentName: string // 组件名或区块名
|
||||
componentType?: 'block' // 为区块时设置此值
|
||||
id: string // 唯一ID
|
||||
props?: IProps // 组件属性
|
||||
children?: IComponentSchema[] | string // 子组件
|
||||
condition?: ICondition // 条件渲染
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
[key: string]: IPropValue | any
|
||||
}
|
||||
|
||||
type IPropValue = string | number | boolean | Array<any> | Object | IJSExpression | II18n | IJSFunction | IJSResource
|
||||
|
||||
interface IJSExpression {
|
||||
type: 'JSExpression'
|
||||
value: string // 如 "this.state.count"
|
||||
model?: boolean | { prop: string } // 双向绑定: true 表示 v-model,{prop:"xxx"} 表示 v-model:xxx
|
||||
params?: string[] // 事件附加参数 (追加在 event 之后)
|
||||
}
|
||||
|
||||
// ⚠️ 事件绑定规则 (CRITICAL - 常见错误区域):
|
||||
// 1. 事件绑定必须使用 JSExpression,不能用 JSFunction
|
||||
// 2. 引用 methods 中定义的方法
|
||||
// 3. 第一个参数自动是 event,params 中的参数追加在后面
|
||||
// 4. ❌ 禁止:JSExpression 的 value 中包含 function 定义
|
||||
// 示例:
|
||||
// ✅ 正确 - 绑定: "onClick": { "type": "JSExpression", "value": "this.handleClick", "params": ["'id'"] }
|
||||
// ❌ 错误 - 绑定: "onClick": { "type": "JSExpression", "value": "function(event) { ... }" }
|
||||
// 调用: handleClick(event, 'id')
|
||||
// 方法定义: "handleClick": { "type": "JSFunction", "value": "function(event, id) { ... }" }
|
||||
//
|
||||
// 记忆口诀:
|
||||
// - JSExpression = 引用 (this.methodName)
|
||||
// - JSFunction = 定义 (function() {...})
|
||||
// - 事件绑定用引用 (JSExpression),方法定义用函数 (JSFunction)
|
||||
|
||||
// ⚠️ 双向绑定规则:
|
||||
// 1. 标准双向绑定使用 model: true
|
||||
// "modelValue": { "type": "JSExpression", "value": "this.state.text", "model": true }
|
||||
// 等价于 Vue: v-model="state.text"
|
||||
// 2. 具名双向绑定使用 model: { "prop": "xxx" }
|
||||
// "visible": { "type": "JSExpression", "value": "this.state.visible", "model": { "prop": "visible" } }
|
||||
// 等价于 Vue: v-model:visible="state.visible"
|
||||
|
||||
interface II18n {
|
||||
type: 'i18n'
|
||||
key: string // 国际化key
|
||||
}
|
||||
|
||||
interface IJSFunction {
|
||||
type: 'JSFunction'
|
||||
value: string // 函数字符串
|
||||
}
|
||||
|
||||
interface IJSResource {
|
||||
type: 'JSResource'
|
||||
value: string // 如 "this.utils.formatDate()"
|
||||
}
|
||||
|
||||
interface ICondition {
|
||||
type: 'JSExpression'
|
||||
value: string // 条件表达式
|
||||
}
|
||||
```
|
||||
|
||||
### 属性类型说明
|
||||
|
||||
| 类型 | 说明 | 示例 |
|
||||
| -------------- | ---------- | -------------------------------------------------------- |
|
||||
| 字面值 | 直接值 | `"text"`, `123`, `true` |
|
||||
| `JSExpression` | 表达式绑定 | `{"type": "JSExpression", "value": "this.state.count"}` |
|
||||
| `i18n` | 国际化 | `{"type": "i18n", "key": "app.title"}` |
|
||||
| `JSFunction` | 函数 | `{"type": "JSFunction", "value": "function() {}"}` |
|
||||
| `JSResource` | 资源引用 | `{"type": "JSResource", "value": "this.utils.format()"}` |
|
||||
|
||||
### ⚠️ 常见错误:事件绑定中的类型混淆
|
||||
|
||||
**错误示例** (在事件绑定中使用 `JSExpression` 但 `value` 中包含函数定义):
|
||||
|
||||
```json
|
||||
// ❌ 错误
|
||||
{
|
||||
"componentName": "TinyButton",
|
||||
"props": {
|
||||
"onClick": {
|
||||
"type": "JSExpression",
|
||||
"value": "function(event) { this.doSomething(); }"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**正确做法** (将函数定义放在 `methods` 中,事件绑定引用方法):
|
||||
|
||||
```json
|
||||
// ✅ 正确
|
||||
{
|
||||
"methods": {
|
||||
"handleClick": {
|
||||
"type": "JSFunction",
|
||||
"value": "function(event) { this.doSomething(); }"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "TinyButton",
|
||||
"props": {
|
||||
"onClick": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.handleClick"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键规则**:
|
||||
|
||||
- `JSExpression.value` = 方法引用 (如 `this.methodName`)
|
||||
- `JSFunction.value` = 函数定义 (如 `function() {...}`)
|
||||
- 事件绑定用 `JSExpression`,函数定义用 `JSFunction`
|
||||
|
||||
---
|
||||
|
||||
## 区块结构
|
||||
|
||||
### 区块 Schema
|
||||
|
||||
```typescript
|
||||
interface IBlockSchema {
|
||||
componentName: 'Block' // 固定值
|
||||
fileName: string // 区块文件名
|
||||
label: string // 区块HTML标签
|
||||
css?: string // 区块CSS
|
||||
props?: IProps // 区块属性(可配置)
|
||||
state?: Record<string, any> // 区块状态
|
||||
methods?: Record<string, IJSFunction> // 区块方法
|
||||
lifeCycles?: Record<string, IJSFunction> // 生命周期
|
||||
schema: IBlockSchemaConfig // 区块对外暴露的配置schema
|
||||
children?: IComponentSchema[] // 区块内容
|
||||
dataSource?: IDataSource // 区块数据源
|
||||
}
|
||||
|
||||
interface IBlockSchemaConfig {
|
||||
properties: IPropertyConfig[] // 可配置属性
|
||||
events?: Record<string, IEventConfig> // 可触发事件
|
||||
slots?: Record<string, any> // 插槽定义
|
||||
}
|
||||
|
||||
interface IPropertyConfig {
|
||||
label: { zh_CN: string }
|
||||
description?: { zh_CN: string }
|
||||
content: IPropertyItem[]
|
||||
}
|
||||
|
||||
interface IPropertyItem {
|
||||
property: string // 属性名
|
||||
type: string | string[] // 属性类型
|
||||
defaultValue: any // 默认值
|
||||
label: { text: { zh_CN: string } }
|
||||
widget: {
|
||||
// 配置组件
|
||||
component: string
|
||||
props?: any
|
||||
}
|
||||
required?: boolean
|
||||
cols?: number
|
||||
}
|
||||
```
|
||||
|
||||
### 区块使用示例
|
||||
|
||||
在页面中引用区块:
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "MyBlock", // 区块的fileName
|
||||
"componentType": "block",
|
||||
"id": "block-001",
|
||||
"props": {
|
||||
"title": "标题", // 传递给区块的props
|
||||
"data": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.state.list"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 保留字
|
||||
|
||||
以下`componentName`为保留关键字,不允许使用同名物料:
|
||||
|
||||
| ComponentName | 说明 | 用途 |
|
||||
| ------------- | -------------------- | ------------------------------------- |
|
||||
| `Page` | 页面容器 | 配合`fileName`确定页面名称 |
|
||||
| `Block` | 区块容器 | 配合`fileName`确定区块名称 |
|
||||
| `Component` | 业务组件容器(预留) | - |
|
||||
| `Template` | 虚拟容器,不渲染 | 用于具名插槽,children 为[]时出码跳过 |
|
||||
| `Slot` | 插槽定义 | 定义具名插槽 |
|
||||
| `Collection` | 数据源容器,不渲染 | 提供数据源,出码跳过 |
|
||||
| `Text` | 文本节点 | 使用 span 渲染,text 属性包含内容 |
|
||||
|
||||
---
|
||||
|
||||
## 插槽语法
|
||||
|
||||
### 定义插槽
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "slot",
|
||||
"props": {
|
||||
"name": "formSlot"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "tiny-input",
|
||||
"props": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
生成代码:`<slot><tiny-input></tiny-input></slot>`
|
||||
|
||||
### 使用作用域插槽
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "template",
|
||||
"props": {
|
||||
"slot": {
|
||||
"name": "footer",
|
||||
"params": ["row"]
|
||||
}
|
||||
},
|
||||
"children": [...]
|
||||
}
|
||||
```
|
||||
|
||||
生成代码:`<template #footer="{ row }">...</template>`
|
||||
|
||||
### 表格插槽示例
|
||||
|
||||
```json
|
||||
{
|
||||
"slots": {
|
||||
"header": {
|
||||
"type": "JSSlot",
|
||||
"params": ["column"],
|
||||
"value": [
|
||||
{
|
||||
"componentName": "div",
|
||||
"children": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据源
|
||||
|
||||
### 数据源结构
|
||||
|
||||
```typescript
|
||||
interface IDataSource {
|
||||
dataHandler?: string
|
||||
list: IDataSourceItem[]
|
||||
}
|
||||
|
||||
interface IDataSourceItem {
|
||||
id: string | number
|
||||
name: string
|
||||
desc?: string
|
||||
app: string
|
||||
type: 'fetch' | 'value'
|
||||
data?: {
|
||||
columns?: Array<any>
|
||||
data?: Array<any>
|
||||
dataHandler?: IJSFunction
|
||||
errorHandler?: IJSFunction
|
||||
option?: {
|
||||
method: string
|
||||
url: string
|
||||
}
|
||||
shouldFetch?: IJSFunction
|
||||
willFetch?: IJSFunction
|
||||
}
|
||||
value?: {
|
||||
data?: Array<any>
|
||||
columns?: Array<any>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Collection 容器使用数据源
|
||||
|
||||
```json
|
||||
{
|
||||
"componentName": "collection",
|
||||
"props": {
|
||||
"dataSource": "tableData"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "tiny-grid",
|
||||
"props": {
|
||||
"data": {
|
||||
"type": "JSExpression",
|
||||
"value": "this.tableData"
|
||||
},
|
||||
"columns": [...]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 国际化
|
||||
|
||||
### i18n 结构
|
||||
|
||||
```typescript
|
||||
interface II18n {
|
||||
[locale: string]: {
|
||||
[key: string]: string // key-value对,支持模板如 "Hello ${name}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### i18n 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"i18n": {
|
||||
"zh-CN": {
|
||||
"app-title": "我的应用",
|
||||
"welcome": "你好 ${name}"
|
||||
},
|
||||
"en-US": {
|
||||
"app-title": "My App",
|
||||
"welcome": "Hello ${name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 i18n
|
||||
|
||||
在 props 中:
|
||||
|
||||
```json
|
||||
{
|
||||
"props": {
|
||||
"text": {
|
||||
"type": "i18n",
|
||||
"key": "app-title"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
带参数:
|
||||
|
||||
```json
|
||||
{
|
||||
"props": {
|
||||
"text": {
|
||||
"type": "i18n",
|
||||
"key": "welcome",
|
||||
"params": {
|
||||
"name": "World"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TinyEngine CSS Syntax Checker
|
||||
*
|
||||
* 检查DSL中的CSS字段是否有语法错误(基础模式:括号匹配、基本语法,无需额外依赖)。
|
||||
*
|
||||
* 零依赖(仅用 Node 标准库)。
|
||||
* (原 tinycss2 / postcss 模式依赖外部环境,已精简;basic 是默认且为编排脚本使用的模式。)
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/** 普通对象判定(非 null、非数组) */
|
||||
function isPlainObject(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包外层包装结构,返回内层 DSL(页面在 page_content 内,区块在 content 内)。
|
||||
* 仅当内层确实是含 componentName 的节点时才解包;否则原样返回。
|
||||
*/
|
||||
function extractInnerDsl(dslData) {
|
||||
if (isPlainObject(dslData)) {
|
||||
for (const key of ['page_content', 'content']) {
|
||||
const inner = dslData[key];
|
||||
if (isPlainObject(inner) && 'componentName' in inner) {
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dslData;
|
||||
}
|
||||
|
||||
/** CSS 语法检查器(基础模式,无需额外依赖) */
|
||||
export class BasicCssChecker {
|
||||
/** @param {*} dslData */
|
||||
constructor(dslData) {
|
||||
this.dsl = dslData;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
/** 检查 CSS 语法 */
|
||||
check() {
|
||||
// 从外层包装(page_content/content)解包到内层 DSL 后再读取 css
|
||||
const inner = extractInnerDsl(this.dsl);
|
||||
const cssString = inner.css ?? '';
|
||||
|
||||
if (!cssString) {
|
||||
this.warnings.push('No CSS field found');
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._checkCss(cssString);
|
||||
}
|
||||
|
||||
/** 基础检查:括号匹配、基本语法 */
|
||||
_checkCss(css) {
|
||||
// 检查括号匹配
|
||||
const stack = [];
|
||||
for (let i = 0; i < css.length; i++) {
|
||||
const char = css[i];
|
||||
if (char === '{') {
|
||||
stack.push([char, i]);
|
||||
} else if (char === '}') {
|
||||
if (stack.length === 0 || stack[stack.length - 1][0] !== '{') {
|
||||
this.errors.push(`Unmatched '}' at position ${i}`);
|
||||
return false;
|
||||
}
|
||||
stack.pop();
|
||||
} else if (char === '(') {
|
||||
stack.push([char, i]);
|
||||
} else if (char === ')') {
|
||||
if (stack.length === 0 || stack[stack.length - 1][0] !== '(') {
|
||||
this.errors.push(`Unmatched ')' at position ${i}`);
|
||||
return false;
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length) {
|
||||
for (const [char, pos] of stack) {
|
||||
this.errors.push(`Unclosed '${char}' at position ${pos}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移除注释进行检查
|
||||
const cssNoComments = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
|
||||
// 检查是否有 CSS 规则
|
||||
if (!cssNoComments.includes('{')) {
|
||||
this.warnings.push('CSS may not contain any rules');
|
||||
}
|
||||
|
||||
// 检查分号使用
|
||||
const rules = [...cssNoComments.matchAll(/\{([^}]*)\}/g)].map((m) => m[1]);
|
||||
for (const rule of rules) {
|
||||
const properties = rule.split(';');
|
||||
// 最后一个可能为空
|
||||
for (const propRaw of properties.slice(0, -1)) {
|
||||
const prop = propRaw.trim();
|
||||
if (prop && !prop.includes(':')) {
|
||||
this.warnings.push(`Property without colon: ${prop.slice(0, 50)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
/** 生成报告 */
|
||||
report() {
|
||||
const lines = [];
|
||||
if (this.errors.length) {
|
||||
lines.push('❌ CSS Errors:');
|
||||
for (const error of this.errors) lines.push(` - ${error}`);
|
||||
}
|
||||
if (this.warnings.length) {
|
||||
lines.push('⚠️ CSS Warnings:');
|
||||
for (const warning of this.warnings) lines.push(` - ${warning}`);
|
||||
}
|
||||
if (this.errors.length === 0 && this.warnings.length === 0) {
|
||||
lines.push('✅ CSS check passed!');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// 可用模式表(仅保留 basic)
|
||||
const CHECKERS = { basic: BasicCssChecker };
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: check_css.mjs <dsl-file> [mode]');
|
||||
console.log(' mode: basic (default)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = process.argv[2];
|
||||
const mode = process.argv[3] || 'basic';
|
||||
|
||||
// 读取 DSL 文件
|
||||
let dslData;
|
||||
try {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
dslData = JSON.parse(text);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.log(`❌ Invalid JSON: ${e.message}`);
|
||||
} else if (e.code === 'ENOENT') {
|
||||
console.log(`❌ File not found: ${filePath}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 选择检查器
|
||||
const CheckerClass = CHECKERS[mode];
|
||||
if (!CheckerClass) {
|
||||
console.log(`❌ Unknown mode: ${mode}`);
|
||||
console.log(`Available modes: ${Object.keys(CHECKERS).join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const checker = new CheckerClass(dslData);
|
||||
const isValid = checker.check();
|
||||
console.log(checker.report());
|
||||
process.exit(isValid ? 0 : 1);
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
if (path.resolve(process.argv[1] || '') === __filename) {
|
||||
main();
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TinyEngine Event Binding Checker
|
||||
*
|
||||
* 检查DSL文件中的事件绑定是否正确使用JSExpression引用方法,
|
||||
* 而不是在value中直接写函数定义。
|
||||
*
|
||||
* 零依赖(仅用 Node 标准库)。
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/** 普通对象判定(非 null、非数组) */
|
||||
function isPlainObject(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包外层包装结构,返回内层 DSL。
|
||||
* 落盘的页面/区块文件是"外层包装 + 内层 DSL":
|
||||
* - 页面 DSL 在 page_content 内
|
||||
* - 区块 DSL 在 content 内
|
||||
* 仅当内层确实是含 componentName 的节点时才解包,避免误吞同名普通字段;否则原样返回。
|
||||
*/
|
||||
function extractInnerDsl(dslData) {
|
||||
if (isPlainObject(dslData)) {
|
||||
for (const key of ['page_content', 'content']) {
|
||||
const inner = dslData[key];
|
||||
if (isPlainObject(inner) && 'componentName' in inner) {
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dslData;
|
||||
}
|
||||
|
||||
const EVENT_KEYS = [
|
||||
'onClick', 'onChange', 'onKeyup', 'onKeyDown', 'onKeyPress',
|
||||
'onFocus', 'onBlur', 'onSubmit', 'onInput', 'onTabClick',
|
||||
'onCurrentChange', 'onSizeChange', 'onCheckChange',
|
||||
'onNodeClick', 'onRowClick', 'onCellClick',
|
||||
];
|
||||
|
||||
export class EventBindingChecker {
|
||||
/** @param {*} dslData */
|
||||
constructor(dslData) {
|
||||
this.dsl = dslData;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
/** 检查所有事件绑定 */
|
||||
check() {
|
||||
// 从外层包装(page_content/content)解包到内层 DSL 后再检查
|
||||
const inner = extractInnerDsl(this.dsl);
|
||||
this._checkNode(inner);
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
/** 递归检查节点 */
|
||||
_checkNode(node) {
|
||||
if (isPlainObject(node)) {
|
||||
// 检查当前节点的事件绑定
|
||||
this._checkEventBindings(node);
|
||||
|
||||
// 递归检查子节点(仅当 children 是数组;字符串子节点无需处理)
|
||||
if (Array.isArray(node.children)) {
|
||||
for (const child of node.children) {
|
||||
this._checkNode(child);
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(node)) {
|
||||
for (const item of node) {
|
||||
this._checkNode(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查单个节点的事件绑定(含 props 内的事件绑定) */
|
||||
_checkEventBindings(node) {
|
||||
const component = Object.prototype.hasOwnProperty.call(node, 'componentName') ? node.componentName : 'unknown';
|
||||
|
||||
// 两处都需要校验,避免漏检 props 内的事件。
|
||||
this._checkEventHolder(node, component);
|
||||
if (isPlainObject(node.props)) {
|
||||
this._checkEventHolder(node.props, component);
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查某个属性容器(节点本身或其 props)内的事件绑定 */
|
||||
_checkEventHolder(holder, component) {
|
||||
// 检查所有可能的事件属性
|
||||
for (const key of EVENT_KEYS) {
|
||||
if (key in holder) {
|
||||
const value = holder[key];
|
||||
if (isPlainObject(value)) {
|
||||
this._checkEventValue(component, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 也检查以 'on' 开头的属性
|
||||
for (const [key, value] of Object.entries(holder)) {
|
||||
if (key.startsWith('on') && !EVENT_KEYS.includes(key)) {
|
||||
if (isPlainObject(value)) {
|
||||
this._checkEventValue(component, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查事件值 */
|
||||
_checkEventValue(component, eventKey, value) {
|
||||
const valueType = value.type;
|
||||
const valueContent = Object.prototype.hasOwnProperty.call(value, 'value') ? value.value : '';
|
||||
|
||||
// 错误1: 使用 JSFunction 类型进行事件绑定
|
||||
if (valueType === 'JSFunction') {
|
||||
this.errors.push(
|
||||
`${component}.${eventKey}: 使用了JSFunction类型,应该使用JSExpression引用methods中的方法`
|
||||
);
|
||||
}
|
||||
|
||||
// 错误2: JSExpression 的 value 中包含函数定义
|
||||
if (valueType === 'JSExpression' && typeof valueContent === 'string' && valueContent.startsWith('function')) {
|
||||
this.errors.push(
|
||||
`${component}.${eventKey}: JSExpression的value中包含函数定义 '${valueContent.slice(0, 30)}...',` +
|
||||
`应该引用方法如 'this.methodName'`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** 生成报告 */
|
||||
report() {
|
||||
const lines = [];
|
||||
if (this.errors.length) {
|
||||
lines.push('❌ 发现事件绑定错误:');
|
||||
for (const error of this.errors) lines.push(` - ${error}`);
|
||||
}
|
||||
if (this.warnings.length) {
|
||||
lines.push('⚠️ 警告:');
|
||||
for (const warning of this.warnings) lines.push(` - ${warning}`);
|
||||
}
|
||||
if (this.errors.length === 0 && this.warnings.length === 0) {
|
||||
lines.push('✅ 所有事件绑定检查通过!');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: check_event_bindings.mjs <dsl-file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = process.argv[2];
|
||||
|
||||
let dslData;
|
||||
try {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
dslData = JSON.parse(text);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.log(`❌ Invalid JSON: ${e.message}`);
|
||||
} else if (e.code === 'ENOENT') {
|
||||
console.log(`❌ File not found: ${filePath}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const checker = new EventBindingChecker(dslData);
|
||||
const isValid = checker.check();
|
||||
console.log(checker.report());
|
||||
process.exit(isValid ? 0 : 1);
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
if (path.resolve(process.argv[1] || '') === __filename) {
|
||||
main();
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TinyEngine Component Catalog Query
|
||||
*
|
||||
* 从 designer-demo/public/mock/bundle.json 查询组件元数据,
|
||||
* 避免把 ~1MB 全量清单加载进上下文。零依赖(仅用 Node 标准库)。
|
||||
*
|
||||
* 用法:
|
||||
* node query_components.mjs list 列出全部组件(名 / 中文名 / 分类 / 描述)
|
||||
* node query_components.mjs categories 列出所有分类及组件数
|
||||
* node query_components.mjs cat <分类关键字> 列出某分类下的组件
|
||||
* node query_components.mjs props <组件名或中文> 查某组件的属性表(支持模糊匹配)
|
||||
* node query_components.mjs search <关键字> 按名 / 中文名 / 描述 / 分类搜索
|
||||
*
|
||||
* 可用 BUNDLE_JSON=<path> 环境变量覆盖 bundle.json 位置。
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const SCRIPT_DIR = path.dirname(__filename);
|
||||
// scripts/ → tinyengine-dsl-generator → skills → .agents → 仓库根
|
||||
const REPO_ROOT = path.resolve(SCRIPT_DIR, '..', '..', '..', '..');
|
||||
const DEFAULT_BUNDLE = path.join(REPO_ROOT, 'designer-demo', 'public', 'mock', 'bundle.json');
|
||||
|
||||
function loadComponents() {
|
||||
const file = process.env.BUNDLE_JSON || DEFAULT_BUNDLE;
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
console.error(`❌ 找不到 bundle.json: ${file}(可用 BUNDLE_JSON=<path> 覆盖)`);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
console.error(`❌ bundle.json 解析失败: ${e.message}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
return json?.data?.materials?.components ?? [];
|
||||
}
|
||||
|
||||
/** 取一个节点的中文标签,兼容 label.text.zh_CN / label.zh_CN / name.zh_CN */
|
||||
function zhLabel(node) {
|
||||
if (!node) return '';
|
||||
return node.text?.zh_CN || node.label?.text?.zh_CN || node.label?.zh_CN || node.name?.zh_CN || '';
|
||||
}
|
||||
|
||||
/** 组件名(强制字符串,少数条目 component 字段非字符串) */
|
||||
function cname(c) {
|
||||
return String(c?.component ?? '');
|
||||
}
|
||||
|
||||
function truncate(s, n = 46) {
|
||||
s = (s || '').replace(/\s+/g, ' ').trim();
|
||||
return s.length > n ? s.slice(0, n - 1) + '…' : s;
|
||||
}
|
||||
|
||||
function pad(s, n) {
|
||||
s = String(s ?? '');
|
||||
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
||||
}
|
||||
|
||||
function listAll(comps) {
|
||||
console.log(`${pad('componentName', 22)} ${pad('中文名', 12)} ${pad('category', 12)} description`);
|
||||
console.log('-'.repeat(86));
|
||||
for (const c of comps) {
|
||||
console.log(`${pad(c.component, 22)} ${pad(zhLabel(c), 12)} ${pad(c.category || '', 12)} ${truncate(c.description)}`);
|
||||
}
|
||||
console.log(`\n共 ${comps.length} 个组件`);
|
||||
}
|
||||
|
||||
function categories(comps) {
|
||||
const m = new Map();
|
||||
for (const c of comps) {
|
||||
const k = c.category || '(无分类)';
|
||||
m.set(k, (m.get(k) || 0) + 1);
|
||||
}
|
||||
for (const [k, n] of [...m].sort((a, b) => b[1] - a[1])) {
|
||||
console.log(`${pad(k, 16)} ${n}`);
|
||||
}
|
||||
}
|
||||
|
||||
function byCategory(comps, cat) {
|
||||
const hits = comps.filter((c) => (c.category || '').toLowerCase().includes(cat.toLowerCase()));
|
||||
if (hits.length === 0) {
|
||||
console.log(`没有匹配分类 "${cat}" 的组件。运行 "categories" 查看全部分类。`);
|
||||
return;
|
||||
}
|
||||
console.log(`分类匹配 "${cat}"(${hits.length} 个):`);
|
||||
for (const c of hits) console.log(` ${pad(c.component, 22)} ${pad(zhLabel(c), 12)} ${truncate(c.description)}`);
|
||||
}
|
||||
|
||||
/** 精确 → 模糊(componentName / 中文名)匹配,返回单个 / 多个 / 未命中 */
|
||||
function findComp(comps, name) {
|
||||
const exact = comps.find((c) => cname(c) === name);
|
||||
if (exact) return { comp: exact };
|
||||
|
||||
const lower = name.toLowerCase();
|
||||
const byName = comps.filter((c) => cname(c).toLowerCase().includes(lower));
|
||||
if (byName.length === 1) return { comp: byName[0] };
|
||||
|
||||
const byZh = comps.filter((c) => zhLabel(c).includes(name));
|
||||
if (byZh.length === 1) return { comp: byZh[0] };
|
||||
|
||||
const pooled = new Map();
|
||||
for (const c of [...byName, ...byZh]) pooled.set(cname(c), c);
|
||||
if (pooled.size > 0) return { multiple: [...pooled.values()] };
|
||||
return {};
|
||||
}
|
||||
|
||||
function showProps(comp) {
|
||||
console.log(`${comp.component} — ${zhLabel(comp)}(分类: ${comp.category || '?'})`);
|
||||
if (comp.description) console.log(comp.description);
|
||||
if (comp.npm?.package) {
|
||||
const imp = comp.npm.destructuring ? `{ ${comp.npm.exportName} }` : comp.npm.exportName;
|
||||
console.log(`npm: ${comp.npm.package} ${imp}`);
|
||||
}
|
||||
console.log('');
|
||||
const groups = comp.schema?.properties || [];
|
||||
if (groups.length === 0) {
|
||||
console.log('(该组件无 props schema)');
|
||||
return;
|
||||
}
|
||||
console.log(`${pad('property', 18)}${pad('widget', 22)}${pad('required', 9)}label / description`);
|
||||
console.log('-'.repeat(86));
|
||||
let count = 0;
|
||||
for (const g of groups) {
|
||||
for (const item of g.content || []) {
|
||||
const prop = item.property || '?';
|
||||
const widget = item.widget?.component || '';
|
||||
const req = item.required ? 'required' : '';
|
||||
const desc = truncate(item.description?.zh_CN || zhLabel(item), 40);
|
||||
console.log(`${pad(prop, 18)}${pad(widget, 22)}${pad(req, 9)}${desc}`);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
console.log(`\n共 ${count} 个属性`);
|
||||
}
|
||||
|
||||
function search(comps, kw) {
|
||||
const lower = kw.toLowerCase();
|
||||
const hits = comps.filter(
|
||||
(c) =>
|
||||
cname(c).toLowerCase().includes(lower) ||
|
||||
zhLabel(c).includes(kw) ||
|
||||
(c.description || '').toLowerCase().includes(lower) ||
|
||||
(c.category || '').toLowerCase().includes(lower)
|
||||
);
|
||||
if (hits.length === 0) {
|
||||
console.log(`没有匹配 "${kw}" 的组件。`);
|
||||
return;
|
||||
}
|
||||
console.log(`匹配 "${kw}"(${hits.length} 个):`);
|
||||
for (const c of hits) console.log(` ${pad(c.component, 22)} ${pad(zhLabel(c), 12)} ${pad(c.category || '', 10)} ${truncate(c.description, 34)}`);
|
||||
}
|
||||
|
||||
function usage() {
|
||||
console.log(`TinyEngine 组件查询
|
||||
|
||||
用法:
|
||||
node query_components.mjs list 列出全部组件
|
||||
node query_components.mjs categories 列出分类
|
||||
node query_components.mjs cat <分类> 某分类下的组件
|
||||
node query_components.mjs props <组件名|中文> 某组件的属性表(模糊匹配)
|
||||
node query_components.mjs search <关键字> 全字段搜索
|
||||
|
||||
示例:
|
||||
node query_components.mjs props TinyButton
|
||||
node query_components.mjs props button
|
||||
node query_components.mjs cat 表单`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const [cmd, ...rest] = process.argv.slice(2);
|
||||
const comps = loadComponents();
|
||||
|
||||
switch (cmd) {
|
||||
case undefined:
|
||||
case '--help':
|
||||
case '-h':
|
||||
return usage();
|
||||
case 'list':
|
||||
return listAll(comps);
|
||||
case 'categories':
|
||||
case 'cats':
|
||||
return categories(comps);
|
||||
case 'cat':
|
||||
if (!rest[0]) return usage();
|
||||
return byCategory(comps, rest.join(' '));
|
||||
case 'props':
|
||||
if (!rest[0]) return usage();
|
||||
{
|
||||
const r = findComp(comps, rest.join(' '));
|
||||
if (r.comp) return showProps(r.comp);
|
||||
if (r.multiple) {
|
||||
console.log(`"${rest.join(' ')}" 匹配到多个组件,请指定更精确的名字:`);
|
||||
for (const c of r.multiple) console.log(` ${pad(c.component, 22)} ${zhLabel(c)}`);
|
||||
return;
|
||||
}
|
||||
console.log(`未找到组件 "${rest.join(' ')}"。运行 "list" 查看全部,或用 "search <关键字>"。`);
|
||||
}
|
||||
return;
|
||||
case 'search':
|
||||
if (!rest[0]) return usage();
|
||||
return search(comps, rest.join(' '));
|
||||
default:
|
||||
console.error(`未知命令: ${cmd}`);
|
||||
return usage();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#!/bin/bash
|
||||
# TinyEngine DSL 综合验证脚本
|
||||
# 运行所有检查:结构验证、事件绑定检查、CSS检查
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DSL_FILE="$1"
|
||||
|
||||
if [ -z "$DSL_FILE" ]; then
|
||||
echo "Usage: validate_all.sh <dsl-file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$DSL_FILE" ]; then
|
||||
echo "❌ File not found: $DSL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "======================================"
|
||||
echo "TinyEngine DSL 综合验证"
|
||||
echo "文件: $DSL_FILE"
|
||||
echo "======================================"
|
||||
echo
|
||||
|
||||
# 1. 结构验证
|
||||
echo "1️⃣ 结构验证..."
|
||||
node "$SCRIPT_DIR/validate_dsl.mjs" "$DSL_FILE" || exit 1
|
||||
echo
|
||||
|
||||
# 2. 事件绑定检查
|
||||
echo "2️⃣ 事件绑定检查..."
|
||||
node "$SCRIPT_DIR/check_event_bindings.mjs" "$DSL_FILE" || exit 1
|
||||
echo
|
||||
|
||||
# 3. CSS 语法检查
|
||||
echo "3️⃣ CSS 语法检查..."
|
||||
node "$SCRIPT_DIR/check_css.mjs" "$DSL_FILE" basic || exit 1
|
||||
echo
|
||||
|
||||
echo "======================================"
|
||||
echo "✅ 所有验证通过!"
|
||||
echo "======================================"
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TinyEngine DSL Validator
|
||||
*
|
||||
* 验证生成的DSL是否符合TinyEngine协议规范。
|
||||
*
|
||||
* 零依赖(仅用 Node 标准库)。
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/** 把 JS 值映射为类型名,用于告警里的 "got: <类型>" 描述。 */
|
||||
function typeName(value) {
|
||||
if (value === null) return 'null';
|
||||
if (Array.isArray(value)) return 'array';
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'number':
|
||||
return Number.isInteger(value) ? 'integer' : 'number';
|
||||
default:
|
||||
return 'object';
|
||||
}
|
||||
}
|
||||
|
||||
/** 普通对象判定(非 null、非数组) */
|
||||
function isPlainObject(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export class TinyEngineValidator {
|
||||
/**
|
||||
* @param {*} dslData DSL 数据
|
||||
* @param {string} [schemaType='auto'] 'page' | 'block' | 'app' | 'auto'
|
||||
*/
|
||||
constructor(dslData, schemaType = 'auto') {
|
||||
this.original = dslData;
|
||||
// 落盘文件是"外层包装 + 内层 DSL",这里解包到内层 DSL 再做协议校验。
|
||||
const [dsl, fromWrapper] = TinyEngineValidator._unwrap(dslData);
|
||||
this.dsl = dsl;
|
||||
this._fromWrapper = fromWrapper;
|
||||
this.schemaType = schemaType;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
|
||||
// 自动检测 schema 类型
|
||||
if (schemaType === 'auto') {
|
||||
this.schemaType = this._detectSchemaType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别并解包外层包装结构。
|
||||
* TinyEngine 落盘的页面/区块文件是"外层包装 + 内层 DSL"结构:
|
||||
* - 页面:真正的页面 DSL 在 page_content 内
|
||||
* - 区块:真正的区块 DSL 在 content 内
|
||||
* 返回 [内层DSL, 是否来自包装]。若不是包装结构,原样返回 [原数据, false]。
|
||||
* 仅当内层确实是含 componentName 的节点时才解包,避免误吞同名普通字段。
|
||||
*/
|
||||
static _unwrap(dslData) {
|
||||
if (isPlainObject(dslData)) {
|
||||
for (const key of ['page_content', 'content']) {
|
||||
const inner = dslData[key];
|
||||
if (isPlainObject(inner) && 'componentName' in inner) {
|
||||
return [inner, true];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [dslData, false];
|
||||
}
|
||||
|
||||
/** 自动检测 schema 类型 */
|
||||
_detectSchemaType() {
|
||||
if ('componentName' in this.dsl) {
|
||||
const cn = this.dsl.componentName;
|
||||
if (cn === 'Page') return 'page';
|
||||
if (cn === 'Block') return 'block';
|
||||
// 其它 componentName 落到 unknown(不进入 app 分支)
|
||||
} else if ('componentsTree' in this.dsl || 'version' in this.dsl) {
|
||||
return 'app';
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/** 验证 DSL,返回是否有效 */
|
||||
validate() {
|
||||
if (this.schemaType === 'page') return this._validatePage();
|
||||
if (this.schemaType === 'block') return this._validateBlock();
|
||||
if (this.schemaType === 'app') return this._validateApp();
|
||||
this.errors.push(`Unknown schema type: ${this.schemaType}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
_validatePage() {
|
||||
for (const field of ['componentName', 'fileName']) {
|
||||
if (!(field in this.dsl)) this.errors.push(`Missing required field: ${field}`);
|
||||
}
|
||||
|
||||
if (this.dsl.componentName !== 'Page') {
|
||||
this.errors.push(`Page componentName must be 'Page', got: ${this.dsl.componentName}`);
|
||||
}
|
||||
|
||||
// Page 外层包装的 app 引用必须是字符串:pages.js list()/create() 均用 appId.toString()
|
||||
// 查询;写成数字会导致直接落盘的 page 文件查不到。
|
||||
if (this._fromWrapper && isPlainObject(this.original)) {
|
||||
const appRef = this.original.app;
|
||||
if (appRef !== undefined && typeof appRef !== 'string') {
|
||||
this.warnings.push(
|
||||
`Page wrapper 'app' should be string, got: ${typeName(appRef)} (pages.js queries with appId.toString(); numeric app ref won't be found by list())`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 原始页面协议(IPageSchema)要求 meta;但外层包装格式把 meta 等元信息上提到包装层,
|
||||
// page_content 内不再含 meta。因此仅在校验"裸"页面 DSL 时强制要求 meta。
|
||||
if ('meta' in this.dsl) {
|
||||
this._validateMeta(this.dsl.meta);
|
||||
} else if (!this._fromWrapper) {
|
||||
this.errors.push('Missing required field: meta');
|
||||
}
|
||||
|
||||
if (this.dsl.children) {
|
||||
this._validateChildren(this.dsl.children);
|
||||
}
|
||||
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
_validateBlock() {
|
||||
for (const field of ['componentName', 'fileName']) {
|
||||
if (!(field in this.dsl)) this.errors.push(`Missing required field: ${field}`);
|
||||
}
|
||||
|
||||
if (this.dsl.componentName !== 'Block') {
|
||||
this.errors.push(`Block componentName must be 'Block', got: ${this.dsl.componentName}`);
|
||||
}
|
||||
|
||||
if ('schema' in this.dsl) {
|
||||
this._validateBlockSchema(this.dsl.schema);
|
||||
}
|
||||
|
||||
if (this.dsl.children) {
|
||||
this._validateChildren(this.dsl.children);
|
||||
}
|
||||
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
_validateApp() {
|
||||
for (const field of ['version', 'componentsMap', 'componentsTree']) {
|
||||
if (!(field in this.dsl)) this.errors.push(`Missing required field: ${field}`);
|
||||
}
|
||||
|
||||
// 验证 app ID 格式
|
||||
if ('id' in this.dsl) {
|
||||
const rootId = this.dsl.id;
|
||||
if (!Number.isInteger(rootId)) {
|
||||
this.warnings.push(`App Schema 'id' should be integer, got: ${typeName(rootId)} (will be coerced)`);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 meta.appId 格式
|
||||
if ('meta' in this.dsl && 'appId' in this.dsl.meta) {
|
||||
const appId = this.dsl.meta.appId;
|
||||
if (!Number.isInteger(appId)) {
|
||||
this.warnings.push(`meta.appId should be integer, got: ${typeName(appId)} (will be coerced)`);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 componentsMap
|
||||
if ('componentsMap' in this.dsl) {
|
||||
this._validateComponentsMap(this.dsl.componentsMap);
|
||||
}
|
||||
|
||||
// 验证 componentsTree
|
||||
if ('componentsTree' in this.dsl) {
|
||||
for (const page of this.dsl.componentsTree) {
|
||||
const pageValidator = new TinyEngineValidator(page, 'auto');
|
||||
if (!pageValidator.validate()) {
|
||||
const fileName = Object.prototype.hasOwnProperty.call(page, 'fileName') ? page.fileName : '?';
|
||||
for (const e of pageValidator.errors) {
|
||||
this.errors.push(`[Page ${fileName}] ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
_validateMeta(meta) {
|
||||
for (const field of ['id', 'title', 'router', 'creator', 'isHome', 'parentId', 'rootElement']) {
|
||||
if (!(field in meta)) this.errors.push(`Missing meta field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
_validateComponentsMap(componentsMap) {
|
||||
for (const comp of componentsMap) {
|
||||
for (const field of ['componentName', 'package', 'exportName']) {
|
||||
if (!(field in comp)) this.errors.push(`componentsMap missing field: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_validateBlockSchema(schema) {
|
||||
if ('properties' in schema) {
|
||||
for (const prop of schema.properties) {
|
||||
if (!('content' in prop)) this.errors.push("Block schema property missing 'content'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_validateChildren(children) {
|
||||
// children 可以是字符串(文本子节点),按协议(IComponentSchema[] | string)
|
||||
// 这是合法形态,无需逐项校验;递归调用遇到字符串时同样直接返回。
|
||||
if (typeof children === 'string') return;
|
||||
|
||||
children.forEach((child, i) => {
|
||||
if (!isPlainObject(child)) {
|
||||
this.errors.push(`Child at index ${i} is not an object`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('componentName' in child)) {
|
||||
this.errors.push(`Child at index ${i} missing componentName`);
|
||||
}
|
||||
|
||||
// 检查 ID
|
||||
if (!('id' in child)) {
|
||||
this.warnings.push(`Child at index ${i} missing id (recommended)`);
|
||||
}
|
||||
|
||||
// 检查 props 中是否有错误的 'class' 字段(应该是 'className')
|
||||
if ('props' in child && isPlainObject(child.props)) {
|
||||
if ('class' in child.props) {
|
||||
const component = Object.prototype.hasOwnProperty.call(child, 'componentName') ? child.componentName : 'unknown';
|
||||
this.errors.push(
|
||||
`${component} at index ${i} uses 'class' in props, should use 'className' instead (React/Vue convention)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证嵌套 children
|
||||
if (child.children) {
|
||||
this._validateChildren(child.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 生成验证报告 */
|
||||
report() {
|
||||
const lines = [];
|
||||
if (this.errors.length) {
|
||||
lines.push('❌ Validation Errors:');
|
||||
for (const error of this.errors) lines.push(` - ${error}`);
|
||||
}
|
||||
if (this.warnings.length) {
|
||||
lines.push('⚠️ Warnings:');
|
||||
for (const warning of this.warnings) lines.push(` - ${warning}`);
|
||||
}
|
||||
if (this.errors.length === 0 && this.warnings.length === 0) {
|
||||
lines.push('✅ Validation passed!');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: validate_dsl.mjs <dsl-file> [schema-type]');
|
||||
console.log(' schema-type: page, block, app, or auto (default)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = process.argv[2];
|
||||
const schemaType = process.argv[3] || 'auto';
|
||||
|
||||
let dslData;
|
||||
try {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
dslData = JSON.parse(text);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.log(`❌ Invalid JSON: ${e.message}`);
|
||||
} else if (e.code === 'ENOENT') {
|
||||
console.log(`❌ File not found: ${filePath}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const validator = new TinyEngineValidator(dslData, schemaType);
|
||||
const isValid = validator.validate();
|
||||
console.log(validator.report());
|
||||
process.exit(isValid ? 0 : 1);
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
if (path.resolve(process.argv[1] || '') === __filename) {
|
||||
main();
|
||||
}
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* TinyEngine Page DSL 综合验证
|
||||
*
|
||||
* 验证包装格式的页面DSL文件(包含 name, id, app, route, page_content 等字段)。
|
||||
* 运行所有检查:结构验证、事件绑定检查、CSS检查。
|
||||
*
|
||||
* 零依赖(仅用 Node 标准库)。
|
||||
* 通过子进程调用同目录下的 check_event_bindings.mjs / check_css.mjs(子进程直接继承 stdout/stderr)。
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/** 普通对象判定(非 null、非数组) */
|
||||
function isPlainObject(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/** 把 JS 值映射为类型名,用于告警里的 "got: <类型>" 描述。 */
|
||||
function typeName(value) {
|
||||
if (value === null) return 'null';
|
||||
if (Array.isArray(value)) return 'array';
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'number':
|
||||
return Number.isInteger(value) ? 'integer' : 'number';
|
||||
default:
|
||||
return 'object';
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取并解析 JSON 文件;失败时抛出 SyntaxError(JSON)或带 code 的系统错误(文件)。 */
|
||||
function readJson(filePath) {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
||||
/** 验证包装格式的页面文件 */
|
||||
function validatePageWrapper(filePath) {
|
||||
console.log(`验证文件: ${filePath}`);
|
||||
console.log('='.repeat(50));
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = readJson(filePath);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.log(`❌ Invalid JSON: ${e.message}`);
|
||||
} else if (e.code === 'ENOENT') {
|
||||
console.log(`❌ File not found: ${filePath}`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是包装格式
|
||||
if (!('page_content' in data)) {
|
||||
console.log('❌ 不是有效的页面文件(缺少 page_content 字段)');
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageContent = data.page_content;
|
||||
|
||||
// 检查必要字段
|
||||
for (const field of ['name', 'id', 'app', 'route']) {
|
||||
if (!(field in data)) {
|
||||
console.log(`❌ 缺少必要字段: ${field}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 app 字段格式:页面外层 app 引用必须是字符串。
|
||||
// pages.js list()/create() 均用 appId.toString() 查询,数字 app 会导致直接落盘的页面查不到。
|
||||
if ('app' in data) {
|
||||
const appField = data.app;
|
||||
if (typeof appField !== 'string') {
|
||||
console.log(
|
||||
`⚠️ WARNING: 'app' field should be string, got: ${typeName(appField)} (pages.js queries with appId.toString(); numeric app ref won't be found by list())`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 page_content 中的必要字段
|
||||
if (!('componentName' in pageContent)) {
|
||||
console.log('❌ page_content 缺少 componentName 字段');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pageContent.componentName !== 'Page') {
|
||||
console.log(`❌ componentName 必须是 'Page',实际是: ${pageContent.componentName}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!('fileName' in pageContent)) {
|
||||
console.log('❌ page_content 缺少 fileName 字段');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('✅ 包装格式检查通过');
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 运行所有检查器 */
|
||||
function runCheckers(filePath) {
|
||||
// 1. 事件绑定检查
|
||||
console.log('\n1️⃣ 事件绑定检查...');
|
||||
const eventResult = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(SCRIPT_DIR, 'check_event_bindings.mjs'), filePath],
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
if (eventResult.status !== 0) return false;
|
||||
|
||||
// 2. CSS 检查
|
||||
console.log('\n2️⃣ CSS 语法检查...');
|
||||
const cssResult = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(SCRIPT_DIR, 'check_css.mjs'), filePath, 'basic'],
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
if (cssResult.status !== 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 检查是否正确使用 className 而不是 class */
|
||||
function checkClassNameUsage(filePath) {
|
||||
let data;
|
||||
try {
|
||||
data = readJson(filePath);
|
||||
} catch (e) {
|
||||
// JSON 语法错误或文件读取问题(如 ENOENT)由其他检查负责报告;
|
||||
// 此处不掩盖其他意外运行错误。
|
||||
if (e instanceof SyntaxError || (e && typeof e.code === 'string')) {
|
||||
return true;
|
||||
}
|
||||
console.log(`❌ className 检查异常: ${e}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageContent = Object.prototype.hasOwnProperty.call(data, 'page_content')
|
||||
? data.page_content
|
||||
: {};
|
||||
const errors = [];
|
||||
|
||||
const checkNode = (node) => {
|
||||
if (isPlainObject(node)) {
|
||||
// 检查 props 中是否有 'class'
|
||||
if ('props' in node && isPlainObject(node.props)) {
|
||||
if ('class' in node.props) {
|
||||
const component = Object.prototype.hasOwnProperty.call(node, 'componentName')
|
||||
? node.componentName
|
||||
: 'unknown';
|
||||
errors.push(`${component} 使用了 'class' 而不是 'className'`);
|
||||
}
|
||||
}
|
||||
|
||||
// 递归检查 children
|
||||
if (Array.isArray(node.children)) {
|
||||
for (const child of node.children) checkNode(child);
|
||||
}
|
||||
} else if (Array.isArray(node)) {
|
||||
for (const item of node) checkNode(item);
|
||||
}
|
||||
};
|
||||
|
||||
checkNode(pageContent);
|
||||
|
||||
if (errors.length) {
|
||||
console.log("\n❌ 发现错误的 'class' 使用(应该使用 'className'):");
|
||||
for (const error of errors) console.log(` - ${error}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('\n✅ className 检查通过');
|
||||
return true;
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: validate_page.mjs <page-file>');
|
||||
console.log('验证包装格式的TinyEngine页面DSL文件');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = process.argv[2];
|
||||
|
||||
// 运行所有检查
|
||||
let allPassed = true;
|
||||
|
||||
// 1. 包装格式检查
|
||||
if (!validatePageWrapper(filePath)) allPassed = false;
|
||||
|
||||
// 2. className 检查
|
||||
if (!checkClassNameUsage(filePath)) allPassed = false;
|
||||
|
||||
// 3. 运行其他检查器
|
||||
if (!runCheckers(filePath)) allPassed = false;
|
||||
|
||||
// 总结
|
||||
console.log('\n' + '='.repeat(50));
|
||||
if (allPassed) {
|
||||
console.log('✅ 所有验证通过!');
|
||||
} else {
|
||||
console.log('❌ 验证失败,请修复错误后重试');
|
||||
}
|
||||
|
||||
process.exit(allPassed ? 0 : 1);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
if [ ! $version ];
|
||||
then npm version 0.1.0-`date "+%Y%m%d%H%M%S"`;
|
||||
else npm version $version;
|
||||
fi
|
||||
|
||||
npm install
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "[ERROR] build falid!"
|
||||
exit 1
|
||||
fi
|
||||
echo '[INFO] build completed'
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
version: 1.0
|
||||
name: tiny-engine
|
||||
language: nodejs
|
||||
|
||||
# 构建工具
|
||||
dependencies:
|
||||
base:
|
||||
nodejs: best
|
||||
|
||||
# 构建机器
|
||||
machine:
|
||||
standard:
|
||||
euler:
|
||||
- default
|
||||
|
||||
# 构建脚本
|
||||
scripts:
|
||||
- sh ./.build_config/build.sh
|
||||
|
||||
# 构建产物
|
||||
artifacts:
|
||||
npm_deploy:
|
||||
- config_path: ./package.json
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
version: 2.0
|
||||
|
||||
steps:
|
||||
pre_codecheck:
|
||||
- checkout
|
||||
|
||||
tool_params:
|
||||
secsolar:
|
||||
source_dir: ./
|
||||
cmetrics:
|
||||
exclude: vite.config.js|package.json|index.js|axios.js|.eslintrc.js|mockServer|packages/engine-cli/template|packages/vue-generator/test|packages/vue-generator/src/templates|packages/build/vite-plugin-meta-comments/src/test
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
SQL_HOST=localhost
|
||||
SQL_PORT=3306
|
||||
SQL_USER=root
|
||||
SQL_PASSWORD=admin
|
||||
SQL_DATABASE=tiny_engine
|
||||
|
||||
backend_url=http://localhost:9090/material-center/api/component/bundle/create
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
name: '🐛 Bug report'
|
||||
description: Create a report to help us improve Tiny Engine
|
||||
title: '🐛 [Bug]: '
|
||||
labels: ['🐛 bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill out the following carefully in order to better fix the problem.
|
||||
- type: input
|
||||
id: Environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
**Depending on your browser and operating system, websites may behave differently from one environment to another. Make sure your developers know your technical environment.**
|
||||
placeholder: Please browser information.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Version
|
||||
description: |
|
||||
### **Check if the issue is reproducible with the latest stable version.**
|
||||
You can use the command `node -v` to view it
|
||||
placeholder: latest
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: tiny-vue-version
|
||||
attributes:
|
||||
label: Version
|
||||
description: |
|
||||
### **Check if the issue is reproducible with the latest stable version.**
|
||||
You can use the command `npm ls @opentiny/vue` to view it
|
||||
placeholder: latest
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: minimal-repo
|
||||
attributes:
|
||||
label: Link to minimal reproduction
|
||||
description: |
|
||||
**Provide a streamlined CodePen / CodeSandbox or GitHub repository link as much as possible. Please don't fill in a link randomly, it will only close your issue directly.**
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Step to reproduce
|
||||
description: |
|
||||
**After the replay is turned on, what actions do we need to perform to make the bug appear? Simple and clear steps can help us locate the problem more quickly. Please clearly describe the steps of reproducing the issue. Issues without clear reproducing steps will not be repaired. If the issue marked with 'need reproduction' does not provide relevant steps within 7 days, it will be closed directly.**
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What is expected
|
||||
placeholder: Please Input
|
||||
- type: textarea
|
||||
id: actually
|
||||
attributes:
|
||||
label: What is actually happening
|
||||
placeholder: Please Input
|
||||
- type: input
|
||||
id: project-name
|
||||
attributes:
|
||||
label: What is your project name
|
||||
description: We also welcome you to fill in more detailed project information in the following issue [#334](https://github.com/opentiny/tiny-engine/issues/334).
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-comments
|
||||
attributes:
|
||||
label: Any additional comments (optional)
|
||||
description: |
|
||||
**Some background / context of how you ran into this bug.**
|
||||
placeholder: Please Input
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Questions or need help
|
||||
url: https://github.com/opentiny/tiny-engine/discussions
|
||||
about: Add this WeChat(opentiny-official), we will invite you to the WeChat discussion group later.
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
name: ✨ Feature Request
|
||||
description: Propose new features to @opentiny/tiny-engine to improve it.
|
||||
title: '✨ [Feature]: '
|
||||
labels: ['✨ feature']
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-solve
|
||||
attributes:
|
||||
label: What problem does this feature solve
|
||||
description: |
|
||||
Explain your use case, context, and rationale behind this feature request. More importantly, what is the end user experience you are trying to build that led to the need for this feature?
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature-api
|
||||
attributes:
|
||||
label: What does the proposed API look like
|
||||
description: |
|
||||
Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use Markdown to format your code blocks.
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: project-name
|
||||
attributes:
|
||||
label: What is your project name
|
||||
description: We also welcome you to fill in more detailed project information in the following issue [#334](https://github.com/opentiny/tiny-engine/issues/334).
|
||||
placeholder: Please Input
|
||||
validations:
|
||||
required: true
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
English | [简体中文](https://github.com/opentiny/tiny-engine/blob/develop/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.zh-CN.md)
|
||||
|
||||
# PR
|
||||
|
||||
## PR Checklist
|
||||
|
||||
Please check if your PR fulfills the following requirements:
|
||||
|
||||
- [ ] The commit message follows our [Commit Message Guidelines](https://github.com/opentiny/tiny-engine/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
- [ ] Built its own designer, fully self-validated
|
||||
|
||||
## PR Type
|
||||
|
||||
What kind of change does this PR introduce?
|
||||
|
||||
<!-- Please check the one that applies to this PR using "x". -->
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Feature
|
||||
- [ ] Code style update (formatting, local variables)
|
||||
- [ ] Refactoring (no functional changes, no api changes)
|
||||
- [ ] Build related changes
|
||||
- [ ] CI related changes
|
||||
- [ ] Documentation content changes
|
||||
- [ ] Other... Please describe:
|
||||
|
||||
## Background and solution
|
||||
<!--
|
||||
1. Describe the problem and the scenario.
|
||||
2. New features need to be described and attached with renderings.
|
||||
3. Screenshots or GIFs involving UI/Interaction changes/Bugfix before and after modification are required.
|
||||
-->
|
||||
|
||||
### What is the current behavior?
|
||||
|
||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||
|
||||
Issue Number: N/A
|
||||
|
||||
### What is the new behavior?
|
||||
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
## Other information
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
[English](https://github.com/opentiny/tiny-engine/blob/develop/.github/PULL_REQUEST_TEMPLATE.md) | 简体中文
|
||||
|
||||
# PR
|
||||
|
||||
## PR Checklist
|
||||
|
||||
请检查您的 PR 是否满足以下要求:
|
||||
|
||||
- [ ] commit message遵循我们的[提交贡献指南](https://github.com/opentiny/tiny-engine/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] 添加了更改内容的测试用例(用于bugfix/功能)
|
||||
- [ ] 文档已添加/更新(用于bugfix/功能)
|
||||
- [ ] 是否构建了自己的设计器,经过了充分的自验证
|
||||
|
||||
## PR 类型
|
||||
|
||||
这个PR的类型是?
|
||||
|
||||
- [ ] 日常 bug 修复
|
||||
- [ ] 新特性支持
|
||||
- [ ] 代码风格优化
|
||||
- [ ] 重构
|
||||
- [ ] 构建优化
|
||||
- [ ] 测试用例
|
||||
- [ ] 文档更新
|
||||
- [ ] 分支合并
|
||||
- [ ] 其他改动(请补充)
|
||||
|
||||
|
||||
## 需求背景和解决方案
|
||||
|
||||
<!--
|
||||
1. 要解决的具体问题。
|
||||
2. 新增特性,需要进行功能描述,并附上效果图。
|
||||
3. 涉及UI/交互变动/Bugfix需要有修改前&修改后截图或 GIF。
|
||||
-->
|
||||
|
||||
|
||||
Issue Number: N/A
|
||||
|
||||
### 修改前
|
||||
|
||||
|
||||
### 修改后
|
||||
|
||||
## 此PR是否含有 breaking change?
|
||||
|
||||
- [ ] 是
|
||||
- [ ] 否
|
||||
|
||||
<!-- 如果此 PR 包含breaking change,请在下面从用户角度描述具体变化和其他风险。-->
|
||||
|
||||
## Other information
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
version: v1
|
||||
|
||||
labels:
|
||||
- label: 'ignore-for-release'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[vV]?\d+(\.\d+){2}.*'
|
||||
body: '#ignore'
|
||||
- label: 'breaking-change'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^.+!:|#breaking'
|
||||
body: '## Does this PR introduce a breaking change\?\s+- \[[xX]\] *Yes'
|
||||
- label: 'enhancement'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[fF]eat.*'
|
||||
- label: 'bug'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[fF]ix.*'
|
||||
- label: 'documentation'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[dD](ocs|ocumentation).*'
|
||||
files: 'docs/**/*.md'
|
||||
- label: 'refactoring'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[rR]efactor.*'
|
||||
- label: 'test'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[tT]est.*'
|
||||
files: '**/__tests__/**'
|
||||
- label: 'chore'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[cC]hore.*'
|
||||
- label: 'ci'
|
||||
sync: true
|
||||
matcher:
|
||||
title: '^[cC]i.*'
|
||||
files: '.github/**'
|
||||
- label: 'ospp'
|
||||
sync: true
|
||||
matcher:
|
||||
baseBranch: '^ospp-\d+\/.*'
|
||||
- label: '1.x'
|
||||
sync: true
|
||||
matcher:
|
||||
baseBranch: '^v1\.x'
|
||||
- label: 'release'
|
||||
sync: true
|
||||
matcher:
|
||||
baseBranch: '^release\/v2.*'
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
categories:
|
||||
- title: Breaking Changes 🛠
|
||||
labels:
|
||||
- Semver-Major
|
||||
- breaking-change
|
||||
- title: Exciting New Features 🎉
|
||||
labels:
|
||||
- Semver-Minor
|
||||
- feature
|
||||
- enhancement
|
||||
- title: Bug Fixes 🐛
|
||||
labels:
|
||||
- Semver-Patch
|
||||
- bug
|
||||
- title: "📖 Documentation"
|
||||
labels:
|
||||
- documentation
|
||||
- title: "🔧 Maintenance"
|
||||
labels:
|
||||
- refactoring
|
||||
- test
|
||||
- unit-test
|
||||
- chore
|
||||
- ci
|
||||
- title: "Other Changes"
|
||||
labels: ["*"]
|
||||
collapse: true
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.repository == 'opentiny/tiny-engine'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm run build:plugin && pnpm run build:alpha > /tmp/build-alpha.log 2>&1
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-alpha-log
|
||||
path: /tmp/build-alpha.log
|
||||
|
||||
- name: Parse Publish tag
|
||||
id: parse_tag
|
||||
run: |
|
||||
tag_name="${GITHUB_REF#refs/tags/}"
|
||||
if [[ "$tag_name" == *alpha* ]]; then
|
||||
echo "dist_tag=alpha" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$tag_name" == *beta* ]]; then
|
||||
echo "dist_tag=beta" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$tag_name" == *rc* ]]; then
|
||||
echo "dist_tag=rc" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "dist_tag=latest" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Verify clean working directory
|
||||
run: |
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
echo "Working directory is not clean"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify package version match tag
|
||||
run: |
|
||||
tag_name="${GITHUB_REF#refs/tags/}"
|
||||
package_version=$(pnpm lerna list --scope=@opentiny/tiny-engine --json | jq -r '.[0].version')
|
||||
if [[ "$tag_name" != "v$package_version" ]]; then
|
||||
echo "Tag name $tag_name does not match package version $package_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish package to npm
|
||||
run: pnpm lerna publish from-package --dist-tag ${{steps.parse_tag.outputs.dist_tag}} --yes
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
name: Pull Request Auto Labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited]
|
||||
|
||||
permissions:
|
||||
# Setting up permissions in the workflow to limit the scope of what it can do. Optional!
|
||||
contents: read # the config file
|
||||
pull-requests: write # for labeling pull requests (on: pull_request_target or on: pull_request)
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fuxingloh/multi-labeler@v4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }} # optional, default to '${{ github.token }}'
|
||||
config-path: .github/auto-labeler.yml # optional, default to '.github/labeler.yml'
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
name: Deploy to CDN
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
is_latest_release:
|
||||
description: '当前分支是否是最新release版本'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
check-secrets:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
secrets-ready: ${{ steps.check.outputs.secrets-ready }}
|
||||
steps:
|
||||
- name: Check required secrets
|
||||
id: check
|
||||
run: |
|
||||
if [[ -z "${{ secrets.HUAWEI_CLOUD_AK }}" ]] || \
|
||||
[[ -z "${{ secrets.HUAWEI_CLOUD_SK }}" ]] || \
|
||||
[[ -z "${{ secrets.HUAWEI_CLOUD_ENDPOINT }}" ]] || \
|
||||
[[ -z "${{ secrets.HUAWEI_CLOUD_BUCKET }}" ]]; then
|
||||
echo "secrets-ready=false" >> $GITHUB_OUTPUT
|
||||
echo "::error::Required Huawei Cloud secrets are not configured."
|
||||
echo "::error::Please set: HUAWEI_CLOUD_AK, HUAWEI_CLOUD_SK, HUAWEI_CLOUD_ENDPOINT, HUAWEI_CLOUD_BUCKET"
|
||||
exit 1
|
||||
fi
|
||||
echo "secrets-ready=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ All required secrets are configured"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-secrets
|
||||
outputs:
|
||||
version-timestamp: ${{ steps.prepare-version.outputs.version_timestamp }}
|
||||
cdn-base: ${{ steps.prepare-version.outputs.cdn_base }}
|
||||
cdn-base-latest: ${{ steps.prepare-version.outputs.cdn_base_latest }}
|
||||
obs-path: ${{ steps.prepare-version.outputs.obs_path }}
|
||||
obs-path-latest: ${{ steps.prepare-version.outputs.obs_path_latest }}
|
||||
concurrency:
|
||||
group: deploy-cdn
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: add environment variable
|
||||
run: |
|
||||
VITE_ORIGIN_URL="https://agent.opentiny.design/"
|
||||
cat <<EOF >> designer-demo/env/.env.alpha
|
||||
# ---- appended by CI (deploy-cdn) ----
|
||||
VITE_ORIGIN=$VITE_ORIGIN_URL
|
||||
EOF
|
||||
echo "VITE_ORIGIN_URL=$VITE_ORIGIN_URL"
|
||||
|
||||
- id: prepare-version
|
||||
name: Prepare version-timestamp
|
||||
run: |
|
||||
# Extract version from package.json
|
||||
VERSION=$(node -p "require('./designer-demo/package.json').version")
|
||||
|
||||
# Generate timestamp in YYYYMMDD-HHMMSS format
|
||||
TIMESTAMP=$(TZ="Asia/Shanghai" date +%Y%m%d-%H%M%S)
|
||||
|
||||
# Combine for version-timestamp
|
||||
VERSION_TIMESTAMP="${VERSION}-${TIMESTAMP}"
|
||||
|
||||
# Set CDN base path
|
||||
CDN_BASE="https://res-static.opentiny.design/tiny-engine-designer/${VERSION_TIMESTAMP}/"
|
||||
CDN_BASE_LATEST="https://res-static.opentiny.design/tiny-engine-designer/latest/"
|
||||
OBS_PATH="tiny-engine-designer/${VERSION_TIMESTAMP}/"
|
||||
OBS_PATH_LATEST="tiny-engine-designer/latest"
|
||||
|
||||
# Export as environment variables for subsequent steps
|
||||
echo "VERSION_TIMESTAMP=$VERSION_TIMESTAMP" >> $GITHUB_ENV
|
||||
echo "CDN_BASE=$CDN_BASE" >> $GITHUB_ENV
|
||||
echo "OBS_PATH=$OBS_PATH" >> $GITHUB_ENV
|
||||
|
||||
# Set outputs for job-level export
|
||||
echo "version_timestamp=$VERSION_TIMESTAMP" >> $GITHUB_OUTPUT
|
||||
echo "cdn_base=$CDN_BASE" >> $GITHUB_OUTPUT
|
||||
echo "cdn_base_latest=$CDN_BASE_LATEST" >> $GITHUB_OUTPUT
|
||||
echo "obs_path=$OBS_PATH" >> $GITHUB_OUTPUT
|
||||
echo "obs_path_latest=$OBS_PATH_LATEST" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Version-Timestamp: $VERSION_TIMESTAMP"
|
||||
echo "CDN Base: $CDN_BASE"
|
||||
echo "OBS Path: $OBS_PATH"
|
||||
|
||||
- name: Run Build
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pnpm run build:plugin 2>&1 | tee /tmp/build-plugin.log
|
||||
# Run build:alpha equivalent with --base parameter
|
||||
pnpm run build:alpha --base=${{ env.CDN_BASE }} 2>&1 | tee /tmp/build-alpha.log
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: designer-demo-dist
|
||||
path: ./designer-demo/dist/
|
||||
retention-days: 1
|
||||
|
||||
deploy-cdn:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-secrets, build]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: designer-demo-dist
|
||||
path: ./designer-demo/dist/
|
||||
|
||||
- name: Install obsutil
|
||||
run: |
|
||||
curl -o obsutil.tar.gz https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_amd64.tar.gz
|
||||
tar -xzf obsutil.tar.gz
|
||||
chmod +x obsutil_linux_amd64_*/obsutil
|
||||
sudo mv obsutil_linux_amd64_*/obsutil /usr/local/bin/obsutil
|
||||
|
||||
- name: Configure and Upload to OBS
|
||||
run: |
|
||||
obsutil config -i=${{ secrets.HUAWEI_CLOUD_AK }} \
|
||||
-k=${{ secrets.HUAWEI_CLOUD_SK }} \
|
||||
-e=${{ secrets.HUAWEI_CLOUD_ENDPOINT }}
|
||||
# Upload to versioned path
|
||||
obsutil cp ./designer-demo/dist \
|
||||
obs://${{ secrets.HUAWEI_CLOUD_BUCKET }}/${{ needs.build.outputs.obs-path }} \
|
||||
-r -f -flat
|
||||
|
||||
# If is_latest_release is true, also upload to latest path
|
||||
if [ "${{ github.event.inputs.is_latest_release }}" = "true" ]; then
|
||||
# use cdn-base-latest replace cdn-base in all ./designer-demo/dist files
|
||||
find ./designer-demo/dist -type f \( -name "*.html" -o -name "*.js" -o -name "*.mjs" -o -name "*.css" \) \
|
||||
-exec sed -i "s|${{ needs.build.outputs.cdn-base }}|${{ needs.build.outputs.cdn-base-latest }}|g" {} +
|
||||
obsutil cp ./designer-demo/dist \
|
||||
obs://${{ secrets.HUAWEI_CLOUD_BUCKET }}/${{ needs.build.outputs.obs-path-latest }} \
|
||||
-r -f -flat
|
||||
fi
|
||||
|
||||
echo "Uploaded to: obs://${{ secrets.HUAWEI_CLOUD_BUCKET }}/${{ needs.build.outputs.obs-path }}"
|
||||
echo "CDN URL: https://res-static.opentiny.design/${{ needs.build.outputs.obs-path }}"
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
name: Deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
backend_url:
|
||||
description: 'Backend Base URL (e.g. https://agent.bytedev.site/)'
|
||||
required: true
|
||||
default: 'https://agent.bytedev.site/'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy-gh-pages:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: deploy-gh-pages
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: add environment variable
|
||||
env:
|
||||
ENV_BACKEND_URL: ${{ github.event.inputs.backend_url || 'https://agent.bytedev.site/' }}
|
||||
run: |
|
||||
if [[ "$ENV_BACKEND_URL" == *$'\n'* || "$ENV_BACKEND_URL" == *$'\r'* ]]; then
|
||||
echo "::error::backend_url must be a single-line value"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$ENV_BACKEND_URL" =~ ^https?://[a-zA-Z0-9._-]+(:[0-9]+)?(/[a-zA-Z0-9._/~:%-]*)?$ ]]; then
|
||||
echo "::error::backend_url must be an http(s) base URL using only URL-safe characters (letters, digits, . _ - / ~ : %)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENV_BACKEND_URL="${ENV_BACKEND_URL%/}/"
|
||||
|
||||
cat <<EOF >> designer-demo/env/.env.alpha
|
||||
# ---- appended by CI (gh-pages) ----
|
||||
VITE_ORIGIN=$ENV_BACKEND_URL
|
||||
EOF
|
||||
echo "VITE_ORIGIN_URL=$ENV_BACKEND_URL"
|
||||
- name: Run Build
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pnpm run build:plugin 2>&1 | tee /tmp/build-plugin.log
|
||||
pnpm run build:alpha 2>&1 | tee /tmp/build-alpha.log
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./designer-demo/dist/
|
||||
keep_files: true
|
||||
force_orphan: false
|
||||
user_name: 'github-actions[bot]'
|
||||
user_email: 'github-actions[bot]@users.noreply.github.com'
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
name: 'issue-translator'
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: usthe/issues-translate-action@v2.7
|
||||
with:
|
||||
IS_MODIFY_TITLE: false
|
||||
# 非必须,决定是否需要修改issue标题内容
|
||||
# 若是true,则机器人账户@Issues-translate-bot必须拥有修改此仓库issue权限。可以通过邀请@Issues-translate-bot加入仓库协作者实现。
|
||||
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically.
|
||||
# 非必须,自定义机器人翻译的前缀开始内容。
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
name: Push And Create PR Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: []
|
||||
pull_request:
|
||||
branches: [develop, main, refactor/develop, release/*]
|
||||
|
||||
jobs:
|
||||
push-check:
|
||||
runs-on: ubuntu-latest # windows-latest || macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Get changed files
|
||||
id: get_changed_files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
**.js
|
||||
**.vue
|
||||
**.jsx
|
||||
- name: Run ESLint
|
||||
run: npx eslint ${{steps.get_changed_files.outputs.all_changed_files}}
|
||||
- name: Run Build
|
||||
run: pnpm run build:plugin && pnpm run build:alpha > build-alpha.log 2>&1
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-alpha-log
|
||||
path: build-alpha.log
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
dist/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
lerna-debug.log
|
||||
packages/design-core/bundle-deps
|
||||
designer-demo/bundle-deps
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
tmp
|
||||
temp
|
||||
__pycache__
|
||||
|
||||
# .claude/skills is a generated link to .agents/skills (see scripts/link-skills.js)
|
||||
.claude/skills
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# npm run lint
|
||||
npx lint-staged -q
|
||||
|
||||
23
.npmignore
|
|
@ -1,23 +0,0 @@
|
|||
.build_config
|
||||
.cid
|
||||
.codecheck
|
||||
.husky
|
||||
.vscode
|
||||
# 只忽略根目录的 dist 文件夹
|
||||
/dist
|
||||
test
|
||||
node_modules
|
||||
.editorconfig
|
||||
.eslintignore
|
||||
.eslintrc.js
|
||||
.prettierignore
|
||||
.prettierrc
|
||||
jsconfig.json
|
||||
package-lock.json
|
||||
|
||||
# 忽略可能存在的其它编辑器文件夹
|
||||
.idea
|
||||
|
||||
/packages/design-core/public/mock/*
|
||||
**/**/tmp
|
||||
**/**/temp
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
dist
|
||||
package-lock.json
|
||||
**/node_modules/**
|
||||
# 忽略该文件夹下的测试对比文件,防止自动去掉分号之后导致测试失败
|
||||
packages/build/vite-plugin-meta-comments/test/expected/**
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "none",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
120
AGENTS.md
|
|
@ -1,120 +0,0 @@
|
|||
# TinyEngine — Repository Instructions for Coding Agents
|
||||
|
||||
## Purpose and Scope
|
||||
|
||||
This file is the canonical source of truth for repo-wide agent instructions.
|
||||
|
||||
- Applies to the whole repository unless a closer `AGENTS.md` overrides it for a subtree.
|
||||
- `CLAUDE.md` is a compatibility entrypoint that imports this file. Do not maintain a second independent copy of the same rules.
|
||||
- Keep this file limited to repo-wide guidance. Package-specific implementation details belong in package-level instruction files.
|
||||
|
||||
## Repository Snapshot
|
||||
|
||||
- Monorepo: pnpm workspaces + lerna (independent versioning)
|
||||
- Primary stack: Vue 3, Vite, JavaScript/TypeScript
|
||||
- Package manager: `pnpm` only for interactive work in this repo
|
||||
- Designer app: `designer-demo/`
|
||||
- Local mock backend: `mockServer/`
|
||||
|
||||
## Working Model
|
||||
|
||||
- Inspect the affected package, its `package.json`, and the nearest instruction file before editing.
|
||||
- Keep changes scoped. Do not normalize unrelated files or rename fixtures just for consistency.
|
||||
- Prefer targeted package-level validation over whole-repo commands when possible.
|
||||
- Treat `pnpm lint` and `pnpm format` as mutating commands, not read-only verification.
|
||||
- Do not invoke `npm` or `yarn` directly for normal repo work. Existing package scripts may still shell out internally; leave that alone unless the task is specifically about package scripts.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Read-mostly commands
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm dev
|
||||
pnpm build:plugin
|
||||
pnpm build:alpha
|
||||
pnpm --filter @opentiny/tiny-engine-dsl-vue test:unit
|
||||
```
|
||||
|
||||
### Mutating commands
|
||||
|
||||
```sh
|
||||
pnpm lint # ESLint with --fix
|
||||
pnpm format # Prettier --write
|
||||
```
|
||||
|
||||
Canonical script definitions live in:
|
||||
|
||||
- `package.json`
|
||||
- `packages/*/package.json`
|
||||
- `.github/workflows/push-check.yml`
|
||||
- `.github/workflows/Release.yml`
|
||||
|
||||
## Verification Matrix
|
||||
|
||||
Run the smallest sufficient verification for the change surface, then expand if the change is broad or risky.
|
||||
|
||||
1. Docs-only changes:
|
||||
No code verification required unless the docs change commands or workflow descriptions that should be checked against source files.
|
||||
2. `packages/vue-generator/**`:
|
||||
Run the affected testcase or `pnpm --filter @opentiny/tiny-engine-dsl-vue test:unit`.
|
||||
If generator behavior changes, run the full `test:unit` suite before handoff and inspect any changed `expected/*.vue` files.
|
||||
3. Published library packages under `packages/**`:
|
||||
Run the package-local `test` script if one exists.
|
||||
Run `pnpm build:plugin` when build output or published package behavior may be affected.
|
||||
4. `designer-demo/**` or shared packages consumed by the demo:
|
||||
Run `pnpm build:alpha`.
|
||||
5. Cross-package build or release-facing changes:
|
||||
Run `pnpm build:plugin` and `pnpm build:alpha`.
|
||||
6. Config, workspace, CI, or release script changes:
|
||||
Verify the directly affected command(s) after approval.
|
||||
|
||||
## Approval Boundaries
|
||||
|
||||
### Always OK
|
||||
|
||||
- Read any source file
|
||||
- Run targeted tests and builds
|
||||
- Edit implementation files inside existing packages
|
||||
- Add or update tests that match the scope of the change
|
||||
- Update docs that reflect current repo behavior
|
||||
|
||||
### Ask First
|
||||
|
||||
- Changing workspace, lerna, pnpm, ESLint, Prettier, or TypeScript configuration
|
||||
- Modifying CI workflows, release scripts, or publish flows
|
||||
- Upgrading major dependencies or changing pinned overrides
|
||||
- Reordering or adding/removing default vue-generator attribute hooks
|
||||
- Large-scale edits to generated mappings or vendored patches
|
||||
|
||||
When asking first, include:
|
||||
|
||||
- what you want to change
|
||||
- why the current rules or implementation are insufficient
|
||||
- what verification you would run after approval
|
||||
|
||||
### Never
|
||||
|
||||
- Use `npm` or `yarn` directly for routine repo commands
|
||||
- Skip hooks with `--no-verify`
|
||||
- Hardcode versions for workspace packages
|
||||
- Edit `patches/` without understanding the upstream issue and the patch purpose
|
||||
- Rewrite generated expectations or snapshots without validating the new output first
|
||||
|
||||
## Task-Specific Expectations
|
||||
|
||||
- Bug fix:
|
||||
Add or update a regression test when behavior changes.
|
||||
- Refactor:
|
||||
Preserve behavior and prove it with targeted verification.
|
||||
- Snapshot or generated output change:
|
||||
Explain why the output changed and list the affected fixture directories.
|
||||
- Commit or PR work:
|
||||
Only do it if asked. Use Conventional Commits and target `develop` unless the user specifies otherwise.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `pnpm install` is enforced by `preinstall`; npm and yarn are rejected for direct repo usage.
|
||||
- `pnpm lint` writes fixes. Use it deliberately.
|
||||
- CI relies on `build:plugin` and `build:alpha`, not only lint or unit tests.
|
||||
- Test directories such as `test/`, `expected/`, and `output/` are not always linted; do not treat lint success as fixture validation.
|
||||
17
CHANGELOG.md
|
|
@ -1,17 +0,0 @@
|
|||
# 更新日志
|
||||
|
||||
## v1.0.0-alpha.0
|
||||
|
||||
`2023/09/25`
|
||||
|
||||
### 📢 破坏性变更
|
||||
|
||||
无
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 首个版本提交
|
||||
|
||||
### 🐞 缺陷修复
|
||||
|
||||
无
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# TinyEngine — Claude Code Entry
|
||||
|
||||
This file is intentionally thin. The canonical repo-wide instructions live in `AGENTS.md`.
|
||||
|
||||
@./AGENTS.md
|
||||
|
||||
When working inside a subtree that has its own `CLAUDE.md`, follow the closer file as an extension of these repo-wide rules.
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
We are glad that you are willing to contribute to the TinyEngine open source project. There are many forms of contribution. You can choose one or more of them based on your strengths and interests:
|
||||
|
||||
- Report [new defect](https://github.com/opentiny/tiny-engine/issues/new?template=bug-report.yml).
|
||||
- Provide more detailed information for the [existing defects](https://github.com/opentiny/tiny-engine/labels/bug), such as supplementary screenshots, more detailed reproduction steps, minimum reproducible demo links, etc.
|
||||
- Submit Pull requests to fix typos in the document or make the document clearer and better.
|
||||
- Add the official assistant WeChat `opentiny-official` and join the technical exchange group to participate in the discussion.
|
||||
|
||||
When you personally use the TinyEngine component library and participate in many of the above contributions, as you become familiar with TinyEngine , you can try to do something more challenging, such as:
|
||||
|
||||
- Fix the defect. You can start with [Good-first issue](https://github.com/opentiny/tiny-engine/labels/good%20first%20issue).
|
||||
- Implementation of new features
|
||||
- Complete unit tests.
|
||||
- Translate documents
|
||||
- Participate in code review.
|
||||
|
||||
## Bug Reports
|
||||
|
||||
If you encounter problems in the process of using TinyEngine components, you are welcome to submit Issue to us. Before submitting Issue, please read the relevant [official documentation](https://opentiny.design/tiny-engine) carefully to confirm whether this is a defect or an unimplemented function.
|
||||
|
||||
If it is a defect, select [Bug report](https://github.com/opentiny/tiny-engine/issues/new?template=bug-report.yml) template when creating a new Issue. The title follows the format of `[toolkitName/pluginName/EngineCore] defect description`. For example: `[tiny-engine-toolbar-refresh] The refresh function cannot be used`.
|
||||
|
||||
Issue that reports defects mainly needs to fill in the following information:
|
||||
|
||||
- Version numbers of `tiny-engine` and `node`.
|
||||
- The performance of the defect can be illustrated by screenshot, and if there is an error, the error message can be posted.
|
||||
- Defect reproduction step, preferably with a minimum reproducible demo link.
|
||||
|
||||
If it is a new feature, select [Feature request](https://github.com/opentiny/tiny-engine/issues/new?template=feature-request.yml) template. The title follows the format of `[toolkitName/pluginName/EngineCore] new feature description`. For example: `[tiny-engine-theme] New Blue Theme`.
|
||||
|
||||
The following information is required for the Issue of the new feature:
|
||||
|
||||
- What problems does this feature mainly solve for users?
|
||||
- What is the api of this feature?
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Before submitting pull request, please make sure that your submission is in line with the overall plan of TinyEngine. Generally, issues that marked as [bug](https://github.com/opentiny/tiny-engine/labels/bug) are encouraged to submit pull requests. If you are not sure, you can create a [Discussion](https://github.com/opentiny/tiny-engine/discussions) for discussion.
|
||||
|
||||
Local startup steps:
|
||||
|
||||
- Click the Fork button in the upper right corner of the [TinyEngine](https://github.com/opentiny/tiny-engine) code repository to fork the upstream warehouse to the personal warehouse.
|
||||
- Clone personal warehouse to local
|
||||
- Run `npm install` under the TinyEngine root directory to install node dependencies.
|
||||
- Run `npm install` under the TinyEngine mockServer to install node dependencies
|
||||
- Run `npm run serve` under the TinyEngine root directory, and then `run npm run dev` in the mockServer directory to start local development.
|
||||
|
||||
```shell
|
||||
# username indicates the user name. Replace it before running the command.
|
||||
git clone git@github.com:username/tiny-engine.git
|
||||
cd tiny-engine
|
||||
git remote add upstream git@github.com:opentiny/tiny-engine.git
|
||||
pnpm i
|
||||
|
||||
# Start the project.
|
||||
$ pnpm dev
|
||||
|
||||
```
|
||||
|
||||
To submit a PR:
|
||||
|
||||
- Create a new branch `git checkout -b username/feature1`. The name of the branch should be `username/feat-xxx` / `username/fix-xxx`.
|
||||
- Local coding.
|
||||
- Submit according to [Commit Message Format](https://www.conventionalcommits.org/zh-hans/v1.0.0/) specification. PR that do not conform to the submission specification will not be merged.
|
||||
- Submit to remote repository: `git push origin branchName`.
|
||||
- (Optional) Synchronize upstream repository dev branch latest code: `git pull upstream develop`.
|
||||
- Open the [Pull requests](https://github.com/opentiny/tiny-engine/pulls) link of the TinyEngine code repository and click the New pull request button to submit the PR.
|
||||
- Project Committer conducts Code Review and makes comments.
|
||||
- The PR author adjusts the code according to the opinion. Please note that when a branch initiates PR, the subsequent commit will be synchronized automatically, and there is no need to resubmit the PR.
|
||||
- Project administrator merges PR.
|
||||
|
||||
The contribution process is over, thank you for your contribution!
|
||||
|
||||
## Join the open source community
|
||||
|
||||
If you are interested in our open source projects, please join our open source community in the following ways.
|
||||
|
||||
- Add the official assistant WeChat: opentiny-official, join our technical exchange group
|
||||
- Join the mailing list: opentiny@googlegroups.com
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# 贡献指南
|
||||
|
||||
很高兴你有意愿参与 TinyEngine 开源项目的贡献,参与贡献的形式有很多种,你可以根据自己的特长和兴趣选择其中的一个或多个:
|
||||
|
||||
- 报告[新缺陷](https://github.com/opentiny/tiny-engine/issues/new?template=bug-report.yml)
|
||||
- 为[已有缺陷](https://github.com/opentiny/tiny-engine/labels/bug)提供更详细的信息,比如补充截图、提供更详细的复现步骤、提供最小可复现 demo 链接等
|
||||
- 提交 Pull requests 修复文档中的错别字或让文档更清晰和完善
|
||||
- 添加官方小助手微信 opentiny-official,加入技术交流群参与讨论
|
||||
|
||||
当你亲自使用 TinyEngine 组件库,并参与多次以上形式的贡献,对 TinyEngine 逐渐熟悉之后,可以尝试做一些更有挑战的事情,比如:
|
||||
|
||||
- 修复缺陷,可以先从 [Good-first issue](https://github.com/opentiny/tiny-engine/labels/good%20first%20issue) 开始
|
||||
- 实现新特性
|
||||
- 完善单元测试
|
||||
- 翻译文档
|
||||
- 参与代码检视
|
||||
|
||||
## 提交 Issue
|
||||
|
||||
如果你在使用 TinyEngine 组件过程中遇到问题,欢迎给我们提交 Issue,提交 Issue 之前,请先仔细阅读相关的[官方文档](https://opentiny.design/tiny-engine),确认这是一个缺陷还是尚未实现的功能。
|
||||
|
||||
如果是一个缺陷,创建新 Issue 时选择 [Bug report](https://github.com/opentiny/tiny-engine/issues/new?template=bug-report.yml) 模板,标题遵循 `[toolkitName/pluginName/EngineCore]缺陷简述` 的格式,比如:`[tiny-engine-toolbar-refresh] 刷新功能无法使用`。
|
||||
|
||||
报告缺陷的 Issue 主要需要填写以下信息:
|
||||
|
||||
- tiny-engine 和 node 的版本号
|
||||
- 缺陷的表现,可截图辅助说明,如果有报错可贴上报错信息
|
||||
- 缺陷的复现步骤,最好能提供一个最小可复现 demo 链接
|
||||
|
||||
如果是一个新特性,则选择 [Feature request](https://github.com/opentiny/tiny-engine/issues/new?template=feature-request.yml) 模板,标题遵循 `[toolkitName/pluginName/EngineCore]新特性简述` 的格式,比如:`[tiny-engine-theme] 新增蓝色主题`。
|
||||
|
||||
新特性的 Issue 主要需要填写以下信息:
|
||||
|
||||
- 该特性主要解决用户的什么问题
|
||||
- 该特性的 api 是什么样的
|
||||
|
||||
## 提交 PR
|
||||
|
||||
提交 PR 之前,请先确保你提交的内容是符合 TinyEngine 整体规划的,一般已经标记为 [bug](https://github.com/opentiny/tiny-engine/labels/bug) 的 Issue 是鼓励提交 PR 的,如果你不是很确定,可以创建一个 [Discussion](https://github.com/opentiny/tiny-engine/discussions) 进行讨论。
|
||||
|
||||
本地启动步骤:
|
||||
|
||||
- 点击 [TinyEngine](https://github.com/opentiny/tiny-engine) 代码仓库右上角的 Fork 按钮,将上游仓库 Fork 到个人仓库
|
||||
- Clone 个人仓库到本地
|
||||
- 在 TinyEngine 根目录下运行 `pnpm i`, 安装依赖
|
||||
- 在 TinyEngine 根目录下运行 `pnpm dev`,启动本地开发
|
||||
|
||||
```shell
|
||||
# username 为用户名,执行前请替换
|
||||
git clone git@github.com:username/tiny-engine.git
|
||||
cd tiny-engine
|
||||
git remote add upstream git@github.com:opentiny/tiny-engine.git
|
||||
pnpm i
|
||||
|
||||
# 启动项目
|
||||
$ pnpm dev
|
||||
|
||||
```
|
||||
|
||||
提交 PR 的步骤:
|
||||
|
||||
- 创建新分支 `git checkout -b username/feature1`,分支名字建议为 `username/feat-xxx` / `username/fix-xxx`
|
||||
- 本地编码
|
||||
- 遵循 Commit Message Format 规范进行提交,不符合提交规范的 PR 将不会被合并
|
||||
- 提交到远程仓库:git push origin branchName
|
||||
- (可选)同步上游仓库 develop 分支最新代码:git pull upstream develop
|
||||
- 打开 TinyEngine 代码仓库的 [Pull requests](https://github.com/opentiny/tiny-engine/pulls) 链接,点击 New pull request 按钮提交 PR
|
||||
- 项目 Committer 进行 Code Review,并提出意见
|
||||
- PR 作者根据意见调整代码,请注意一个分支发起了 PR 后,后续的 commit 会自动同步,无需重新提交 PR
|
||||
- 项目管理员合并 PR
|
||||
|
||||
贡献流程结束,感谢你的贡献!
|
||||
|
||||
## 加入开源社区
|
||||
|
||||
如果你对我们的开源项目感兴趣,欢迎通过以下方式加入我们的开源社区。
|
||||
|
||||
- 添加官方小助手微信:opentiny-official,加入我们的技术交流群
|
||||
- 加入邮件列表:opentiny@googlegroups.com
|
||||
13
Dockerfile
|
|
@ -1,13 +0,0 @@
|
|||
FROM node:18-alpine as build
|
||||
|
||||
WORKDIR /app/
|
||||
ADD . .
|
||||
RUN npm config set strict-ssl false \
|
||||
&& npm config set registry https://registry.npmmirror.com/ \
|
||||
&& npm install pnpm -g \
|
||||
&& pnpm i \
|
||||
&& pnpm build:plugin \
|
||||
&& pnpm build:prod
|
||||
|
||||
FROM nginx:latest
|
||||
COPY --from=build /app/designer-demo/dist/ /usr/share/nginx/html/
|
||||
22
LICENSE
|
|
@ -1,22 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 - present TinyEngine Authors.
|
||||
Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# tiny-engine
|
||||
|
||||
#### Description
|
||||
TinyEngine是一个低代码引擎,基于这个引擎可以构建或者开发出不同领域的低代码平台。
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
144
README.md
|
|
@ -1,131 +1,37 @@
|
|||
<p align="center">
|
||||
<a href="https://opentiny.design/tiny-engine" target="_blank" rel="noopener noreferrer">
|
||||
<img alt="OpenTiny Logo" src="logo.svg" height="100" style="max-width:100%;">
|
||||
</a>
|
||||
</p>
|
||||
# tiny-engine
|
||||
|
||||
<p align="center">TinyEngine enables developers to customize low-code platforms, build low-code platforms online in real time, and support secondary development or integration of low-code platform capabilities.</p>
|
||||
#### 介绍
|
||||
TinyEngine是一个低代码引擎,基于这个引擎可以构建或者开发出不同领域的低代码平台。
|
||||
|
||||
[](https://deepwiki.com/opentiny/tiny-engine)
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
|
||||
English | [简体中文](README.zh-CN.md)
|
||||
|
||||
🌈 Features:
|
||||
#### 安装教程
|
||||
|
||||
- Cross-end cross-frame front-end components
|
||||
- Supports online real-time construction, secondary development, or being integrated.
|
||||
- Directly generate deployable source code without engine support.
|
||||
- Allows access to third-party components and customized extension plug-ins.
|
||||
- Supports high-code and low-code, and hybrid development and deployment of applications.
|
||||
- The platform accesses LLM capabilities to help developers build applications.
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
## Documentation
|
||||
#### 使用说明
|
||||
|
||||
- intro:https://opentiny.design/tiny-engine#/home
|
||||
- tutorial:https://opentiny.design/tiny-engine#/help-center/index
|
||||
- playground:https://opentiny.design/tiny-engine#/tiny-engine-editor
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
## Usage
|
||||
#### 参与贡献
|
||||
|
||||
### Environment Setup
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
|
||||
- Install Node.js 18+
|
||||
|
||||
- Install pnpm 9+
|
||||
#### 特技
|
||||
|
||||
```sh
|
||||
$ npm install -g pnpm
|
||||
```
|
||||
|
||||
### Create Low-Code Platform using CLI
|
||||
|
||||
```sh
|
||||
# Create low-code platform
|
||||
$ npx @opentiny/tiny-engine-cli@latest create-platform <name>
|
||||
# Enter the low-code platform directory
|
||||
$ cd <name>
|
||||
# Install dependencies
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
### Local development: Start the local mock server and use the mock data of the local mock server.
|
||||
|
||||
> The mock server included with the created low-code platform only provides basic backend mock functionality. If you need to experience the complete backend service capabilities, please refer to the Java backend startup instructions below.
|
||||
|
||||
```sh
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
### Local Development with Java Backend
|
||||
|
||||
Java backend repository: https://github.com/opentiny/tiny-engine-backend-java
|
||||
|
||||
Start Java backend for frontend-backend integration:
|
||||
|
||||
[Frontend-Backend Integration Documentation](https://docs.opentiny.design/tiny-engine/dev/debugging-of-java-backend)
|
||||
|
||||
### Materials Synchronization [Solution](https://docs.opentiny.design/tiny-engine/dev/material-sync-solution)
|
||||
|
||||
```sh
|
||||
$ pnpm splitMaterials
|
||||
```
|
||||
|
||||
```sh
|
||||
$ pnpm buildMaterials
|
||||
```
|
||||
|
||||
Open a browser: `http://localhost:8080/?type=app&id=1&tenant=1&pageid=1`
|
||||
`url search` Parameters:
|
||||
|
||||
- `type=app` Application type
|
||||
- `id=xxx` Application ID
|
||||
- `tenant=xxx` Organization ID
|
||||
- `pageid=xxx` Page ID
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
# Build Designer
|
||||
pnpm run build:alpha or build:prod
|
||||
```
|
||||
|
||||
## Milestones
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
|
||||
1.0.0-beta.x version : 2023-09-25, 2024-05-20
|
||||
1.0.0-rc version(refactor version) : 2024-10-01
|
||||
1.0.0 version : 2024-11-01
|
||||
2.0.0 version : 2024-12-16
|
||||
2.1.0 version : 2025-01-02
|
||||
2.2.0 version : 2025-02-19
|
||||
2.3.0 version : 2025-03-14
|
||||
2.4.0 version : 2025-04-07
|
||||
2.5.0 version : 2025-05-15
|
||||
```
|
||||
|
||||
## 🤝 Participation and Contribution
|
||||
|
||||
If you are interested in our open source project, please join us! 🎉
|
||||
|
||||
Please read the [Contribution Guide](CONTRIBUTING.md) before participating in the contribution.
|
||||
|
||||
- Add official assistant WeChat opentiny-official and join the technical exchange group
|
||||
- Join the mailing list opentiny@googlegroups.com
|
||||
|
||||
## ❤️ Acknowledgments
|
||||
|
||||
Thanks to all the developers who have contributed to TinyEngine!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/opentiny/tiny-engine/graphs/contributors" target="_blank">
|
||||
<img alt="Contributors" src="https://contrib.rocks/image?repo=opentiny/tiny-engine">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
|
|
|
|||
129
README.zh-CN.md
|
|
@ -1,129 +0,0 @@
|
|||
<p align="center">
|
||||
<a href="https://opentiny.design/tiny-engine" target="_blank" rel="noopener noreferrer">
|
||||
<img alt="OpenTiny Logo" src="logo.svg" height="100" style="max-width:100%;">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">TinyEngine低代码引擎使能开发者定制低代码平台,支持在线实时构建低码平台,支持二次开发或集成低码平台能力</p>
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
🌈 特性:
|
||||
|
||||
- 跨端跨框架前端组件
|
||||
- 支持在线实时构建、支持二次开发或被集成
|
||||
- 直接生成可部署的源码,运行时无需引擎支撑
|
||||
- 允许接入第三方组件、允许定制扩展插件
|
||||
- 支持高代码与低代码,混合开发部署应用
|
||||
- 平台接入 AI 大模型能力,辅助开发者构建应用
|
||||
|
||||
## 文档
|
||||
|
||||
- 介绍:https://opentiny.design/tiny-engine#/home
|
||||
- 使用文档:https://opentiny.design/tiny-engine#/help-center/index
|
||||
- 演示应用:https://opentiny.design/tiny-engine#/tiny-engine-editor
|
||||
|
||||
## 使用
|
||||
|
||||
### 环境准备
|
||||
|
||||
- 安装 Node.js 18+
|
||||
|
||||
- 安装 pnpm 9+
|
||||
|
||||
```sh
|
||||
$ npm install -g pnpm
|
||||
```
|
||||
|
||||
### 使用 cli 创建低代码平台
|
||||
|
||||
```sh
|
||||
# 创建低代码平台
|
||||
$ npx @opentiny/tiny-engine-cli@latest create-platform <name>
|
||||
# 进入低代码平台
|
||||
$ cd <name>
|
||||
# 安装依赖
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
### 本地开发,启动本地 mock 服务器,使用本地 mock 服务器的 mock 数据
|
||||
|
||||
> 创建低代码平台后自带的 mock Server 仅包含简单的后端 mock 功能,如果需要体验完整的后端服务能力,请参考下文启动 java 服务端。
|
||||
|
||||
```sh
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
### 本地开发,启动 Java 服务端
|
||||
|
||||
java 服务端代码仓库:https://github.com/opentiny/tiny-engine-backend-java
|
||||
|
||||
启动 Java 服务端进行前后端联调:
|
||||
|
||||
[前后端联调文档](https://docs.opentiny.design/tiny-engine/dev/debugging-of-java-backend)
|
||||
|
||||
### 物料同步[方案](https://docs.opentiny.design/tiny-engine/dev/material-sync-solution)
|
||||
|
||||
```sh
|
||||
$ pnpm splitMaterials
|
||||
```
|
||||
|
||||
```sh
|
||||
$ pnpm buildMaterials
|
||||
```
|
||||
|
||||
浏览器打开:`http://localhost:8080/?type=app&id=1&tenant=1&pageid=1`
|
||||
`url search`参数:
|
||||
|
||||
- `type=app` 应用类型
|
||||
- `id=xxx` 应用 ID
|
||||
- `tenant=xxx` 组织 ID
|
||||
- `pageid=xxx` 页面 ID
|
||||
|
||||
## 构建
|
||||
|
||||
```sh
|
||||
# 构建设计器
|
||||
pnpm run build:alpha 或 build:prod
|
||||
```
|
||||
|
||||
## 里程碑
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
|
||||
1.0.0-beta.x version : 2023-09-25, 2024-05-20
|
||||
1.0.0-rc version(refactor version) : 2024-10-01
|
||||
1.0.0 version : 2024-11-01
|
||||
2.0.0 version : 2024-12-16
|
||||
2.1.0 version : 2025-01-02
|
||||
2.2.0 version : 2025-02-19
|
||||
2.3.0 version : 2025-03-14
|
||||
2.4.0 version : 2025-04-07
|
||||
2.5.0 version : 2025-05-15
|
||||
```
|
||||
|
||||
## 🤝 参与贡献
|
||||
|
||||
如果你对我们的开源项目感兴趣,欢迎加入我们!🎉
|
||||
|
||||
参与贡献之前请先阅读[贡献指南](CONTRIBUTING.zh-CN.md)。
|
||||
|
||||
- 添加官方小助手微信 opentiny-official,加入技术交流群
|
||||
- 加入邮件列表 opentiny@googlegroups.com
|
||||
|
||||
## ❤️ 致谢
|
||||
|
||||
感谢所有为 TinyEngine 做出贡献的开发者们!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/opentiny/tiny-engine/graphs/contributors" target="_blank">
|
||||
<img alt="贡献者" src="https://contrib.rocks/image?repo=opentiny/tiny-engine">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 开源协议
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
export default {
|
||||
id: 'engine.config',
|
||||
theme: 'light',
|
||||
material: ['./mock/bundle.json'],
|
||||
scripts: [],
|
||||
styles: [],
|
||||
// 是否开启 TailWindCSS 特性
|
||||
enableTailwindCSS: true,
|
||||
// 是否开启 使用结构化CSS 特性
|
||||
enableStructuredCss: false
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# alpha mode, used by the "build:alpha" script
|
||||
|
||||
NODE_ENV=production
|
||||
VITE_CDN_DOMAIN=https://registry.npmmirror.com
|
||||
# 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror
|
||||
VITE_CDN_TYPE=npmmirror
|
||||
# VITE_ORIGIN=
|
||||
|
||||
# 错误监控上报 url
|
||||
VITE_ERROR_MONITOR_URL=/platform-center/api/platform/monitoring/event
|
||||
# 是否开启错误监控
|
||||
VITE_ERROR_MONITOR=false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# development mode, used by the "vite" command
|
||||
|
||||
NODE_ENV=development
|
||||
VITE_CDN_DOMAIN=https://registry.npmmirror.com
|
||||
# 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror
|
||||
VITE_CDN_TYPE=npmmirror
|
||||
# request data via alpha service
|
||||
# VITE_ORIGIN=
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# CDN 本地化配置示例
|
||||
|
||||
# 将画布、页面预览需要的 vue、vue-i18n 等等依赖复制到构建产物中
|
||||
VITE_LOCAL_IMPORT_MAPS=true
|
||||
|
||||
# 将本地物料 bundle.json 的 script 和 css 复制到构建产物中
|
||||
VITE_LOCAL_BUNDLE_DEPS=true
|
||||
|
||||
# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称,默认为 local-cdn-static
|
||||
VITE_LOCAL_IMPORT_PATH=local-cdn-static
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# prod mode, used by the "build:prod" script
|
||||
|
||||
NODE_ENV=production
|
||||
# VITE_CDN_DOMAIN=https://unpkg.com
|
||||
VITE_CDN_DOMAIN=https://registry.npmmirror.com
|
||||
# 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror
|
||||
VITE_CDN_TYPE=npmmirror
|
||||
#VITE_ORIGIN=
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "designer-demo",
|
||||
"private": true,
|
||||
"version": "2.11.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "cross-env vite",
|
||||
"dev:withAuth": "cross-env VITE_AUTH=true vite",
|
||||
"build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=6144 vite build --mode alpha",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/tiny-engine": "workspace:^",
|
||||
"@opentiny/tiny-engine-meta-register": "workspace:^",
|
||||
"@opentiny/tiny-engine-utils": "workspace:*",
|
||||
"@opentiny/vue": "~3.20.0",
|
||||
"@opentiny/vue-design-smb": "~3.20.0",
|
||||
"@opentiny/vue-icon": "~3.20.0",
|
||||
"@opentiny/vue-locale": "~3.20.0",
|
||||
"@opentiny/vue-renderless": "~3.20.0",
|
||||
"@opentiny/vue-theme": "~3.20.0",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"vue": "^3.4.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentiny/tiny-engine-mock": "workspace:^",
|
||||
"@opentiny/tiny-engine-vite-config": "workspace:^",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"vite": "^5.4.2",
|
||||
"vitest": "3.0.9"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Design Core Preview</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/preview.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 547 B |
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
// 注册表示例
|
||||
import { META_SERVICE, META_APP } from '@opentiny/tiny-engine-meta-register'
|
||||
import engineConfig from './engine.config'
|
||||
import { HttpService } from './src/composable'
|
||||
import scriptPlugin from './src/plugins/script'
|
||||
|
||||
export default {
|
||||
[META_SERVICE.Http]: HttpService,
|
||||
'engine.config': {
|
||||
...engineConfig
|
||||
},
|
||||
// 配置 false 隐藏工具栏清空按钮
|
||||
[META_APP.Clean]: false,
|
||||
// 配置 false 隐藏大纲树,手动配置 tree-shaking 为 false,仍然不会被 tree-shaking
|
||||
// #__TINY_ENGINE_TREE_SHAKING__: false
|
||||
[META_APP.OutlineTree]: false,
|
||||
// 替换整个页面JS插件,手动配置 tree-shaking 为 true
|
||||
/* #__TINY_ENGINE_TREE_SHAKING__: true */
|
||||
[META_APP.Page]: scriptPlugin,
|
||||
// 新增模块
|
||||
'engine.plugins.custom_id': {
|
||||
...cuttomPlugin,
|
||||
id: 'engine.plugins.custom_id'
|
||||
},
|
||||
// 调整插件顺序
|
||||
[META_APP.Layout]: {
|
||||
options: {
|
||||
relativeLayoutConfig: {
|
||||
[META_APP.Script]: {
|
||||
insertBefore: META_APP.AppManage
|
||||
},
|
||||
// 调整插件顺序
|
||||
[META_APP.Materials]: {
|
||||
insertAfter: META_APP.State
|
||||
},
|
||||
// 调整插件上下位置
|
||||
[META_APP.Schema]: {
|
||||
insertBefore: META_APP.Materials
|
||||
},
|
||||
// 调整工具栏顺序
|
||||
[META_APP.Save]: {
|
||||
insertBefore: META_APP.ThemeSwitch
|
||||
},
|
||||
// 支持切换组
|
||||
[META_APP.Lang]: {
|
||||
insertAfter: META_APP.Breadcrumb
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
import { META_SERVICE, META_APP } from '@opentiny/tiny-engine-meta-register'
|
||||
import engineConfig from './engine.config'
|
||||
import { HttpService } from './src/composable'
|
||||
|
||||
const baseURL = import.meta.env.BASE_URL || '.'
|
||||
const baseURLWithoutSlash = baseURL.replace(/\/$/, '')
|
||||
|
||||
export default {
|
||||
[META_SERVICE.Http]: HttpService,
|
||||
'engine.config': {
|
||||
...engineConfig
|
||||
},
|
||||
// 调整插件顺序示例:
|
||||
[META_APP.Layout]: {
|
||||
options: {
|
||||
relativeLayoutConfig: {
|
||||
[META_APP.Page]: {
|
||||
insertBefore: META_APP.State
|
||||
},
|
||||
// 调整插件顺序
|
||||
[META_APP.OutlineTree]: {
|
||||
insertAfter: META_APP.Materials
|
||||
},
|
||||
// 调整插件上下位置
|
||||
[META_APP.Schema]: {
|
||||
insertBefore: META_APP.Help
|
||||
},
|
||||
// 调整工具栏顺序
|
||||
[META_APP.Save]: {
|
||||
insertAfter: META_APP.GenerateCode
|
||||
},
|
||||
// 支持切换组
|
||||
[META_APP.Lang]: {
|
||||
insertAfter: META_APP.ViewSetting
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[META_APP.Preview]: {
|
||||
options: {
|
||||
// 配置预览跳转的 url:根据实际业务需求进行配置
|
||||
// 文档:https://opentiny.design/tiny-engine#/help-center/course/dev/preview-api
|
||||
previewUrl: ['prod', 'alpha'].includes(import.meta.env.MODE) ? `${baseURLWithoutSlash}/preview.html` : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<div v-if="visible" class="tiny-popup__wrapper">
|
||||
<div class="tiny-sso__box">
|
||||
<div class="tiny-sso__body">
|
||||
<iframe :src="url" class="tiny-sso__body-iframe" frameBorder="0" scrolling="no"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const visible = ref(false)
|
||||
const url = ref('')
|
||||
|
||||
const openLogin = (procession, newUrl) => {
|
||||
visible.value = true
|
||||
url.value = newUrl
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
procession.mePromise.resolve = resolve
|
||||
procession.mePromise.reject = reject
|
||||
})
|
||||
}
|
||||
|
||||
const closeLogin = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
openLogin,
|
||||
closeLogin,
|
||||
visible,
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tiny-popup__wrapper {
|
||||
z-index: 9999;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
|
||||
.tiny-sso__box {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.2);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.tiny-sso__body {
|
||||
text-align: initial;
|
||||
padding: 20px;
|
||||
color: #5a5e66;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
|
||||
.tiny-sso__body-iframe {
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
overflow: hidden;
|
||||
//兼容edge
|
||||
@supports (-ms-ime-align: auto) {
|
||||
height: 460px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
import { createApp } from 'vue'
|
||||
import { HttpService } from '@opentiny/tiny-engine'
|
||||
import { useBroadcastChannel } from '@vueuse/core'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import Login from './Login.vue'
|
||||
|
||||
const LOGIN_EXPIRED_CODE = 401
|
||||
const { BROADCAST_CHANNEL } = constants
|
||||
|
||||
const { post: globalNotify } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify })
|
||||
|
||||
const procession = {
|
||||
promiseLogin: null,
|
||||
mePromise: {}
|
||||
}
|
||||
let loginVM = null
|
||||
|
||||
const showError = (url, message) => {
|
||||
if (message === 'canceled') return // 取消请求场景不报错
|
||||
globalNotify({
|
||||
type: 'error',
|
||||
title: '接口报错',
|
||||
message: `报错接口: ${url} \n报错信息: ${message ?? ''}`
|
||||
})
|
||||
}
|
||||
|
||||
const preRequest = (config) => {
|
||||
const isDevelopEnv = import.meta.env.MODE?.includes('dev')
|
||||
|
||||
if (isDevelopEnv && config.url.match(/\/generate\//)) {
|
||||
config.baseURL = ''
|
||||
}
|
||||
|
||||
const isVsCodeEnv = window.vscodeBridge
|
||||
|
||||
if (isVsCodeEnv) {
|
||||
config.baseURL = ''
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
const preResponse = (res) => {
|
||||
if (res.data?.error) {
|
||||
showError(res.config?.url, res?.data?.error?.message)
|
||||
|
||||
return Promise.reject(res.data.error)
|
||||
}
|
||||
|
||||
return res.data?.data || res.data
|
||||
}
|
||||
|
||||
const openLogin = () => {
|
||||
if (!window.lowcode) {
|
||||
const loginDom = document.createElement('div')
|
||||
document.body.appendChild(loginDom)
|
||||
loginVM = createApp(Login).mount(loginDom)
|
||||
|
||||
window.lowcode = {
|
||||
platformCenter: {
|
||||
Session: {
|
||||
rebuiltCallback: function () {
|
||||
loginVM.closeLogin()
|
||||
|
||||
procession.mePromise.resolve('login ok')
|
||||
procession.promiseLogin = null
|
||||
procession.mePromise = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!procession.promiseLogin) {
|
||||
procession.promiseLogin = loginVM.openLogin(procession, '/api/rebuildSession')
|
||||
procession.promiseLogin.then((response) => {
|
||||
HttpService.apis.request(response.config).then(resolve, reject)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const errorResponse = (error) => {
|
||||
// 用户信息失效时,弹窗提示登录
|
||||
const { response } = error
|
||||
|
||||
if (response?.status === LOGIN_EXPIRED_CODE) {
|
||||
// vscode 插件环境弹出输入框提示登录
|
||||
if (window.vscodeBridge) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
// 浏览器环境弹出小窗登录
|
||||
if (response?.headers['x-login-url']) {
|
||||
return openLogin()
|
||||
}
|
||||
}
|
||||
|
||||
showError(error.config?.url, error?.message)
|
||||
|
||||
return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message)
|
||||
}
|
||||
|
||||
const getConfig = (env = import.meta.env) => {
|
||||
const baseURL = env.VITE_ORIGIN
|
||||
// 仅在本地开发时,启用 withCredentials
|
||||
const dev = env.MODE?.includes('dev')
|
||||
// 获取租户 id
|
||||
const getTenant = () => new URLSearchParams(location.search).get('tenant')
|
||||
|
||||
return {
|
||||
baseURL,
|
||||
withCredentials: dev,
|
||||
headers: {
|
||||
...(dev && { 'x-lowcode-mode': 'develop' }),
|
||||
'x-lowcode-org': getTenant()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const customizeHttpService = () => {
|
||||
const options = {
|
||||
axiosConfig: getConfig(),
|
||||
interceptors: {
|
||||
request: [preRequest],
|
||||
response: [[preResponse, errorResponse]]
|
||||
}
|
||||
}
|
||||
|
||||
HttpService.apis.setOptions(options)
|
||||
|
||||
return HttpService
|
||||
}
|
||||
|
||||
export default customizeHttpService()
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default as HttpService } from './http'
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<template>
|
||||
<span>我是自定义的 input configurator</span>
|
||||
<tiny-input v-model="value" :type="type" :placeholder="placeholder" :rows="rows" @update:modelValue="change">
|
||||
</tiny-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { Input } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
name: 'MyInputConfigurator',
|
||||
components: {
|
||||
TinyInput: Input
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
type: String
|
||||
},
|
||||
suffixIcons: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
dataType: {
|
||||
type: String
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const value = ref(props.modelValue)
|
||||
|
||||
const change = (val) => {
|
||||
emit('update:modelValue', props.dataType === 'Array' ? val.split(',') : val)
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
change
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tiny-svg-size {
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: var(--te-common-text-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import MyInputConfigurator from './MyInputConfigurator.vue'
|
||||
|
||||
export const configurators = {
|
||||
MyInputConfigurator
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
import { configurators } from './configurators/'
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
async function startApp() {
|
||||
const registry = await import('../registry')
|
||||
const { init } = await import('@opentiny/tiny-engine')
|
||||
|
||||
init({
|
||||
// 合并多个注册表
|
||||
registry: [registry.default],
|
||||
configurators,
|
||||
createAppSignal: ['global_service_init_finish']
|
||||
})
|
||||
}
|
||||
|
||||
startApp()
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
import { defineEntry } from '@opentiny/tiny-engine-meta-register'
|
||||
import engineConfig from '../engine.config'
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
async function startApp() {
|
||||
const { initHook, HOOK_NAME, META_SERVICE, initPreview } = await import('@opentiny/tiny-engine')
|
||||
const { HttpService } = await import('./composable')
|
||||
|
||||
const beforeAppCreate = () => {
|
||||
initHook(HOOK_NAME.useEnv, import.meta.env)
|
||||
}
|
||||
|
||||
const registry = {
|
||||
[META_SERVICE.Http]: HttpService,
|
||||
'engine.config': {
|
||||
...engineConfig
|
||||
}
|
||||
}
|
||||
|
||||
defineEntry(registry)
|
||||
|
||||
initPreview({
|
||||
registry,
|
||||
lifeCycles: {
|
||||
beforeAppCreate
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
startApp()
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* localCDN 功能测试
|
||||
* 这个测试文件用于验证 localCDN 本地化功能是否正常工作
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { ensureEnvVarEnabled, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js'
|
||||
|
||||
// 获取当前文件目录
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const projectRoot = path.resolve(__dirname, '..')
|
||||
const distDir = path.resolve(projectRoot, 'dist')
|
||||
const localCdnDir = path.resolve(distDir, 'local-cdn-static')
|
||||
const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha')
|
||||
|
||||
describe('localCDN 功能测试', () => {
|
||||
beforeAll(() => {
|
||||
// 备份环境变量文件
|
||||
backupEnvFile(envAlphaPath)
|
||||
|
||||
// 确保环境变量正确设置
|
||||
let envContent = fs.readFileSync(envAlphaPath, 'utf-8')
|
||||
|
||||
// 确保关键环境变量已启用
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS')
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS')
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_PATH', 'local-cdn-static')
|
||||
|
||||
// 写回更新后的环境变量
|
||||
fs.writeFileSync(envAlphaPath, envContent)
|
||||
|
||||
// 执行构建
|
||||
execSync('pnpm run build:alpha', {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
})
|
||||
|
||||
// 测试结束后恢复原始环境变量
|
||||
afterAll(() => {
|
||||
restoreEnvFile(envAlphaPath)
|
||||
})
|
||||
|
||||
it('应该在构建后生成 local-cdn-static 目录', () => {
|
||||
expect(fs.existsSync(localCdnDir)).toBe(true)
|
||||
})
|
||||
|
||||
it('应该正确复制 @vue/devtools-api 依赖', () => {
|
||||
// 寻找 @vue/devtools-api 文件夹
|
||||
const devToolsDirs = fs.readdirSync(path.resolve(localCdnDir, '@vue'))
|
||||
.find(dir => dir.startsWith('devtools-api@'))
|
||||
|
||||
expect(devToolsDirs).toBeDefined()
|
||||
|
||||
// 检查 index.js 是否存在
|
||||
const indexJsExists = fs.existsSync(path.resolve(localCdnDir, '@vue', devToolsDirs, 'lib/esm/index.js'))
|
||||
|
||||
expect(indexJsExists).toBe(true)
|
||||
})
|
||||
|
||||
it('应该正确复制 vue 依赖', () => {
|
||||
// 寻找 vue 文件夹
|
||||
const runtimeDirs = fs.readdirSync(localCdnDir).find(dir => dir.startsWith('vue@'))
|
||||
|
||||
expect(runtimeDirs).toBeDefined()
|
||||
|
||||
const vueProdDist = path.resolve(localCdnDir, runtimeDirs, 'dist/vue.runtime.esm-browser.js')
|
||||
|
||||
expect(fs.existsSync(vueProdDist)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* localCDN bundle依赖本地化测试
|
||||
* 测试物料需要的CDN资源本地化功能
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { ensureEnvVarEnabled, updateCdnDomain, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js'
|
||||
|
||||
// 获取当前文件目录
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const projectRoot = path.resolve(__dirname, '..')
|
||||
const publicDir = path.resolve(projectRoot, 'public')
|
||||
const bundleJsonDir = path.resolve(publicDir, 'mock')
|
||||
const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha')
|
||||
const distDir = path.resolve(projectRoot, 'dist')
|
||||
const bundleJsonPath = path.resolve(bundleJsonDir, 'bundle.json')
|
||||
|
||||
// 准备测试用的 bundle.json 文件
|
||||
const testBundleJson = {
|
||||
data: {
|
||||
materials: {
|
||||
packages: [
|
||||
{
|
||||
"name": "TinyVue组件库",
|
||||
"package": "@opentiny/vue",
|
||||
"version": "3.20.0",
|
||||
"script": "https://unpkg.com/@opentiny/vue-runtime@3.20/dist3/tiny-vue-pc.mjs",
|
||||
"css": "https://unpkg.com/@opentiny/vue-theme@3.20/index.css"
|
||||
},
|
||||
{
|
||||
"name": "element-plus组件库",
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://registry.npmmirror.com/element-plus/2.4.2/files/dist/index.full.mjs",
|
||||
"css": "https://registry.npmmirror.com/element-plus/2.4.2/files/dist/index.css"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('localCDN bundle依赖本地化测试', () => {
|
||||
let originalBundleJson = null
|
||||
|
||||
beforeAll(() => {
|
||||
// 备份环境变量
|
||||
backupEnvFile(envAlphaPath)
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(bundleJsonDir)) {
|
||||
fs.mkdirSync(bundleJsonDir, { recursive: true })
|
||||
}
|
||||
|
||||
// 备份原始的 bundle.json 文件(如果存在)
|
||||
if (fs.existsSync(bundleJsonPath)) {
|
||||
originalBundleJson = fs.readFileSync(bundleJsonPath, 'utf-8')
|
||||
}
|
||||
|
||||
// 创建测试用的 bundle.json
|
||||
fs.writeFileSync(bundleJsonPath, JSON.stringify(testBundleJson, null, 2))
|
||||
|
||||
// 设置环境变量
|
||||
let envContent = fs.readFileSync(envAlphaPath, 'utf-8')
|
||||
|
||||
// 更新CDN域名
|
||||
envContent = updateCdnDomain(envContent, 'https://unpkg.com')
|
||||
|
||||
// 确保启用了 bundle 依赖本地化
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS')
|
||||
|
||||
fs.writeFileSync(envAlphaPath, envContent)
|
||||
|
||||
// 执行构建
|
||||
execSync('pnpm run build:alpha', {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
})
|
||||
|
||||
// 测试完成后清理测试文件并恢复环境变量
|
||||
afterAll(() => {
|
||||
// 恢复原始的 bundle.json 文件
|
||||
if (originalBundleJson) {
|
||||
fs.writeFileSync(bundleJsonPath, originalBundleJson)
|
||||
} else if (fs.existsSync(bundleJsonPath)) {
|
||||
// 如果原始文件不存在,则删除测试创建的文件
|
||||
fs.unlinkSync(bundleJsonPath)
|
||||
}
|
||||
|
||||
// 恢复环境变量
|
||||
restoreEnvFile(envAlphaPath)
|
||||
})
|
||||
|
||||
it('应该将物料CDN依赖本地化', () => {
|
||||
const distBundleJsonPath = path.resolve(distDir, 'mock', 'bundle.json')
|
||||
|
||||
// 检查构建后的 bundle.json 是否存在
|
||||
expect(fs.existsSync(distBundleJsonPath)).toBe(true)
|
||||
|
||||
// 检查构建后的 bundle.json 中是否已将远程URL替换为本地路径
|
||||
const packages = JSON.parse(fs.readFileSync(distBundleJsonPath, 'utf-8')).data.materials.packages
|
||||
|
||||
// 检查 vue 的路径是否已本地化
|
||||
expect(packages[0].script).not.toContain('https://unpkg.com')
|
||||
expect(packages[0].script).toContain('./material-static/')
|
||||
|
||||
// 检查 element-plus 的路径是否已本地化
|
||||
expect(packages[1].script).toContain('https://registry.npmmirror.com')
|
||||
expect(packages[1].script).not.toContain('./material-static/')
|
||||
expect(packages[1].css).toContain('https://registry.npmmirror.com')
|
||||
expect(packages[1].css).not.toContain('./material-static/')
|
||||
})
|
||||
|
||||
it('应该将物料依赖包复制到产物CDN目录', () => {
|
||||
const localCdnDir = path.resolve(distDir, 'material-static/@opentiny')
|
||||
|
||||
// 检查 vue 是否已复制
|
||||
const tinyVueDir = fs.readdirSync(localCdnDir)
|
||||
.find(dir => dir.startsWith('vue-runtime@'))
|
||||
const tinyVueThemeDir = fs.readdirSync(localCdnDir)
|
||||
.find(dir => dir.startsWith('vue-theme@'))
|
||||
expect(tinyVueDir).toBeDefined()
|
||||
|
||||
// 检查 tiny-vue-pc.mjs 是否存在
|
||||
const tinyVueJsPath = path.resolve(localCdnDir, tinyVueDir, 'dist3', 'tiny-vue-pc.mjs')
|
||||
const tinyVueCssPath = path.resolve(localCdnDir, tinyVueThemeDir, 'index.css')
|
||||
|
||||
expect(fs.existsSync(tinyVueJsPath)).toBe(true)
|
||||
expect(fs.existsSync(tinyVueCssPath)).toBe(true)
|
||||
|
||||
// 检查 element-plus 是否已复制
|
||||
const elementDir = fs.readdirSync(localCdnDir)
|
||||
.find(dir => dir.startsWith('element-plus@'))
|
||||
|
||||
expect(elementDir).not.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/**
|
||||
* localCDN 自定义配置测试
|
||||
* 测试文档中描述的自定义配置功能
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { ensureEnvVarEnabled, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js'
|
||||
|
||||
// 获取当前文件目录
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const projectRoot = path.resolve(__dirname, '..')
|
||||
const viteConfigPath = path.resolve(projectRoot, 'vite.config.js')
|
||||
const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha')
|
||||
const originalViteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
|
||||
const distDir = path.resolve(projectRoot, 'dist')
|
||||
|
||||
/**
|
||||
* 更新 registry.js 文件以支持自定义 importMap
|
||||
*/
|
||||
function updateRegistryFile() {
|
||||
const registryPath = path.resolve(projectRoot, 'registry.js')
|
||||
|
||||
if (!fs.existsSync(registryPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 备份原始文件
|
||||
fs.copyFileSync(registryPath, registryPath + '.bak')
|
||||
|
||||
const registryContent = fs.readFileSync(registryPath, 'utf-8')
|
||||
|
||||
// 向 config 对象添加 importMap
|
||||
const updatedContent = registryContent.replace(
|
||||
/config: {([^}]*)}/,
|
||||
`config: {$1,
|
||||
importMap: {
|
||||
imports: {
|
||||
'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js"
|
||||
}
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
fs.writeFileSync(registryPath, updatedContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复 registry.js 文件
|
||||
*/
|
||||
function restoreRegistryFile() {
|
||||
const registryPath = path.resolve(projectRoot, 'registry.js')
|
||||
const backupPath = registryPath + '.bak'
|
||||
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.copyFileSync(backupPath, registryPath)
|
||||
fs.unlinkSync(backupPath)
|
||||
}
|
||||
}
|
||||
|
||||
describe('localCDN 自定义配置测试', () => {
|
||||
beforeAll(() => {
|
||||
// 备份原始的 vite.config.js
|
||||
fs.writeFileSync(viteConfigPath + '.bak', originalViteConfig)
|
||||
|
||||
// 备份环境变量文件
|
||||
backupEnvFile(envAlphaPath)
|
||||
|
||||
// 修改 vite.config.js 添加自定义配置
|
||||
const updatedViteConfig = originalViteConfig.replace(
|
||||
'const baseConfig = useTinyEngineBaseConfig({',
|
||||
`const baseConfig = useTinyEngineBaseConfig({
|
||||
importMapLocalConfig: {
|
||||
importMap: {
|
||||
imports: {
|
||||
'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js"
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
'vue': {
|
||||
filePathInPackage: '/dist/'
|
||||
}
|
||||
}
|
||||
},`
|
||||
)
|
||||
|
||||
fs.writeFileSync(viteConfigPath, updatedViteConfig)
|
||||
|
||||
// 确保环境变量设置
|
||||
let envContent = fs.readFileSync(envAlphaPath, 'utf-8')
|
||||
|
||||
// 确保关键环境变量已启用
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS')
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS')
|
||||
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_PATH', 'local-cdn-static')
|
||||
|
||||
// 写回更新后的环境变量
|
||||
fs.writeFileSync(envAlphaPath, envContent)
|
||||
|
||||
// 修改 registry.js 以支持自定义 importMap
|
||||
updateRegistryFile()
|
||||
|
||||
// 执行构建
|
||||
execSync('pnpm run build:alpha', {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
})
|
||||
|
||||
// 测试完成后恢复原始配置
|
||||
afterAll(() => {
|
||||
// 恢复 vite.config.js
|
||||
if (fs.existsSync(viteConfigPath + '.bak')) {
|
||||
fs.copyFileSync(viteConfigPath + '.bak', viteConfigPath)
|
||||
fs.unlinkSync(viteConfigPath + '.bak')
|
||||
}
|
||||
|
||||
// 恢复环境变量
|
||||
restoreEnvFile(envAlphaPath)
|
||||
|
||||
// 恢复 registry.js
|
||||
restoreRegistryFile()
|
||||
})
|
||||
|
||||
it('应该正确应用自定义 importMap 配置', () => {
|
||||
const localCdnDir = path.resolve(distDir, 'local-cdn-static')
|
||||
|
||||
// 检查 vue 是否被正确复制
|
||||
const vueDirs = fs.readdirSync(localCdnDir)
|
||||
.filter(dir => dir.startsWith('vue@'))
|
||||
.map(dir => path.resolve(localCdnDir, dir))
|
||||
|
||||
expect(vueDirs.length).toBeGreaterThan(0)
|
||||
|
||||
// 检查 dist 目录是否存在
|
||||
const distExists = vueDirs.some(dir => {
|
||||
return fs.existsSync(path.resolve(dir, 'dist'))
|
||||
})
|
||||
|
||||
expect(distExists).toBe(true)
|
||||
|
||||
// 检查 vue.global.prod.js 文件是否存在
|
||||
const vueJsExists = vueDirs.some(dir => {
|
||||
return fs.existsSync(path.resolve(dir, 'dist', 'vue.runtime.esm-browser.js'))
|
||||
})
|
||||
|
||||
expect(vueJsExists).toBe(true)
|
||||
|
||||
// 检查 dist 目录下的文件数量是否大于1 (因为我们的复制配置是复制整个文件夹)
|
||||
const distFileCount = vueDirs.some(dir => {
|
||||
const distPath = path.resolve(dir, 'dist')
|
||||
return fs.existsSync(distPath) && fs.readdirSync(distPath).length > 1
|
||||
})
|
||||
|
||||
expect(distFileCount).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
* 环境变量处理工具函数
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
|
||||
/**
|
||||
* 更新环境变量,如果变量不存在或值为false则设置为true
|
||||
* @param {string} content - 环境变量文件内容
|
||||
* @param {string} key - 环境变量名
|
||||
* @returns {string} - 更新后的内容
|
||||
*/
|
||||
export function ensureEnvVarEnabled(content, key, value = 'true') {
|
||||
// 检查是否包含该环境变量
|
||||
const regex = new RegExp(`${key}\\s*=\\s*(.*)`, 'm')
|
||||
const match = content.match(regex)
|
||||
|
||||
if (!match) {
|
||||
// 变量不存在,添加
|
||||
return `${content}\n${key}=${value}`
|
||||
} else if (match[1].trim() !== value) {
|
||||
// 变量存在但值为跟 value 不相等,替换为提供的值 value
|
||||
return content.replace(regex, `${key}=${value}`)
|
||||
}
|
||||
|
||||
// 变量已存在且不是false,保持不变
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新环境变量中的CDN域名
|
||||
* @param {string} content - 环境变量文件内容
|
||||
* @param {string} cdnDomain - CDN域名
|
||||
* @returns {string} - 更新后的内容
|
||||
*/
|
||||
export function updateCdnDomain(content, cdnDomain) {
|
||||
const regex = /VITE_CDN_DOMAIN\s*=\s*(.*)/m
|
||||
const match = content.match(regex)
|
||||
|
||||
if (!match) {
|
||||
return `${content}\nVITE_CDN_DOMAIN=${cdnDomain}`
|
||||
} else {
|
||||
return content.replace(regex, `VITE_CDN_DOMAIN=${cdnDomain}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份环境变量文件
|
||||
* @param {string} envFilePath - 环境变量文件路径
|
||||
* @returns {void}
|
||||
*/
|
||||
export function backupEnvFile(envFilePath) {
|
||||
if (fs.existsSync(envFilePath)) {
|
||||
const backupPath = envFilePath + '.bak'
|
||||
fs.copyFileSync(envFilePath, backupPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复环境变量文件
|
||||
* @param {string} envFilePath - 环境变量文件路径
|
||||
* @returns {void}
|
||||
*/
|
||||
export function restoreEnvFile(envFilePath) {
|
||||
const backupPath = envFilePath + '.bak'
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.copyFileSync(backupPath, envFilePath)
|
||||
fs.unlinkSync(backupPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import path from 'node:path'
|
||||
import { defineConfig, mergeConfig } from 'vite'
|
||||
import { useTinyEngineBaseConfig } from '@opentiny/tiny-engine-vite-config'
|
||||
|
||||
export default defineConfig((configEnv) => {
|
||||
const baseConfig = useTinyEngineBaseConfig({
|
||||
viteConfigEnv: configEnv,
|
||||
root: __dirname,
|
||||
iconDirs: [path.resolve(__dirname, './node_modules/@opentiny/tiny-engine/assets/')],
|
||||
useSourceAlias: true,
|
||||
envDir: './env',
|
||||
registryPath: './registry.js'
|
||||
})
|
||||
|
||||
const customConfig = {
|
||||
envDir: './env',
|
||||
publicDir: path.resolve(__dirname, './public'),
|
||||
server: {
|
||||
port: 8090
|
||||
}
|
||||
}
|
||||
|
||||
return mergeConfig(baseConfig, customConfig)
|
||||
})
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
import path from 'node:path'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
testTimeout: 1_000 * 60 * 10, // 10分钟超时,因为构建可能需要较长时间
|
||||
include: ['tests/**/*.test.js'],
|
||||
hookTimeout: 1_000 * 60 * 10, // 10分钟超时,因为构建可能需要较长时间
|
||||
// 这里需要串行执行,否则构建会相互覆盖,无法测试
|
||||
fileParallelism: false
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
}
|
||||
})
|
||||
125
docs/README.md
|
|
@ -1,125 +0,0 @@
|
|||
# 目录
|
||||
|
||||
## 使用指南
|
||||
|
||||
- 新手指引
|
||||
- [简介](./getting-started/introduction.md)
|
||||
- [快速上手](./getting-started/quick-start.md)
|
||||
- 基础功能
|
||||
- [初识设计器](./basic-features/intro-to-designer.md)
|
||||
- [设计前端应用流程](./basic-features/frontend-application-flow.md)
|
||||
- [设计器界面模块简介](./basic-features/designer-ui-modules.md)
|
||||
- [页面管理](./basic-features/page-management.md)
|
||||
- [使用组件](./basic-features/using-components.md)
|
||||
- [样式设置](./basic-features/style-settings.md)
|
||||
- [使用状态管理和变量绑定](./basic-features/state-management-and-variable-binding.md)
|
||||
- [行内样式绑定状态变量](./basic-features/inline-style-variable-binding.md)
|
||||
- [查看大纲树](./basic-features/outline-tree.md)
|
||||
- [国际化](./basic-features/internationalization.md)
|
||||
- [页面和区块预览](./basic-features/page-and-block-preview.md)
|
||||
- 进阶功能
|
||||
- [区块管理](./advanced-features/block-management.md)
|
||||
- [使用JS面板和事件绑定](./advanced-features/js-panel-and-event-binding.md)
|
||||
- [使用工具类方法 utils](./advanced-features/using-utils-methods.md)
|
||||
- [高级面板设置](./advanced-features/advanced-panel-settings.md)
|
||||
- [如何使用插槽](./advanced-features/how-to-use-slots.md)
|
||||
- [循环渲染](./advanced-features/loop-rendering.md)
|
||||
- [条件渲染](./advanced-features/conditional-rendering.md)
|
||||
- [集成ChatGPT搭建简单页面能力](./advanced-features/integrating-chatgpt-for-simple-pages.md)
|
||||
- [数据源和Collection—远程字段](./advanced-features/data-source-and-collection-remote-fields.md)
|
||||
- [数据源和Collection—mock数据](./advanced-features/data-source-and-collection-mock-data.md)
|
||||
- [数据源和Collection—使用数据源](./advanced-features/data-source-and-collection-usage.md)
|
||||
- 路由能力
|
||||
- [页面支持嵌套路由](./advanced-features/route-capabilities/page-support-nested-route.md)
|
||||
- [路由bar一键清除预览页面路径](./advanced-features/route-capabilities/route-bar-clear-preview-page.md)
|
||||
- [路由bar高亮显示预览页面路径](./advanced-features/route-capabilities/route-bar-current-page-highlight.md)
|
||||
- [RouterView组件支持预览子界面](./advanced-features/route-capabilities/route-view-support-preview-subpage.md)
|
||||
- [主题切换功能](./advanced-features/theme-switch.md)
|
||||
- [画布快捷操作](./advanced-features/canvas-shortcuts.md)
|
||||
- 教程
|
||||
- [从零搭建一个页面](./tutorials/build-a-page-from-scratch.md)
|
||||
- [第一期2023.10.27](./tutorials/issue-1-2023.10.27.md)
|
||||
|
||||
## 平台开发指南
|
||||
|
||||
- 开始
|
||||
- [简介](./development-getting-started/dev-intro.md)
|
||||
- [快速上手](./development-getting-started/dev-quick-start.md)
|
||||
- [前后端启动联调(Java服务端)](./development-getting-started/debugging-of-java-backend.md)
|
||||
- 更新日志
|
||||
- [更新日志](./changelog/changelog.md)
|
||||
- [v2.7升级指南](./changelog/v2.7-upgrade-guide.md)
|
||||
- 解决方案
|
||||
- [前端及Java服务端docker部署](./solutions/front-backend-docker-deployment.md)
|
||||
- [Java服务端部署](./solutions/server-deployment-solution-java.md)
|
||||
- [Node.js服务端部署](./solutions/server-deployment-solution.md)
|
||||
- [区块发布方案(Node.js服务端)](./solutions/block-release-solution.md)
|
||||
- [区块局域网发布方案(Node.js服务端)](./solutions/block-lan-release-solution.md)
|
||||
- [设计器中引入第三方组件库](./solutions/third-party-library-in-designer.md)
|
||||
- [物料同步方案](./solutions/material-sync-solution.md)
|
||||
- [本地化CDN方案](./solutions/import-map-local.md)
|
||||
- [全新区块构建方案](./solutions/block-construction-solution.md)
|
||||
- [全新画布通信方案](./solutions/canvas-communication-solution.md)
|
||||
- 扩展能力介绍
|
||||
- [新架构介绍](./extension-capabilities-overview/new-architecture.md)
|
||||
- [注册表](./extension-capabilities-overview/registry.md)
|
||||
- [注册表(新版)](./extension-capabilities-overview/new-registry.md)
|
||||
- [注册表高级特性](./extension-capabilities-overview/new-registry-advanced.md)
|
||||
- [元服务和元应用](./extension-capabilities-overview/meta-services-and-meta-apps.md)
|
||||
- 扩展能力使用教程
|
||||
- [如何开发插件](./extension-capabilities-tutorial/how-to-develop-plugins.md)
|
||||
- 出码功能
|
||||
- [出码功能简介与使用](./extension-capabilities-tutorial/code-output-function/code-output-overview-and-usage.md)
|
||||
- [如何自定义出码](./extension-capabilities-tutorial/code-output-function/how-to-customize-code-output.md)
|
||||
- [如何自定义出码插件](./extension-capabilities-tutorial/code-output-function/how-to-customize-code-output-plugins.md)
|
||||
- [自定义页面出码插件](./extension-capabilities-tutorial/code-output-function/custom-page-code-output-plugin.md)
|
||||
- [官方出码能力API](./extension-capabilities-tutorial/code-output-function/official-code-output-api.md)
|
||||
- [定制插件UI](./extension-capabilities-tutorial/customize-plugin-ui.md)
|
||||
- [定制元服务逻辑](./extension-capabilities-tutorial/customize-meta-service-logic.md)
|
||||
- [开发设置器组件](./extension-capabilities-tutorial/develop-configurator-components.md)
|
||||
- [AI插件使用前配置](./extension-capabilities-tutorial/ai-plugin-configuration.md)
|
||||
- [如何自定义主题](./extension-capabilities-tutorial/how-to-custom-theme.md)
|
||||
- API
|
||||
- [API总览](./api/api-overview.md)
|
||||
- 前端API
|
||||
- [主包API](./api/frontend-api/main-package-api.md)
|
||||
- [画布API](./api/frontend-api/canvas-api.md)
|
||||
- [全局布局API](./api/frontend-api/global-layout-api.md)
|
||||
- [物料API](./api/frontend-api/material-api.md)
|
||||
- [设置面板API](./api/frontend-api/settings-panel-api.md)
|
||||
- [预览API](./api/frontend-api/preview-api.md)
|
||||
- 后端API
|
||||
- [AI功能接口](./api/backend-api/ai-function-api.md)
|
||||
- [应用管理](./api/backend-api/app-management.md)
|
||||
- [区块分类](./api/backend-api/block-categories.md)
|
||||
- [应用工具类管理](./api/backend-api/app-utility-management.md)
|
||||
- [区块管理](./api/backend-api/block-management-api.md)
|
||||
- [数据源管理](./api/backend-api/data-source-management.md)
|
||||
- [物料中心](./api/backend-api/material-center.md)
|
||||
- [页面管理](./api/backend-api/page-management-api.md)
|
||||
- [APP服务](./api/backend-api/app-services.md)
|
||||
- 实战案例
|
||||
- [PDM元数据审批电子流](./practical-cases/pdm-metadata-approval-workflow.md)
|
||||
- [图元编排设计器](./practical-cases/graphical-element-arrangement-designer.md)
|
||||
- [SMB轻量应用服务](./practical-cases/smb-lightweight-application-service.md)
|
||||
|
||||
## 网站文档
|
||||
|
||||
- 生态中心
|
||||
- [介绍](./ecosystem-center/ecosystem-intro.md)
|
||||
- [如何导入组件库](./ecosystem-center/how-to-import-library.md)
|
||||
- [如何发布区块](./ecosystem-center/how-to-publish-block.md)
|
||||
- [发布其他生态](./ecosystem-center/publish-other-ecosystems.md)
|
||||
- 关于应用
|
||||
- [创建应用(创建空白应用、从模板创建应用)](./about-applications/create-application-blank-or-template.md)
|
||||
- [开发应用](./about-applications/develop-application.md)
|
||||
- 关于物料
|
||||
- [介绍](./about-materials/materials-intro.md)
|
||||
- [创建物料资产包](./about-materials/create-material-asset-package.md)
|
||||
- [添加组件库和区块](./about-materials/add-library-and-blocks.md)
|
||||
- [构建物料资产包](./about-materials/build-material-asset-package.md)
|
||||
- 关于设计器
|
||||
- [介绍](./about-designer/designer-intro.md)
|
||||
- [创建设计器](./about-designer/create-designer.md)
|
||||
- [定制物料资产包、主题、DSL、工具栏和插件栏](./about-designer/customize-material-package-themes-dsl-toolbar-plugins.md)
|
||||
- [定制设计器](./about-designer/customize-designer.md)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# 关于应用
|
||||
|
||||
## 如何创建应用
|
||||
设计器定制完成后,用户可以在 [我的设计器](https://www.opentiny.design/tiny-engine#/my-platform) 中创建应用。选择对应的设计器,在设计器右下方点击“创建应用”。
|
||||
创建应用有两种方式:
|
||||
|
||||
1.创建空白应用 → 填写必要的字段 → 保存。
|
||||
|
||||

|
||||
|
||||
2.从模板创建应用→ 选择应用模板 → 填写必要的字段 → 保存。
|
||||
|
||||

|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# 关于应用
|
||||
|
||||
## 如何去开发应用
|
||||
|
||||
应用创建完成后,即可在 **我的应用** 中看到这个应用,点击 **开发应用** 即可前往可视化设计器进行可视化地搭建该应用下的 *页面* 和 *区块*。
|
||||
|
||||

|
||||
|
||||
### 可视化设计器
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
|
@ -1,6 +0,0 @@
|
|||
# 关于设计器
|
||||
|
||||
## 如何创建一个设计器
|
||||
|
||||
用户可以在 [我的设计器](https://www.opentiny.design/tiny-engine#/my-platform) 中创建设计器,创建设计器 → 填写必要的字段 → 确定
|
||||

|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# 关于设计器
|
||||
|
||||
## 如何定制一个设计器
|
||||
|
||||
物料资产包、主题、工具、插件和DSL定制完成后,有两种方式可以构建设计器。
|
||||
|
||||
方式一:可视化构建
|
||||
|
||||

|
||||
|
||||
方式二:由源码构建
|
||||
|
||||

|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# 关于设计器
|
||||
|
||||
## 如何定制物料资产包、主题、DSL、工具栏和插件栏
|
||||
|
||||
设计器创建完成后会自动打开编辑页面,用户可以在这里定制设计器的*物料资产包*、*主题*、*工具*、*插件*和*DSL*,如下图:
|
||||
|
||||
1. **定制物料资产包:** 物料资产包 **必选且唯一** , **不允许删除** 物料资产包,可以添加其他物料资产包来替换当前的。
|
||||

|
||||
|
||||
2. **定制主题:** 主题 **必选且唯一** ,**不允许删除** 主题,可以添加其他主题来替换当前的
|
||||

|
||||
|
||||
3. **定制DSL:** DSL为 **单选**。DSL是将物料的Schema 解析成不同技术栈源码的转换工具,所以DSL的必须和选择的物料在技术栈保持一致。
|
||||

|
||||
|
||||
4. **定制工具:** 可以将工具拖入上方位置栏,规划定制的设计器里工具的位置。工具可以多选,也可以删除。
|
||||

|
||||
|
||||
5. **定制插件:** 可以将插件拖入中间位置栏,规划定制的设计器里插件的位置。插件可以多选,也可以删除。
|
||||

|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# 构建自定义设计器
|
||||
|
||||
## 什么是定制扩展能力
|
||||
|
||||
什么是扩展能力呢,一方面我们可以快速拥有一个官方标准的设计器,另外一方面如果用户有独特的业务功能需要,我们可以不用看它的源码、不用关心其实现,用户可以使用 API、插件等方式快速开发自己的工具,插件,DSL等的npm包,用于构建用户自定义的设计器。而设计器引擎对于设计器的扩展能力支持基本上覆盖了设计器的所有功能点。
|
||||

|
||||
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
|
@ -1,19 +0,0 @@
|
|||
# 添加组件库/区块
|
||||
|
||||
## 如何添加组件库与区块
|
||||
|
||||
可视化设计器已为您提供官方组件库与一些区块,物料资产包创建完成后会自动打开编辑页面,用户可以在这里添加组件库与区块,如下图:
|
||||
|
||||
* **添加组件库:** 选中未选择的组件库
|
||||

|
||||
|
||||
* **添加区块:** 选中未选择的区块
|
||||

|
||||
|
||||
* **移除组件库:** 取消选中已经选择的组件库,并且确认
|
||||

|
||||
|
||||
* **移除区块:** 取消选中已经选择的区块,并且确认
|
||||

|
||||
|
||||
* 用户也可以在生态中心录入自己的组件库与区块。
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# 构建物料资产包
|
||||
|
||||
## 如何构建物料资产包
|
||||
|
||||
添加完组件库与区块后,点击底部 **构建物料资产包** 按钮,即可完成物料资产包的构建。构建完成后即可看到发布地址。
|
||||

|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# 创建物料资产包
|
||||
|
||||
## 如何创建物料资产包
|
||||
|
||||
用户可以在生态中心创建物料资产包,*新建物料资产包* → *填写必要的字段* → *保存。*
|
||||
当前支持Vue和Angular两种技术栈,用户可以任意选择。当用户选择了Vue技术栈时,则只能添加Vue的组件与区块,不能添加Angular的组件库与区块。
|
||||
|
||||
物料资产包版本是用户自定义的,用户可以将物料资产包回退到任一版本。
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 365 KiB |
|
Before Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
|
@ -1,16 +0,0 @@
|
|||
# 关于物料资产包
|
||||
|
||||
## 什么是物料
|
||||
|
||||
物料是可视化页面搭建的原料,按照粒度可分为组件库和区块
|
||||
|
||||
1. **组件库**:组件库一般是按照组件的性质进行组织。组件是页面搭建最小的可复用单元,其只对外暴露配置项,用户无需感知其内部实现。组件库在设计器定制完成后 **不能再次添加与删除** 。
|
||||
2. **区块**:区块可以包含一个或多个组件也可以包含其他区块,带有一定的业务逻辑,能够实现更丰富的功能与表现。区块分为**消费侧**和**管理侧**,在区块管理侧,用户可以向设计器中拖入一个区块后可以编辑其内部的组件和区块配置,暴露区块的属性和事件供消费区块的时候使用,最后发布区块,消费侧区块就会更新为最新的区块内容。区块在设计器定制完成后仍能添加与删除。
|
||||
|
||||
设计器中的物料需要进行一定的配置和处理,才能让用户在设计器使用起来。这个过程中,需要一份配置文件,也就是物料资产包。物料资产包文件中,针对每个物料定义了它们在设计器中的使用描述。
|
||||
|
||||
## 什么是物料资产包
|
||||
|
||||
在设计器中,我们可以看到,组件与区块面板不只提供一个组件或区块,它们是以集合的形式提供给设计器的,而物料资产包正是这些组件与区块构成的集合。
|
||||
|
||||

|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# 高级面板设置
|
||||
|
||||
> 选中组件之后,我们可以在高级面板对组件进行事件的绑定、以及循环渲染、条件渲染等高级设置
|
||||
|
||||
## 条件渲染
|
||||
|
||||
在页面开发中,我们可能需要根据某些条件来动态显示或隐藏页面中的内容,举个例子:我们希望当用户已经登录的时候,显示欢迎登录的文字,未登录的时候,显示请登录的文字。
|
||||
那么,我们可以在组件上面绑定条件渲染,点击绑定变量,选择变量 state.isLogin,那么,我们的组件就会根据变量 state.isLogin 的真假值来进行渲染。
|
||||
|
||||

|
||||
|
||||
## 循环渲染
|
||||
|
||||
我们的页面可能有若干份重复的、动态生成的内容,比如商品列表页,比如表格数据。这时候,我们就需要用到循环渲染
|
||||
|
||||
我们可以在高级面板中指定循环数据绑定的变量、迭代的变量名、索引变量名、以及唯一的 key。
|
||||
举例:假如我们的状态变量中有一个镜像列表,我们希望渲染出来镜像的 icon、镜像名称以及镜像版本,那么我们可以使用循环渲染来实现:
|
||||
|
||||

|
||||
|
||||
相关概念关联:
|
||||
|
||||
- 循环数据,即需要循环渲染的数组,在这里是 state.imageList
|
||||
- 迭代变量名,在循环渲染子项对应的变量名,默认为 item
|
||||
- 索引变量名,循环渲染的索引变量名,默认为 index
|
||||
- key,标识唯一的 key,默认为 index
|
||||
|
||||
最终出码:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div v-for="(item, index) in state.imageList" :key="index">
|
||||
<span>{{ item.title }}</span>
|
||||
<!---列表细节--->
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 事件绑定
|
||||
|
||||
我们可以给选中的组件进行事件的绑定:
|
||||
|
||||
- 在绑定事件下拉按钮中选中需要绑定的事件
|
||||
- 随后在事件绑定弹窗中指定方法名称
|
||||
- 指定拓展参数
|
||||
- 点击确定,在随后弹出的JS面板中进行绑定方法的具体逻辑实现
|
||||
|
||||

|
||||
|
||||
### 拓展参数相关说明
|
||||
|
||||
在某些点击事件中,我们不仅仅希望得知事件是否被点击,我们还希望在点击事件中获得一些额外的参数,这时候,我们就可以用拓展参数,下面举例说明:
|
||||
我们希望为镜像列表中的列表项绑定点击事件,然后将镜像的 id 和版本传入到事件处理函数中。那么,我们就可以使用拓展参数进行传入。
|
||||
|
||||
```bash
|
||||
# 循环渲染项迭代变量名 item
|
||||
# 拓展参数设置:
|
||||
["item.imageId", "item.imageVersion"]
|
||||
```
|
||||
|
||||
最终出码:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div v-for="(item, index) in state.imageList" @click="(e) => handleClick(e, item.imageId, item.imageVersion)">
|
||||
<!---列表细节--->
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||