feat: 新增数据接口管理

This commit is contained in:
bqy_fe 2021-06-26 21:41:13 +08:00
parent c543fe894a
commit eeb9c839f9
13 changed files with 667 additions and 278 deletions

View File

@ -1,5 +1,7 @@
# 基于 Vite2.x + Vue3.x + TypeScript H5 低代码平台 # 基于 Vite2.x + Vue3.x + TypeScript H5 低代码平台
### 后续可能会。。。搭建 PC 端后台管理系统低代码平台
[![license](https://img.shields.io/github/license/buqiyuan/vite-vue3-lowcode.svg)](LICENSE) [![license](https://img.shields.io/github/license/buqiyuan/vite-vue3-lowcode.svg)](LICENSE)
**中文** | [English](./README.EN.md) **中文** | [English](./README.EN.md)
@ -34,7 +36,7 @@ git clone --single-branch https://gitee.com/buqiyuan/vite-vue3-lowcode.git
- [x] 动态添加页面 - [x] 动态添加页面
- [x] 拖拽式生成组件 - [x] 拖拽式生成组件
- [ ] service worker + indexeddb 实现无服务端的前端交互 - [ ] service worker + indexeddb 实现无服务端的前端交互
- [ ] 数据源管理 - [ ] 数据源管理(支持导入 swagger JSON 生成数据模型及接口)
- [ ] 提供预置函数 - [ ] 提供预置函数
- [ ] 更多组件的封装 - [ ] 更多组件的封装
- [ ] 其他... - [ ] 其他...

5
components.d.ts vendored
View File

@ -15,8 +15,6 @@ declare module 'vue' {
ElTabPane: typeof import('element-plus/es/el-tab-pane')['default'] ElTabPane: typeof import('element-plus/es/el-tab-pane')['default']
ElTabs: typeof import('element-plus/es/el-tabs')['default'] ElTabs: typeof import('element-plus/es/el-tabs')['default']
ElDialog: typeof import('element-plus/es/el-dialog')['default'] ElDialog: typeof import('element-plus/es/el-dialog')['default']
ElCollapseItem: typeof import('element-plus/es/el-collapse-item')['default']
ElCollapse: typeof import('element-plus/es/el-collapse')['default']
ElTag: typeof import('element-plus/es/el-tag')['default'] ElTag: typeof import('element-plus/es/el-tag')['default']
ElDropdownItem: typeof import('element-plus/es/el-dropdown-item')['default'] ElDropdownItem: typeof import('element-plus/es/el-dropdown-item')['default']
ElDropdownMenu: typeof import('element-plus/es/el-dropdown-menu')['default'] ElDropdownMenu: typeof import('element-plus/es/el-dropdown-menu')['default']
@ -25,6 +23,9 @@ declare module 'vue' {
ElInput: typeof import('element-plus/es/el-input')['default'] ElInput: typeof import('element-plus/es/el-input')['default']
ElFormItem: typeof import('element-plus/es/el-form-item')['default'] ElFormItem: typeof import('element-plus/es/el-form-item')['default']
ElForm: typeof import('element-plus/es/el-form')['default'] ElForm: typeof import('element-plus/es/el-form')['default']
ElCollapseItem: typeof import('element-plus/es/el-collapse-item')['default']
ElCollapse: typeof import('element-plus/es/el-collapse')['default']
ElPopconfirm: typeof import('element-plus/es/el-popconfirm')['default']
} }
} }

View File

@ -27,7 +27,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"dayjs": "^1.10.5", "dayjs": "^1.10.5",
"dexie": "^3.0.3", "dexie": "^3.0.3",
"element-plus": "1.0.2-beta.53", "element-plus": "1.0.2-beta.52",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.25.2", "monaco-editor": "^0.25.2",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
@ -62,7 +62,7 @@
"gh-pages": "^3.2.3", "gh-pages": "^3.2.3",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"prettier": "^2.3.1", "prettier": "^2.3.2",
"pretty-quick": "^3.1.1", "pretty-quick": "^3.1.1",
"sass": "1.35.1", "sass": "1.35.1",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
@ -72,7 +72,7 @@
"typescript": "^4.3.4", "typescript": "^4.3.4",
"vite": "2.3.8", "vite": "2.3.8",
"vite-plugin-components": "^0.11.2", "vite-plugin-components": "^0.11.2",
"vite-plugin-style-import": "^1.0.0", "vite-plugin-style-import": "^1.0.1",
"vite-plugin-windicss": "^1.1.1", "vite-plugin-windicss": "^1.1.1",
"vue-eslint-parser": "^7.6.0", "vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.2.0", "vue-tsc": "^0.2.0",

