refactor: 调整代码风格

pull/1/head
zestack 1 year ago
parent daa880fc94
commit cf5949cb7d
  1. 22
      eslint.config.js
  2. 6
      package.json
  3. 414
      src/App.vue
  4. 7
      src/assets/vue.svg
  5. 27
      src/engineer/components/Activity.vue
  6. 4
      src/engineer/components/Builder.vue
  7. 32
      src/engineer/components/Canvas.vue
  8. 52
      src/engineer/components/Configurator.vue
  9. 14
      src/engineer/components/Designer.vue
  10. 12
      src/engineer/components/Engineer.vue
  11. 25
      src/engineer/context.ts
  12. 68
      src/engineer/render.ts
  13. 12
      src/engineer/utils/background.ts
  14. 25
      src/engineer/utils/border.ts
  15. 8
      src/engineer/utils/gap.ts
  16. 16
      src/engineer/utils/insets.ts
  17. 6
      src/engineer/utils/is.ts
  18. 19
      src/engineer/utils/object.ts
  19. 12
      src/engineer/utils/radii.ts
  20. 17
      src/engineer/utils/shadow.ts
  21. 6
      src/engineer/utils/unit.ts
  22. 2
      src/engineer/views/AudioView.vue
  23. 10
      src/engineer/views/ImageView.vue
  24. 2
      src/engineer/views/TextView.vue
  25. 17
      src/engineer/views/View.vue

@ -1,22 +0,0 @@
import antfu from '@antfu/eslint-config'
export default await antfu({
stylistic: {
indent: 2,
quotes: 'single',
},
typescript: true,
vue: true,
rules: {
'vue/singleline-html-element-content-newline': 'off',
'vue/html-self-closing': ['error', {
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
}],
},
})

@ -6,19 +6,15 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.8",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@antfu/eslint-config": "^2.1.1",
"@types/node": "^20.9.4",
"@vitejs/plugin-vue": "^4.5.0",
"eslint": "^8.54.0",
"less": "^4.2.0",
"typescript": "^5.2.2",
"vite": "^5.0.0",

