52 changed files with 794 additions and 121 deletions
@ -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' |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
/* |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-24 23:19:48 |
|||
* @LastEditTime: 2021-06-24 23:35:57 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: axios简单的封装 |
|||
* @FilePath: \vite-vue3-lowcode\src\utils\http\request.ts |
|||
*/ |
|||
import axios, { AxiosRequestConfig } from 'axios' |
|||
import qs from 'qs' |
|||
import store from '@/store' |
|||
import { Toast } from 'vant' |
|||
import router from '@/router' |
|||
import { ContentTypeEnum } from './httpEnum' |
|||
|
|||
// create an axios instance
|
|||
const service = axios.create({ |
|||
baseURL: import.meta.env.VITE_API_URL as string, // url = base api url + request url
|
|||
withCredentials: true, // send cookies when cross-domain requests
|
|||
timeout: 10000 // request timeout
|
|||
}) |
|||
interface CustomAxiosRequestConfig extends AxiosRequestConfig { |
|||
hideLoading?: boolean |
|||
} |
|||
|
|||
interface BaseResponse<T = any> { |
|||
code: number |
|||
data: T |
|||
msg: string |
|||
} |
|||
|
|||
// request拦截器 request interceptor
|
|||
service.interceptors.request.use( |
|||
(config: CustomAxiosRequestConfig) => { |
|||
// 不传递默认开启loading
|
|||
if (!config.hideLoading) { |
|||
// loading
|
|||
Toast.loading({ |
|||
forbidClick: true |
|||
}) |
|||
} |
|||
console.log(store.getters, "store.getters['user']") |
|||
if (store.getters['user/token']) { |
|||
config.headers['X-Token'] = store.getters['user/token'] |
|||
} |
|||
const contentType = config.headers['content-type'] || config.headers['Content-Type'] |
|||
const data = config.data |
|||
if (config.method?.toLocaleUpperCase() == 'POST' && data) { |
|||
if (ContentTypeEnum.FORM_DATA == contentType) { |
|||
const fd = new FormData() |
|||
Object.keys(data).forEach((key) => fd.append(key, data[key])) |
|||
config.data = fd |
|||
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) { |
|||
config.data = qs.stringify(config.data) |
|||
} |
|||
} |
|||
return config |
|||
}, |
|||
(error) => { |
|||
// do something with request error
|
|||
console.log(error) // for debug
|
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
// respone拦截器
|
|||
service.interceptors.response.use( |
|||
(response) => { |
|||
Toast.clear() |
|||
const res = response.data |
|||
if (res.code && res.code !== 0) { |
|||
// 登录超时,重新登录
|
|||
if (res.code === 401) { |
|||
// store.dispatch('FedLogOut').then(() => {
|
|||
// location.reload()
|
|||
// })
|
|||
router.replace('/error') |
|||
} else { |
|||
Toast(res.msg || '服务器访问出错了~') |
|||
} |
|||
return Promise.reject(res || 'error') |
|||
} else { |
|||
return Promise.resolve(response) |
|||
} |
|||
}, |
|||
(error: Error) => { |
|||
if (error.message?.includes('timeout')) { |
|||
Toast('请求超时!') |
|||
} |
|||
console.log('err' + error) // for debug
|
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
|
|||
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => { |
|||
return new Promise((resolve, reject) => { |
|||
service |
|||
.request<BaseResponse<T>>(config) |
|||
.then((res) => resolve(res.data)) |
|||
.catch((err) => reject(err)) |
|||
}) |
|||
} |
|||
|
|||
export default request |
|||
@ -0,0 +1,255 @@ |
|||
<!-- |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-24 18:36:03 |
|||
* @LastEditTime: 2021-06-25 21:38:33 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: 数据源管理 |
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\index.vue |
|||
--> |
|||
<template> |
|||
<el-button class="!my-10px" type="primary" size="small" @click="showModelMoal">添加</el-button> |
|||
<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> |
|||
<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> |
|||
|
|||
<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 = () => ({ 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> |
|||
|
|||
<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> |
|||
@ -0,0 +1,11 @@ |
|||
/* |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-12 22:18:48 |
|||
* @LastEditTime: 2021-06-24 18:31:54 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: 统一导出组件 |
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\index.ts |
|||
*/ |
|||
|
|||
export { CrossSortableOptionsEditor } from './cross-sortable-options-editor/cross-sortable-options-editor' |
|||
export { TablePropEditor } from './table-prop-editor/table-prop-editor' |
|||
@ -1,6 +1,6 @@ |
|||
import { VisualEditorProps } from '../../../../visual-editor.props' |
|||
import { VisualEditorProps } from '@/visual-editor/visual-editor.props' |
|||
import { defineComponent, getCurrentInstance, onMounted, PropType, reactive, createApp } from 'vue' |
|||
import { defer } from '../../../../utils/defer' |
|||
import { defer } from '@/visual-editor/utils/defer' |
|||
import { ElButton, ElDialog, ElTable, ElTableColumn, ElInput } from 'element-plus' |
|||
import { cloneDeep } from 'lodash' |
|||
|
|||
@ -0,0 +1,19 @@ |
|||
/* |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-24 11:01:45 |
|||
* @LastEditTime: 2021-06-24 11:12:56 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: 事件-动作 |
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\event-action\index.tsx |
|||
*/ |
|||
import { defineComponent } from 'vue' |
|||
|
|||
export const EventAction = defineComponent({ |
|||
setup() { |
|||
return () => ( |
|||
<> |
|||
<div>事件流</div> |
|||
</> |
|||
) |
|||
} |
|||
}) |
|||
@ -1,14 +1,13 @@ |
|||
/* |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-12 22:18:48 |
|||
* @LastEditTime: 2021-06-23 22:17:38 |
|||
* @LastEditTime: 2021-06-24 18:30:44 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: |
|||
* @Description: 统一导出组件 |
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\index.ts |
|||
*/ |
|||
|
|||
export { TablePropEditor } from './table-prop-editor/table-prop-editor' |
|||
export { AttrEditor } from './attr-editor/AttrEditor' |
|||
export { Animate } from './animate/Animate' |
|||
export { CrossSortableOptionsEditor } from './cross-sortable-options-editor/cross-sortable-options-editor' |
|||
export { PageSetting } from './page-setting/pageSetting' |
|||
export { EventAction } from './event-action/' |
|||
|
|||
@ -1,8 +1,30 @@ |
|||
/* |
|||
* @Author: 卜启缘 |
|||
* @Date: 2021-06-01 09:45:21 |
|||
* @LastEditTime: 2021-06-24 21:57:31 |
|||
* @LastEditors: 卜启缘 |
|||
* @Description: 公用的工具函数 |
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\utils\index.ts |
|||
*/ |
|||
|
|||
/** |
|||
* @name: index |
|||
* @author: 卜启缘 |
|||
* @date: 2021/5/6 0:04 |
|||
* @description:index |
|||
* @update: 2021/5/6 0:04 |
|||
* @description 部署应用时的基本URL |
|||
*/ |
|||
export const BASE_URL = import.meta.env.BASE_URL |
|||
|
|||
/** |
|||
* @description 生成UUID |
|||
* @param {boolean} [noSymbol=false] 是否需要 - 分隔符 |
|||
* @returns {string} |
|||
*/ |
|||
export function generateUUID(noSymbol = false) { |
|||
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { |
|||
const r = (Math.random() * 16) | 0, |
|||
v = c == 'x' ? r : (r & 0x3) | 0x8 |
|||
return v.toString(16) |
|||
}) |
|||
if (noSymbol) { |
|||
uuid = uuid.replace(/-/g, '') |
|||
} |
|||
return uuid |
|||
} |
|||
|
|||
Loading…
Reference in new issue