34
src/enums/httpEnum.ts Normal file
View File

@ -0,0 +1,34 @@
/**
* @description:
*/
export enum ResultEnum {
SUCCESS = 0,
ERROR = -1,
TIMEOUT = 10042,
TYPE = 'success'
}
/**
* @description:
*/
export enum RequestEnum {
GET = 'GET',
POST = 'POST',
PATCH = 'PATCH',
PUT = 'PUT',
DELETE = 'DELETE'
}
/**
* @description: contentTyp类型
*/
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// json
TEXT = 'text/plain;charset=UTF-8',
// form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8'
}

View File

@ -0,0 +1,260 @@
<!--
* @Author: 卜启缘
* @Date: 2021-06-24 18:36:03
* @LastEditTime: 2021-06-26 21:34:53
* @LastEditors: 卜启缘
* @Description: 接口请求
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\data-fetch.vue
-->
<template>
<div class="!mb-10px">
<el-button type="primary" size="small" @click="showModelMoal">添加</el-button>
<el-button type="warning" size="small" @click="showImportSwaggerJsonModal"
>导入swagger</el-button
>
</div>
<el-collapse v-model="state.activeNames">
<template v-for="item in apis" :key="item.key">
<el-collapse-item :title="item.name" :name="item.key">
<template #title>
<div class="model-item-title">
<span>{{ item.name }}</span>
<div class="model-actions">
<i class="el-icon-edit" @click="editApiItem(item)"></i>
<el-popconfirm
confirm-button-text="确定"
cancel-button-text="取消"
icon="el-icon-info"
icon-color="red"
title="确定要删除该接口吗?"
@confirm="deleteFetchApi(item.key)"
>
<template #reference>
<i class="el-icon-delete"></i>
</template>
</el-popconfirm>
</div>
</div>
</template>
<div class="low-model-item">
<pre class="code">{{ JSON.stringify(item, null, 2) }}</pre>
</div>
</el-collapse-item>
</template>
</el-collapse>
</template>
<script setup lang="tsx">
import { reactive, computed } from 'vue'
import {
ElForm,
ElFormItem,
ElInput,
ElSelect,
ElOption,
ElButton,
ElMessage,
ElCascader
} from 'element-plus'
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData'
import type { FetchApiItem, VisualEditorModel } from '@/visual-editor/visual-editor.utils'
import { useModal } from '@/visual-editor/hooks/useModal'
import { cloneDeep } from 'lodash'
import { generateUUID } from '@/visual-editor/utils/'
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum'
interface IState {
activeNames: string[]
ruleFormRef: any
ruleForm: FetchApiItem
}
const { jsonData, incrementFetchApi, updateFetchApi, deleteFetchApi } = useVisualData()
/**
* @description 接口集合
*/
const apis = computed(() => cloneDeep(jsonData.actions.fetch.apis))
/**
* @description 模型集合
*/
const models = computed(() => cloneDeep(jsonData.models))
/**
* @description 是否处于编辑状态
*/
const isEdit = computed(() => apis.value.some((item) => item.key == state.ruleForm.key))
/**
* @description 创建空的数据接口对象
*/
const createEmptyApiItem = (): FetchApiItem => ({
key: generateUUID(),
name: '',
options: {
url: '', // url
method: RequestEnum.GET, //
contentType: 'JSON' //
},
data: {
bind: '', //
recv: '' //
}
})
const state = reactive<IState>({
activeNames: [],
ruleFormRef: null,
ruleForm: createEmptyApiItem()
})
const rules = {
name: [{ required: true, message: '请输入接口名称', trigger: 'change' }],
'options.url': [{ required: true, message: '请输入接口名称', trigger: 'change' }],
'options.contentType': [{ required: true, message: '请选择内容类型', trigger: 'change' }]
}
const handleBindChange = (e: VisualEditorModel[]) => {
console.log(e, 'kkk')
}
/**
* @description 显示添加接口弹窗
*/
const showModelMoal = () => {
useModal({
title: `${isEdit.value ? '编辑' : '新增'}接口`,
props: {
width: 600
},
content: () => (
<ElForm
model={state.ruleForm}
ref={(el) => el && (state.ruleFormRef = el)}
label-width="100px"
size={'mini'}
rules={rules}
class="demo-ruleForm"
>
<ElFormItem label="名称" prop="name">
<ElInput v-model={state.ruleForm.name} placeholder={'请输入接口名称'}></ElInput>
</ElFormItem>
<ElFormItem label="接口" prop={'options.url'}>
<ElInput v-model={state.ruleForm.options.url} placeholder={'请输入接口地址'}>
{{
prepend: () => (
<ElSelect v-model={state.ruleForm.options.method} class={'w-90px'}>
{Object.keys(RequestEnum).map((key) => (
<ElOption key={key} label={key} value={key}></ElOption>
))}
</ElSelect>
)
}}
</ElInput>
</ElFormItem>
<ElFormItem label="内容类型" prop={'options.contentType'}>
<ElSelect v-model={state.ruleForm.options.contentType}>
{Object.keys(ContentTypeEnum).map((key) => (
<ElOption key={key} label={key} value={key}></ElOption>
))}
</ElSelect>
</ElFormItem>
<ElFormItem label="请求数据" prop={'data.bind'}>
<ElCascader
clearable={true}
props={{
checkStrictly: true,
children: 'entitys',
label: 'name',
value: 'key',
expandTrigger: 'hover'
}}
placeholder="请选择绑定的请求数据"
onChange={handleBindChange}
v-model={state.ruleForm.data.bind}
options={models.value}
></ElCascader>
</ElFormItem>
</ElForm>
),
onConfirm: () => {
return new Promise((resolve, reject) => {
state.ruleFormRef.validate((valid) => {
if (valid) {
if (isEdit.value) {
updateFetchApi(cloneDeep(state.ruleForm))
} else {
incrementFetchApi(cloneDeep(state.ruleForm))
}
ElMessage.success(`${isEdit.value ? '修改' : '新增'}接口成功!`)
state.ruleForm = createEmptyApiItem()
resolve('submit!')
} else {
reject()
console.log('error submit!!')
return false
}
})
})
},
onCancel: () => (state.ruleForm = createEmptyApiItem())
})
}
/**
* @description 编辑模型
*/
const editApiItem = (apiItem: FetchApiItem) => {
console.log(apiItem)
state.ruleForm = cloneDeep(apiItem)
showModelMoal()
}
/**
* @description 显示导入swagger JSON模态框
*/
const showImportSwaggerJsonModal = () => {
ElMessage.info('敬请期待!')
}
</script>
<style lang="scss" scoped>
.code {
padding: 4px 10px;
font-size: 12px;
line-height: 1.4;
}
.model-item-title {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.model-actions {
i {
padding: 6px;
margin: 0 2px;
font-weight: bold;
cursor: pointer;
border-radius: 2px;
opacity: 0.7;
transition: all 0.1s;
&:hover {
background-color: #f1f1f1;
opacity: 1;
}
&.el-icon-delete {
color: #f44336;
}
&.el-icon-edit {
color: #2196f3;
}
}
}
}
</style>

