22 changed files with 2748 additions and 726 deletions
@ -0,0 +1,499 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-07-12 15:08:07 |
||||
|
@ 备注: 表单画布 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import FormGroup from './formGroup.vue' |
||||
|
import { FormData,FormList,FormDataStyle } from '@/api/DesignForm/types' |
||||
|
import { getRequest } from '@/api/DesignForm' |
||||
|
import { useRoute, useRouter } from 'vue-router' |
||||
|
import { |
||||
|
constGetControlByName, |
||||
|
constSetFormOptions, |
||||
|
constFormBtnEvent, |
||||
|
constControlChange, |
||||
|
constFormProps, |
||||
|
appendOrRemoveStyle |
||||
|
} from '@/api/DesignForm/utils' |
||||
|
import formatResult from '@/utils/DesignForm/formatResult' |
||||
|
import formChangeValue from '@/utils/DesignForm/formChangeValue' |
||||
|
import { jsonParseStringify } from '@/utils/DesignForm' |
||||
|
|
||||
|
|
||||
|
|
||||
|
const props = withDefaults( |
||||
|
defineProps<{ |
||||
|
formData: FormData |
||||
|
type?: number // 1新增;2修改;3查看(表单模式) ;4查看; 5设计 |
||||
|
disabled?: boolean // 禁用表单提交 |
||||
|
requestUrl?: string // 编辑数据请求url |
||||
|
beforeRequest?: Function // 请求编辑数据前参数处理方法,可对请求参数处理 |
||||
|
afterResponse?: Function | string // 请求数据加载完成后数据处理方法,可对返回数据处理 |
||||
|
addUrl?: string // 表单数据新增提交保存url |
||||
|
editUrl?: string // 表单数据修改保存提交url |
||||
|
beforeSubmit?: Function | string // 表单提交前数据处理,可对提交数据处理,新增和保存都会触发 |
||||
|
afterSubmit?: Function // 表单提交后,默认提示提交结果,可return false阻止提示 |
||||
|
value?: { [key: string]: any } // 表单初始值,同setValue |
||||
|
options?: { [key: string]: any } // 表单组件选项,同setOptions |
||||
|
dict?: object // 固定匹配的字典 |
||||
|
isSearch?: boolean // 列表里作为筛选使用 |
||||
|
}>(), |
||||
|
{ |
||||
|
type: 1, // 1新增;2修改;3查看(表单模式) ;4查看; 5设计 |
||||
|
formData: () => { |
||||
|
return { |
||||
|
list: [], |
||||
|
form: {}, |
||||
|
config: { |
||||
|
style:'' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
dict: () => { |
||||
|
return {} |
||||
|
}, |
||||
|
isSearch: false, |
||||
|
issave:{ |
||||
|
type:Boolean, |
||||
|
default:true |
||||
|
}, |
||||
|
} |
||||
|
) |
||||
|
const emits = defineEmits<{ |
||||
|
(e: 'btnClick', type: string): void |
||||
|
(e: 'change', val: any): void // 表单组件值发生变化时 |
||||
|
(e: 'update:issave', type: boolean): void |
||||
|
}>() |
||||
|
const route = useRoute() |
||||
|
const router = useRouter() |
||||
|
|
||||
|
const loading = ref(false) |
||||
|
|
||||
|
let timer = 0 |
||||
|
let eventName = '' |
||||
|
let getValueEvent = '' |
||||
|
// 注册window事件 |
||||
|
const setWindowEvent = (bool?: boolean) => { |
||||
|
if (props.formData.list.length > 0) { |
||||
|
const formName = props.formData.form?.name |
||||
|
if (!formName) { |
||||
|
// 导出.vue时,name可以没有 |
||||
|
return |
||||
|
} |
||||
|
eventName = `get${formName}ControlByName` |
||||
|
getValueEvent = `get${formName}ValueByName` |
||||
|
if (formName && (!window[eventName as any] || !bool)) { |
||||
|
// 根据name获取当前数据项 |
||||
|
// @ts-ignore |
||||
|
window[eventName] = (name: string) => { |
||||
|
return getNameForEach(props.formData.list, name) |
||||
|
} |
||||
|
// 根据name获取当前项的值 |
||||
|
// @ts-ignore |
||||
|
window[getValueEvent] = (name: string) => { |
||||
|
return model.value[name] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
watch( |
||||
|
() => props.formData, |
||||
|
() => { |
||||
|
console.log("监听数据----->1",props.formData) |
||||
|
emits('update:issave', false) |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
) |
||||
|
watch( |
||||
|
() => props.formData.config, |
||||
|
() => { |
||||
|
if (timer < 2) { |
||||
|
setWindowEvent() // 简单判断下,这里不是每次都更新 |
||||
|
} |
||||
|
timer++ |
||||
|
appendRemoveStyle(true) // 更新样式 |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
) |
||||
|
setWindowEvent() |
||||
|
// 设置全局事件结束 |
||||
|
const resultDict = ref({}) |
||||
|
// 处理表单值开始 |
||||
|
const model = ref<any>({}) |
||||
|
// 获取表单初始model值 |
||||
|
const getInitModel = () => { |
||||
|
const obj = {} |
||||
|
forEachGetFormModel(props.formData.list, obj) |
||||
|
model.value = obj |
||||
|
} |
||||
|
watch( |
||||
|
() => props.formData.list, |
||||
|
() => { |
||||
|
// formData从接口获取时 |
||||
|
getInitModel() |
||||
|
} |
||||
|
) |
||||
|
// 从表单数据里提取表单所需的model |
||||
|
const forEachGetFormModel = (list: FormList[], obj: any) => { |
||||
|
list.forEach((item: any) => { |
||||
|
if (['table', 'flex'].includes(item.type)) { |
||||
|
obj[item.name] = jsonParseStringify(item.tableData) |
||||
|
} else if (['grid', 'tabs'].includes(item.type)) { |
||||
|
item.columns.forEach((col: any) => { |
||||
|
forEachGetFormModel(col.list, obj) |
||||
|
}) |
||||
|
} else if (['card', 'div'].includes(item.type)) { |
||||
|
forEachGetFormModel(item.list, obj) |
||||
|
} else { |
||||
|
const excludeType = ['title', 'divider', 'txt', 'button'] |
||||
|
if (excludeType.indexOf(item.type) === -1) { |
||||
|
obj[item.name] = jsonParseStringify(item.control.modelValue) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 表单组件值改变事件 tProp为子表格相关 |
||||
|
provide(constControlChange, ({ key, value, data, tProp }: any) => { |
||||
|
if (key) { |
||||
|
if (!tProp) { |
||||
|
// 表格和弹性布局不是这里更新,只触change |
||||
|
model.value[key] = value |
||||
|
} |
||||
|
// 支持在线方式数据处理,如A组件值改变时,可自动修改B组件的值,可参考请假流程自动时长计算 |
||||
|
if (props.formData.events?.change) { |
||||
|
model.value = props.formData.events.change(key, model.value) |
||||
|
} |
||||
|
const onFormChange = props.formData.events?.change |
||||
|
if (onFormChange) { |
||||
|
if (typeof onFormChange === 'function') { |
||||
|
model.value = onFormChange(key, model.value) |
||||
|
} else { |
||||
|
model.value = formChangeValue(key, model.value, onFormChange) |
||||
|
} |
||||
|
} |
||||
|
// 当表格和弹性内的字段和外面字段冲突时,可通过tProps区分 |
||||
|
emits('change', { key, value, model: model.value, data, tProp }) |
||||
|
} |
||||
|
}) |
||||
|
const dictForm = computed(() => { |
||||
|
const storage = window.localStorage.getItem('akFormDict') |
||||
|
let storageDict = {} |
||||
|
if (storage) { |
||||
|
storageDict = JSON.parse(storage) |
||||
|
} |
||||
|
// 全局的、当前表单配置的以及接口返回的 |
||||
|
return Object.assign({}, storageDict, props.dict, resultDict.value) |
||||
|
}) |
||||
|
// 表单参数 |
||||
|
const formProps = computed(() => { |
||||
|
return { |
||||
|
model: model.value, |
||||
|
type: props.type, |
||||
|
hideField: props.formData.config?.hideField as [], |
||||
|
showColon: props.formData.form.showColon, |
||||
|
dict: dictForm.value |
||||
|
} |
||||
|
}) |
||||
|
provide(constFormProps, formProps) |
||||
|
|
||||
|
// 提供一个方法,用于根据name从formData.list里查找数据 |
||||
|
const getNameForEach = (data: any, name: string) => { |
||||
|
let temp = {} |
||||
|
for (const key in data) { |
||||
|
const dataKey = data[key] |
||||
|
if (dataKey.name === name) { |
||||
|
return dataKey |
||||
|
} |
||||
|
if (['grid', 'tabs'].includes(dataKey.type)) { |
||||
|
dataKey.columns.forEach((co: any) => { |
||||
|
temp = getNameForEach(co.list, name) |
||||
|
}) |
||||
|
} |
||||
|
if (['card', 'div'].includes(dataKey.type)) { |
||||
|
temp = getNameForEach(dataKey.list, name) |
||||
|
} |
||||
|
} |
||||
|
return temp |
||||
|
} |
||||
|
const getControlByName = (name: string) => { |
||||
|
return getNameForEach(props.formData.list, name) |
||||
|
} |
||||
|
provide(constGetControlByName, getControlByName) |
||||
|
// 表单检验方法 |
||||
|
const ruleForm = ref() |
||||
|
const validate = (callback: any) => { |
||||
|
ruleForm.value.validate((valid: boolean, fields: any) => { |
||||
|
let fieldValue = fields |
||||
|
if (valid) { |
||||
|
// 校验通过,返回当前表单的值 |
||||
|
fieldValue = getValue() |
||||
|
} |
||||
|
callback(valid, fieldValue) |
||||
|
}) |
||||
|
} |
||||
|
// 提供一个取值的方法 |
||||
|
const getValue = (filter?: boolean) => { |
||||
|
if (filter) { |
||||
|
const obj: any = {} |
||||
|
for (const key in model.value) { |
||||
|
// eslint-disable-next-line no-prototype-builtins |
||||
|
if (model.value.hasOwnProperty(key)) { |
||||
|
const val = (model.value as any)[key] |
||||
|
if (!/^\s*$/.test(val)) { |
||||
|
obj[key] = val |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return obj |
||||
|
} else { |
||||
|
return model.value |
||||
|
} |
||||
|
} |
||||
|
// 对表单设置初始值 |
||||
|
const setValue = (obj: { [key: string]: any }, filter?: boolean) => { |
||||
|
// 分两种,false时将obj所有值合并到model,当obj有某些值不存于表单中,也会合并到model,当提交表单也会提交此值 |
||||
|
// true则过滤没用的值,即存在当前表单的才合并 |
||||
|
if (filter) { |
||||
|
for (const key in obj) { |
||||
|
if (model.value[key] !== undefined) { |
||||
|
model.value[key] = obj[key] |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
model.value = Object.assign({}, model.value, jsonParseStringify(obj)) // 防止列表直接使用set方法对弹窗表单编辑,当重置表单时当前行数据被清空 |
||||
|
} |
||||
|
} |
||||
|
// 对表单选择项快速设置 |
||||
|
const setFormOptions = ref({}) |
||||
|
provide(constSetFormOptions, setFormOptions) |
||||
|
const setOptions = (obj: { [key: string]: string[] }) => { |
||||
|
setFormOptions.value = obj |
||||
|
} |
||||
|
// 追加移除style样式 |
||||
|
const appendRemoveStyle = (type?: boolean) => { |
||||
|
try { |
||||
|
const { |
||||
|
config:{ style } |
||||
|
} = props.formData |
||||
|
appendOrRemoveStyle('formStyle', style || '', type) |
||||
|
} catch (e) { |
||||
|
/* empty */ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 按钮组件事件 |
||||
|
provide(constFormBtnEvent, (obj: any) => { |
||||
|
emits('btnClick', obj.key) |
||||
|
if ([3, 4, 5].includes(props.type)) { |
||||
|
return ElMessage.error('当前模式不能提交表单') |
||||
|
} |
||||
|
switch (obj.key) { |
||||
|
case 'submit': |
||||
|
submit() // 提交 |
||||
|
break |
||||
|
case 'reset': |
||||
|
resetFields() // 重置 |
||||
|
break |
||||
|
case 'cancel': // 取消返回, |
||||
|
router.go(-1) //这个刷新后可能会失败 |
||||
|
break |
||||
|
} |
||||
|
}) |
||||
|
// 获取表单数据,编辑时,外部调用 |
||||
|
const getData = (params = {}) => { |
||||
|
const requestUrl = props.formData.config?.requestUrl || props.requestUrl |
||||
|
if (props.type === 5 || !requestUrl || props.isSearch) { |
||||
|
console.error('执行了获取数据方法,但配置有误!') |
||||
|
return |
||||
|
} |
||||
|
loading.value = true |
||||
|
const newParams: any = params |
||||
|
// 同时可使用props或是events里的事件,根据使用使用其中一种即可 |
||||
|
let newParams2 |
||||
|
const beforeRequest = props.formData.events?.beforeRequest |
||||
|
if (typeof beforeRequest === 'function') { |
||||
|
newParams2 = beforeRequest(newParams, route) |
||||
|
} |
||||
|
if (typeof props.beforeRequest === 'function') { |
||||
|
newParams2 = props.beforeRequest(newParams, route) |
||||
|
} |
||||
|
if (newParams2 === false) { |
||||
|
// 停止数据请求 |
||||
|
return |
||||
|
} |
||||
|
getRequest(requestUrl, newParams2 ?? newParams) |
||||
|
.then(res => { |
||||
|
// console.log(res) |
||||
|
loading.value = false |
||||
|
const result = res.data |
||||
|
if (result) { |
||||
|
let formatRes: any = result |
||||
|
// 比较适用通用表单,保存在服务端 |
||||
|
const afterResponse = props.formData.events?.afterResponse |
||||
|
if (typeof afterResponse === 'string' && afterResponse) { |
||||
|
formatRes = formatResult(result, afterResponse) |
||||
|
} else if (typeof afterResponse === 'function') { |
||||
|
formatRes = afterResponse(result) ?? result |
||||
|
} |
||||
|
// 比较适用于导出vue文件 |
||||
|
if (typeof props.afterResponse === 'string' && props.afterResponse) { |
||||
|
formatRes = formatResult(result, props.afterResponse) |
||||
|
} else if (typeof props.afterResponse === 'function') { |
||||
|
formatRes = props.afterResponse(result) ?? result |
||||
|
} |
||||
|
if (formatRes === false) { |
||||
|
return |
||||
|
} |
||||
|
setValue(formatRes.result || formatRes) |
||||
|
nextTick(() => { |
||||
|
// 将dict保存,可用于从接口中设置表单组件options。 |
||||
|
if (formatRes.dict && Object.keys(formatRes.dict).length) { |
||||
|
resultDict.value = formatRes.dict |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
.catch((res: any) => { |
||||
|
loading.value = false |
||||
|
return ElMessage.error(res.message) |
||||
|
}) |
||||
|
} |
||||
|
const submit = (params = {}) => { |
||||
|
const addUrl = props.formData.config?.addUrl || props.addUrl |
||||
|
const editUrl = props.formData.config?.editUrl || props.editUrl |
||||
|
const apiUrl = props.type === 1 ? addUrl : editUrl |
||||
|
if (props.isSearch || !apiUrl || loading.value) { |
||||
|
if (!props.isSearch && !apiUrl) { |
||||
|
console.error( |
||||
|
new Error('请在表单设计处配置接口事件url或选择数据源或设置props') |
||||
|
) |
||||
|
} |
||||
|
// 列表里作为筛选时,不提交表单 |
||||
|
return |
||||
|
} |
||||
|
validate((valid: boolean, fields: any) => { |
||||
|
if (valid) { |
||||
|
const formatParams = Object.assign({}, fields, params) |
||||
|
let submitParams |
||||
|
const beforeSubmit = props.formData.events?.beforeSubmit |
||||
|
if (beforeSubmit) { |
||||
|
if (typeof beforeSubmit === 'function') { |
||||
|
submitParams = beforeSubmit(formatParams, route) |
||||
|
} else { |
||||
|
submitParams = formatResult(formatParams, beforeSubmit) |
||||
|
} |
||||
|
} |
||||
|
if (props.beforeSubmit && typeof props.beforeSubmit === 'string') { |
||||
|
submitParams = formatResult(formatParams, props.beforeSubmit) |
||||
|
} else if (typeof props.beforeSubmit === 'function') { |
||||
|
submitParams = props.beforeSubmit(formatParams, route) |
||||
|
} |
||||
|
if (submitParams === false) { |
||||
|
return |
||||
|
} |
||||
|
loading.value = true |
||||
|
// 提交保存表单 |
||||
|
getRequest(apiUrl, submitParams ?? formatParams) |
||||
|
.then((res: any) => { |
||||
|
afterSubmit('success', res) |
||||
|
}) |
||||
|
.catch(res => { |
||||
|
afterSubmit('fail', res) |
||||
|
}) |
||||
|
} else { |
||||
|
// 没通过校验 |
||||
|
afterSubmit('validate', fields) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
// 不管成功失败,有事件时都需要执行回调 |
||||
|
const afterSubmit = (type: string, res: any) => { |
||||
|
const afterSubmit = props.formData.events?.afterSubmit |
||||
|
let notReturn |
||||
|
if (typeof afterSubmit === 'function') { |
||||
|
notReturn = afterSubmit(type, res) |
||||
|
} else if (typeof props.afterSubmit === 'function') { |
||||
|
notReturn = props.afterSubmit(type, res) |
||||
|
} |
||||
|
loading.value = false |
||||
|
// 不管结果,重置表单,防再次打开时保留上一次的值 |
||||
|
// resetFields() |
||||
|
if (notReturn === false) { |
||||
|
// 有返回false时则不提示 |
||||
|
return |
||||
|
} |
||||
|
if (type === 'success') { |
||||
|
ElMessage.success(res.message || '保存成功!') |
||||
|
} else if (type === 'fail') { |
||||
|
ElMessage.error(res.message || '保存失败!') |
||||
|
} |
||||
|
} |
||||
|
// 表单初始值 |
||||
|
watch( |
||||
|
() => props.value, |
||||
|
(v: any) => { |
||||
|
v && setValue(v) |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true |
||||
|
} |
||||
|
) |
||||
|
// 表单options |
||||
|
watch( |
||||
|
() => props.options, |
||||
|
(v: any) => { |
||||
|
v && setOptions(v) |
||||
|
} |
||||
|
) |
||||
|
// ------------------------数据处理结束------------------------ |
||||
|
// 重置表单方法 |
||||
|
const resetFields = () => { |
||||
|
ruleForm.value.resetFields() |
||||
|
// setValue(Object.assign(model.value, obj || {})) // 这才能清空组件显示的值 |
||||
|
} |
||||
|
onMounted(() => { |
||||
|
getInitModel() |
||||
|
nextTick(() => { |
||||
|
appendRemoveStyle(true) |
||||
|
}) |
||||
|
}) |
||||
|
onUnmounted(() => { |
||||
|
if (eventName) { |
||||
|
// @ts-ignore |
||||
|
window[eventName] = '' |
||||
|
} |
||||
|
appendRemoveStyle() |
||||
|
}) |
||||
|
defineExpose({ |
||||
|
setOptions, |
||||
|
setValue, |
||||
|
getValue, |
||||
|
validate, |
||||
|
resetFields, |
||||
|
getData, |
||||
|
submit |
||||
|
}) |
||||
|
</script> |
||||
|
<template> |
||||
|
<div v-loading="loading"> |
||||
|
<el-form |
||||
|
v-bind="formData.form" |
||||
|
ref="ruleForm" |
||||
|
:model="model as any" |
||||
|
:disabled="disabled || type === 3" |
||||
|
class="add-form" |
||||
|
:class="{ |
||||
|
'design-form': type === 5, |
||||
|
'detail-form': type === 3 || type === 4 |
||||
|
}" |
||||
|
> |
||||
|
<FormGroup :data="formData.list" /> |
||||
|
<slot></slot> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,437 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-07-14 08:46:51 |
||||
|
@ 备注: 表单组 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { computed, watch, inject, onUnmounted } from 'vue' |
||||
|
import Draggable from 'vuedraggable-es' |
||||
|
import FormItem from './formItem.vue' |
||||
|
import ChildTable from './childTable.vue' |
||||
|
import Tooltips from '@/components/DesignForm/tooltip.vue' |
||||
|
import FlexBox from './flexBox.vue' |
||||
|
import { useDesignFormStore } from '@/store/DesignForm/designForm' |
||||
|
import { FormList } from '@/api/DesignForm/types' |
||||
|
import { |
||||
|
constFormBtnEvent, |
||||
|
constFormProps |
||||
|
} from '@/api/DesignForm/utils' |
||||
|
import { Md5 } from 'ts-md5'; |
||||
|
import { jsonParseStringify } from '@/utils/DesignForm' |
||||
|
|
||||
|
const props = withDefaults( |
||||
|
defineProps<{ |
||||
|
data: FormList[] |
||||
|
}>(), |
||||
|
{ |
||||
|
data: () => { |
||||
|
return [] |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
const store = useDesignFormStore() as any |
||||
|
const formProps = inject(constFormProps, {}) as any |
||||
|
const type = computed(() => { |
||||
|
console.log("11111",formProps.value.type) |
||||
|
return formProps.value.type |
||||
|
}) |
||||
|
const state = reactive({ |
||||
|
clone: true, // 允许clone |
||||
|
gridAdd: false |
||||
|
}) |
||||
|
const dataList = ref<any>(props.data) |
||||
|
watch( |
||||
|
() => props.data, |
||||
|
(v: FormList[]) => { |
||||
|
dataList.value = v |
||||
|
} |
||||
|
) |
||||
|
const activeKey = computed(() => { |
||||
|
return store.activeKey |
||||
|
}) |
||||
|
|
||||
|
// 不能嵌套 |
||||
|
const notNested = (type: string) => { |
||||
|
const controlType = ['grid', 'table', 'tabs', 'div', 'flex', 'card'] |
||||
|
return controlType.includes(type) |
||||
|
} |
||||
|
// 删除或复制 |
||||
|
const click = (action: string, index: number, item?: any) => { |
||||
|
if (type.value !== 5) { |
||||
|
return // 非设计模式 |
||||
|
} |
||||
|
if (action === 'clone') { |
||||
|
const key = item.type + new Date().getTime().toString() |
||||
|
const newItem = jsonParseStringify(item) |
||||
|
dataList.value.splice(index, 0, Object.assign(newItem, { name: key })) |
||||
|
} else if (action === 'del') { |
||||
|
dataList.value.splice(index, 1) |
||||
|
// 清空右侧栏信息 |
||||
|
store.setActiveKey('') |
||||
|
store.setControlAttr({}) |
||||
|
} else if (action === 'gridAdd') { |
||||
|
item.columns.push({ |
||||
|
list: [], |
||||
|
attr: { span: 12 } |
||||
|
}) |
||||
|
} else if (action === 'delGridChild') { |
||||
|
item.splice(index, 1) |
||||
|
} |
||||
|
} |
||||
|
const draggableAdd = (evt: any) => { |
||||
|
if (type.value !== 5) { |
||||
|
return // 非设计模式 |
||||
|
} |
||||
|
const newIndex = evt.newIndex |
||||
|
const key = new Date().getTime().toString() |
||||
|
const obj: any = dataList.value[newIndex] |
||||
|
const isNested = evt.target && evt.target.getAttribute('data-type') // 不能嵌套 |
||||
|
if (isNested === 'not-nested' && notNested(obj.type)) { |
||||
|
dataList.value.splice(newIndex, 1) |
||||
|
return |
||||
|
} |
||||
|
if (!obj) { |
||||
|
return |
||||
|
} |
||||
|
const label = obj.label || obj.item.label |
||||
|
delete obj.label |
||||
|
delete obj.icon |
||||
|
let objectItem = {} |
||||
|
// 不需要添加item的项 |
||||
|
const notNeedItem = [ |
||||
|
'txt', |
||||
|
'title', |
||||
|
'button', |
||||
|
'table', |
||||
|
'grid', |
||||
|
'tabs', |
||||
|
'flex', |
||||
|
'div' |
||||
|
] |
||||
|
if (!notNeedItem.includes(obj.type)) { |
||||
|
objectItem = { |
||||
|
item: { |
||||
|
label: label |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// 不需要name的组件 |
||||
|
let nameObj = {} |
||||
|
const notNeedName = [ |
||||
|
'txt', |
||||
|
'title', |
||||
|
'button', |
||||
|
'grid', |
||||
|
'tabs', |
||||
|
'divider', |
||||
|
'div', |
||||
|
'card' |
||||
|
] |
||||
|
if (!notNeedName.includes(obj.type) && !obj.name) { |
||||
|
nameObj = { |
||||
|
name: obj.type + key |
||||
|
} |
||||
|
} |
||||
|
Object.assign(obj, nameObj, objectItem) |
||||
|
groupClick(obj) |
||||
|
} |
||||
|
const getGroupName = (item: any) => { |
||||
|
if (item.name) { |
||||
|
return item.name |
||||
|
} else { |
||||
|
let md5Object:any = new Md5() |
||||
|
md5Object.appendAsciiStr(JSON.stringify(item)) |
||||
|
let md5Str = md5Object.end() |
||||
|
return md5Str |
||||
|
} |
||||
|
} |
||||
|
// 点击激活当前 |
||||
|
const groupClick = (item: any, ele?: string) => { |
||||
|
// 设计模式下才执行 |
||||
|
if (type.value !== 5) { |
||||
|
return |
||||
|
} |
||||
|
if (ele) { |
||||
|
item.type = ele |
||||
|
} |
||||
|
store.setActiveKey(getGroupName(item)) |
||||
|
store.setControlAttr(item) |
||||
|
// grid时显示添加列按钮 |
||||
|
state.gridAdd = item.type === 'grid' |
||||
|
state.clone = !notNested(item.type) |
||||
|
} |
||||
|
// 返回栅格宽度 |
||||
|
const getFormItemStyle = (ele: FormList) => { |
||||
|
if (ele.config?.span === 0) { |
||||
|
return { width: 'auto', margin: '0 5px' } |
||||
|
} |
||||
|
if (ele.config && ele.config.span) { |
||||
|
return { width: (ele.config.span / 24) * 100 + '%' } |
||||
|
} |
||||
|
} |
||||
|
// 根据关联条件显示隐藏当前项 |
||||
|
const linksShow = (el: FormList, index: number) => { |
||||
|
// 当前项设置了关联条件,当关联主体的值等于当前组件设定的值时 |
||||
|
if (!el.config) { |
||||
|
return true |
||||
|
} |
||||
|
const key = el.config.linkKey |
||||
|
const value = el.config.linkValue |
||||
|
const linkResult = el.config.linkResult |
||||
|
if (key && value && type.value !== 5) { |
||||
|
const Fn = new Function('$', `return (${value})`) |
||||
|
const pass = Fn(formProps.value.model) |
||||
|
if (linkResult === 'disabled') { |
||||
|
// 设置为disabled后返回显示状态 |
||||
|
dataList.value[index].control.disabled = pass |
||||
|
return true |
||||
|
} else { |
||||
|
return pass |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
// 根据表单设置不显示指定字段 |
||||
|
const linksIf = (obj: FormList) => { |
||||
|
const { type } = formProps.value |
||||
|
const { config: { disabledAdd, disabledEdit, disabledDetail } = {} } = obj |
||||
|
if (type === 1) { |
||||
|
if (disabledAdd) { |
||||
|
// 编辑页 || 新增页 |
||||
|
return false // 不显示 |
||||
|
} |
||||
|
} else if (type === 2) { |
||||
|
// 编辑 |
||||
|
if (disabledEdit) { |
||||
|
return false |
||||
|
} |
||||
|
} else if (type === 4 || type === 3) { |
||||
|
// 查看 |
||||
|
if (disabledDetail) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
// 如果当前字段的name值存在于表单数据的vIf中,则不显示 |
||||
|
const vIf: string | string[] = formProps.value.hideField |
||||
|
if (vIf?.length > 0 && obj.name) { |
||||
|
return vIf.indexOf(obj.name) === -1 // 存在时返回false隐藏 |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
//按钮点击事件 |
||||
|
const injectBtnEvent:any = inject(constFormBtnEvent) |
||||
|
const clickBtn = (control: any) => { |
||||
|
// 0: '提交表单', |
||||
|
// 1: '重置表单', |
||||
|
// 2: '取消返回', |
||||
|
// 3: '无动作(自定义)' |
||||
|
if (type.value !== 5) { |
||||
|
// 非设计模式才触发事件 |
||||
|
injectBtnEvent && injectBtnEvent(control) |
||||
|
} |
||||
|
} |
||||
|
onUnmounted(() => { |
||||
|
// console.log('formGroup onUnmounted') |
||||
|
dataList.value = {} |
||||
|
store.setActiveKey('') |
||||
|
store.setControlAttr({}) |
||||
|
}) |
||||
|
</script> |
||||
|
<template> |
||||
|
<draggable |
||||
|
itemKey="id" |
||||
|
:list="dataList" |
||||
|
name="fade" |
||||
|
class="drag" |
||||
|
v-bind="{ |
||||
|
group: 'form', |
||||
|
ghostClass: 'ghost', |
||||
|
animation: 200, |
||||
|
handle: '.drag-move', |
||||
|
disabled: type !== 5 |
||||
|
}" |
||||
|
@add="draggableAdd" |
||||
|
> |
||||
|
<template #item="{ element, index }"> |
||||
|
<div |
||||
|
class="group" |
||||
|
:class="{ |
||||
|
['group-' + element.type]: true, |
||||
|
active: activeKey === getGroupName(element) |
||||
|
}" |
||||
|
:style="getFormItemStyle(element)" |
||||
|
@click.stop="groupClick(element)" |
||||
|
v-show="linksShow(element, index)" |
||||
|
v-if="linksIf(element)" |
||||
|
> |
||||
|
<template v-if="element.type === 'tabs'"> |
||||
|
<div class="form-tabs"> |
||||
|
<el-tabs |
||||
|
v-bind="element.control" |
||||
|
:class="[element.config?.className]" |
||||
|
> |
||||
|
<el-tab-pane |
||||
|
v-for="(item, tIndex) in element.columns" |
||||
|
:label="item.label" |
||||
|
:key="tIndex" |
||||
|
> |
||||
|
<form-group :data="item.list" data-type="not-nested" /> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'title'"> |
||||
|
<div |
||||
|
class="title" |
||||
|
:class="[element.config.className]" |
||||
|
v-bind="element.control" |
||||
|
> |
||||
|
<span v-html="element.control.modelValue"></span> |
||||
|
<Tooltips |
||||
|
:content="element.config.help" |
||||
|
v-if="element.config.help" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'txt'"> |
||||
|
<div |
||||
|
v-bind="element.control" |
||||
|
:class="[element.config.className]" |
||||
|
v-html="element.control.modelValue" |
||||
|
></div> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'table'"> |
||||
|
<div class="form-table" v-if="type === 5"> |
||||
|
<form-group :data="element.list" data-type="not-nested" /> |
||||
|
</div> |
||||
|
<child-table v-else :data="element" /> |
||||
|
</template> |
||||
|
|
||||
|
|
||||
|
<template v-else-if="element.type === 'grid'"> |
||||
|
<el-row class="form-row" :class="[element.className]"> |
||||
|
<el-col |
||||
|
class="form-col" |
||||
|
:class="{ |
||||
|
'active-col': activeKey === getGroupName(col), |
||||
|
[col.className]: col.className |
||||
|
}" |
||||
|
v-bind="col.attr" |
||||
|
v-for="(col, i) in element.columns" |
||||
|
:key="i" |
||||
|
@click.stop="groupClick(col, 'gridChild')" |
||||
|
> |
||||
|
<form-group :data="col.list" data-type="not-nested" /> |
||||
|
<div class="drag-control" v-if="type === 5"> |
||||
|
<div class="item-control"> |
||||
|
<i |
||||
|
class="icon-del" |
||||
|
@click.stop=" |
||||
|
click('delGridChild', i as number, element.columns) |
||||
|
" |
||||
|
></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'card'"> |
||||
|
<el-collapse model-value="1"> |
||||
|
<el-collapse-item :title="element.item.label" name="1"> |
||||
|
<template #title v-if="element.help"> |
||||
|
{{ element.item.label }} |
||||
|
<Tooltips :content="element.help" /> |
||||
|
</template> |
||||
|
<form-group :data="element.list" data-type="not-nested" /> |
||||
|
</el-collapse-item> |
||||
|
</el-collapse> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'divider'"> |
||||
|
<el-divider v-bind="element.control" |
||||
|
>{{ element.item && element.item.label }} |
||||
|
</el-divider> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'div'"> |
||||
|
<div |
||||
|
class="div-layout" |
||||
|
v-bind="element.control" |
||||
|
:class="{ |
||||
|
[element.className]: element.className, |
||||
|
inline: element.config?.inline, |
||||
|
[element.config?.textAlign]: element.config?.textAlign |
||||
|
}" |
||||
|
> |
||||
|
<form-group :data="element.list" data-type="not-nested" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'flex'"> |
||||
|
<form-group |
||||
|
:data="element.list" |
||||
|
data-type="not-nested" |
||||
|
v-if="type === 5" |
||||
|
/> |
||||
|
<flex-box :data="element" v-else /> |
||||
|
<el-button |
||||
|
style="position: relative; top: -28px; left: 10px" |
||||
|
v-if="element.config.addBtnText && type === 5" |
||||
|
size="small" |
||||
|
>{{ element.config.addBtnText }}</el-button |
||||
|
> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'button'"> |
||||
|
<div |
||||
|
:class="[element.config?.className]" |
||||
|
:style="{ 'text-align': element.config?.textAlign }" |
||||
|
> |
||||
|
<el-button |
||||
|
v-bind="element.control" |
||||
|
@click="clickBtn(element.control)" |
||||
|
>{{ element.control?.label }}</el-button |
||||
|
> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<template v-else-if="element.type === 'inputSlot' && type !== 5"> |
||||
|
<!-- 除设计外其他无需处理--> |
||||
|
</template> |
||||
|
|
||||
|
<FormItem v-else :data="element" /> |
||||
|
|
||||
|
<template v-if="type === 5"> |
||||
|
<div class="drag-control"> |
||||
|
<div class="item-control"> |
||||
|
<i |
||||
|
class="icon-plus" |
||||
|
@click.stop="click('gridAdd', index, element)" |
||||
|
v-if="state.gridAdd" |
||||
|
title="添加列" |
||||
|
></i> |
||||
|
<i |
||||
|
class="icon-clone" |
||||
|
@click.stop="click('clone', index, element)" |
||||
|
v-if="state.clone" |
||||
|
title="克隆" |
||||
|
></i> |
||||
|
<i class="icon-del" @click.stop="click('del', index)"></i> |
||||
|
</div> |
||||
|
<div class="drag-move icon-move"></div> |
||||
|
</div> |
||||
|
<div class="tooltip" style="display: none;">{{ element.name }}</div> |
||||
|
</template> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
</template> |
||||
|
</draggable> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,203 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-07-14 16:38:32 |
||||
|
@ 备注: |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { onMounted, watch, ref, computed, onUnmounted } from 'vue' |
||||
|
import { ElMessage } from 'element-plus' |
||||
|
import { getRequest } from '@/api/DesignForm' |
||||
|
const props = withDefaults( |
||||
|
defineProps<{ |
||||
|
modelValue: string |
||||
|
placeholder?: string |
||||
|
width?: string |
||||
|
height?: string |
||||
|
blobUrl?: string |
||||
|
imgUrl?: string |
||||
|
config?: any |
||||
|
}>(), |
||||
|
{ |
||||
|
modelValue: '', |
||||
|
placeholder: '请输入内容', |
||||
|
width: '100%', |
||||
|
height: '300px', |
||||
|
blobUrl: '', |
||||
|
imgUrl: '', |
||||
|
config: () => { |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
const emits = defineEmits<{ |
||||
|
(e: 'update:modelValue', value: string): void |
||||
|
}>() |
||||
|
const contentValue = ref(props.modelValue) |
||||
|
// 参数自定义初始化 |
||||
|
// eslint-disable-next-line max-len |
||||
|
const buttonPlugins ='preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media code codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount autosave ' |
||||
|
// 导入工具栏 |
||||
|
// eslint-disable-next-line max-len |
||||
|
const toolbar ='fullscreen undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor table image | alignleft aligncenter alignright alignjustify outdent indent | styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | media charmap emoticons pagebreak insertdatetime print preview | code selectall searchreplace visualblocks | indent2em lineheight formatpainter axupimgs' |
||||
|
const toolbarSimple ='undo cut copy paste pastetext |forecolor backcolor bold italic underline strikethrough|alignleft aligncenter alignright alignjustify|' |
||||
|
const commInit = { |
||||
|
selector: '#myTextarea', |
||||
|
cleanup: true, |
||||
|
language: 'zh_CN', // 语言类型 |
||||
|
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px', // 字体大小 |
||||
|
lineheight_formats: '0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5', // 行高配置,也可配置成"12px 14px 16px 20px"这种形式 |
||||
|
branding: false, // tiny技术支持信息是否显示 |
||||
|
resize: false, // 编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号 |
||||
|
// statusbar: false, //最下方的元素路径和字数统计那一栏是否显示 |
||||
|
elementpath: false, // 元素路径是否显示 |
||||
|
height: props.height, // 允许外界传进来高度和placeholder |
||||
|
width: props.width, |
||||
|
placeholder: props.placeholder, |
||||
|
init_instance_callback: (editor: any) => { |
||||
|
// editor.setContent('') |
||||
|
editor.on('NodeChange Change KeyUp SetContent', () => { |
||||
|
contentValue.value = editor.getContent() |
||||
|
emits('update:modelValue', editor.getContent()) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
// 图片上传 |
||||
|
const imgUploadFn = (blobInfo: any, progress: number) => |
||||
|
new Promise((resolve, reject) => { |
||||
|
// https://www.tiny.cloud/docs/tinymce/6/file-image-upload/#images_upload_handler |
||||
|
console.log("图片上传",progress) |
||||
|
const params = new FormData() |
||||
|
params.append('file', blobInfo.blob()) |
||||
|
let options = {} |
||||
|
if (props.imgUrl) { |
||||
|
options = { |
||||
|
url: props.imgUrl |
||||
|
} |
||||
|
} |
||||
|
getRequest('upload', params, options) |
||||
|
.then(res => { |
||||
|
// console.log(res) |
||||
|
// console.log(res.data.path) |
||||
|
if (res.data.code === 1) { |
||||
|
resolve(res.data.path) // 上传成功,在成功函数里填入图片路径 |
||||
|
// console.log('[文件上传]', res.data) |
||||
|
} else { |
||||
|
reject('上传失败') |
||||
|
} |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
reject('上传出错,示例暂不提供上传接口') |
||||
|
}) |
||||
|
}) |
||||
|
const fileUpload = (callback: any, value: string, meta: any) => { |
||||
|
const filetype ='.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4' |
||||
|
//后端接收上传文件的地址 |
||||
|
const input = document.createElement('input') //创建文件选择 |
||||
|
input.setAttribute('type', 'file') |
||||
|
input.setAttribute('accept', filetype) |
||||
|
input.click() |
||||
|
input.onchange = () => { |
||||
|
const file = input?.files && input.files[0] //获取文件信息 |
||||
|
// console.log(file) |
||||
|
// meta对应于file_picker_types的三种类型 |
||||
|
let attr = {} |
||||
|
if (meta.filetype === 'file') { |
||||
|
attr = { text: file?.name } |
||||
|
} |
||||
|
if (meta.filetype === 'image') { |
||||
|
attr = { alt: file?.name } |
||||
|
} |
||||
|
if (meta.filetype === 'media') { |
||||
|
// attr={source2: 'alt.ogg', poster: 'image.jpg'} |
||||
|
} |
||||
|
/*if(file.type.slice(0,5)=='image'&&file.size/1024/1024>2){ |
||||
|
alert("上传失败,图片大小请控制在2M以内") |
||||
|
}else if(file.type.slice(0,5)=='video'&&file.size/1024/1024>500){ |
||||
|
alert("上传失败,视频大小请控制在 500M 以内") |
||||
|
}else if(file.size/1024/1024>10){ |
||||
|
alert("上传失败,文件大小请控制在 10M 以内") |
||||
|
}*/ |
||||
|
const params = new FormData() |
||||
|
params.append('file', file as any) |
||||
|
let options = {} |
||||
|
if (props.blobUrl) { |
||||
|
options = { |
||||
|
url: props.blobUrl |
||||
|
} |
||||
|
} |
||||
|
getRequest('upload', params, options) |
||||
|
.then(res => { |
||||
|
if (res.data.code === 1) { |
||||
|
callback(res.data.path, attr) // 上传成功,在成功函数里填入图片路径 |
||||
|
} else { |
||||
|
ElMessage.error(res.data?.message) |
||||
|
} |
||||
|
}) |
||||
|
.catch(res => { |
||||
|
ElMessage.error(res.data?.message) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
const defaultInit = { |
||||
|
plugins: buttonPlugins, // 插件配置 |
||||
|
toolbar: toolbar, // 工具栏配置,设为false则隐藏 |
||||
|
menubar: true, // 菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单” |
||||
|
//emoticons_database_url: './tinymce/emoticons/js/emojis.js', |
||||
|
// eslint-disable-next-line max-len |
||||
|
font_formats:'微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;', // 字体样式 微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif, 宋体=simsun,serif,仿宋体=FangSong,黑体=SimHei,Arial=arial, |
||||
|
// content_style: 'p {margin-block-start: 0; margin-block-end: 0; color: #606D81; font-size: 14px;}; table { border: 1px}', // 直接自定义可编辑区域的css样式 |
||||
|
content_css: false, // 以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入 |
||||
|
paste_data_images: true, // 图片是否可粘贴 |
||||
|
// 允许外界传进来高度和placeholder |
||||
|
// 粘贴图片 自动处理 base64 |
||||
|
urlconverter_callback: (url: string, node: string) => { |
||||
|
if (node === 'img' && url.startsWith('blob:')) { |
||||
|
// @ts-ignore |
||||
|
tinymce.activeEditor && tinymce.activeEditor.uploadImages() |
||||
|
} |
||||
|
return url |
||||
|
}, |
||||
|
// 图片上传 |
||||
|
images_upload_handler: imgUploadFn, |
||||
|
file_picker_types: 'file image media', //分别对应三个类型文件的上传:link插件,image和axupimgs插件,media插件。想屏蔽某个插件的上传就去掉对应的参数 |
||||
|
file_picker_callback: fileUpload |
||||
|
} |
||||
|
const simpleInit = { |
||||
|
plugins: '', // 插件配置 |
||||
|
toolbar: toolbarSimple, // 工具栏配置,设为false则隐藏 |
||||
|
menubar: false, // 菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 |
||||
|
font_formats: '', |
||||
|
paste_data_images: false // 图片是否可粘贴 |
||||
|
} |
||||
|
const myInit = computed(() => { |
||||
|
const styleType = |
||||
|
props.config?.style === 'simple' ? simpleInit : defaultInit |
||||
|
return Object.assign(commInit, styleType) |
||||
|
}) |
||||
|
onMounted(() => { |
||||
|
// @ts-ignore |
||||
|
tinymce.init(myInit.value) |
||||
|
}) |
||||
|
onUnmounted(() => { |
||||
|
// @ts-ignore |
||||
|
tinymce.remove() |
||||
|
}) |
||||
|
// 侦听默认值 外界第一次传进来一个 v-model 就赋值给 contentValue |
||||
|
watch( |
||||
|
() => props.modelValue, |
||||
|
(n: any) => { |
||||
|
if (n && n !== contentValue.value) { |
||||
|
contentValue.value = n |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
</script> |
||||
|
<template> |
||||
|
<textarea id="myTextarea" v-model="contentValue"></textarea> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
.tox-tinymce-aux { |
||||
|
z-index: 99999 !important; /*el-dialog层为2014,默认时在el弹出层显示不了编辑器里的弹窗*/ |
||||
|
z-index: 99999; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,84 @@ |
|||||
|
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; |
||||
|
import { useUserStoreHook } from '@/store/modules/user'; |
||||
|
|
||||
|
// 创建 axios 实例
|
||||
|
const service = axios.create({ |
||||
|
baseURL: import.meta.env.VITE_APP_BASE_API, |
||||
|
timeout: 50000, |
||||
|
}); |
||||
|
|
||||
|
// 请求拦截器
|
||||
|
service.interceptors.request.use( |
||||
|
(config: InternalAxiosRequestConfig) => { |
||||
|
const userStore = useUserStoreHook(); |
||||
|
if (userStore.tokenIng) { |
||||
|
config.headers.Authorization = userStore.tokenIng; |
||||
|
} |
||||
|
if (userStore.userKey) { |
||||
|
config.headers["user-key"] = userStore.userKey; |
||||
|
} |
||||
|
if (userStore.userToken) { |
||||
|
config.headers["user-token"] = userStore.userToken; |
||||
|
} |
||||
|
return config; |
||||
|
}, |
||||
|
(error: any) => { |
||||
|
return Promise.reject(error); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 响应拦截器
|
||||
|
service.interceptors.response.use( |
||||
|
(response: AxiosResponse) => { |
||||
|
const { code, msg } = response.data; |
||||
|
if (code === 0) { |
||||
|
return response.data; |
||||
|
} |
||||
|
if (code === 7 || code === 300 || code === 301 || code === 302){ |
||||
|
ElMessageBox.confirm("身份令牌已失效!请重新登录!", "提示", { |
||||
|
confirmButtonText: "确定", |
||||
|
type: "warning", |
||||
|
}).then(() => { |
||||
|
localStorage.clear(); |
||||
|
window.location.href = "/"; |
||||
|
}); |
||||
|
return response.data; |
||||
|
} |
||||
|
// 响应数据为二进制流处理(Excel导出)
|
||||
|
if (response.data instanceof ArrayBuffer) { |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
ElMessage.error(msg || '系统出错'); |
||||
|
return Promise.reject(new Error(msg || 'Error')); |
||||
|
}, |
||||
|
(error: any) => { |
||||
|
if (error.response.data) { |
||||
|
const { code, msg } = error.response.data; |
||||
|
// token 过期,重新登录
|
||||
|
if (code === 'A0230') { |
||||
|
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { |
||||
|
confirmButtonText: '确定', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
localStorage.clear(); |
||||
|
window.location.href = '/'; |
||||
|
}); |
||||
|
}else if(code === 7 || code === 300 || code === 301 || code === 302){ |
||||
|
ElMessageBox.confirm("身份令牌已失效!请重新登录!", "提示", { |
||||
|
confirmButtonText: "确定", |
||||
|
type: "warning", |
||||
|
}).then(() => { |
||||
|
localStorage.clear(); |
||||
|
window.location.href = "/"; |
||||
|
}); |
||||
|
} else { |
||||
|
ElMessage.error(msg || '系统出错'); |
||||
|
} |
||||
|
} |
||||
|
return Promise.reject(error.message); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 导出 axios 实例
|
||||
|
export default service; |
||||
@ -0,0 +1,230 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-09-13 10:43:12 |
||||
|
@ 备注: 数据结构表单 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { publicFormTableStruct,formTabelStruct } from "@/api/DesignForm/type"; |
||||
|
import { callBackFormTableVersion,haveFormTabelcont,optimizeOrRepairFormTable,haveFormTablelist } from '@/api/DesignForm/requestapi' |
||||
|
//引入页面 |
||||
|
import SetupField from '@/views/sysworkflow/codepage/setupfield.vue' |
||||
|
|
||||
|
|
||||
|
import { |
||||
|
Ticket, |
||||
|
Finished |
||||
|
} from '@element-plus/icons-vue' |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
isshow:{ |
||||
|
type:Boolean, |
||||
|
default:true |
||||
|
}, |
||||
|
formcont:{ |
||||
|
type:Object, |
||||
|
default(){ |
||||
|
return {} |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
const emits = defineEmits(["update:isshow"]); |
||||
|
const isShow = computed({ |
||||
|
get: () => props.isshow, |
||||
|
set: (val) => { |
||||
|
emits("update:isshow", val); |
||||
|
}, |
||||
|
}); |
||||
|
//字段列表 |
||||
|
const formTableFieldList = ref<formTabelStruct[]>() |
||||
|
const versionIndex = ref("") //当前版本 |
||||
|
const formTableIndex = ref("") //当前表单 |
||||
|
const versionAry = ref<publicFormTableStruct[]>([]) //版本列表 |
||||
|
const formTableAry = ref<publicFormTableStruct[]>([]) //表单列表 |
||||
|
const isEditForm = ref(false) |
||||
|
const formTableCont = ref<publicFormTableStruct>() //表单内容 |
||||
|
const isEditFormField = ref(false) |
||||
|
const tableLoading = ref(false) |
||||
|
//选择版本 |
||||
|
const clickVersion = (val:any) =>{ |
||||
|
// console.log("切换版本",val,versionIndex.value) |
||||
|
formTableAry.value.slice(0,formTableAry.value.length) |
||||
|
// console.log("舒适化",formTableAry.value) |
||||
|
haveFormTablelist({id:val.toString()}) |
||||
|
.then(({data})=>{ |
||||
|
// console.log("选择版本",data,formTableAry.value) |
||||
|
formTableAry.value = data |
||||
|
if(data.length > 0){ |
||||
|
data.forEach( item =>{ |
||||
|
if(item.ismain){ |
||||
|
formTableIndex.value = item.name |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
.finally(()=>{ |
||||
|
getTableFieldList(versionIndex.value,formTableIndex.value) |
||||
|
}) |
||||
|
} |
||||
|
//选择表单 |
||||
|
const clickFormTable = (val:any) =>{ |
||||
|
// console.log("切换表单",val,formTableIndex.value) |
||||
|
getTableFieldList(versionIndex.value,val) |
||||
|
} |
||||
|
//获取版本及表列表 |
||||
|
const getVersionFormTable = () =>{ |
||||
|
callBackFormTableVersion({id:props.formcont.id}) |
||||
|
.then(({ data }) =>{ |
||||
|
// console.log("获取版本及表列表--->",data) |
||||
|
versionAry.value = data.version |
||||
|
formTableAry.value = data.tablelist |
||||
|
if(data.version){ |
||||
|
if(data.version.length > 0){ |
||||
|
data.version.forEach( item =>{ |
||||
|
if(item.ismain){ |
||||
|
versionIndex.value = item.versionid |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
if(data.tablelist){ |
||||
|
if(data.tablelist.length > 0){ |
||||
|
data.tablelist.forEach( item =>{ |
||||
|
if(item.ismain){ |
||||
|
formTableIndex.value = item.name |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
.finally(() =>{ |
||||
|
// console.log("初始化数据结构-->",formTableIndex.value,versionIndex.value) |
||||
|
getTableFieldList(versionIndex.value,formTableIndex.value) |
||||
|
}); |
||||
|
} |
||||
|
function getTableFieldList(id:string,name:string){ |
||||
|
tableLoading.value = true; |
||||
|
haveFormTabelcont({"id":id, "name":name}) |
||||
|
.then(({data}) =>{ |
||||
|
// console.log("初始化数据结构-->",data) |
||||
|
formTableFieldList.value = data.filedlist |
||||
|
isEditForm.value = data.isedit |
||||
|
}) |
||||
|
.finally(()=>{ |
||||
|
tableLoading.value = false; |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
//监听 |
||||
|
watch(()=>props.isshow,()=>{ |
||||
|
if(props.isshow){ |
||||
|
getVersionFormTable() |
||||
|
} |
||||
|
}) |
||||
|
//编辑字段 |
||||
|
const editCustomerFormField = (val:any) =>{ |
||||
|
// optimizeOrRepairFormTable() |
||||
|
formTableCont.value = val |
||||
|
isEditFormField.value = true |
||||
|
} |
||||
|
//优化修复表单 |
||||
|
const optimizeOrRepairTable = (val:number) =>{ |
||||
|
optimizeOrRepairFormTable({name:formTableIndex.value,optimizerender:val}) |
||||
|
.then((data:any)=>{ |
||||
|
// console.log("表单处理",data,formTableIndex.value,val) |
||||
|
ElMessage.success(data.msg || '加载异常') |
||||
|
}) |
||||
|
} |
||||
|
//刷新表单 |
||||
|
const refreshTable = () =>{ |
||||
|
getTableFieldList(versionIndex.value,formTableIndex.value) |
||||
|
} |
||||
|
</script> |
||||
|
<template> |
||||
|
<el-dialog v-model="isShow" :title="'<'+props.formcont.name+'>数据结构'" width="70%" draggable> |
||||
|
<div class="common-layout"> |
||||
|
<el-container> |
||||
|
<el-main style="padding: 0;"> |
||||
|
<!-- 表结构 --> |
||||
|
<el-tabs v-model="formTableIndex" class="form_version" style="height: 40px;" @tab-change="clickFormTable"> |
||||
|
<el-tab-pane v-for="item in formTableAry" :key="item.name" :name="item.name" > |
||||
|
<template #label> |
||||
|
<el-icon v-if="item.ismain"><Management /></el-icon> |
||||
|
<el-icon v-else><Memo /></el-icon> |
||||
|
{{ item.name }} |
||||
|
</template> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
<el-table v-loading="tableLoading" :data="formTableFieldList" border height="460" style="width: 100%;"> |
||||
|
<el-table-column fixed prop="field" label="字段" width="120" /> |
||||
|
<el-table-column prop="type" label="类型" width="120" /> |
||||
|
<el-table-column prop="collation" label="排序规则" width="180" /> |
||||
|
<el-table-column prop="attribute" label="属性" width="100" /> |
||||
|
<el-table-column prop="null" label="空" align="center" /> |
||||
|
<el-table-column prop="default" label="默认值" align="center" /> |
||||
|
<el-table-column prop="extra" label="补充信息" width="200" /> |
||||
|
<el-table-column fixed="right" prop="comment" label="备注" width="200" /> |
||||
|
<el-table-column fixed="right" v-if="isEditForm" label="操作" align="center"> |
||||
|
<template #default="scope"> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
link |
||||
|
size="small" |
||||
|
@click.stop="editCustomerFormField(scope.row)" |
||||
|
> |
||||
|
<i-ep-edit />编辑 |
||||
|
</el-button> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
|
||||
|
<el-row style="margin-top: 10px;margin-bottom: 10px;"> |
||||
|
<el-col :span="24"> |
||||
|
<el-button :icon="Finished" type="primary" @click.stop="optimizeOrRepairTable(1)">优化表单</el-button> |
||||
|
<el-button :icon="Ticket" type="warning" @click.stop="optimizeOrRepairTable(2)">修复表单</el-button> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
</el-main> |
||||
|
<el-aside width="100px"> |
||||
|
<!-- 版本 --> |
||||
|
<el-tabs v-model="versionIndex" tab-position="right" style="height: 500px;" @tab-change="clickVersion"> |
||||
|
<el-tab-pane v-for="item in versionAry" :key="item.versionid" :label="item.name" :name="item.versionid"> |
||||
|
<template #label> |
||||
|
<div class="version_field" title="ConfigConfigConfig"> |
||||
|
{{ item.name }} |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</el-aside> |
||||
|
</el-container> |
||||
|
</div> |
||||
|
<SetupField v-model:isEditFormField="isEditFormField" :formtablecont="formTableCont" :formname="formTableIndex" @refreshtable="refreshTable" /> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
<style lang='scss'> |
||||
|
.form_version{ |
||||
|
height: 500px; |
||||
|
|
||||
|
} |
||||
|
.form_table{ |
||||
|
height: 500px; |
||||
|
width: 100%; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
.form_version > .el-tabs__content{ |
||||
|
padding: 0; |
||||
|
} |
||||
|
.version_field{ |
||||
|
width:60px; |
||||
|
|
||||
|
// white-space: normal; |
||||
|
// word-break: break-word; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
.but_clic{ |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,237 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-09-14 09:47:27 |
||||
|
@ 备注: 编辑表单字段 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { gogoBackFormTabelStruct } from "@/api/DesignForm/type"; |
||||
|
import { formFieldHandle,editFormField } from '@/api/DesignForm/requestapi' |
||||
|
const fieldType = ["tinyint","smallint","mediumint","int","integer","bigint","decimal", "float", "double","char","varchar","binary","varbinary","enum","set","text","tinytext","mediumtext","longtext","blob","year","date","time","datetime","timestamp"]; |
||||
|
const props = defineProps({ |
||||
|
isEditFormField:{ |
||||
|
type:Boolean, |
||||
|
default:true |
||||
|
}, |
||||
|
formname:{ |
||||
|
type:String, |
||||
|
default:"" |
||||
|
}, |
||||
|
formtablecont:{ |
||||
|
type:Object, |
||||
|
default(){ |
||||
|
return {} |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const unsignedIsShow = ref(false); //符号 |
||||
|
const decimalIsShow = ref(false); //小数点 |
||||
|
const fieldLenghtIsShow = ref(false); //字段长度 |
||||
|
const isNull = ref(false); //是否显示空置 |
||||
|
const butLoad = ref(false); |
||||
|
|
||||
|
const formTableField = reactive<gogoBackFormTabelStruct>({ |
||||
|
formname:props.formname, |
||||
|
field:"", |
||||
|
type:"", |
||||
|
attribute:"", |
||||
|
collation:"", |
||||
|
null:"", |
||||
|
key:"", |
||||
|
default:"", |
||||
|
extra:"", |
||||
|
privileges:"", |
||||
|
comment:"", |
||||
|
integer:"", |
||||
|
decimal:"", |
||||
|
}) |
||||
|
const dialogTitle = ref<string>() |
||||
|
const isEditOk = ref(false) |
||||
|
|
||||
|
const emits = defineEmits(["update:isEditFormField","refreshtable","xxxxx"]); |
||||
|
const isShow = computed({ |
||||
|
get: () => props.isEditFormField, |
||||
|
set: (val) => { |
||||
|
emits("update:isEditFormField", val); |
||||
|
emits("refreshtable"); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
watch(()=>props.isEditFormField,()=>{ |
||||
|
// console.log("监听状态----->",props.isEditFormField,props.formtablecont) |
||||
|
if(props.isEditFormField){ |
||||
|
if(props.formtablecont.comment != null && props.formtablecont.comment != ""){ |
||||
|
dialogTitle.value = "编辑<"+props.formtablecont.field+">字段(PS:"+props.formtablecont.comment+")" |
||||
|
}else{ |
||||
|
dialogTitle.value = "编辑<"+props.formtablecont.field+">字段" |
||||
|
} |
||||
|
formFieldHandle(props.formtablecont) |
||||
|
.then(({data})=>{ |
||||
|
// console.log("监听状态---1-->",data) |
||||
|
formTableField.formname=props.formname |
||||
|
formTableField.field = data.field; |
||||
|
formTableField.type = data.type; |
||||
|
formTableField.attribute = data.attribute; |
||||
|
formTableField.collation = data.collation; |
||||
|
formTableField.null = data.null; |
||||
|
formTableField.key = data.key; |
||||
|
formTableField.default = data.default; |
||||
|
formTableField.extra = data.extra; |
||||
|
formTableField.privileges = data.privileges; |
||||
|
formTableField.comment = data.comment; |
||||
|
formTableField.integer = data.integer; |
||||
|
formTableField.decimal = data.decimal; |
||||
|
if(["tinyint","smallint","mediumint","int","integer","bigint","decimal", "float", "double"].indexOf(data.type) != -1){ |
||||
|
unsignedIsShow.value = true |
||||
|
}else{ |
||||
|
unsignedIsShow.value = false |
||||
|
} |
||||
|
if(["decimal", "float", "double"].indexOf(data.type) != -1){ |
||||
|
decimalIsShow.value = true |
||||
|
}else{ |
||||
|
decimalIsShow.value = false |
||||
|
} |
||||
|
if(["enum","set","text","tinytext","mediumtext","longtext","blob","year","date","time","datetime","timestamp"].indexOf(data.type) != -1){ |
||||
|
fieldLenghtIsShow.value = false |
||||
|
}else{ |
||||
|
fieldLenghtIsShow.value = true |
||||
|
} |
||||
|
}) |
||||
|
// formTableField.value = props.formtablecont |
||||
|
}else{ |
||||
|
if(isEditOk.value){ |
||||
|
emits("refreshtable") |
||||
|
} |
||||
|
initData() |
||||
|
// formTableField.value=new Value(props.formtablecont) |
||||
|
} |
||||
|
}) |
||||
|
//初始化数据 |
||||
|
function initData(){ |
||||
|
formTableField.formname=""; |
||||
|
formTableField.field = ""; //field; |
||||
|
formTableField.type = ""; //type; |
||||
|
formTableField.attribute = ""; //attribute; |
||||
|
formTableField.collation = ""; //collation; |
||||
|
formTableField.null = ""; //null; |
||||
|
formTableField.key = ""; //key; |
||||
|
formTableField.default = ""; //default; |
||||
|
formTableField.extra = ""; //extra; |
||||
|
formTableField.privileges = ""; //privileges; |
||||
|
formTableField.comment = ""; //comment; |
||||
|
formTableField.integer = ""; //integer; |
||||
|
formTableField.decimal = ""; //decimal; |
||||
|
unsignedIsShow.value = false; |
||||
|
decimalIsShow.value = false; |
||||
|
fieldLenghtIsShow.value = false; |
||||
|
} |
||||
|
/** |
||||
|
* 关闭弹窗 |
||||
|
*/ |
||||
|
function closeDialog() { |
||||
|
isShow.value = false; |
||||
|
emits("update:isEditFormField", false); |
||||
|
initData(); |
||||
|
} |
||||
|
//提交表单 |
||||
|
function sendFormData(){ |
||||
|
butLoad.value=true; |
||||
|
// console.log("提交数据--->",props.formname,formTableField) |
||||
|
editFormField(formTableField) |
||||
|
.then((data)=>{ |
||||
|
// console.log("提交数据--1->",data) |
||||
|
ElMessage.success('成功') |
||||
|
isEditOk.value=true; |
||||
|
}) |
||||
|
.finally(()=>{ |
||||
|
butLoad.value=false; |
||||
|
closeDialog(); |
||||
|
}) |
||||
|
} |
||||
|
//判断数据类型 |
||||
|
const judgeFieldType = (typeStr:string) => { |
||||
|
if(["tinyint","smallint","mediumint","int","integer","bigint","decimal", "float", "double"].indexOf(typeStr) != -1){ |
||||
|
unsignedIsShow.value = true |
||||
|
formTableField.attribute = "unsigned" |
||||
|
formTableField.integer = "3" |
||||
|
formTableField.decimal = "" |
||||
|
}else{ |
||||
|
unsignedIsShow.value = false |
||||
|
formTableField.attribute = "" |
||||
|
} |
||||
|
if(["decimal", "float", "double"].indexOf(typeStr) != -1){ |
||||
|
decimalIsShow.value = true |
||||
|
}else{ |
||||
|
decimalIsShow.value = false |
||||
|
} |
||||
|
if(["enum","set","text","tinytext","mediumtext","longtext","blob","year","date","time","datetime","timestamp"].indexOf(typeStr) != -1){ |
||||
|
fieldLenghtIsShow.value = false |
||||
|
formTableField.integer = "" |
||||
|
formTableField.decimal = "" |
||||
|
}else{ |
||||
|
fieldLenghtIsShow.value = true |
||||
|
} |
||||
|
if(["char","varchar","binary","varbinary"].indexOf(typeStr) != -1){ |
||||
|
formTableField.integer = "255" |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<template> |
||||
|
<el-dialog |
||||
|
v-model="isShow" |
||||
|
width="550" |
||||
|
:title="dialogTitle" |
||||
|
append-to-body |
||||
|
draggable |
||||
|
@close="closeDialog" |
||||
|
> |
||||
|
<el-form :model="formTableField" label-width="80px" border> |
||||
|
<el-form-item label="字段"> |
||||
|
{{ formTableField.field }} |
||||
|
</el-form-item> |
||||
|
<el-form-item label="类型"> |
||||
|
<el-select v-model="formTableField.type" class="m-2" placeholder="请选择数据类型" @change="judgeFieldType(formTableField.type)"> |
||||
|
<el-option |
||||
|
v-for="item in fieldType" |
||||
|
:key="item" |
||||
|
:label="item" |
||||
|
:value="item" |
||||
|
/> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
<el-form-item v-if="unsignedIsShow" label="无符号"> |
||||
|
<el-radio-group v-model="formTableField.attribute" class="ml-4"> |
||||
|
<el-radio label="unsigned" size="large">正数</el-radio> |
||||
|
<el-radio label="" size="large">区分正负</el-radio> |
||||
|
</el-radio-group> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="不是Null"> |
||||
|
<el-radio-group v-model="formTableField.null" class="ml-4"> |
||||
|
<el-radio label="YES" size="large">YES</el-radio> |
||||
|
<el-radio label="NO" size="large">NO</el-radio> |
||||
|
</el-radio-group> |
||||
|
</el-form-item> |
||||
|
<el-form-item v-if="fieldLenghtIsShow" label="长度"> |
||||
|
<el-input v-model="formTableField.integer" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item v-if="decimalIsShow" label="小数点"> |
||||
|
<el-input v-model="formTableField.decimal" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="默认值"> |
||||
|
<el-input v-model="formTableField.default" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="备注"> |
||||
|
<el-input v-model="formTableField.comment" type="textarea" /> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
<template #footer> |
||||
|
<span class="dialog-footer"> |
||||
|
<el-button @click="closeDialog">取消</el-button> |
||||
|
<el-button type="primary" @click="sendFormData">提交</el-button> |
||||
|
</span> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,14 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2023-09-21 13:41:04 |
||||
|
@ 备注: 任务 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
|
||||
|
</script> |
||||
|
<template> |
||||
|
<div>任务列表</div> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
|
||||
|
</style> |
||||
Loading…
Reference in new issue