feat: iframe preview

This commit is contained in:
bqy_fe 2021-05-06 00:35:05 +08:00
parent 925d71cc5a
commit ea986f3e6e
21 changed files with 2058 additions and 362 deletions

View File

@ -1,7 +1,7 @@
# 只在生产模式中被载入 # 只在生产模式中被载入
# 网站前缀 # 网站前缀
VITE_BASE_URL = /vite-vue3-lowcode VITE_BASE_URL = /vite-vue3-lowcode/
# 是否删除console # 是否删除console
VITE_DROP_CONSOLE = true VITE_DROP_CONSOLE = true

View File

@ -23,38 +23,40 @@ module.exports = {
], ],
rules: { rules: {
'vue/require-default-prop': 'off', 'vue/require-default-prop': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-types': 'off', '@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off'
'@typescript-eslint/no-unused-vars': [ // '@typescript-eslint/no-unused-vars': [
'error', // 'error',
{ // {
argsIgnorePattern: '^_', // argsIgnorePattern: '^_',
varsIgnorePattern: '^_' // varsIgnorePattern: '^_'
} // }
], // ],
'no-unused-vars': [ // 'no-unused-vars': [
'error', // 'error',
{ // {
argsIgnorePattern: '^_', // argsIgnorePattern: '^_',
varsIgnorePattern: '^_' // varsIgnorePattern: '^_'
} // }
], // ],
'vue/html-self-closing': [ // 'vue/html-self-closing': [
'error', // 'error',
{ // {
html: { // html: {
void: 'always', // void: 'always',
normal: 'never', // normal: 'never',
component: 'always' // component: 'always'
}, // },
svg: 'always', // svg: 'always',
math: 'always' // math: 'always'
} // }
] // ]
}, },
settings: {} settings: {}
} }

View File