View File

@ -0,0 +1,290 @@
<!--
* @Author: 卜启缘
* @Date: 2021-06-24 18:36:03
* @LastEditTime: 2021-06-26 21:35:13
* @LastEditors: 卜启缘
* @Description: 数据模型管理
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\data-model.vue
-->
<template>
<div class="!mb-10px">
<el-button type="primary" size="small" @click="showModelMoal">添加</el-button>
<el-button type="warning" size="small" @click="showImportSwaggerJsonModal"
>导入swagger</el-button
>
</div>
<el-collapse v-model="state.activeNames">
<template v-for="item in models" :key="item.key">
<el-collapse-item :title="item.name" :name="item.key">
<template #title>
<div class="model-item-title">
<span>{{ item.name }}</span>
<div class="model-actions">
<i class="el-icon-edit" @click="editModel(item)"></i>
<el-popconfirm
confirm-button-text="确定"
cancel-button-text="取消"
icon="el-icon-info"
icon-color="red"
title="确定要删除该模型吗?"
@confirm="deleteModel(item.key)"
>
<template #reference>
<i class="el-icon-delete"></i>
</template>
</el-popconfirm>
</div>
</div>
</template>
<template v-for="entity in item.entitys" :key="entity.key">
<div class="low-model-item">
<pre class="code">{{ JSON.stringify(entity, null, 2) }}</pre>
</div>
</template>
</el-collapse-item>
</template>
</el-collapse>
</template>
<script setup lang="tsx">
import { reactive, computed } from 'vue'
import {
ElForm,
ElFormItem,
ElInput,
ElSelect,
ElOption,
ElCard,
ElButton,
ElMessage
} from 'element-plus'
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData'
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils'
import { useModal } from '@/visual-editor/hooks/useModal'
import { cloneDeep } from 'lodash'
import { generateUUID } from '@/visual-editor/utils/'
interface IState {
activeNames: string[]
ruleFormRef: any
ruleForm: VisualEditorModel
}
const { jsonData, incrementModel, updateModel, deleteModel } = useVisualData()
/**
* @description 模型集合
*/
const models = computed(() => cloneDeep(jsonData.models))
/**
* @description 是否处于编辑状态
*/
const isEdit = computed(() => models.value.some((item) => item.key == state.ruleForm.key))
/**
* @description 创建空的实体对象
*/
const createEmptyEntity = () => ({ key: '', name: '', type: 'string', value: '' })
/**
* @description 创建空的数据模型
*/
const createEmptyModel = () => ({
name: '',
key: generateUUID(),
entitys: [createEmptyEntity()]
})
const state = reactive<IState>({
activeNames: [],
ruleFormRef: null,
ruleForm: createEmptyModel()
})
/**
* @param {number} 索引
* @description 删除实体项
*/
const deleteEntityItem = (index: number) => {
state.ruleForm.entitys.splice(index, 1)
}
/**
* @description 添加实体项
*/
const addEntityItem = () => {
state.ruleForm.entitys.push(createEmptyEntity())
}
/**
* @description 显示添加接口弹窗
*/
const showModelMoal = () => {
useModal({
title: `${isEdit.value ? '编辑' : '新增'}数据源`,
props: {
width: 600
},
content: () => (
<ElForm
model={state.ruleForm}
ref={(el) => el && (state.ruleFormRef = el)}
label-width="100px"
size={'mini'}
class="demo-ruleForm"
>
<ElFormItem
label="数据源名称"
prop="name"
rules={[{ required: true, message: '请输入数据源名称', trigger: 'change' }]}
>
<ElInput v-model={state.ruleForm.name} placeholder={'请输入数据源名称'}></ElInput>
</ElFormItem>
{!state.ruleForm.entitys.length && (
<ElFormItem>
<ElButton onClick={addEntityItem} type={'primary'} size={'mini'}>
添加实体
</ElButton>
</ElFormItem>
)}
{state.ruleForm.entitys.map((entity, index) => (
<ElCard
key={index}
shadow={'hover'}
class={'mt-10px'}
v-slots={{
header: () => (
<div class={'flex justify-between'}>
<ElFormItem
label="实体名称"
prop={`entitys.${index}.name`}
rules={[{ required: true, message: '请输入实体名称', trigger: 'change' }]}
showMessage={false}
class={'w-300px !mb-0'}
>
<ElInput v-model={entity.name} placeholder={'请输入实体名称'}></ElInput>
</ElFormItem>
<div>
<ElButton onClick={() => deleteEntityItem(index)} type={'danger'} size={'mini'}>
删除
</ElButton>
<ElButton onClick={addEntityItem} type={'primary'} size={'mini'}>
添加
</ElButton>
</div>
</div>
)
}}
>
<ElFormItem
label="实体字段"
prop={`entitys.${index}.key`}
rules={[{ required: true, message: '请输入实体字段', trigger: 'change' }]}
>
<ElInput v-model={entity.key} placeholder={'请输入实体字段'}></ElInput>
</ElFormItem>
<ElFormItem
label="数据类型"
prop={`entitys.${index}.type`}
rules={[{ required: true, message: '请输入数据类型', trigger: 'change' }]}
>
<ElSelect v-model={entity.type}>
{fieldTypes.map((typeItem) => (
<ElOption
key={typeItem.value}
label={typeItem.label}
value={typeItem.value}
></ElOption>
))}
</ElSelect>
</ElFormItem>
<ElFormItem label="默认数据" prop={`entitys.${index}.value`}>
<ElInput
v-model={entity.value}
placeholder={'实体默认数据,不填则为对应类型数据'}
></ElInput>
</ElFormItem>
</ElCard>
))}
</ElForm>
),
onConfirm: () => {
return new Promise((resolve, reject) => {
state.ruleFormRef.validate((valid) => {
if (valid) {
if (isEdit.value) {
updateModel(cloneDeep(state.ruleForm))
} else {
incrementModel(cloneDeep(state.ruleForm))
}
ElMessage.success(`${isEdit.value ? '修改' : '新增'}模型成功!`)
state.ruleForm = createEmptyModel()
resolve('submit!')
} else {
reject()
console.log('error submit!!')
return false
}
})
})
},
onCancel: () => (state.ruleForm = createEmptyModel())
})
}
/**
* @description 编辑模型
*/
const editModel = (model: VisualEditorModel) => {
console.log(model)
state.ruleForm = cloneDeep(model)
showModelMoal()
}
/**
* @description 显示导入swagger JSON模态框
*/
const showImportSwaggerJsonModal = () => {
ElMessage.info('敬请期待!')
}
</script>
<style lang="scss" scoped>
.code {
padding: 4px 10px;
font-size: 12px;
line-height: 1.4;
}
.model-item-title {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.model-actions {
i {
padding: 6px;
margin: 0 2px;
font-weight: bold;
cursor: pointer;
border-radius: 2px;
opacity: 0.7;
transition: all 0.1s;
&:hover {
background-color: #f1f1f1;
opacity: 1;
}
&.el-icon-delete {
color: #f44336;
}
&.el-icon-edit {
color: #2196f3;
}
}
}
}
</style>