@ -1,130 +1,110 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from 'vue'
import type { Block, Module } from './engineer'
import DddBuilder, { hash } from './engineer'
let nextId = Date.now()
const blocks = ref<Block[]>([
{
vid: hash(`${++nextId}`),
mid: '',
theme: {
radius: 24,
color: 'yellow',
},
children: {
type: 'text',
key: 'textName',
},
const blocks = ref<Block[]>([{
vid: hash(`${++nextId}`),
mid: '',
theme: {
radius: 24,
color: 'yellow',
},
{
vid: hash(`${++nextId}`),
mid: '',
theme: {
color: 'pink',
margin: 12,
flexible: true,
},
children: {
type: 'image',
key: 'imageKey',
link: 'linkKey',
radius: 24,
},
children: {
type: 'text',
key: 'textName',
},
{
vid: hash(`${++nextId}`),
mid: '',
theme: {
color: '#fafbfc',
margin: 12,
padding: 24,
mainAlign: 'center',
crossAlign: 'center',
flexible: true,
},
children: {
type: 'audio',
key: 'audioKey',
title: 'title',
},
}, {
vid: hash(`${++nextId}`),
mid: '',
theme: {
color: 'pink',
margin: 12,
flexible: true,
},
{
vid: hash(`${++nextId}`),
mid: '',
children: {
type: 'image',
key: 'imageKey',
link: 'linkKey',
radius: 24,
},
}, {
vid: hash(`${++nextId}`),
mid: '',
theme: {
color: '#fafbfc',
margin: 12,
padding: 24,
mainAlign: 'center',
crossAlign: 'center',
flexible: true,
},
children: {
type: 'audio',
key: 'audioKey',
title: 'title',
},
}, {
vid: hash(`${++nextId}`),
mid: '',
theme: {
radius: 24,
color: 'teal',
},
children: [{
theme: {
radius: 24,
color: 'teal',
flexible: true,
gap: 12,
crossAlign: 'center',
},
children: [
{
theme: {
flexible: true,
gap: 12,
crossAlign: 'center',
},
children: [
{
theme: {
padding: 12,
margin: 12,
grow: 1,
},
children: {
vid: hash(`${++nextId}`),
theme: {
fontSize: 32,
},
children: 'left',
},
},
{
vid: hash(`${++nextId}`),
theme: {
width: 'auto',
padding: {
vertical: 4,
horizontal: 12,
},
color: 'pink',
radius: 16,
},
children: 'right222',
},
{
theme: {
width: 48,
},
},
],
children: [{
theme: {
padding: 12,
margin: 12,
grow: 1,
},
],
},
...Array.from(Array.from({ length: 3 })).map<Block>((_, i) => ({
vid: hash(`${++nextId}`),
mid: '',
children: [
{
children: {
vid: hash(`${++nextId}`),
theme: {
fontSize: 22,
fontSize: 32,
},
children: `测试${i}`,
children: 'left',
},
],
})),
])
}, {
vid: hash(`${++nextId}`),
theme: {
width: 'auto',
padding: {
vertical: 4,
horizontal: 12,
},
color: 'pink',
radius: 16,
},
children: 'right222',
}, {
theme: {
width: 48,
},
}],
}],
}, ...Array.from(Array.from({length: 3})).map<Block>((_, i) => ({
vid: hash(`${++nextId}`),
mid: '',
children: [{
vid: hash(`${++nextId}`),
theme: {
fontSize: 22,
},
children: `测试${i}`,
}],
}))])
const categories = ref([
{ icon: 'trash', text: '媒体' },
{ icon: 'trash', text: '图表' },
{ icon: 'trash', text: '商品' },
{ icon: 'trash', text: '功能' },
{ icon: 'trash', text: '素材' },
].map(c => ({
const categories = ref([{icon: 'trash', text: '媒体'}, {icon: 'trash', text: '图表'}, {icon: 'trash', text: '商品'}, {icon: 'trash', text: '功能'}, {icon: 'trash', text: '素材'}].map(c => ({
...c,
modules: Array.from(Array.from({ length: 102 })).map<Module>((_, i) => ({
modules: Array.from(Array.from({length: 102})).map<Module>((_, i) => ({
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: `${c.text}${i + 1}`,
@ -132,139 +112,107 @@ const categories = ref([
referenceCount: 0,
image: undefined,
configs: [],
children: [
{
vid: hash(`${++nextId}`),
theme: {
height: 68,
width: 375,
border: { color: '#eee' },
},
children: [
String(`${c.text}${i + 1}`),
],
children: [{
vid: hash(`${++nextId}`),
theme: {
height: 68,
width: 375,
border: {color: '#eee'},
},
],
children: [String(`${c.text}${i + 1}`)],
}],
})),
})))
categories.value.unshift({
icon: 'trash',
text: '基础',
modules: [
{
modules: [{
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: '轮播图',
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [{
type: 'list',
field: 'items',
label: '',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true,
configs: [{
type: 'image',
field: 'image',
//
inlines: [{
type: 'text',
field: 'title',
label: '标题',
help: '选填,不超过 4 个字',
}, {
type: 'text',
field: 'link',
label: '链接',
// help: '', // "${label}"
}],
label: '图片',
required: true,
}],
}, {
type: 'object',
field: 'indicator',
label: '指示器',
configs: [{
type: 'mark',
field: 'style',
label: '指示器样式',
values: [{label: '圆形', value: 'circle'}, {label: '直线', value: 'line'}, {label: '数字', value: 'number'}],
}, {
type: 'mark',
field: 'position',
label: '指示器位置',
values: [{label: '居左', value: 'left'}, {label: '居中', value: 'center'}, {label: '居右', value: 'right'}],
}, {
type: 'color',
field: 'color',
label: '指示器颜色',
}],
}, {
type: 'object',
field: 'background',
label: '背景',
configs: [{
type: 'bool',
field: 'enabled',
label: '是否显示背景色',
}, {
type: 'background',
field: 'value',
// image
//
features: ['color', 'gradient'],
label: '背景',
}],
}],
theme: {
height: 200,
padding: {
horizontal: 12.0,
},
color: 'cyan',
},
children: [{
vid: hash(`${++nextId}`),
mid: hash(`${++nextId}`),
title: '轮播图',
maxReferenceCount: -1,
referenceCount: 0,
image: undefined,
configs: [
{
type: 'list',
field: 'items',
label: '',
help: '最多可添加10张图片,建议宽度750px;鼠标拖拽左侧圆点可调整图片顺序',
addable: true,
configs: [
{
type: 'image',
field: 'image',
//
inlines: [
{
type: 'text',
field: 'title',
label: '标题',
help: '选填,不超过 4 个字',
},
{
type: 'text',
field: 'link',
label: '链接',
// help: '', // "${label}"
},
],
label: '图片',
required: true,
},
],
},
{
type: 'object',
field: 'indicator',
label: '指示器',
configs: [
{
type: 'mark',
field: 'style',
label: '指示器样式',
values: [
{ label: '圆形', value: 'circle' },
{ label: '直线', value: 'line' },
{ label: '数字', value: 'number' },
],
},
{
type: 'mark',
field: 'position',
label: '指示器位置',
values: [
{ label: '居左', value: 'left' },
{ label: '居中', value: 'center' },
{ label: '居右', value: 'right' },
],
},
{
type: 'color',
field: 'color',
label: '指示器颜色',
},
],
},
{
type: 'object',
field: 'background',
label: '背景',
configs: [
{
type: 'bool',
field: 'enabled',
label: '是否显示背景色',
},
{
type: 'background',
field: 'value',
// image
//
features: ['color', 'gradient'],
label: '背景',
},
],
},
],
theme: {
height: 200,
padding: {
horizontal: 12.0,
},
color: 'cyan',
width: '100%',
height: '100%',
color: 'white',
radius: 12.0,
textAlign: 'center',
},
children: [
{
vid: hash(`${++nextId}`),
theme: {
width: '100%',
height: '100%',
color: 'white',
radius: 12.0,
textAlign: 'center',
},
children: '内容',
},
],
},
],
children: '内容',
}],
}],
})
const data = ref<Record<string, Record<string, unknown>>>({
@ -284,8 +232,8 @@ const data = ref<Record<string, Record<string, unknown>>>({
<template>
<DddBuilder
:categories="categories"
:blocks="blocks"
:categories="categories"
:sequence="nextId"
:sources="data"
/>

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img"
class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198">
<path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path>
<path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path>
<path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 475 B

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref, unref } from 'vue'
import Draggable from 'vuedraggable'
import type { Block, Module } from '../types'
@ -14,25 +14,26 @@ const ctx = useContext()
// ID
function clone(v: any): any {
if (v == null || typeof v != 'object')
if (v == null || typeof v != 'object') {
return v
if (Array.isArray(v))
}
if (Array.isArray(v)) {
return v.slice().map(clone)
}
const s = Object.create(null)
for (const [key, val] of Object.entries(v)) {
if (key === 'vid' && typeof val === 'string' && val.length === 8)
if (key === 'vid' && typeof val === 'string' && val.length === 8) {
s[key] = hash(`${++ctx.nextId}`)
else
} else {
s[key] = clone(val)
}
}
return s
}
function cloneModule(src: Module): Block {
// eslint-disable-next-line unused-imports/no-unused-vars
const { maxReferenceCount, referenceCount, image, configs, ...attrs } = unref(src)
const {maxReferenceCount, referenceCount, image, configs, ...attrs} = unref(src)
return clone(attrs)
}
</script>
@ -47,8 +48,8 @@ function cloneModule(src: Module): Block {
<div
v-for="(c, i) in ctx.categories"
:key="i"
class="ddd-activity-tabs-item"
:class="{ 'is-active': currentTab === i }"
class="ddd-activity-tabs-item"
@click.stop="currentTab = i"
>
<!-- <Icon :name="c.icon" /> -->
@ -58,17 +59,17 @@ function cloneModule(src: Module): Block {
<template v-for="(cat, idx) in ctx.categories" :key="idx">
<Draggable
v-show="currentTab === idx"
class="ddd-activity-tabs-content"
:list="cat.modules"
:group="{ name: 'modules', pull: 'clone' }"
:clone="cloneModule"
:component-dat="{ sort: false }"
:group="{ name: 'modules', pull: 'clone' }"
:list="cat.modules"
:sort="false"
class="ddd-activity-tabs-content"
item-key="id"
>
<template #item="{ element }">
<div class="ddd-activity-module">
<img v-if="element.image" class="ddd-activity-module-image" :src="element.image" />
<img v-if="element.image" :src="element.image" class="ddd-activity-module-image">
<div v-else class="ddd-activity-module-image">{{ element.title }}</div>
<div class="ddd-activity-module-title">{{ element.title }}</div>
<span class="ddd-activity-module-tips">释放鼠标将组件添加到此处</span>

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import type { UnwrapNestedRefs } from 'vue'
import { provide, reactive, unref } from 'vue'
import type { Block, Category } from '../types'
@ -64,7 +64,7 @@ ctx = reactive<EngineContext>({
export(): Exported {
const attrs = unref(ctx.page)
const data = unref(ctx.sources)
return { attrs, data }
return {attrs, data}
},
})
provideBlockId('')

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { computed, ref } from 'vue'
import Draggable from 'vuedraggable'
import { useContext } from '../context'
@ -87,36 +87,36 @@ function handleCanvasClick(e: MouseEvent): void {
<template>
<div
class="ddd-canvas"
:class="{
'is-focused': ctx.canvas.focused,
'is-active': ctx.activeViewId === '#canvas',
'is-hover': ctx.hoverViewId === '#canvas',
}"
class="ddd-canvas"
>
<!-- 中间自定义内容 -->
<!-- @change="handleDraggingOver" -->
<Draggable
:key="key"
v-model="ctx.blocks"
class="ddd-canvas-viewer"
:key="key"
:animation="200"
:disabled="false"
:component-data="{
tag: 'div',
type: 'transition-group',
}"
:disabled="false"
:group="{
name: 'emulator',
put: 'modules',
}"
class="ddd-canvas-viewer"
ghost-class="is-ghost"
item-key="id"
@click="handleCanvasClick"
@start="handleDragStart"
@end="handleDragEnd"
@add="handleDragAdd"
@change="key++"
@click="handleCanvasClick"
@end="handleDragEnd"
@start="handleDragStart"
>
<template #header>
<div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" />
@ -129,7 +129,7 @@ function handleCanvasClick(e: MouseEvent): void {
</template>
<template #item="{ element }">
<RenderBlock class="ddd-view" :block="element" />
<RenderBlock :block="element" class="ddd-view" />
</template>
</Draggable>
@ -137,13 +137,13 @@ function handleCanvasClick(e: MouseEvent): void {
<DddView
v-show="ctx.page.header.enabled"
vid="#header"
class="ddd-canvas-header"
:class="{ 'is-bordered': ctx.page.header.bordered ?? true }"
class="ddd-canvas-header"
>
<div v-show="ctx.page.safeAreaInsetTop && !ctx.page.fullscreen" class="ddd-canvas-safe-area-inset-top" />
<div
class="ddd-canvas-header-title"
:style="{ textAlign: ctx.page.header.centerTitle ? 'center' : undefined }"
class="ddd-canvas-header-title"
v-text="ctx.page.header.title"
/>
</DddView>
@ -162,12 +162,12 @@ function handleCanvasClick(e: MouseEvent): void {
<!-- 顶部状态栏小程序胶囊按钮底部操作指示器 -->
<template v-if="!ctx.page.fullscreen">
<div class="ddd-canvas-statusbar">
<img style="height:20px" alt="" src="data:image/png;base64,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" />
<img alt="" src="data:image/png;base64,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" style="height:20px">
</div>
<div v-if="ctx.target === 'routine'" class="ddd-canvas-routine-button">
<svg width="87" height="32" viewBox="0 0 87 32" xmlns="http://www.w3.org/2000/svg">
<rect width="87" height="32" rx="16" x="0" y="0" fill="rgba(0,0,0,0.3)" />
<rect id="rect5" width="0.5" height="18.5" fill="rgba(255,255,255,0.6)" x="43.5" y="6.5" />
<svg height="32" viewBox="0 0 87 32" width="87" xmlns="http://www.w3.org/2000/svg">
<rect fill="rgba(0,0,0,0.3)" height="32" rx="16" width="87" x="0" y="0" />
<rect id="rect5" fill="rgba(255,255,255,0.6)" height="18.5" width="0.5" x="43.5" y="6.5" />
<path d="M 19.5,16.25 A 3.25,3.25 0 1 1 22.75,19.5 3.25,3.25 0 0 1 19.5,16.25 Z M 28,16.5 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z m -14.5,0 a 2,2 0 1 1 2,2 2,2 0 0 1 -2,-2 z" fill="#ffffff" />
<path d="m 59.187,21.813 a 8.716,8.716 0 0 1 -2.52,-6.222 8,8 0 0 1 2.52,-5.8 8.915,8.915 0 0 1 6.223,-2.458 8.288,8.288 0 0 1 5.8,2.459 8.18,8.18 0 0 1 2.457,5.799 8.918,8.918 0 0 1 -2.46,6.223 8,8 0 0 1 -5.797,2.519 8.714,8.714 0 0 1 -6.223,-2.52 z m -0.52,-5.739 a 6.521,6.521 0 0 0 6.741,6.259 6.423,6.423 0 0 0 6.259,-6.259 6.521,6.521 0 0 0 -6.259,-6.741 6.593,6.593 0 0 0 -6.741,6.741 z m 3.5,-0.241 a 3,3 0 1 1 3,3 3,3 0 0 1 -3,-3 z" fill="#ffffff" />
</svg>
@ -177,7 +177,7 @@ function handleCanvasClick(e: MouseEvent): void {
</div>
</template>
<style scoped lang="less">
<style lang="less" scoped>
.ddd-canvas {
--canvas-measure-line: v-bind(measureLine);

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref, watch } from 'vue'
import type { ConfiguratorType } from '../context'
import { useContext, useModule } from '../context'
@ -13,9 +13,9 @@ interface Tab {
}
const tabs: Tab[] = [
{ key: 'page', text: '页面' },
{ key: 'block', text: '内容' },
{ key: 'view', text: '样式' },
{key: 'page', text: '页面'},
{key: 'block', text: '内容'},
{key: 'view', text: '样式'},
]
const currentTab = ref<Tab['key']>()
@ -33,8 +33,8 @@ watch(() => ctx.configurator, (v) => {
<button
v-for="(t) in tabs"
:key="t.key"
type="button"
:class="{ 'is-active': currentTab === t.key }"
type="button"
@click="currentTab = t.key"
v-text="t.text"
/>
@ -44,36 +44,36 @@ watch(() => ctx.configurator, (v) => {
<legend>应用</legend>
<div>
<label>
<input v-model="ctx.target" type="radio" name="ctx.target" value="routine" />
<input v-model="ctx.target" name="ctx.target" type="radio" value="routine">
<span>微信小程序</span>
</label>
<label>
<input v-model="ctx.target" type="radio" name="ctx.target" value="app" />
<input v-model="ctx.target" name="ctx.target" type="radio" value="app">
<span>手机APP</span>
</label>
</div>
<div>
<label>
<input v-model="ctx.page.fullscreen" type="checkbox" />
<input v-model="ctx.page.fullscreen" type="checkbox">
<span>全屏模式</span>
</label>
</div>
<div>
<label>
<input v-model="ctx.page.safeAreaInsetTop" type="checkbox" />
<input v-model="ctx.page.safeAreaInsetTop" type="checkbox">
<span>开启顶部安全区域</span>
</label>
</div>
<div>
<label>
<input v-model="ctx.page.safeAreaInsetBottom" type="checkbox" />
<input v-model="ctx.page.safeAreaInsetBottom" type="checkbox">
<span>开启底部安全区域</span>
</label>
</div>
<div>
<label>
<span>页面背景颜色</span>
<input v-model="ctx.page.backgroundColor" type="color" />
<input v-model="ctx.page.backgroundColor" type="color">
</label>
</div>
</fieldset>
@ -82,54 +82,54 @@ watch(() => ctx.configurator, (v) => {
<legend>头部</legend>
<div>
<label>
<input v-model="ctx.page.header.enabled" type="checkbox" />
<input v-model="ctx.page.header.enabled" type="checkbox">
<span>开启头部功能</span>
</label>
</div>
<div>
<label>
<span>页面标题</span>
<input v-model="ctx.page.header.title" type="text" placeholder="页面标题" />
<input v-model="ctx.page.header.title" placeholder="页面标题" type="text">
</label>
<label>
<input v-model="ctx.page.header.centerTitle" type="checkbox" />
<input v-model="ctx.page.header.centerTitle" type="checkbox">
<span>标题居中</span>
</label>
</div>
<div>
<label>
<input v-model="ctx.page.header.custom" type="checkbox" />
<input v-model="ctx.page.header.custom" type="checkbox">
<span>内容自定义</span>
</label>
</div>
<div>
<label>
<span>高度</span>
<input v-model="ctx.page.header.height" type="number" min="0" placeholder="头部高度,不包括状态栏" />
<input v-model="ctx.page.header.height" min="0" placeholder="头部高度,不包括状态栏" type="number">
</label>
</div>
<div>
<label>
<span>背景颜色</span>
<input v-model="ctx.page.header.color" type="color" />
<input v-model="ctx.page.header.color" type="color">
</label>
</div>
<div>
<label>
<span>文字颜色</span>
<input v-model="ctx.page.header.textColor" type="color" />
<input v-model="ctx.page.header.textColor" type="color">
</label>
</div>
<div>
<label>
<input v-model="ctx.page.header.bordered" type="checkbox" />
<input v-model="ctx.page.header.bordered" type="checkbox">
<span>开启下边边框</span>
</label>
</div>
<div>
<label>
<span>下边边框颜色</span>
<input v-model="ctx.page.header.borderColor" type="color" />
<input v-model="ctx.page.header.borderColor" type="color">
</label>
</div>
</fieldset>
@ -138,38 +138,38 @@ watch(() => ctx.configurator, (v) => {
<legend>底部</legend>
<div>
<label>
<input v-model="ctx.page.footer.enabled" type="checkbox" />
<input v-model="ctx.page.footer.enabled" type="checkbox">
<span>开启底部功能</span>
</label>
</div>
<div>
<label>
<span>高度</span>
<input v-model="ctx.page.footer.height" type="number" min="0" placeholder="头部高度,不包括状态栏" />
<input v-model="ctx.page.footer.height" min="0" placeholder="头部高度,不包括状态栏" type="number">
</label>
</div>
<div>
<label>
<span>背景颜色</span>
<input v-model="ctx.page.footer.color" type="color" />
<input v-model="ctx.page.footer.color" type="color">
</label>
</div>
<div>
<label>
<span>文字颜色</span>
<input v-model="ctx.page.footer.textColor" type="color" />
<input v-model="ctx.page.footer.textColor" type="color">
</label>
</div>
<div>
<label>
<input v-model="ctx.page.footer.bordered" type="checkbox" />
<input v-model="ctx.page.footer.bordered" type="checkbox">
<span>开启上边框</span>
</label>
</div>
<div>
<label>
<span>上边框颜色</span>
<input v-model="ctx.page.footer.borderColor" type="color" />
<input v-model="ctx.page.footer.borderColor" type="color">
</label>
</div>
</fieldset>

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from 'vue'
import { useContext, useScale } from '../context'
import { DddView } from '../views'
@ -42,12 +42,12 @@ function onMouseup(): void {
function onMousewheel(e: WheelEvent): void {
if (e.ctrlKey || e.metaKey) {
if (e.deltaY < 0)
if (e.deltaY < 0) {
scale.incr()
else
} else {
scale.decr()
}
else {
}
} else {
x.value -= e.deltaX
y.value -= e.deltaY
}
@ -73,10 +73,10 @@ function onMouseover(e: MouseEvent): void {
<template>
<div
class="ddd-designer"
@mousedown.left="onMousedown"
@mouseleave="onMouseup"
@mousemove="onMousemove"
@mouseup="onMouseup"
@mouseleave="onMouseup"
@mousedown.left="onMousedown"
@wheel.passive="onMousewheel"
>
<DddView

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useContext, useScale } from '../context'
import Activity from './Activity.vue'
import Configurator from './Configurator.vue'
@ -20,17 +20,15 @@ const scale = useScale()
</div>
<div style="display: flex;align-items: center">
<button
type="button"
:disabled="ctx.canvas.scale <= scale.min"
type="button"
@click="scale.decr()"
v-text="'-'"
/>
<span
style="display: inline-block;width: 5em;text-align: center"
>{{ Math.ceil(ctx.canvas.scale * 100) }}%</span>
<span style="display: inline-block;width:5em;text-align: center">{{ Math.ceil(ctx.canvas.scale * 100) }}%</span>
<button
type="button"
:disabled="ctx.canvas.scale >= scale.max"
type="button"
@click="scale.incr()"
v-text="'+'"
/>
@ -53,7 +51,7 @@ const scale = useScale()
</div>
</template>
<style scoped lang="less">
<style lang="less" scoped>
.ddd-engineer {
width: 100%;
height: 100%;

@ -11,7 +11,8 @@ export interface CanvasConfig {
scale: number
}
export interface PageConfig extends Omit<Page, 'body'> {}
export interface PageConfig extends Omit<Page, 'body'> {
}
export type ConfiguratorType =
| 'page'
@ -58,9 +59,9 @@ const blockIdKey: InjectionKey<string> = Symbol.for('ddd:block:id')
export function useContext(): UnwrapNestedRefs<EngineContext> {
const config = inject(contextKey)
if (config == null)
if (config == null) {
throw new Error('no config found')
}
return config
}
@ -69,8 +70,9 @@ export function useParentViewId(): string | undefined {
}
export function provideParentViewId(id: string | undefined): void {
if (id)
if (id) {
provide(parentViewIdKey, id)
}
}
export function useBlockId(): string | undefined {
@ -83,15 +85,17 @@ export function provideBlockId(id: string): void {
export function useModule() {
const ctx = useContext()
return computed((): Module | undefined => {
for (const block of ctx.blocks) {
if (block.vid !== ctx.activeBlockId)
if (block.vid !== ctx.activeBlockId) {
continue
}
for (const category of ctx.categories) {
for (const module of category.modules) {
if (module.mid === block.mid)
if (module.mid === block.mid) {
return module
}
}
}
break
@ -128,11 +132,14 @@ export function useScale() {
export function useSource<T = unknown>(source: string | undefined, fallback?: T): ComputedRef<T | undefined> {
const ctx = useContext()
const blockId = useBlockId()
return computed<T | undefined>(() => {
if (!blockId)
if (!blockId) {
throw new Error('without block')
if (!source)
}
if (!source) {
return fallback
}
return valueOf(ctx.sources[blockId], source) ?? fallback
})
}

@ -1,32 +1,8 @@
import type {
CSSProperties,
Component,
Prop,
VNodeChild,
} from 'vue'
import type { Component, CSSProperties, Prop, VNodeChild, } from 'vue'
import { defineComponent, h } from 'vue'
import type {
Axis,
Block,
DataRefer,
Theme,
View,
ViewChildren,
Widget,
} from './types'
import type { Axis, Block, DataRefer, Theme, View, ViewChildren, Widget, } from './types'
import { provideBlockId } from './context'
import {
align,
background,
bordering,
clip,
gap,
insets,
radii,
shadowing,
size,
unit,
} from './utils'
import { align, background, bordering, clip, gap, insets, radii, shadowing, size, unit, } from './utils'
import DddView from './views/View.vue'
import DddTextView from './views/TextView.vue'
import DddImageView from './views/ImageView.vue'
@ -50,15 +26,16 @@ const views: Record<string, Component> = {
}
export function registerWidget(some: Record<string, WidgetRenderFunction>): void {
for (const [key, func] of Object.entries(some))
for (const [key, func] of Object.entries(some)) {
widgets[key] = func
}
}
function renderWidget(widget: Widget, view: View, axis?: Axis): VNodeChild {
const func = widgets[widget.name]
if (typeof func !== 'function')
throw new Error(`unknown widget: ${widget.name}`)
if (typeof func !== 'function') {
throw new TypeError(`unknown widget: ${widget.name}`)
}
return func(widget, view, axis)
}
@ -73,7 +50,7 @@ function isRefer(s: any): s is DataRefer {
}
function renderRefer(refer: DataRefer, axis?: Axis): VNodeChild {
const { key, type, ...attrs } = refer
const {key, type, ...attrs} = refer
const component = views[type]
if (component) {
return h(component, {
@ -99,21 +76,21 @@ export function isFlexible(theme: Theme) {
}
export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
if (view == null)
if (view == null) {
return null
if (typeof view === 'string' || typeof view === 'number')
}
if (typeof view === 'string' || typeof view === 'number') {
return view
if (Array.isArray(view))
}
if (Array.isArray(view)) {
return view.map(v => render(v, axis))
if (isRefer(view))
}
if (isRefer(view)) {
return renderRefer(view)
if (isWidget(view.theme))
}
if (isWidget(view.theme)) {
return renderWidget(view.theme, view, axis)
}
const theme = view.theme
const css: CSSProperties = {
// boxed & spatial
@ -133,11 +110,9 @@ export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
// clip
...clip(theme?.clip),
}
// note: 在前面初始化 css 时使用了参数 axis,
// 所以在这里复用它用于子视图构建
axis = theme?.axis
if (theme?.flexible || (theme && theme.flexible == null && isFlexible(theme))) {
Object.assign(css, {
display: 'flex',
@ -148,7 +123,6 @@ export function render(view?: ViewChildren, axis?: Axis): VNodeChild {
...gap(theme?.gap),
})
}
return h(DddView, {
vid: view.vid,
class: 'ddd-view',
@ -169,9 +143,9 @@ export const RenderBlock = defineComponent({
setup(props) {
provideBlockId(props.block!.vid)
return () => {
if (props.block == null)
if (props.block == null) {
return null
}
return render(props.block)
}
},

@ -2,15 +2,15 @@ import type { CSSProperties } from 'vue'
import type { Gradient } from '../types'
function image(img: string | undefined, g: Gradient | undefined): string | undefined {
if (img == null && g == null)
if (img == null && g == null) {
return undefined
if (img != null)
}
if (img != null) {
return `url(${img})`
if (g == null)
}
if (g == null) {
return undefined
}
const prefix = g.repeatable ? 'repeating-' : ''
if (g.stops?.length === g.colors.length) {
const colors = g.colors.map((c, i) => `${c} ${g.stops![i]}`).join(', ')

@ -4,36 +4,41 @@ import { unit } from './unit'
import { isBorderSide, isSymmetric } from './is'
function borderSide(name: string, s: BorderSide | undefined): CSSProperties | undefined {
if (s == null)
if (s == null) {
return undefined
}
const hasWidth = s.width != null
const hasColor = s.color != null
const hasStyle = s.style != null
const r: Record<string, string | undefined> = {}
if (hasColor)
if (hasColor) {
r[`${name}Color`] = s.color
}
if (hasWidth)
if (hasWidth) {
r[`${name}Width`] = unit(s.width)
else if (hasColor)
} else if (hasColor) {
r[`${name}Width`] = '1px'
}
if (hasStyle)
if (hasStyle) {
r[`${name}Style`] = s.style
else if (hasWidth || hasColor)
} else if (hasWidth || hasColor) {
r[`${name}Style`] = 'solid'
}
return r
}
export function bordering(b: Border | undefined): CSSProperties | undefined {
if (b == null)
if (b == null) {
return undefined
if (isBorderSide(b))
}
if (isBorderSide(b)) {
return borderSide('border', b)
}
if (isSymmetric(b)) {
return {
...borderSide('borderBlock', b.vertical),

@ -5,15 +5,13 @@ export function gap(gap: number | [number, number] | undefined): CSSProperties |
if (!gap) {
return undefined
}
else if (Array.isArray(gap)) {
if (Array.isArray(gap)) {
return {
rowGap: unit(gap[0]),
columnGap: unit(gap[1]),
}
}
else {
return {
gap: unit(gap),
}
return {
gap: unit(gap),
}
}

@ -4,15 +4,15 @@ import { isSymmetric } from './is'
import { unit } from './unit'
export function insets(name: string, i: EdgeInsets | undefined): CSSProperties | undefined {
if (i == null)
if (i == null) {
return undefined
if (typeof i === 'number')
return { [name]: `${i}px` }
if (typeof i === 'string')
return { [name]: i }
}
if (typeof i === 'number') {
return {[name]: `${i}px`}
}
if (typeof i === 'string') {
return {[name]: i}
}
if (isSymmetric(i)) {
return {
[name]: [unit(i.vertical), unit(i.horizontal)].join(' '),

@ -19,7 +19,7 @@ export function isCorners(a: any): a is Corners<any> {
&& !Array.isArray(a)
&& typeof a === 'object'
&& ('topLeft' in a
|| 'topRight' in a
|| 'bottomLeft' in a
|| 'bottomRight' in a)
|| 'topRight' in a
|| 'bottomLeft' in a
|| 'bottomRight' in a)
}

@ -1,23 +1,24 @@
function find(obj: any, keys: string[]) {
if (!keys.length)
if (!keys.length) {
return obj
if (obj == null || typeof obj !== 'object')
}
if (obj == null || typeof obj !== 'object') {
return undefined
}
const key = keys.shift()!
if (!Array.isArray(obj))
if (!Array.isArray(obj)) {
return find(obj[key], keys)
}
const index = Number.parseInt(key)
if (Number.isNaN(index))
if (Number.isNaN(index)) {
return undefined
}
return find(obj[index], keys)
}
export function valueOf(obj: any, key: string) {
if (!key)
if (!key) {
return undefined
}
return find(obj, key.split('.'))
}

@ -3,19 +3,19 @@ import type { Radius, RadiusDouble, RadiusPrimitive } from '../types'
import { isCorners } from './is'
function stringify(radius: RadiusPrimitive | RadiusDouble | undefined): string | undefined {
if (radius == null)
if (radius == null) {
return undefined
if (Array.isArray(radius))
}
if (Array.isArray(radius)) {
return radius.map(stringify).join(' ')
}
return `${radius}px`
}
export function radii(radius: Radius | undefined): CSSProperties | undefined {
if (radius == null)
if (radius == null) {
return undefined
}
if (isCorners(radius)) {
return {
borderTopLeftRadius: stringify(radius.topLeft),

@ -4,25 +4,18 @@ import type { Shadow } from '../types'
const unit = (n: number | undefined): string | undefined => n == null ? undefined : n === 0 ? n.toString() : `${n}px`
function stringify(s: Shadow | undefined): string | undefined {
if (s == null)
if (s == null) {
return undefined
return [
s.inset ? 'inset' : undefined,
unit(s.x),
unit(s.y),
s.blur != null ? unit(s.blur) : s.spread != null ? '0' : undefined,
unit(s.spread),
s.color,
]
}
return [s.inset ? 'inset' : undefined, unit(s.x), unit(s.y), s.blur != null ? unit(s.blur) : s.spread != null ? '0' : undefined, unit(s.spread), s.color]
.filter(n => n != null)
.join(' ')
}
export function shadowing(ss: Shadow | Shadow[] | undefined): CSSProperties | undefined {
if (ss == null)
if (ss == null) {
return undefined
}
if (!Array.isArray(ss)) {
return {
boxShadow: stringify(ss),

@ -1,7 +1,9 @@
export function unit(n: number | undefined | string) {
if (n == null)
if (n == null) {
return undefined
if (typeof n !== 'number')
}
if (typeof n !== 'number') {
return n
}
return `${n}px`
}

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useSource } from '../context'
defineOptions({

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import type { ObjectFit, Radius } from '../types'
import { useSource } from '../context'
import { radii } from '../utils'
@ -23,16 +23,12 @@ const title = useSource<string>(props.title)
<template>
<img
:src="src"
:alt="title"
:src="src"
:style="{
objectFit: fit ?? 'contain',
maxWidth: '100%',
...radii(radius),
}"
/>
>
</template>
<style scoped lang="less">
</style>

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useSource } from '../context'
defineOptions({

@ -1,11 +1,6 @@
<script setup lang="ts">
<script lang="ts" setup>
import type { Component } from 'vue'
import {
provideParentViewId,
useBlockId,
useContext,
useParentViewId,
} from '../context'
import { provideParentViewId, useBlockId, useContext, useParentViewId, } from '../context'
defineOptions({
name: 'DddView',
@ -24,8 +19,9 @@ function handleMousedown(e: Event, id: string | undefined): void {
ctx.activeBlockId = blockId
ctx.hoverViewId = parentId
ctx.canvas.focused = false
if (blockId)
if (blockId) {
ctx.configurator = 'block'
}
if (id) {
e.stopPropagation()
@ -47,8 +43,9 @@ function handleMousedown(e: Event, id: string | undefined): void {
}
function handleMouseleave(id: string | undefined): void {
if (id && ctx.hoverViewId === id && !ctx.draggingViewId)
if (id && ctx.hoverViewId === id && !ctx.draggingViewId) {
ctx.hoverViewId = undefined
}
}
provideParentViewId(props.vid)
@ -57,12 +54,12 @@ provideParentViewId(props.vid)
<template>
<component
:is="is ?? 'div'"
class="ddd-view"
:class="{
'is-active': vid && ctx.activeViewId === vid,
'is-hover': vid && ctx.hoverViewId === vid,
}"
:data-view-id="vid"
class="ddd-view"
@mouseleave="handleMouseleave(vid)"
@mousedown.left="handleMousedown($event, vid)"
>

Loading…
Cancel
Save