@ -32,30 +32,30 @@
"@commitlint/cli": "^12.1.1", "@commitlint/cli": "^12.1.1",
"@commitlint/config-conventional": "^12.1.1", "@commitlint/config-conventional": "^12.1.1",
"@types/node": "^14.14.41", "@types/node": "^14.14.41",
"@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/eslint-plugin": "^4.22.1",
"@typescript-eslint/parser": "^4.21.0", "@typescript-eslint/parser": "^4.22.1",
"@vitejs/plugin-vue": "^1.2.1", "@vitejs/plugin-vue": "^1.2.2",
"@vitejs/plugin-vue-jsx": "^1.1.3", "@vitejs/plugin-vue-jsx": "^1.1.4",
"@vue/compiler-sfc": "^3.0.11", "@vue/compiler-sfc": "^3.0.11",
"commitizen": "^4.2.3", "commitizen": "^4.2.3",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.3.0", "cz-customizable": "^6.3.0",
"eslint": "^7.24.0", "eslint": "^7.25.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.8.0", "eslint-plugin-vue": "^7.9.0",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^10.5.4", "lint-staged": "^10.5.4",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"sass": "^1.32.10", "sass": "^1.32.10",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"vite": "^2.2.3", "vite": "^2.2.4",
"vite-plugin-components": "^0.8.4", "vite-plugin-components": "^0.8.4",
"vite-plugin-style-import": "^0.10.0", "vite-plugin-style-import": "^0.10.0",
"vite-plugin-windicss": "^0.14.6", "vite-plugin-windicss": "^0.14.6",
"vue-eslint-parser": "^7.6.0", "vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.0.25", "vue-tsc": "^0.1.0",
"windicss": "^2.5.14" "windicss": "^2.5.14"
}, },
"repository": { "repository": {

19
preview/App.vue Normal file
View File

@ -0,0 +1,19 @@
<template>
<div>
<router-view #="{ Component }">
<component :is="Component" />
</router-view>
</div>
</template>
<script lang="ts">
export default {
name: 'App'
}
</script>
<style>
body::-webkit-scrollbar {
width: 0;
}
</style>

13
preview/index.html Normal file
View File

@ -0,0 +1,13 @@
<!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>第二个页面</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>

16
preview/main.ts Normal file
View File

@ -0,0 +1,16 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import '@/plugins/vant'
const app = createApp(App)
app.config.globalProperties.$$refs = {}
// if (import.meta.env.DEV) {
window.$$refs = app.config.globalProperties.$$refs
// }
app.use(router).mount('#app')

15
preview/router.ts Normal file
View File

@ -0,0 +1,15 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
{
path: '/:pathMatch(.*)*',
component: () => import('./views/preview.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@ -0,0 +1,27 @@
import { defineComponent } from 'vue'
export default defineComponent({
name: 'CompRender',
props: {
element: {
type: Object,
default: () => ({})
},
config: {
type: Object,
default: () => ({})
}
},
setup(props) {
return () => {
const component = props.config.componentMap[props.element.componentKey]
return component.render({
size: {},
props: props.element.props || {},
model: {},
block: props.element,
custom: {}
})
}
}
})

67
preview/views/preview.vue Normal file
View File

@ -0,0 +1,67 @@
<template>
<template v-for="outItem in jsonData" :key="outItem._vid">
<slot-item :element="outItem" :config="visualConfig" />
</template>
</template>
<script lang="tsx">
import { defineComponent, PropType, reactive, toRefs } from 'vue'
import { visualConfig } from '@/visual.config'
import SlotItem from './slot-item.vue'
/**
* @name: preview
* @author: 卜启缘
* @date: 2021/4/29 23:09
* @descriptionpreview
* @update: 2021/4/29 23:09
*/
export default defineComponent({
name: 'Preview',
components: {
SlotItem
},
emits: ['update:visible'],
setup(props) {
const state = reactive({
jsonData: JSON.parse(sessionStorage.getItem('blocks') || '{}')
})
//
const renderCom = (element) => {
if (Array.isArray(element)) {
return element.map((item) => renderCom(item))
}
const component = props.config.componentMap[element.componentKey]
return component.render({
size: {},
props: element.props || {},
block: element,
model: {},
custom: {}
})
}
return {
...toRefs(state),
visualConfig,
renderCom
}
}
})
</script>
<style lang="scss">
.h5-preview {
overflow: hidden;
.el-dialog__header {
display: none;
}
.simulator {
padding-right: 0;
&::-webkit-scrollbar {
width: 0;
}
}
}
</style>

View File

@ -1,31 +0,0 @@
import { NumberRange } from './number-range'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
export default {
key: 'number-range',
moduleName: 'baseWidgets',
label: '数字范围输入框',
resize: {
width: true
},
preview: () => <NumberRange style={{ width: '100%' }} />,
render: ({ model, size }) => {
return (
<NumberRange
style={{
width: size.width ? `${size.width}px` : null
}}
{...{
start: model.start.value,
'onUpdate:start': model.start.onChange,
end: model.end.value,
'onUpdate:end': model.end.onChange
}}
/>
)
},
model: {
start: '起始绑定字段',
end: '截止绑定字段'
}
} as VisualEditorComponent

View File

@ -1,24 +0,0 @@
.number-range {
display: inline-flex;
width: 225px;
min-height: 40px;
align-items: stretch;
& > div {
flex: 1;
input {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
span {
margin: 0 8px;
display: inline-flex;
align-items: center;
}
}

View File

@ -1,30 +0,0 @@
import { defineComponent } from 'vue'
import { useVModel } from '@vueuse/core'
import './number-range.scss'
export const NumberRange = defineComponent({
props: {
start: { type: String },
end: { type: String }
},
emits: {
'update:start': (_?: string) => true,
'update:end': (_?: string) => true
},
setup(props) {
const startModel = useVModel(props, 'start')
const endModel = useVModel(props, 'end')
return () => (
<div class="number-range">
<div>
<input type="text" v-model={startModel.value} />
</div>
<span>~</span>
<div>
<input type="text" v-model={endModel.value} />
</div>
</div>
)
}
})

View File

@ -1,37 +0,0 @@
import { ElOption, ElSelect } from 'element-plus'
import { createEditorTableProp } from '@/visual-editor/visual-editor.props'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
export default {
key: 'select',
moduleName: 'baseWidgets',
label: '下拉框',
preview: () => <ElSelect />,
render: ({ props, model, custom }) => (
<ElSelect
{...custom}
key={(props.options || []).map((opt: any) => opt.value).join(',')}
{...model.default}
>
{(props.options || []).map((opt: { label: string; value: string }, index: number) => (
<ElOption label={opt.label} value={opt.value} key={index} />
))}
</ElSelect>
),
props: {
options: createEditorTableProp({
label: '下拉选项',
option: {
options: [
{ label: '显示值', field: 'label' },
{ label: '绑定值', field: 'value' },
{ label: '备注', field: 'comments' }
],
showKey: 'label'
}
})
},
model: {
default: '绑定字段'
}
} as VisualEditorComponent

View File

@ -21,7 +21,7 @@ export default defineComponent({
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
//overflow: hidden auto;
@media (max-width: 1314px) { @media (max-width: 1314px) {
padding-right: 0; padding-right: 0;
} }
@ -35,6 +35,7 @@ export default defineComponent({
border-radius: 5px; border-radius: 5px;
box-shadow: 0 8px 12px #ebedf0; box-shadow: 0 8px 12px #ebedf0;
transform: translate(0); transform: translate(0);
overflow: auto;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 0; width: 0;

View File

@ -14,18 +14,19 @@
icon="el-icon-video-play" icon="el-icon-video-play"
circle circle
class="flex-shrink-0" class="flex-shrink-0"
@click="isShowH5Preview = true" @click="runPreview"
/> />
</el-tooltip> </el-tooltip>
</el-col> </el-col>
</el-row> </el-row>
<preview v-model:visible="isShowH5Preview" :json-data="jsonData" :config="config" /> <template v-if="isShowH5Preview">
<preview v-model:visible="isShowH5Preview" />
</template>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, reactive, toRefs } from 'vue' import { defineComponent, reactive, toRefs } from 'vue'
import Preview from './preview.vue' import Preview from './preview.vue'
import { VisualEditorConfig } from '@/visual-editor/visual-editor.utils'
export default defineComponent({ export default defineComponent({
name: 'Header', name: 'Header',
@ -34,16 +35,21 @@ export default defineComponent({
jsonData: { jsonData: {
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, }
config: { type: Object as PropType<VisualEditorConfig>, required: true }
}, },
setup() { setup(props) {
const state = reactive({ const state = reactive({
isShowH5Preview: false isShowH5Preview: false
}) })
const runPreview = () => {
sessionStorage.setItem('blocks', JSON.stringify(props.jsonData.blocks))
state.isShowH5Preview = true
}
return { return {
...toRefs(state) ...toRefs(state),
runPreview
} }
} }
}) })

View File

@ -1,20 +1,19 @@
<template> <template>
<el-dialog v-model="dialogVisible" custom-class="h5-preview" :show-close="false" width="30%"> <el-dialog v-model="dialogVisible" custom-class="h5-preview" :show-close="false" width="360px">
<simulator class="simulator"> <iframe
<template v-for="outItem in jsonDataClone.blocks" :key="outItem._vid"> style="width: 360px; height: 640px"
<slot-item :element="outItem" :config="config" /> :src="`${BASE_URL}preview/#/`"
</template> frameborder="0"
</simulator> scrolling="auto"
></iframe>
</el-dialog> </el-dialog>
</template> </template>
<script lang="tsx"> <script lang="tsx">
import { defineComponent, PropType, reactive, watch, toRefs } from 'vue' import { defineComponent, reactive, watch, toRefs } from 'vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { VisualEditorConfig } from '@/visual-editor/visual-editor.utils'
import Simulator from '../common/simulator.vue'
import SlotItem from './slot-item.vue'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { BASE_URL } from '@/visual-editor/utils'
/** /**
* @name: preview * @name: preview
* @author: 卜启缘 * @author: 卜启缘
@ -24,20 +23,11 @@ import { cloneDeep } from 'lodash'
*/ */
export default defineComponent({ export default defineComponent({
name: 'Preview', name: 'Preview',
components: {
Simulator,
SlotItem
},
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: false default: false
}, }
jsonData: {
type: Object,
default: () => ({})
},
config: { type: Object as PropType<VisualEditorConfig>, required: true }
}, },
emits: ['update:visible'], emits: ['update:visible'],
setup(props, { emit }) { setup(props, { emit }) {
@ -72,6 +62,7 @@ export default defineComponent({
return { return {
...toRefs(state), ...toRefs(state),
BASE_URL,
renderCom renderCom
} }
} }
@ -81,6 +72,9 @@ export default defineComponent({
<style lang="scss"> <style lang="scss">
.h5-preview { .h5-preview {
overflow: hidden; overflow: hidden;
.el-dialog__body {
padding: 0;
}
.el-dialog__header { .el-dialog__header {
display: none; display: none;
} }

View File

@ -0,0 +1,8 @@
/**
* @name: index
* @author:
* @date: 2021/5/6 0:04
* @descriptionindex
* @update: 2021/5/6 0:04
*/
export const BASE_URL = import.meta.env.BASE_URL

View File

@ -33,6 +33,6 @@
"scripthost" "scripthost"
] ]
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], // "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
} }

View File

@ -50,8 +50,24 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
} }
}, },
base: VITE_BASE_URL, // 设置打包路径 base: VITE_BASE_URL, // 设置打包路径
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
preview: resolve(__dirname, 'preview/index.html')
}
}
},
optimizeDeps: { optimizeDeps: {
include: ['vue', 'vue-router', '@vueuse/core'], include: [
'vue',
'vue-router',
'@vueuse/core',
'element-plus',
'vant',
'lodash',
'vuedraggable'
],
exclude: ['vue-demi'] exclude: ['vue-demi']
}, },
server: { server: {

1978
yarn.lock

File diff suppressed because it is too large Load Diff