View File

@ -1,255 +1,31 @@
<!-- <!--
* @Author: 卜启缘 * @Author: 卜启缘
* @Date: 2021-06-24 18:36:03 * @Date: 2021-06-24 18:36:03
* @LastEditTime: 2021-06-25 21:38:33 * @LastEditTime: 2021-06-26 14:15:33
* @LastEditors: 卜启缘 * @LastEditors: 卜启缘
* @Description: 数据源管理 * @Description: 数据源管理
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\index.vue * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\index.vue
--> -->
<template> <template>
<el-button class="!my-10px" type="primary" size="small" @click="showModelMoal">添加</el-button> <el-tabs type="border-card" stretch>
<el-collapse v-model="state.activeNames"> <el-tab-pane label="数据模型">
<template v-for="item in models" :key="item.key"> <data-model />
<el-collapse-item :title="item.name" :name="item.key"> </el-tab-pane>
<template #title> <el-tab-pane label="数据接口">
<div class="model-item-title"> <data-fetch />
<span>{{ item.name }}</span> </el-tab-pane>
<div class="model-actions"> </el-tabs>
<i class="el-icon-edit" @click="editModel(item)"></i>
<i class="el-icon-delete" @click="deleteModel(item.key)"></i>
</div>
</div>
</template>
<template v-for="entity in item.entitys" :key="entity.key">
<div class="low-model-item">
<pre class="code">{{ JSON.stringify(entity, null, 2) }}</pre>
</div>
</template>
</el-collapse-item>
</template>
</el-collapse>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { reactive, computed } from 'vue' import DataModel from './data-model.vue'
import { import DataFetch from './data-fetch.vue'
ElForm,
ElFormItem,
ElInput,
ElSelect,
ElOption,
ElCard,
ElButton,
ElMessage
} from 'element-plus'
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData'
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils'
import { useModal } from '@/visual-editor/hooks/useModal'
import { cloneDeep } from 'lodash'
import { generateUUID } from '@/visual-editor/utils/'
interface IState {
activeNames: string[]
ruleFormRef: any
ruleForm: VisualEditorModel
}
const { jsonData, incrementModel, updateModel, deleteModel } = useVisualData()
/**
* @description 模型集合
*/
const models = computed(() => cloneDeep(jsonData.models))
/**
* @description 是否处于编辑状态
*/
const isEdit = computed(() => models.value.some((item) => item.key == state.ruleForm.key))
/**
* @description 创建空的实体对象
*/
const createEmptyEntity = () => ({ field: '', name: '', type: 'string', value: '' })
/**
* @description 创建空的数据模型
*/
const createEmptyModel = () => ({
name: '',
key: generateUUID(),
entitys: [createEmptyEntity()]
})
const state = reactive<IState>({
activeNames: [],
ruleFormRef: null,
ruleForm: createEmptyModel()
})
/**
* @param {number} 索引
* @description 删除实体项
*/
const deleteEntityItem = (index: number) => {
state.ruleForm.entitys.splice(index, 1)
}
/**
* @description 添加实体项
*/
const addEntityItem = () => {
state.ruleForm.entitys.push(createEmptyEntity())
}
/**
* @description 显示添加接口弹窗
*/
const showModelMoal = () => {
useModal({
title: `${isEdit.value ? '编辑' : '新增'}数据源`,
content: () => (
<ElForm
model={state.ruleForm}
ref={(el) => el && (state.ruleFormRef = el)}
label-width="100px"
size={'mini'}
class="demo-ruleForm"
>
<ElFormItem
label="数据源名称"
prop="name"
rules={[{ required: true, message: '请输入数据源名称', trigger: 'change' }]}
>
<ElInput v-model={state.ruleForm.name} placeholder={'请输入数据源名称'}></ElInput>
</ElFormItem>
{state.ruleForm.entitys.map((entity, index) => (
<ElCard
key={index}
shadow={'hover'}
v-slots={{
header: () => (
<div class={'flex justify-between'}>
<ElFormItem
label="实体名称"
prop={`entitys.${index}.name`}
rules={[{ required: true, message: '请输入实体名称', trigger: 'change' }]}
showMessage={false}
class={'w-300px !mb-0'}
>
<ElInput v-model={entity.name} placeholder={'请输入实体名称'}></ElInput>
</ElFormItem>
<div>
<ElButton onClick={() => deleteEntityItem(index)} type={'danger'} size={'mini'}>
删除
</ElButton>
<ElButton onClick={addEntityItem} type={'primary'} size={'mini'}>
添加
</ElButton>
</div>
</div>
)
}}
>
<ElFormItem
label="实体字段"
prop={`entitys.${index}.field`}
rules={[{ required: true, message: '请输入实体字段', trigger: 'change' }]}
>
<ElInput v-model={entity.field} placeholder={'请输入实体字段'}></ElInput>
</ElFormItem>
<ElFormItem
label="数据类型"
prop={`entitys.${index}.type`}
rules={[{ required: true, message: '请输入数据类型', trigger: 'change' }]}
>
<ElSelect v-model={entity.type}>
{fieldTypes.map((typeItem) => (
<ElOption
key={typeItem.value}
label={typeItem.label}
value={typeItem.value}
></ElOption>
))}
</ElSelect>
</ElFormItem>
<ElFormItem label="默认数据" prop={`entitys.${index}.value`}>
<ElInput
v-model={entity.value}
placeholder={'实体默认数据,不填则为对应类型数据'}
></ElInput>
</ElFormItem>
</ElCard>
))}
</ElForm>
),
onConfirm: () => {
return new Promise((resolve, reject) => {
state.ruleFormRef.validate((valid) => {
if (valid) {
if (isEdit.value) {
updateModel(cloneDeep(state.ruleForm))
} else {
incrementModel(cloneDeep(state.ruleForm))
}
ElMessage.success(`${isEdit.value ? '修改' : '新增'}模型成功!`)
state.ruleForm = createEmptyModel()
resolve('submit!')
} else {
reject()
console.log('error submit!!')
return false
}
})
})
},
onCancel: () => (state.ruleForm = createEmptyModel())
})
}
/**
* @description 编辑模型
*/
const editModel = (model: VisualEditorModel) => {
console.log(model)
state.ruleForm = cloneDeep(model)
showModelMoal()
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.code { ::v-deep(.el-tabs__header) {
padding: 4px 10px; position: sticky;
font-size: 12px; top: 0;
line-height: 1.4; z-index: 10;
}
.model-item-title {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.model-actions {
i {
padding: 6px;
margin: 0 2px;
font-weight: bold;
cursor: pointer;
border-radius: 2px;
opacity: 0.7;
transition: all 0.1s;
&:hover {
background-color: #f1f1f1;
opacity: 1;
}
&.el-icon-delete {
color: #f44336;
}
&.el-icon-edit {
color: #2196f3;
}
}
}
} }
</style> </style>

View File

@ -1,6 +1,11 @@
<!--页面树--> <!--页面树-->
<template> <template>
<el-button type="primary" size="small" style="margin: 10px 0" icon="el-icon-plus" @click="addPage" <el-button
type="primary"
size="small"
class="!my-10px !mx-6px"
icon="el-icon-plus"
@click="addPage"
>添加页面</el-button >添加页面</el-button
> >
<el-tree <el-tree

View File

@ -1,17 +1,17 @@
<!-- <!--
* @Author: 卜启缘 * @Author: 卜启缘
* @Date: 2021-06-24 00:35:17 * @Date: 2021-06-24 00:35:17
* @LastEditTime: 2021-06-25 21:05:57 * @LastEditTime: 2021-06-26 00:24:40
* @LastEditors: 卜启缘 * @LastEditors: 卜启缘
* @Description: 左侧边栏 * @Description: 左侧边栏
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\index.vue * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\index.vue
--> -->
<template> <template>
<el-tabs v-model="activeName" tab-position="left" @tab-click="handleClick"> <el-tabs v-model="activeName" tab-position="left" class="left-aside">
<template v-for="tabItem in tabs" :key="tabItem.componentName"> <template v-for="tabItem in tabs" :key="tabItem.componentName">
<el-tab-pane :name="tabItem.componentName"> <el-tab-pane :name="tabItem.componentName">
<template #label> <template #label>
<div class="flex flex-col items-center justify-center"> <div :ref="(el) => el && (tabItemRef[tabItem.componentName] = el)" class="tab-item">
<i :class="tabItem.icon"></i> <i :class="tabItem.icon"></i>
{{ tabItem.label }} {{ tabItem.label }}
</div> </div>
@ -23,7 +23,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue' import { defineComponent, reactive, toRefs, onMounted, ComponentInternalInstance } from 'vue'
import { tabs } from './tabs' import { tabs } from './tabs'
import components from './components' import components from './components'
@ -35,34 +35,48 @@ export default defineComponent({
components, components,
setup() { setup() {
const state = reactive({ const state = reactive({
activeName: tabs[0].componentName activeName: tabs[0].componentName,
tabItemRef: {} as { [prop: string]: ComponentInternalInstance | Element }
}) })
const handleClick = (tab, event) => { onMounted(() => {
console.log(tab, event) setTimeout(() => {
} tabs.forEach((item) => {
;(state.tabItemRef[item.componentName] as HTMLDivElement)
?.closest('.el-tabs__item')
?.setAttribute('data-custom-css', '')
})
})
})
return { return {
...toRefs(state), ...toRefs(state),
tabs, tabs
handleClick
} }
} }
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-tabs { .left-aside {
height: 100%; height: 100%;
::v-deep(.el-tabs__item) { ::v-deep(.el-tabs__header.is-left) {
margin-right: 0;
}
::v-deep(.el-tabs__item[data-custom-css]) {
height: 80px; height: 80px;
padding: 20px 16px; padding: 20px 16px;
.tab-item {
@apply flex flex-col items-center justify-center;
[class^='el-icon-'] { [class^='el-icon-'] {
font-size: 20px; font-size: 20px;
} }
} }
}
::v-deep(.el-tabs__content) { ::v-deep(.el-tabs__content) {
height: 100%; height: 100%;

View File

@ -58,8 +58,8 @@ const Modal = defineComponent({
methods.hide() methods.hide()
}, },
onCancel: () => { onCancel: () => {
methods.hide()
state.options.onCancel?.() state.options.onCancel?.()
methods.hide()
} }
} }
@ -69,8 +69,9 @@ const Modal = defineComponent({
<ElDialog <ElDialog
modelValue={state.visible} modelValue={state.visible}
title={state.options.title} title={state.options.title}
destroyOnClose={true}
{...state.options.props} {...state.options.props}
onClose={methods.hide} onClose={handler.onCancel}
> >
{{ {{
default: () => default: () =>

View File

@ -1,6 +1,7 @@
import { VisualEditorProps } from './visual-editor.props' import type { VisualEditorProps } from './visual-editor.props'
import { inject, provide } from 'vue' import { inject, provide } from 'vue'
import { useDotProp } from '@/visual-editor/hooks/useDotProp' import { useDotProp } from '@/visual-editor/hooks/useDotProp'
import type { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum'
/** /**
* @description * @description
@ -51,7 +52,7 @@ export interface VisualEditorPages {
* @description * @description
*/ */
export type EntityType = { export type EntityType = {
field: string // 绑定的字段 输入 key: string // 绑定的字段 输入
name: string // 实体名称 输入 name: string // 实体名称 输入
type: string // 数据类型 选择 type: string // 数据类型 选择
value: string // 默认值 输入 value: string // 默认值 输入
@ -73,8 +74,8 @@ export interface FetchApiItem {
name: string // 当前api名字 name: string // 当前api名字
options: { options: {
url: string // 请求的url url: string // 请求的url
method: string // 请求的方法 method: keyof typeof RequestEnum // 请求的方法
contentType: string // 请求的内容类型 contentType: keyof typeof ContentTypeEnum // 请求的内容类型
} }
data: { data: {
bind: string // 请求绑定对应的某个实体 bind: string // 请求绑定对应的某个实体

View File

@ -12,4 +12,9 @@ Object.keys(containerComponent).forEach((name: string) =>
visualConfig.registry('containerComponents', name, containerComponent[name]) visualConfig.registry('containerComponents', name, containerComponent[name])
) )
console.log(
'%c成功加载组件数量:' + Object.keys(visualConfig.componentMap).length,
'color:#409EFF;background-color:#ecf5ff;padding:0 10px;line-height:2;margin-bottom:4px;'
)
console.log('visualConfig:', visualConfig) console.log('visualConfig:', visualConfig)

View File

@ -2153,10 +2153,10 @@ electron-to-chromium@^1.3.723:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.755.tgz#4b6101f13de910cf3f0a1789ddc57328133b9332" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.755.tgz#4b6101f13de910cf3f0a1789ddc57328133b9332"
integrity sha512-BJ1s/kuUuOeo1bF/EM2E4yqW9te0Hpof3wgwBx40AWJE18zsD1Tqo0kr7ijnOc+lRsrlrqKPauJAHqaxOItoUA== integrity sha512-BJ1s/kuUuOeo1bF/EM2E4yqW9te0Hpof3wgwBx40AWJE18zsD1Tqo0kr7ijnOc+lRsrlrqKPauJAHqaxOItoUA==
element-plus@1.0.2-beta.53: element-plus@1.0.2-beta.52:
version "1.0.2-beta.53" version "1.0.2-beta.52"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.2-beta.53.tgz#52208eccce487f452ffc5de8722cb718d6e02953" resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.2-beta.52.tgz#c7ed94d498e390658478aa2438bc35247325b2d0"
integrity sha512-PhhOBy1BWSVGWpCcu8fkmjcXvnAlc5dvith9vbNo1hPjm1cqf3AZbbt955VUTyFKWOLUOV9oD76HrNEG5ZTlxg== integrity sha512-oAuJHwXyvM4dsuOz7HSDPIBVPqRJ1KEzFzGqYdqbBjQ/aw79uCJxvS9Q4q9/XrPMfPire09+bPTypiIaHkNBhA==
dependencies: dependencies:
"@popperjs/core" "^2.4.4" "@popperjs/core" "^2.4.4"
"@types/lodash" "^4.14.161" "@types/lodash" "^4.14.161"
@ -4785,10 +4785,10 @@ prettier@^1.16.4, prettier@^1.18.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.3.1: prettier@^2.3.2:
version "2.3.1" version "2.3.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA== integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
pretty-quick@^3.1.1: pretty-quick@^3.1.1:
version "3.1.1" version "3.1.1"
@ -6331,10 +6331,10 @@ vite-plugin-components@^0.11.2:
magic-string "^0.25.7" magic-string "^0.25.7"
minimatch "^3.0.4" minimatch "^3.0.4"
vite-plugin-style-import@^1.0.0: vite-plugin-style-import@^1.0.1:
version "1.0.0" version "1.0.1"
resolved "https://registry.yarnpkg.com/vite-plugin-style-import/-/vite-plugin-style-import-1.0.0.tgz#344b7f14bcb5aefeb730d3192012c19a0604bb5e" resolved "https://registry.yarnpkg.com/vite-plugin-style-import/-/vite-plugin-style-import-1.0.1.tgz#bf61337dd11e4ebc0f355f271e06d374b1ca5c79"
integrity sha512-5KCFN+WePRHsjZBtSPsN3Ii/Uas3Ld7d4B2s/I0NB4Iv7SuXuFudMz5IjoFaBqsbitXP7WEJ4XnZFan1fW1hAg== integrity sha512-qinzdBxqkmX4fEyLZJVIBQTrgKivbROgcKJfBRz66knbghYAoe9cQeRptWKh35hE7obiidItWSCrHWkeu+JMVw==
dependencies: dependencies:
"@rollup/pluginutils" "^4.1.0" "@rollup/pluginutils" "^4.1.0"
change-case "^4.1.2" change-case "^4.1.2"