commit:升级到vue3,更新最近工作流技术栈,支持sa-token

This commit is contained in:
Jerry
2024-07-05 22:42:33 +08:00
parent bbcc608584
commit 565ecb6371
1751 changed files with 236790 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<template>
<div class="active-widget-menu">
<el-icon v-if="clone != null" @click.stop="onCopy"><CopyDocument /></el-icon>
<el-icon style="margin-left: 3px" @click.stop="onDelete"><Delete /></el-icon>
</div>
</template>
<script setup lang="ts">
import { CopyDocument, Delete } from '@element-plus/icons-vue';
import { ANY_OBJECT } from '@/types/generic';
const emit = defineEmits<{
copy: [ANY_OBJECT];
delete: [ANY_OBJECT];
}>();
const props = defineProps<{
widget: ANY_OBJECT;
clone?: (widget: ANY_OBJECT) => ANY_OBJECT | null;
}>();
const cloneWidget = (widget: ANY_OBJECT) => {
if (props.clone && typeof props.clone === 'function') {
return props.clone(widget);
}
console.warn('not found clone method');
return null;
};
const onCopy = () => {
const widget = cloneWidget(props.widget);
if (widget) {
emit('copy', widget);
}
};
const onDelete = () => {
emit('delete', props.widget);
};
</script>
<style lang="scss" scoped>
.active-widget-menu {
position: absolute;
right: -1px;
bottom: -1px;
z-index: 1000;
height: 20px;
padding: 0 5px;
color: white;
background: $color-primary;
border-radius: 2px 0 0;
line-height: 20px;
i {
font-size: 12px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<el-card
class="online-base-card form-card base-card"
:shadow="widget.props.shadow"
:body-style="{
padding: (widget.props.padding == null ? 15 : widget.props.padding) + 'px',
}"
:style="{
'margin-bottom':
(widget.props.paddingBottom || (widget.props.basicInfo || {}).paddingBottom || 0) + 'px',
}"
>
<template v-slot:header>
<div class="base-card-header table-draggable">
<span>{{ widget.showName }}</span>
<div class="base-card-operation"></div>
</div>
</template>
<el-row>
<el-col :span="24">
<el-row :gutter="form().gutter">
<VueDraggable
draggable=".custom-widget-item"
v-model="childWidgetList"
group="componentsGroup"
:style="getDrableBoxStyle"
style="position: relative; overflow: hidden; width: 100%"
:disabled="!isEdit"
:move="onDragMove"
>
<template v-if="childWidgetList.length > 0">
<el-col
class="custom-widget-item"
:class="{ active: isEdit && form().isActive(subWidget) }"
v-for="subWidget in childWidgetList"
:key="subWidget.variableName"
:span="subWidget.props.span"
>
<div
class="widget-item"
:class="{ active: isEdit && form().isActive(subWidget) }"
v-if="form().getWidgetVisible(subWidget)"
>
<div
v-if="subWidget.widgetType === SysCustomWidgetType.Table"
:style="getTableStyle(subWidget)"
style="margin-bottom: 18px"
>
<OnlineCardTable
:widget="subWidget"
:value="form().getTableData(subWidget)"
@input="(dataList:ANY_OBJECT[]) => form().setTableData(subWidget, dataList)"
@click.stop="onWidgetClick(subWidget)"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Block"
:style="getBlockStyle(subWidget)"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomBlock
v-model:value="subWidget.childWidgetList"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Card"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineBaseCard
:widget="subWidget"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Tabs"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomTabs
:widget="subWidget"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Text"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomWidget
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
:value="getWidgetValue(subWidget) || subWidget.props.text"
:style="{
'margin-bottom': (subWidget.props.paddingBottom || 0) + 'px',
}"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Image"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomWidget
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
:value="getWidgetValue(subWidget)"
:src="subWidget.props.src"
:style="{
'margin-bottom': (subWidget.props.paddingBottom || 0) + 'px',
}"
@widgetClick="onWidgetClick"
/>
</div>
<el-form-item
v-else
:label="subWidget.showName"
:prop="subWidget.propString"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomWidget
:widget="subWidget"
:value="getWidgetValue(subWidget)"
@input="(val:ANY_OBJECT) => onValueChange(subWidget, val)"
@change="(val:ANY_OBJECT|null, dictData:ANY_OBJECT|null) => onWidgetValueChange(subWidget, val, dictData)"
/>
</el-form-item>
<ActiveWidgetMenu
v-if="isEdit && form().isActive(subWidget)"
:widget="subWidget"
:clone="form().cloneWidget"
@copy="onCopyWidget"
@delete="onDeleteWidget(subWidget)"
/>
</div>
</el-col>
</template>
<div v-else-if="isEdit" class="info">
<div style="width: 100px; height: 100px">
<el-icon><UploadFilled /></el-icon>
</div>
<span>请拖入组件进行编辑</span>
</div>
</VueDraggable>
</el-row>
</el-col>
</el-row>
</el-card>
</template>
<script lang="ts">
export default {
name: 'OnlineBaseCard',
};
</script>
<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue';
import { VueDraggable } from 'vue-draggable-plus';
import { ANY_OBJECT } from '@/types/generic';
import { SysCustomWidgetType } from '@/common/staticDict';
import OnlineCustomBlock from '@/online/components/OnlineCustomBlock.vue';
import OnlineCardTable from './OnlineCardTable.vue';
import OnlineCustomWidget from './OnlineCustomWidget.vue';
import ActiveWidgetMenu from './ActiveWidgetMenu.vue';
import { WidgetProps, WidgetEmit } from './types/widget';
import { useWidget } from './hooks/widget';
import OnlineCustomTabs from './OnlineCustomTabs.vue';
const emit = defineEmits<WidgetEmit>();
interface CardProps extends WidgetProps {
// 是否显示边框
showBorder?: boolean;
height?: string;
}
const props = withDefaults(defineProps<CardProps>(), {
value: () => [],
isEdit: false,
showBorder: true,
});
const form = inject('form', () => {
console.error('OnlineCustomBaseCard: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const { childWidgetList, onWidgetClick, onDeleteWidget, onCopyWidget } = useWidget(props, emit);
const getDrableBoxStyle = computed(() => {
let tempHeight = props.height;
if (props.height == null || props.height === '') {
tempHeight = props.isEdit && props.widget.childWidgetList.length <= 0 ? '150px' : '0px';
}
return {
'min-height': tempHeight,
};
});
const getTableStyle = (widget: ANY_OBJECT) => {
if (widget.widgetType !== SysCustomWidgetType.Table) return;
return {};
};
const getBlockStyle = (widget: ANY_OBJECT) => {
return {
'margin-bottom': (widget.props.paddingBottom || 0) + 'px',
padding: props.isEdit ? '5px' : undefined,
border: props.isEdit ? '1px solid #e8eaec' : undefined,
};
};
const getWidgetValue = (widget: ANY_OBJECT) => {
return form().getWidgetValue(widget);
};
const onDragMove = (e: ANY_OBJECT) => {
// 容器组件不能改变位置
let widgetType = e.relatedContext.element ? e.relatedContext.element.widgetType : undefined;
return widgetType !== SysCustomWidgetType.Block && widgetType !== SysCustomWidgetType.Card;
};
const onValueChange = (widget: ANY_OBJECT, value: ANY_OBJECT) => {
return form().onValueChange(widget, value);
};
const onWidgetValueChange = (
widget: ANY_OBJECT,
value: ANY_OBJECT | null,
dictData: ANY_OBJECT | null,
) => {
return form().onWidgetValueChange(widget, value, dictData);
};
</script>
<style scoped>
.info {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
text-align: center;
color: #999;
flex-direction: column;
vertical-align: middle;
}
.info div {
width: 80px;
height: 80px;
font-size: 60px;
text-align: center;
border: 1px dashed #d9dbdd;
border-radius: 6px;
line-height: 100px;
}
.info span {
margin-top: 10px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<el-card class="online-card-table base-card" shadow="never" :body-style="{ padding: '0px' }">
<el-row>
<el-col
:span="24"
:style="{
height: widget.props.height != null ? widget.props.height - 53 + 'px' : undefined,
}"
>
<OnlineCustomTable
:dataList="tableWidget.dataList"
:widget="widget"
:height="widget.props.height ? widget.props.height - 53 : 200"
border="default"
:multiSelect="batchDelete"
:operationList="widget.operationList"
:getTableIndex="tableWidget.getTableIndex"
:sortChange="tableWidget.onSortChange"
:onSelectChange="onSelectRowChange"
@operationClick="onOperationClick"
/>
</el-col>
</el-row>
</el-card>
</template>
<script setup lang="ts">
import { ElMessageBox, ElMessage } from 'element-plus';
import { ANY_OBJECT } from '@/types/generic';
import { useTable } from '@/common/hooks/useTable';
import { TableOptions } from '@/common/types/pagination';
import { OnlineFormEventType, SysCustomWidgetOperationType } from '@/common/staticDict';
import { findItemFromList } from '@/common/utils';
import OnlineCustomTable from './OnlineCustomTable.vue';
const emit = defineEmits<{ input: [ANY_OBJECT[]] }>();
const props = withDefaults(
defineProps<{
value: Array<ANY_OBJECT>;
widget: ANY_OBJECT;
}>(),
{ value: () => [], isEdit: false, showBorder: true },
);
const form = inject('form', () => {
console.error('OnlineCardTable: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const selectRows = ref<ANY_OBJECT[]>([]);
const batchDelete = ref(false);
const loadTableData = () => {
return Promise.resolve({
dataList: props.value,
totalCount: props.value.length,
});
};
const loadTableDataVerify = () => {
return true;
};
const tableOptions: TableOptions<ANY_OBJECT> = {
loadTableData: loadTableData,
verifyTableParameter: loadTableDataVerify,
};
const tableWidget = reactive(useTable(tableOptions));
const refresh = () => {
tableWidget.refreshTable(true, 1);
};
watch(
() => props.value,
() => {
refresh();
},
{
deep: true,
immediate: true,
},
);
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
if (props.widget.relation != null) {
if (row == null) {
console.log('新增记录', res, props, tableWidget);
// 新增记录
row = res[props.widget.relation.variableName];
onTableDataListChange([
...tableWidget.dataList,
{
...row,
__cascade_add_id__: new Date().getTime(),
},
]);
} else {
console.log('更新记录', res, props, tableWidget);
// 更新记录
// TODO 为什么这里要重新赋值
//row = res[props.widget.relation.variableName];
onTableDataListChange(
tableWidget.dataList.map((item: ANY_OBJECT) => {
if (row != null && row.__cascade_add_id__ != null) {
return row.__cascade_add_id__ === item.__cascade_add_id__ ? row : item;
} else {
return row != null &&
row[props.widget.primaryColumnName] === item[props.widget.primaryColumnName]
? row
: item;
}
}),
);
}
}
};
const onSelectRowChange = (rows: ANY_OBJECT[]) => {
selectRows.value = rows;
};
const onOperationClick = (operation: ANY_OBJECT, row: ANY_OBJECT | null) => {
if (operation.type === SysCustomWidgetOperationType.BATCH_DELETE) {
onBatchDelete(operation);
} else if (operation.type === SysCustomWidgetOperationType.DELETE && row != null) {
onDeleteRow(row);
} else {
console.log('OnlineCardTable onOperationClick -> form', operation, form());
form().handlerOperation(operation, {
isEdit: form().isEdit,
saveData: false,
widget: props.widget,
rowData: row,
callback: (res: ANY_OBJECT) => {
handlerEditOperate(row, res);
},
});
}
};
const onBatchDelete = (operation: ANY_OBJECT) => {
console.log('onBatchDelete operation', operation);
if (selectRows.value.length <= 0) {
ElMessage.error('请选择要批量删除的数据!');
return;
}
ElMessageBox.confirm('是否删除选中数据?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
onTableDataListChange(
tableWidget.dataList.filter(row => {
// 通过主键查找
let temp = findItemFromList(
selectRows.value,
row[props.widget.primaryColumnName],
props.widget.primaryColumnName,
);
// 通过新增临时主键查找
if (temp == null && row.__cascade_add_id__ != null) {
temp = findItemFromList(selectRows.value, row.__cascade_add_id__, '__cascade_add_id__');
}
return temp == null;
}),
);
});
};
const onDeleteRow = (data: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除当前数据?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
onTableDataListChange(
tableWidget.dataList.filter(row => {
if (data.__cascade_add_id__ != null) {
return data.__cascade_add_id__ !== row.__cascade_add_id__;
} else {
return data[props.widget.primaryColumnName] !== row[props.widget.primaryColumnName];
}
}),
);
})
.catch(e => {
console.warn(e);
});
};
const onTableDataListChange = (dataList: ANY_OBJECT[]) => {
emit('input', dataList);
};
const refreshData = (data: ANY_OBJECT) => {
if (data.path === 'thirdOnlineEditForm/' + props.widget.variableName) {
handlerEditOperate(data.rowData, data.data);
}
};
onMounted(() => {
//let widget = props.widget;
//widget.widgetImpl = getCurrentInstance();
window.addEventListener(
'message',
event => {
if (event.data.type === 'refreshData') {
refreshData(event.data.data);
}
},
false,
);
});
</script>
<style scoped>
.base-card :deep(.el-card__header) {
padding: 0 10px !important;
}
.online-card-table :deep(.vxe-table--border-line) {
border: none !important;
}
</style>

View File

@@ -0,0 +1,325 @@
<template>
<el-row class="online-custom-block" :gutter="0">
<el-col :span="24">
<el-row :gutter="form().gutter">
<VueDraggable
class="custom-block-draggable"
:class="form().mode === 'pc' ? 'el-row' : ''"
draggable=".custom-widget-item"
v-model="values"
group="componentsGroup"
ghostClass="ghost"
chosenClass="chosen"
:style="getDrableBoxStyle"
style="position: relative; display: contents; overflow: hidden; width: 100%"
:disabled="!isEdit"
:move="onDragMove"
@add="onDragAdd"
>
<template v-if="Array.isArray(value) && value.length > 0">
<el-col
class="custom-widget-item"
:class="{ active: isEdit && form().isActive(subWidget) }"
v-for="subWidget in value"
:key="subWidget.variableName"
:span="subWidget.props.span || (subWidget.props.basicInfo || {}).span"
>
<div
class="widget-item"
:class="{ active: isEdit && form().isActive(subWidget) }"
v-if="form().getWidgetVisible(subWidget)"
@click.stop="onWidgetClick(subWidget)"
>
<div
v-if="subWidget.widgetType === SysCustomWidgetType.Table"
:style="getTableStyle(subWidget)"
>
<OnlineCardTable
:widget="subWidget"
:ref="subWidget.variableName"
:value="form().getTableData(subWidget)"
@input="(dataList:ANY_OBJECT[]) => form().setTableData(subWidget, dataList)"
@click.stop="onWidgetClick(subWidget)"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Block"
:style="getBlockStyle(subWidget)"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomBlock
:ref="subWidget.variableName"
v-model:value="subWidget.childWidgetList"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Card"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineBaseCard
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<div
v-else-if="subWidget.widgetType === SysCustomWidgetType.Tabs"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomTabs
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</div>
<OnlineCustomWidget
v-else-if="subWidget.widgetType === SysCustomWidgetType.Text"
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
:value="getWidgetValue(subWidget) || subWidget.props.text"
:style="{
'margin-bottom': (subWidget.props.paddingBottom || 0) + 'px',
}"
@widgetClick="onWidgetClick"
/>
<OnlineCustomWidget
v-else-if="subWidget.widgetType === SysCustomWidgetType.Image"
:ref="subWidget.variableName"
:widget="subWidget"
:isEdit="isEdit"
:value="getWidgetValue(subWidget)"
:src="subWidget.props.src"
:style="{
'margin-bottom': (subWidget.props.paddingBottom || 0) + 'px',
}"
@widgetClick="onWidgetClick"
/>
<component
:is="form().mode === 'pc' ? ElFormItem : 'div'"
v-else
:label="subWidget.showName"
inset
:prop="subWidget.propString"
:class="{
'rich-input': subWidget.widgetType === SysCustomWidgetType.RichEditor,
}"
:label-width="
subWidget.showName == null || subWidget.showName === ''
? isEdit
? '0px'
: '0px'
: undefined
"
@click.stop="onWidgetClick(subWidget)"
>
<OnlineCustomWidget
:widget="subWidget"
:ref="subWidget.variableName"
:value="getWidgetValue(subWidget)"
@input="val => onValueChange(subWidget, val)"
@change="(val: ANY_OBJECT|undefined, detail: ANY_OBJECT|null) => onWidgetValueChange(subWidget, val, detail)"
/>
</component>
<ActiveWidgetMenu
v-if="isEdit && form().isActive(subWidget)"
:widget="subWidget"
:clone="form().cloneWidget"
@copy="onCopyWidget"
@delete="onDeleteWidget(subWidget)"
/>
</div>
</el-col>
</template>
<div v-else-if="isEdit" class="info mover">
<div style="width: 100px; height: 100px">
<el-icon><UploadFilled /></el-icon>
</div>
<span>请拖入组件进行编辑</span>
</div>
</VueDraggable>
</el-row>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue';
import { VueDraggable } from 'vue-draggable-plus';
import { ElMessageBox, ElFormItem } from 'element-plus';
import { ANY_OBJECT } from '@/types/generic';
import { SysCustomWidgetType } from '@/common/staticDict';
import ActiveWidgetMenu from './ActiveWidgetMenu.vue';
import OnlineCustomWidget from './OnlineCustomWidget.vue';
import OnlineCustomTabs from './OnlineCustomTabs.vue';
import OnlineBaseCard from './OnlineBaseCard.vue';
import OnlineCardTable from './OnlineCardTable.vue';
interface IEmit {
(event: 'widgetClick', value: ANY_OBJECT | null): void;
(event: 'dragAdd', value: ANY_OBJECT): void;
(event: 'update:value', value: ANY_OBJECT | ANY_OBJECT[]): void;
}
const emit = defineEmits<IEmit>();
interface IProps {
// v-model:value 调用者必须使用这种方式赋值,才能被更新
value: Array<ANY_OBJECT>;
// 是否显示边框
showBorder?: boolean;
isEdit?: boolean;
height?: string;
}
const props = withDefaults(defineProps<IProps>(), {
value: () => [],
isEdit: false,
showBorder: true,
});
const form = inject('form', () => {
console.error('OnlineCustomBlock: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const getDrableBoxStyle = computed(() => {
let tempHeight = props.height;
if (props.height == null || props.height === '') {
tempHeight = props.isEdit && props.value.length <= 0 ? '150px' : '0px';
}
return {
'min-height': tempHeight,
};
});
const getTableStyle = (widget: ANY_OBJECT) => {
if (widget.widgetType !== SysCustomWidgetType.Table) return;
return {
'margin-bottom': (widget.props.paddingBottom || 0) + 'px',
};
};
const getBlockStyle = (widget: ANY_OBJECT) => {
return {
'margin-bottom':
(widget.props.paddingBottom || (widget.props.basicInfo || {}).paddingBottom || 0) + 'px',
padding: props.isEdit ? '5px' : undefined,
border: props.isEdit ? '1px solid #e8eaec' : undefined,
};
};
const getChartStyle = (widget: ANY_OBJECT) => {
return {
'margin-bottom':
(widget.props.paddingBottom || (widget.props.basicInfo || {}).paddingBottom || 0) + 'px',
};
};
const values = computed({
get() {
return props.value;
},
set(val: Array<ANY_OBJECT>) {
console.log('block values>>>>>>>>>>>>', val);
emit('update:value', val);
},
});
const onWidgetClick = (widget: ANY_OBJECT | null = null) => {
console.log('block block block onWidgetClick', widget);
emit('widgetClick', widget);
};
const onDragAdd = (e: DragEvent) => {
console.log('block onDragAdd', e);
emit('dragAdd', { list: props.value, dragEvent: e });
};
const onDragMove = (e: ANY_OBJECT) => {
console.log('block dragMove', e);
// 容器组件不能改变位置
let widgetType = e.relatedContext.element ? e.relatedContext.element.widgetType : undefined;
return widgetType !== SysCustomWidgetType.Block && widgetType !== SysCustomWidgetType.Card;
};
const onCopyWidget = (widget: ANY_OBJECT) => {
emit('update:value', [...props.value, widget]);
};
const onDeleteWidget = (widget: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此组件?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
emit(
'update:value',
props.value.filter(item => item !== widget),
);
onWidgetClick(null);
})
.catch(e => {
console.warn(e);
});
};
const getWidgetValue = (widget: ANY_OBJECT) => {
return form().getWidgetValue(widget);
};
const onValueChange = (
widget: ANY_OBJECT,
value: string | boolean | Date | number | ANY_OBJECT | Array<ANY_OBJECT> | undefined | null,
) => {
console.log('[[[ block value change ]]]', value, typeof value);
if (value instanceof Event) {
console.log('block onValueChange', value);
if (value.target instanceof HTMLInputElement) {
return form().onValueChange(widget, value.target.value);
}
}
return form().onValueChange(widget, value);
};
const onWidgetValueChange = (
widget: ANY_OBJECT,
value: ANY_OBJECT | null | undefined,
detail: ANY_OBJECT | null,
) => {
console.log('block onWidgetValueChange>>>>>>>>>>>>>>', value, widget, form().onWidgetValueChange);
return form().onWidgetValueChange(widget, value, detail);
};
onMounted(() => {
console.log('Block Block Block => form', form());
});
</script>
<style lang="scss" scoped>
.ghost {
height: 30px;
border-radius: 3px;
}
.info {
// position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
text-align: center;
color: #999;
flex-direction: column;
vertical-align: middle;
}
.info div {
width: 80px;
height: 80px;
font-size: 60px;
text-align: center;
border: 1px dashed #d9dbdd;
border-radius: 6px;
line-height: 100px;
}
.info span {
margin-top: 10px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<el-row :justify="align">
<img :src="imageUrl" :style="getStyle" />
</el-row>
</template>
<script setup lang="ts">
import { EpPropMergeType } from 'element-plus/es/utils';
import { ANY_OBJECT } from '@/types/generic';
import { useUpload } from '@/common/hooks/useUpload';
import { API_CONTEXT } from '@/api/config';
const props = withDefaults(
defineProps<{
widget: ANY_OBJECT | null;
value?: string;
src?: string;
fit?: string;
align?:
| EpPropMergeType<
StringConstructor,
'center' | 'space-around' | 'space-between' | 'space-evenly' | 'end' | 'start',
unknown
>
| undefined;
width?: string;
height?: string;
radius?: number;
round?: boolean;
}>(),
{
align: 'start',
radius: 5,
round: false,
},
);
const { parseUploadData, getUploadFileUrl } = useUpload();
const form = inject('form', () => {
console.error('OnlineCustomImage: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const fileList = ref<ANY_OBJECT[]>([]);
const getStyle = computed<ANY_OBJECT>(() => {
let temp = props.round ? '50%' : props.radius + 'px';
return {
width: props.width != null ? props.width : '200px',
height: props.height != null ? props.height : '200px',
'object-fit': props.fit,
'border-radius': temp,
};
});
const buildFlowParam = computed(() => {
let flowParam: ANY_OBJECT = {};
let flowData = form().flowData;
if (flowData) {
if (flowData.processDefinitionKey)
flowParam.processDefinitionKey = flowData.processDefinitionKey;
if (flowData.processInstanceId) flowParam.processInstanceId = flowData.processInstanceId;
if (flowData.taskId) flowParam.taskId = flowData.taskId;
}
return flowParam;
});
const imageUrl = computed(() => {
console.log('imageUrl', fileList, props.src);
if (Array.isArray(fileList.value) && fileList.value.length > 0) {
return fileList.value[0].url;
} else {
return isBase64(props.src) ? props.src : getDownloadUrl.value;
}
});
const getDownloadUrl = computed(() => {
if (props.value) {
//console.log('getDownloadUrl 1', props);
if (props.widget?.relation) {
return (
'admin/online/onlineOperation/downloadOneToManyRelation/' +
(props.widget.datasource || {}).variableName
);
} else {
return (
'admin/online/onlineOperation/downloadDatasource/' +
(props.widget?.datasource || {}).variableName
);
}
} else {
let imgUrl;
try {
imgUrl = props.src ? JSON.parse(props.src) : undefined;
if (imgUrl) {
imgUrl = getUploadFileUrl(imgUrl, { filename: imgUrl.filename });
}
} catch (e) {
console.warn(e);
imgUrl = null;
}
//console.log('getDownloadUrl 2', imgUrl);
return imgUrl;
}
});
watch(
() => props.value,
(newValue: string | undefined) => {
console.log('CustomImage.value change', newValue);
setTimeout(() => {
fileList.value = [];
if (newValue) {
let downloadParams: ANY_OBJECT = {
...buildFlowParam.value,
datasourceId: (props.widget?.datasource || {}).datasourceId,
fieldName: props.widget?.column?.columnName,
asImage: true,
dataId: form().getPrimaryData(props.widget) || '',
};
if (props.widget?.relation) downloadParams.relationId = props.widget.relation.relationId;
let temp = JSON.parse(newValue);
temp = Array.isArray(temp)
? temp.map(item => {
return {
...item,
downloadUri: getDownloadUrl.value,
};
})
: [];
fileList.value = parseUploadData(JSON.stringify(temp), downloadParams);
}
}, 30);
},
{
deep: true,
immediate: true,
},
);
function isBase64(src: string | undefined) {
return src && /^data:image\/\w+;base64,/.test(src);
}
</script>

View File

@@ -0,0 +1,88 @@
<template>
<div class="custom-label">
<span v-if="isText"> {{ showText }} </span>
<div v-if="isHtml" v-html="showText" />
</div>
</template>
<script setup lang="ts">
import { SysCustomWidgetType } from '@/common/staticDict';
import { SysOnlineFieldKind } from '@/common/staticDict/online';
import { ANY_OBJECT } from '@/types/generic';
const props = defineProps<{
value?: number | string | boolean | Array<ANY_OBJECT>;
widget: ANY_OBJECT;
}>();
const form = inject('form', () => {
console.error('OnlineCustomLabel: form not injected');
return { isEdit: false };
});
const isText = computed(() => {
return (
[
SysCustomWidgetType.Label,
SysCustomWidgetType.Input,
SysCustomWidgetType.NumberInput,
SysCustomWidgetType.NumberRange,
SysCustomWidgetType.Slider,
SysCustomWidgetType.Radio,
SysCustomWidgetType.CheckBox,
SysCustomWidgetType.Switch,
SysCustomWidgetType.Select,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Date,
SysCustomWidgetType.DateRange,
SysCustomWidgetType.UserSelect,
SysCustomWidgetType.DeptSelect,
SysCustomWidgetType.DataSelect,
].indexOf(props.widget.widgetType) !== -1
);
});
const isHtml = computed(() => {
return (
(props.widget.widgetType === SysCustomWidgetType.RichEditor ||
props.widget.widgetType === SysCustomWidgetType.Label) &&
props.widget.column &&
props.widget.column.fieldKind === SysOnlineFieldKind.RICH_TEXT
);
});
const showText = computed(() => {
// console.log('OnlineCustomLabel.showText', props, form());
switch (props.widget.widgetType) {
case SysCustomWidgetType.Label:
case SysCustomWidgetType.Input:
case SysCustomWidgetType.NumberInput:
case SysCustomWidgetType.Slider:
case SysCustomWidgetType.Date:
case SysCustomWidgetType.RichEditor:
case SysCustomWidgetType.Radio:
case SysCustomWidgetType.CheckBox:
case SysCustomWidgetType.Select:
case SysCustomWidgetType.Cascader:
case SysCustomWidgetType.UserSelect:
case SysCustomWidgetType.DeptSelect:
case SysCustomWidgetType.DataSelect:
return !props.value
? form().isEdit
? 'XXXXXXXXXX'
: undefined
: Array.isArray(props.value)
? props.value.join(',')
: props.value;
case SysCustomWidgetType.NumberRange:
case SysCustomWidgetType.DateRange:
return Array.isArray(props.value) && props.value.length > 1
? props.value[0] + ' 至 ' + props.value[1]
: form().isEdit
? 'XXXXX 至 XXXXX'
: undefined;
case SysCustomWidgetType.Switch:
return props.value ? '是' : '否';
default:
return '';
}
});
</script>

View File

@@ -0,0 +1,567 @@
<template>
<table-box
ref="table"
style="height: 100%"
:data="dataList"
:key="tableKey"
show-overflow="title"
show-header-overflow="title"
class="draggable-widget page-table"
header-cell-class-name="table-header-gray"
:hasImageColumn="hasImageColumn"
:size="layoutStore.defaultFormItemSize"
:keep-source="rowEdit"
:edit-config="{
trigger: 'manual',
mode: 'row',
enabled: rowEdit,
showIcon: false,
autoClear: false,
showStatus: true,
}"
:edit-closed="onSaveRowData"
:seq-config="{ seqMethod }"
:hasExtend="hasExtend"
@sort-change="onSortChange"
@checkbox-select-change="onCheckBoxChange"
@radio-select-change="onRadioSelectChange"
:sort-config="{ remote: remoteSort }"
@refresh="onRefresh"
>
<template
v-slot:operator
v-if="
operationList.filter(row => {
return row.enabled && !row.rowOperation;
}).length > 0 && !form().readOnly
"
>
<el-button
class="table-operation"
v-if="operationVisible(SysCustomWidgetOperationType.BATCH_DELETE)"
:size="layoutStore.defaultFormItemSize"
:type="getOperation(SysCustomWidgetOperationType.BATCH_DELETE).btnType"
:plain="getOperation(SysCustomWidgetOperationType.BATCH_DELETE).plain"
:disabled="operationDisabled(SysCustomWidgetOperationType.BATCH_DELETE)"
@click="onOperationClick(getOperation(SysCustomWidgetOperationType.BATCH_DELETE))"
:icon="Delete"
>{{ getOperation(SysCustomWidgetOperationType.BATCH_DELETE).name || '批量删除' }}</el-button
>
<el-button
class="table-operation"
v-if="operationVisible(SysCustomWidgetOperationType.EXPORT)"
:size="layoutStore.defaultFormItemSize"
:type="getOperation(SysCustomWidgetOperationType.EXPORT).btnType"
:plain="getOperation(SysCustomWidgetOperationType.EXPORT).plain"
:disabled="operationDisabled(SysCustomWidgetOperationType.EXPORT)"
@click="onOperationClick(getOperation(SysCustomWidgetOperationType.EXPORT))"
:icon="Download"
>{{ getOperation(SysCustomWidgetOperationType.EXPORT).name || '导出' }}</el-button
>
<el-button
class="table-operation"
v-if="operationVisible(SysCustomWidgetOperationType.PRINT)"
:size="layoutStore.defaultFormItemSize"
:type="getOperation(SysCustomWidgetOperationType.PRINT).btnType"
:plain="getOperation(SysCustomWidgetOperationType.PRINT).plain"
:disabled="operationDisabled(SysCustomWidgetOperationType.PRINT)"
@click="onOperationClick(getOperation(SysCustomWidgetOperationType.PRINT))"
>{{ getOperation(SysCustomWidgetOperationType.PRINT).name || '打印' }}</el-button
>
<el-button
class="table-operation"
v-if="operationVisible(SysCustomWidgetOperationType.ADD)"
:size="layoutStore.defaultFormItemSize"
:type="getOperation(SysCustomWidgetOperationType.ADD).btnType"
:plain="getOperation(SysCustomWidgetOperationType.ADD).plain"
:disabled="operationDisabled(SysCustomWidgetOperationType.ADD)"
@click="onOperationClick(getOperation(SysCustomWidgetOperationType.ADD))"
:icon="Plus"
>{{ getOperation(SysCustomWidgetOperationType.ADD).name || '新建' }}</el-button
>
</template>
<vxe-column v-if="hasBatchOperation && !form().readOnly" type="checkbox" :width="40" />
<vxe-column v-if="singleSelect" type="radio" align="center" :width="50" />
<vxe-column v-if="tableColumnList.length > 0" type="seq" title="序号" :width="50" />
<template v-for="tableColumn in tableColumnList" :key="tableColumn.column?.columnId">
<!-- Boolean类型字段 -->
<vxe-column
v-if="tableColumn.column && tableColumn.column.objectFieldType === 'Boolean'"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<el-tag
:size="layoutStore.defaultFormItemSize"
:type="getObjectValue(row, tableColumn.showFieldName) ? 'success' : 'danger'"
>
{{ getObjectValue(row, tableColumn.showFieldName) ? '是' : '否' }}
</el-tag>
</template>
</vxe-column>
<!-- 图片类型字段 -->
<vxe-column
v-else-if="
tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE
"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<el-image
v-for="item in parseTableUploadData(tableColumn, row)"
:preview-src-list="getTablePictureList(tableColumn, row)"
class="table-cell-image"
:key="item.url"
:src="item.url"
fit="fill"
>
<template v-slot:error>
<div class="table-cell-image">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</template>
</vxe-column>
<!-- 文件下载类型字段 -->
<vxe-column
v-else-if="tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<a
v-for="item in parseTableUploadData(tableColumn, row)"
:key="item.url"
href="javascript:void(0);"
@click="downloadFile(item.url, item.name)"
>
{{ item.name }}
</a>
</template>
</vxe-column>
<!-- 其他类型 -->
<vxe-column
v-else
:title="tableColumn.showName"
:field="tableColumn.showFieldName"
:width="tableColumn.columnWidth"
:sortable="tableColumn.sortable"
/>
</template>
<vxe-column
title="操作"
:width="widget.props.operationColumnWidth || 160"
fixed="right"
:show-overflow="false"
v-if="(rowEdit || tableOperationList.length > 0) && tableColumnList.length > 0"
>
<template #default="{ row }">
<el-button
v-for="operation in tableOperationList"
:key="operation.id"
v-show="form().checkOperationVisible(operation, row)"
:size="layoutStore.defaultFormItemSize"
link
:class="operation.btnClass"
:disabled="
!form().checkOperationPermCode(operation) ||
form().checkOperationDisabled(operation, row)
"
@click="onOperationClick(operation, row)"
>
{{ operation.name }}
</el-button>
</template>
</vxe-column>
<template v-slot:empty>
<div class="table-empty unified-font">
<img src="@/assets/img/empty.png" />
<span>暂无数据</span>
</div>
</template>
<template v-slot:pagination>
<slot name="pagination" />
</template>
</table-box>
</template>
<script setup lang="ts">
import { VxeColumn } from 'vxe-table';
import { Picture, Delete, Download, Plus } from '@element-plus/icons-vue';
import TableBox from '@/components/TableBox/index.vue';
import { useDownload } from '@/common/hooks/useDownload';
import { ANY_OBJECT } from '@/types/generic';
import { findItemFromList, getObjectValue } from '@/common/utils';
import { SysCustomWidgetOperationType } from '@/common/staticDict';
import { SysOnlineFieldKind } from '@/common/staticDict/online';
import { useUpload } from '@/common/hooks/useUpload';
import { SortInfo } from '@/common/types/sortinfo';
import { API_CONTEXT } from '@/api/config';
const { downloadFile } = useDownload();
const { parseUploadData } = useUpload();
const emit = defineEmits<{
delete: [ANY_OBJECT];
operationClick: [ANY_OBJECT, ANY_OBJECT | null];
refresh: [];
}>();
const props = withDefaults(
defineProps<{
dataList?: Array<ANY_OBJECT>;
height?: string | number;
border?: string;
// 是否支持行内编辑
rowEdit?: boolean;
// 是否支持多选
multiSelect?: boolean;
// 是否支持单选
singleSelect?: boolean;
// 表格操作列表
operationList?: Array<ANY_OBJECT>;
widget: ANY_OBJECT;
// 获取行序号
getTableIndex?: (value: number) => number;
// 排序改变
sortChange?: (value: SortInfo) => void;
// 多选选中改变
onSelectChange?: (values: Array<ANY_OBJECT>) => void;
// 单选中改变
onRadioChange?: (value: ANY_OBJECT) => void;
}>(),
{
dataList: () => [],
height: 'auto',
border: 'full',
rowEdit: false,
multiSelect: false,
singleSelect: false,
operationList: () => [],
},
);
const table = ref<ANY_OBJECT>();
const form = inject('form', () => {
console.error('OnlineCustomTable: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
const slots = useSlots();
// const editInfo = reactive<ANY_OBJECT>({
// editRow: undefined,
// });
const sortInfo = ref<SortInfo | null>(null);
const buildFlowParam = computed(() => {
let flowParam: ANY_OBJECT = {};
let flowData = form().flowData;
if (flowData) {
if (flowData.processDefinitionKey)
flowParam.processDefinitionKey = flowData.processDefinitionKey;
if (flowData.processInstanceId) flowParam.processInstanceId = flowData.processInstanceId;
if (flowData.taskId) flowParam.taskId = flowData.taskId;
}
return flowParam;
});
const tableColumnList = computed(() => {
console.log('>>> ===', props.widget.props.tableColumnList);
let tempList =
props.widget && props.widget.props && Array.isArray(props.widget.props.tableColumnList)
? props.widget.props.tableColumnList
: [];
const res: ANY_OBJECT[] = [];
for (const tempItem of tempList) {
const item = { ...tempItem };
if (item.fieldType === 0 || item.fieldType == null) {
// 绑定表字段
if (item.column) item.showFieldName = item.column.columnName;
if (props.widget.relation == null && item.relation != null) {
item.showFieldName = item.relation.variableName + '.' + item.showFieldName;
}
if (item.column && item.column.dictInfo) {
item.showFieldName = item.showFieldName + 'DictMap.name';
}
} else {
// 自定义字段
item.showFieldName = item.customFieldName;
}
res.push(item);
}
return res;
});
const tableOperationList = computed(() => {
return props.operationList.filter(item => {
let temp = item.enabled && item.rowOperation;
if (temp && form().readOnly) {
temp = temp && item.readOnly;
}
return temp;
});
});
const hasBatchOperation = computed(() => {
let batchDeleteOperation = findItemFromList(
props.operationList,
SysCustomWidgetOperationType.BATCH_DELETE,
'type',
);
let printOperation = findItemFromList(
props.operationList,
SysCustomWidgetOperationType.PRINT,
'type',
);
return (
(batchDeleteOperation != null && batchDeleteOperation.enabled) ||
(printOperation != null && printOperation.enabled && !printOperation.rowOperation)
);
});
const hasImageColumn = computed(() => {
return (
tableColumnList.value.filter((tableColumn: ANY_OBJECT) => {
return tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE;
}).length > 0
);
});
const tableKey = computed(() => {
return (props.widget || {}).variableName + new Date().getTime() + tableColumnList.value.length;
});
const remoteSort = computed(() => {
return props.widget ? (props.widget.props || {}).paged : false;
});
const hasExtend = computed(() => {
return (
(props.operationList.filter(row => {
return row.enabled && !row.rowOperation;
}).length > 0 &&
!form().readOnly) ||
(slots.operator && slots.operator()) != null
);
});
const onRefresh = () => {
emit('refresh');
};
const hasOperator = (type: string) => {
let temp = getOperation(type);
return temp && temp.enabled;
};
const getOperation = (type: string) => {
return findItemFromList(props.operationList, type, 'type') || {};
};
const operationVisible = (type: string) => {
let operation = getOperation(type);
if (!operation) return false;
return !form().readOnly && hasOperator(type) && form().checkOperationVisible(operation);
};
const operationDisabled = (type: string) => {
let operation = getOperation(type);
return form().checkOperationDisabled(operation) || !form().checkOperationPermCode(operation);
};
const seqMethod = (data: ANY_OBJECT) => {
if (props.getTableIndex) {
return props.getTableIndex(data.seq - 1);
} else {
return data.seq;
}
};
const onSortChange = (data: ANY_OBJECT) => {
if (!props.widget.props.paged) return;
let fieldName = data.property.replace('DictMap.name', '');
let order = data.order;
if (order == null) {
fieldName = undefined;
}
if (order === 'asc') order = 'ascending';
if (
sortInfo.value != null &&
sortInfo.value?.prop === fieldName &&
sortInfo.value?.order === order
) {
return;
}
sortInfo.value = {
prop: fieldName,
order: order,
};
if (props.sortChange) {
props.sortChange(sortInfo.value);
}
};
const onCheckBoxChange = () => {
if (table.value && typeof props.onSelectChange === 'function') {
let selectRows = table.value.getTableImpl().getCheckboxRecords(true);
props.onSelectChange(selectRows);
}
};
const onRadioSelectChange = () => {
if (table.value && typeof props.onRadioChange === 'function') {
let selectRow = table.value.getTableImpl().getRadioRecord();
props.onRadioChange(selectRow);
}
};
// const setSelectedRow = (rowNum: number) => {
// table.value.getTableImpl().setRadioRow(props.dataList[rowNum]);
// nextTick(onRadioSelectChange);
// };
// 取消行内编辑
// const cancelRowEvent = (row: ANY_OBJECT) => {
// if (form().isEdit) return;
// table.value
// .getTableImpl()
// .clearActived()
// .then(() => {
// // 还原行数据
// table.value.getTableImpl().revertData(row);
// editInfo.editRow = undefined;
// })
// .catch(e => {
// console.warn(e);
// });
// };
// 启动行内编辑
// const editRowEvent = (row: ANY_OBJECT) => {
// if (form().isEdit) return;
// table.value.getTableImpl().setEditRow(row);
// editInfo.editRow = row;
// };
// 保存行内编辑数据
// const saveRowEvent = (row: ANY_OBJECT) => {
// if (form().isEdit) return;
// table.value
// .getTableImpl()
// .clearActived()
// .then(() => {
// table.value.getTableImpl().reloadRow(row);
// editInfo.editRow = undefined;
// });
// };
const onSaveRowData = ({ row }: { row: ANY_OBJECT }) => {
console.log(row);
};
const onOperationClick = (operation: ANY_OBJECT, row: ANY_OBJECT | null = null) => {
emit('operationClick', operation, row);
};
const refreshColumn = () => {
nextTick(() => {
if (table.value) table.value.getTableImpl().refreshColumn();
});
};
const getDownloadUrl = (tableColumn: ANY_OBJECT) => {
let downloadUrl = null;
if (form().flowData != null) {
downloadUrl = API_CONTEXT + '/flow/flowOnlineOperation/download';
} else {
if (tableColumn.relationId) {
downloadUrl =
API_CONTEXT +
'/online/onlineOperation/downloadOneToManyRelation/' +
(props.widget.datasource || {}).variableName;
} else {
downloadUrl =
API_CONTEXT +
'/online/onlineOperation/downloadDatasource/' +
(props.widget.datasource || {}).variableName;
}
}
return downloadUrl;
};
const parseTableUploadData = (tableColumn: ANY_OBJECT, row: ANY_OBJECT) => {
let jsonData = getObjectValue(row, tableColumn.showFieldName);
console.log('parseTableUploadData', tableColumn, row, jsonData);
if (!jsonData) return [];
let downloadParams: ANY_OBJECT = {
...buildFlowParam.value,
datasourceId: props.widget.datasource.datasourceId,
relationId: tableColumn.relationId,
fieldName: tableColumn.column?.columnName,
asImage: tableColumn.column?.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE,
};
if (props.widget.primaryColumnName != null) {
downloadParams.dataId = row[props.widget.primaryColumnName] || '';
}
let downloadUrl = getDownloadUrl(tableColumn);
console.log('parseTableUploadData downloadUrl', downloadUrl);
console.log('parseTableUploadData jsonData.toString()', jsonData.toString());
let temp = JSON.parse(jsonData.toString());
temp = Array.isArray(temp)
? temp.map(item => {
return {
...item,
downloadUri: downloadUrl,
};
})
: [];
return parseUploadData(JSON.stringify(temp), downloadParams);
};
const getTablePictureList = (tableColumn: ANY_OBJECT, row: ANY_OBJECT) => {
let temp = parseTableUploadData(tableColumn, row);
if (Array.isArray(temp)) {
return temp.map(item => item.url);
}
};
const formatListData = (data: ANY_OBJECT) => {
Object.keys(data).forEach(key => {
let subData = data[key];
if (typeof subData === 'object' && key.indexOf('DictMap') === -1) {
formatListData(subData);
} else {
// 如果是多选字典字段那么把选中的字典值拼接成DictMap去显示
if (key.indexOf('DictMapList') !== -1 && Array.isArray(data[key])) {
let newKey = key.replace('DictMapList', 'DictMap');
data[newKey] = {
id: data[key.replace('DictMapList', '')],
name: data[key].map((subItem: ANY_OBJECT) => subItem.name).join(','),
};
}
}
});
};
watch(
() => props.dataList,
() => {
if (Array.isArray(props.dataList)) {
props.dataList.forEach(item => {
formatListData(item);
});
//console.log('OnlineCumstomTable.dataList', props.dataList);
}
},
{
immediate: true,
},
);
watch(
() => tableColumnList.value,
() => {
refreshColumn();
},
{
immediate: true,
},
);
watch(
() => props.widget.props.operationColumnWidth,
() => {
refreshColumn();
},
);
</script>
<style scoped>
.table-operation {
display: inline-block;
}
.table-operation + .table-operation {
margin-left: 8px;
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div
class="table-draggable"
:style="{
'margin-bottom':
(widget.props.paddingBottom || (widget.props.basicInfo || {}).paddingBottom || 0) + 'px',
}"
>
<el-tabs v-if="childWidgetList.length > 0" v-model="activePanel" :type="widget.props.type">
<el-tab-pane
v-for="subWidget in childWidgetList"
:key="subWidget.variableName"
:label="subWidget.showName"
:name="subWidget.variableName"
:lazy="true"
>
<OnlineCustomBlock
v-model:value="subWidget.childWidgetList"
:isEdit="isEdit"
@widgetClick="onWidgetClick"
/>
</el-tab-pane>
</el-tabs>
<div
v-if="isEdit && (widget.childWidgetList || []).length <= 0"
class="info"
style="border: 1px solid #e8eaec"
>
<div style="width: 100px; height: 100px">
<el-icon><UploadFilled /></el-icon>
</div>
<span>请添加标签页</span>
</div>
</div>
</template>
<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue';
import OnlineCustomBlock from '@/online/components/OnlineCustomBlock.vue';
import { WidgetProps, WidgetEmit } from './types/widget';
import { useWidget } from './hooks/widget';
const emit = defineEmits<WidgetEmit>();
const props = withDefaults(defineProps<WidgetProps>(), { isEdit: false });
const activePanel = ref();
// const form = inject('form', () => {
// return { isEdit: false } as ANY_OBJECT;
// });
const { childWidgetList, onWidgetClick } = useWidget(props, emit);
// TODO 监听到变化之后,行为是否妥当
watch(
() => props.widget.childWidgetList,
() => {
if (Array.isArray(props.widget.childWidgetList) && props.widget.childWidgetList.length > 0) {
activePanel.value = props.widget.childWidgetList[0].variableName;
}
},
{
// 不能使用深度监听否则会导致组件属性变化之后tabs会重置选中状态
//deep: true,
immediate: true,
},
);
</script>
<style scoped>
.info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 20px;
text-align: center;
vertical-align: middle;
color: #999;
}
.info div {
width: 80px;
height: 80px;
font-size: 60px;
text-align: center;
border: 1px dashed #d9dbdd;
border-radius: 6px;
line-height: 100px;
}
.info span {
margin-top: 10px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div
class="online-custom-text"
:style="{
background: bgColor,
height: height ? height + 'px' : undefined,
'justify-content': valign,
padding: padding + 'px',
}"
>
<div :style="getStyle">{{ value }}</div>
</div>
</template>
<script setup lang="ts">
import { StyleValue } from 'vue';
const props = withDefaults(
defineProps<{
value?: number | string | Date;
align?: string;
valign?: string;
bgColor?: string;
height?: number;
textIndent?: number;
fontSize?: number;
padding?: number;
fontColor?: string;
fontBold?: boolean;
fontItalic?: boolean;
}>(),
{
value: '',
align: 'left',
valign: 'center',
height: 25,
textIndent: 0,
fontSize: 14,
padding: 2,
fontColor: '#383838',
fontBold: false,
fontItalic: false,
},
);
const getStyle = computed<StyleValue>(() => {
return {
width: '100%',
'text-indent': props.textIndent + 'em',
'text-align': props.align,
'max-height': props.height ? props.height + 'px' : undefined,
'line-height': 1.5,
'font-size': props.fontSize + 'px',
color: props.fontColor,
'font-weight': props.fontBold ? 600 : 400,
'font-style': props.fontItalic ? 'italic' : undefined,
overflow: 'hidden',
'word-break': 'break-word',
} as StyleValue;
});
</script>
<style scoped>
.online-custom-text {
display: flex;
flex-direction: column;
padding: 2px;
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<el-tree
class="online-custom-tree"
ref="tree"
:data="treeDataList"
node-key="id"
:show-checkbox="multiple"
:highlight-current="true"
:default-expand-all="true"
:auto-expand-parent="true"
:expand-on-click-node="false"
@check-change="onSelectChange"
@node-click="onNodeClick"
>
<template v-slot="{ data }">
<el-row class="node-item" justify="space-between" align="middle">
<div class="text">{{ data.name }}</div>
</el-row>
</template>
</el-tree>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch, computed, nextTick, inject } from 'vue';
import { treeDataTranslate, findItemFromList } from '@/common/utils';
import { ANY_OBJECT } from '@/types/generic';
const props = withDefaults(
defineProps<{
value?: number | string | ANY_OBJECT[];
widget: ANY_OBJECT;
multiple?: boolean;
dataList?: ANY_OBJECT[];
filter?: ANY_OBJECT;
}>(),
{
multiple: false,
dataList: () => [],
},
);
const tree = ref();
const form = inject('form', () => {
console.error('OnlineCustomTree: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
// 左树右表布局,左侧过滤条件
const leftFilterObject = computed(() => {
return form().filter;
});
const treeDataList = computed(() => {
let tempList = (props.dataList || []).filter(item => {
item.children = null;
return (
leftFilterObject.value == null ||
leftFilterObject.value.name == null ||
item.name.indexOf(leftFilterObject.value.name) !== -1
);
});
let temp = treeDataTranslate(tempList, 'id', 'parentId');
console.log('temp', temp);
if (props.multiple) {
return temp;
} else {
return [
{
id: '',
name: '全部',
},
...temp,
];
}
});
const onNodeClick = () => {
console.log('onNodeClick');
if (!props.multiple) onSelectChange();
};
const emit = defineEmits<{
'update:value': [number | string | ANY_OBJECT[] | undefined];
change: [number | string | ANY_OBJECT[] | undefined, ANY_OBJECT | null];
}>();
const onValueChange = () => {
let temp = props.value;
if (tree.value) {
if (props.multiple) {
temp = tree.value.getCheckedKeys();
} else {
temp = tree.value.getCurrentKey();
}
}
console.log('onValueChange', temp);
emit('update:value', temp);
let dictData = props.multiple
? null
: findItemFromList(props.dataList, temp as string | number, 'id');
emit('change', temp, dictData);
};
const onSelectChange = () => {
console.log('onSelectChange');
nextTick(() => {
onValueChange();
});
};
const setTreeSelectNode = () => {
if (tree.value) {
if (props.multiple) {
tree.value.setCheckedKeys(props.value || []);
} else {
tree.value.setCurrentKey(props.value);
}
}
};
watch(
() => props.value,
() => {
setTreeSelectNode();
},
{
immediate: true,
},
);
watch(
() => props.dataList,
() => {
setTimeout(() => {
setTreeSelectNode();
}, 50);
},
{
immediate: true,
},
);
</script>
<style scoped>
.online-custom-tree :deep(.el-tree-node__content) {
height: 100%;
}
.online-custom-tree :deep(.el-tree-node__expand-icon) {
font-size: 16px;
color: #666;
}
.online-custom-tree :deep(.el-tree-node__expand-icon.is-leaf) {
color: transparent;
cursor: default;
}
.node-item {
min-width: 100px;
padding: 12px 0;
margin-right: 15px;
flex-grow: 1;
}
.node-item .text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
<style lang="scss">
.online-custom-tree .is-current > .el-tree-node__content {
color: $color-primary;
background: $color-primary-light-9 !important;
}
</style>

View File

@@ -0,0 +1,278 @@
<template>
<div class="online-custom-upload">
<el-upload
v-if="!readOnly"
:class="{
'upload-image-item': isImage,
'upload-image-multi': maxFileCount !== 1,
}"
:name="widget.props.fileFieldName"
:headers="getUploadHeaders"
:action="getActionUrl"
:data="uploadData"
:on-success="onUploadSuccess"
:on-remove="onRemoveFile"
:on-error="onUploadError"
:on-exceed="onUploadLimit"
:limit="maxFileCount"
:show-file-list="maxFileCount !== 1 || !isImage"
:list-type="getUploadListType"
:file-list="getUploadFileList"
:disabled="getDisabledStatus()"
>
<!-- 上传图片 -->
<template v-if="isImage && maxFileCount === 1">
<div v-if="getUploadFileList && getUploadFileList[0] != null" style="position: relative">
<img class="upload-image-show" :src="getUploadFileList[0].url" />
<el-icon class="upload-img-del" @click.stop="onRemoveFile(null, null)"><Close /></el-icon>
</div>
<el-icon v-else class="upload-image-item"><Plus /></el-icon>
</template>
<!-- 上传文件 -->
<template v-else-if="!isImage">
<el-button :size="layoutStore.defaultFormItemSize" type="primary">点击上传</el-button>
</template>
</el-upload>
<template v-else>
<template v-if="isImage">
<el-image
v-for="item in uploadWidgetImpl.fileList"
:preview-src-list="(uploadWidgetImpl.fileList || []).map((imgItem:ANY_OBJECT) => imgItem.url)"
class="table-cell-image"
:key="item.url"
:src="item.url"
fit="fill"
>
</el-image>
</template>
<a
v-else
v-for="item in uploadWidgetImpl.fileList"
:key="item.url"
href="javascript:void(0);"
@click="item.url && downloadFile(item.url, item.name)"
>
{{ item.name }}
</a>
</template>
</div>
</template>
<script setup lang="ts">
import { UploadFile, ElMessage } from 'element-plus';
import { Plus, Close } from '@element-plus/icons-vue';
import { useDownload } from '@/common/hooks/useDownload';
import { useUpload } from '@/common/hooks/useUpload';
import { useUploadWidget } from '@/common/hooks/useUploadWidget';
import { OnlineFormEventType } from '@/common/staticDict';
import { ANY_OBJECT } from '@/types/generic';
import { SysOnlineFieldKind } from '@/common/staticDict/online';
import { API_CONTEXT } from '@/api/config';
const emit = defineEmits<{
'update:value': [string | undefined];
'update:modelValue': [string | undefined];
}>();
const props = withDefaults(
defineProps<{ value?: string; widget: ANY_OBJECT; readOnly?: boolean }>(),
{
readOnly: false,
},
);
console.log('OnlineCustomUpload props ', props);
const form = inject('form', () => {
console.error('OnlineCustomUpload: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
const { downloadFile } = useDownload();
const { getUploadHeaders, getUploadActionUrl, parseUploadData, fileListToJson } = useUpload();
const maxFileCount = computed(() => {
console.log('maxFileCount < column', props.widget.column);
return props.widget.column ? props.widget.column.maxFileCount : 1;
});
const buildFlowParam = computed(() => {
let flowParam: ANY_OBJECT = {};
let flowData = form().flowData;
if (flowData) {
if (flowData.processDefinitionKey)
flowParam.processDefinitionKey = flowData.processDefinitionKey;
if (flowData.processInstanceId) flowParam.processInstanceId = flowData.processInstanceId;
if (flowData.taskId) flowParam.taskId = flowData.taskId;
}
return flowParam;
});
const getActionUrl = computed(() => {
if (props.widget.props.actionUrl == null || props.widget.props.actionUrl === '') {
if (props.widget.relation) {
return getUploadActionUrl(
API_CONTEXT +
'/online/onlineOperation/uploadOneToManyRelation/' +
(props.widget.datasource || {}).variableName,
);
} else {
return getUploadActionUrl(
API_CONTEXT +
'/online/onlineOperation/uploadDatasource/' +
(props.widget.datasource || {}).variableName,
);
}
} else {
return getUploadActionUrl(props.widget.props.actionUrl);
}
});
const getDownloadUrl = computed(() => {
if (props.widget.props.downloadUrl == null || props.widget.props.downloadUrl === '') {
if (props.widget.relation) {
return (
API_CONTEXT +
'/online/onlineOperation/downloadOneToManyRelation/' +
(props.widget.datasource || {}).variableName
);
} else {
return (
API_CONTEXT +
'/online/onlineOperation/downloadDatasource/' +
(props.widget.datasource || {}).variableName
);
}
} else {
return props.widget.props.downloadUrl;
}
});
const uploadData = ref<ANY_OBJECT>({});
// const getUploadData = computed(() => {
// //console.log('getUploadData 2', uploadData.value);
// return uploadData.value;
// });
const getUploadListType = computed(() => {
if (maxFileCount.value !== 1 && isImage.value) {
return 'picture-card';
}
return 'text';
});
const getUploadFileList = computed(() => {
return uploadWidgetImpl ? uploadWidgetImpl.fileList : [];
});
const isImage = ref(false);
const uploadWidgetImpl = reactive(
useUploadWidget(props.widget.column ? props.widget.column.maxFileCount : 0),
);
const getDisabledStatus = () => {
if (form().isEdit) return true;
return props.widget.props.disabled;
};
const onValueChange = () => {
// TODO 没找到widgetConfig的定义
const json = fileListToJson(uploadWidgetImpl.fileList);
emit('update:value', json);
emit('update:modelValue', json);
};
const onUploadSuccess = (response: ANY_OBJECT, file: UploadFile, fileList: UploadFile[]) => {
if (response.success) {
//file.filename = response.data.filename;
if (file.raw) file.url = URL.createObjectURL(file.raw);
uploadWidgetImpl.onFileChange(file, fileList);
onValueChange();
} else {
ElMessage.error(response.message);
}
};
const onRemoveFile = (file: UploadFile | null, fileList: UploadFile[] | null) => {
uploadWidgetImpl.onFileChange(file, fileList);
onValueChange();
};
const onUploadError = () => {
ElMessage.error('文件上传失败');
};
const onUploadLimit = () => {
if (maxFileCount.value != null && maxFileCount.value > 0) {
ElMessage.error('已经超出最大上传个数限制');
}
};
onMounted(() => {
console.log('OnlineCustomUpload onMounted', props.widget, props.widget.column);
//let widget = props.widget;
//widget.widgetImpl = getCurrentInstance();
});
watch(
() => props.widget,
() => {
setTimeout(() => {
let column = props.widget.bindData?.column || props.widget.column;
isImage.value = column ? column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE : false;
console.log('watch widget OnlineCustomUpload >>>>>>>>>>', column, isImage.value);
let temp: ANY_OBJECT = {
...buildFlowParam.value,
datasourceId: (props.widget.datasource || {}).datasourceId,
asImage: isImage.value,
fieldName: (props.widget.column || {}).columnName,
};
// console.log(
// 'getUploadData',
// props.widget?.datasource ? { ...props.widget.datasource } : 'undddefined',
// temp,
// );
if ((props.widget.relation || {}).relationId)
temp.relationId = (props.widget.relation || {}).relationId;
let flowData = form().flowData;
if (flowData && flowData.processDefinitionKey)
temp.processDefinitionKey = flowData.processDefinitionKey;
uploadData.value = temp;
}, 30);
},
{
deep: true,
immediate: true,
},
);
watch(
() => props.value,
newValue => {
console.log('OnlineCustomUpload watch value', newValue);
if (!newValue) return;
setTimeout(() => {
uploadWidgetImpl.fileList = [];
if (newValue != null) {
let downloadParams: ANY_OBJECT = {
...buildFlowParam.value,
datasourceId: (props.widget.datasource || {}).datasourceId,
fieldName: props.widget.column.columnName,
asImage: isImage.value,
dataId: form().getPrimaryData(props.widget) || '',
};
if (props.widget.relation) downloadParams.relationId = props.widget.relation.relationId;
let temp = JSON.parse(newValue);
temp = Array.isArray(temp)
? temp.map(item => {
return {
...item,
downloadUri: getDownloadUrl.value,
};
})
: [];
uploadWidgetImpl.fileList = parseUploadData(JSON.stringify(temp), downloadParams);
}
}, 30);
},
{
deep: true,
immediate: true,
},
);
</script>

View File

@@ -0,0 +1,579 @@
<template>
<component
v-bind="getWidgetProps"
:ref="widget.variableName"
:is="getComponent"
:style="getWidgetStyle"
:disabled="getDisabledStatus()"
:widget="widget"
v-model="bindValue"
:value="bindValue"
@change="onValueChange"
:change="onValueChange"
@widgetClick="onWidgetClick"
>
<template v-if="form().mode === 'pc'" v-slot="scope">
<template v-if="widget.widgetType === SysCustomWidgetType.Radio">
<el-radio v-for="item in getAllDropdownData" :key="item.id" :value="item.id">
{{ item.name }}
</el-radio>
</template>
<template
v-else-if="
widget.widgetType === SysCustomWidgetType.Date ||
widget.widgetType === SysCustomWidgetType.DateRange
"
>
<div class="el-date-table-cell">
<span class="el-date-table-cell__text">{{ scope.text }}</span>
</div>
</template>
<template v-else-if="widget.widgetType === SysCustomWidgetType.CheckBox">
<el-checkbox v-for="item in getAllDropdownData" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</template>
<template v-else-if="widget.widgetType === SysCustomWidgetType.Select">
<el-option
v-for="item in getAllDropdownData"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</template>
<template v-if="widget.widgetType === SysCustomWidgetType.Link">
<span>{{ widget.props.showText || widget.showName }}</span>
</template>
</template>
</component>
</template>
<script setup lang="ts">
import {
ElCascader,
ElCheckboxGroup,
ElDatePicker,
ElInput,
ElInputNumber,
ElLink,
ElRadioGroup,
ElSelect,
ElSwitch,
} from 'element-plus';
import { ANY_OBJECT } from '@/types/generic';
import { OnlineFormEventType, SysCustomWidgetType, SysOnlineFormType } from '@/common/staticDict';
import { SysOnlineColumnFilterType, SysOnlineFieldKind } from '@/common/staticDict/online';
import {
treeDataTranslate,
findTreeNode,
findItemFromList,
findTreeNodePath,
} from '@/common/utils';
import RichEditor from '@/components/RichEditor/index.vue';
import UserSelect from '@/components/UserSelect/index.vue';
import DeptSelect from '@/components/DeptSelect/index.vue';
import InputNumberRange from '@/components/InputNumberRange/index.vue';
import OnlineCustomLabel from './OnlineCustomLabel.vue';
import OnlineCustomUpload from './OnlineCustomUpload.vue';
import OnlineCustomTree from './OnlineCustomTree.vue';
import OnlineCustomText from './OnlineCustomText.vue';
import OnlineCustomImage from './OnlineCustomImage.vue';
import { WidgetEmit, WidgetProps } from './types/widget';
import { useWidget } from './hooks/widget';
type ValueType = string | boolean | Date | number | ANY_OBJECT | Array<ANY_OBJECT>;
interface IEmit extends WidgetEmit {
(event: 'change', value: ANY_OBJECT | undefined, params: ANY_OBJECT | null): void;
(event: 'update:value', value: ValueType | undefined): void;
(event: 'update:modelValue', value: ValueType | undefined): void;
(event: 'update:widget', value: ANY_OBJECT): void;
(event: 'input', value: ValueType | undefined): void;
}
const emit = defineEmits<IEmit>();
interface IProps extends WidgetProps {
value?: ValueType;
widget: ANY_OBJECT;
}
const pps = withDefaults(defineProps<IProps>(), {});
console.log('widget pps', pps);
const form = inject('form', () => {
console.error('OnlineCustomWidget: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const parentWidget = inject<ANY_OBJECT | null>('parentWidget', null);
const { propsWidget, onWidgetClick } = useWidget(pps, emit);
const dictDataList = ref<ANY_OBJECT[]>([]);
const tempValue = ref();
const multiSelect = computed(() => {
if (
[
SysCustomWidgetType.Select,
SysCustomWidgetType.CheckBox,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Tree,
].indexOf(pps.widget.widgetType) === -1
) {
return false;
}
if (pps.widget.widgetType === SysCustomWidgetType.CheckBox) return true;
if (
form().formType === SysOnlineFormType.QUERY ||
form().formType === SysOnlineFormType.ADVANCE_QUERY
) {
return (
pps.widget.column &&
pps.widget.column.filterType === SysOnlineColumnFilterType.MULTI_SELECT_FILTER
);
} else if (form().formType === SysOnlineFormType.REPORT) {
return SysCustomWidgetType.CheckBox === pps.widget.widgetType;
} else {
return pps.widget.column && pps.widget.column.fieldKind === SysOnlineFieldKind.MULTI_SELECT;
}
});
const isMobileFilter = computed(() => {
return (
[
SysCustomWidgetType.MobileRadioFilter,
SysCustomWidgetType.MobileCheckBoxFilter,
SysCustomWidgetType.MobileInputFilter,
SysCustomWidgetType.MobileDateRangeFilter,
SysCustomWidgetType.MobileNumberRangeFilter,
SysCustomWidgetType.MobileSwitchFilter,
].indexOf(pps.widget.widgetType) !== -1
);
});
const getAllDropdownData = computed(() => {
console.log('widget getAllDropdownData', pps.widget.props.supportAll, dictDataList);
if (pps.widget.props.supportAll) {
return [
{
id: '',
name: '全部',
},
...dictDataList.value,
];
} else {
return dictDataList.value;
}
});
const getWidgetStyle = computed(() => {
return {
width: pps.widget.widgetType !== SysCustomWidgetType.Link ? '100%' : undefined,
};
});
const getComponent = computed(() => {
console.log(
'widget getComponent',
pps.widget.widgetType,
SysCustomWidgetType.getValue(pps.widget.widgetType),
);
if (
[
SysCustomWidgetType.Text,
SysCustomWidgetType.Image,
SysCustomWidgetType.Upload,
SysCustomWidgetType.Link,
].indexOf(pps.widget.widgetType) === -1 &&
form().readOnly
) {
return OnlineCustomLabel;
}
let mode = form().mode;
console.log('widget getComponent mode', mode);
switch (pps.widget.widgetType) {
case SysCustomWidgetType.Label:
return OnlineCustomLabel;
case SysCustomWidgetType.Input:
return ElInput;
case SysCustomWidgetType.NumberInput:
return ElInputNumber;
case SysCustomWidgetType.NumberRange:
return InputNumberRange;
case SysCustomWidgetType.Switch:
return ElSwitch;
case SysCustomWidgetType.Radio:
return ElRadioGroup;
case SysCustomWidgetType.CheckBox:
return ElCheckboxGroup;
case SysCustomWidgetType.Select:
return ElSelect;
case SysCustomWidgetType.Cascader:
return ElCascader;
case SysCustomWidgetType.Date:
return ElDatePicker;
case SysCustomWidgetType.DateRange:
return ElDatePicker;
case SysCustomWidgetType.Upload:
return OnlineCustomUpload;
case SysCustomWidgetType.RichEditor:
return RichEditor;
case SysCustomWidgetType.Link:
return ElLink;
case SysCustomWidgetType.UserSelect:
return UserSelect;
case SysCustomWidgetType.DeptSelect:
return DeptSelect;
case SysCustomWidgetType.Tree:
return OnlineCustomTree;
case SysCustomWidgetType.Text:
return OnlineCustomText;
case SysCustomWidgetType.Image:
return OnlineCustomImage;
default:
console.warn('widget getComponent 未知类型');
return 'div';
}
});
const getLinkHerf = computed(() => {
let temp = pps.widget.widgetType === SysCustomWidgetType.Link ? pps.widget.props.href : undefined;
return temp;
});
const isDictWidget = computed(() => {
return (
[
SysCustomWidgetType.Select,
SysCustomWidgetType.CheckBox,
SysCustomWidgetType.Radio,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Tree,
SysCustomWidgetType.MobileCheckBoxFilter,
SysCustomWidgetType.MobileRadioFilter,
].indexOf(pps.widget.widgetType) !== -1
);
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const bindValue = computed<string | number | boolean | any[] | ANY_OBJECT | Date | undefined>({
get() {
console.log('widget bind props.value >>', pps.value);
let tempValue = pps.value === undefined ? '' : pps.value;
if (isDictWidget.value) tempValue = tempValue == null ? '' : tempValue + '';
if (multiSelect.value && pps.value && typeof tempValue === 'string') {
tempValue = tempValue.split(',');
if (Array.isArray(tempValue)) {
tempValue = tempValue.filter(item => {
return item != null && item !== '';
});
}
}
if (pps.widget.widgetType === SysCustomWidgetType.CheckBox) {
return tempValue || [];
} else if (pps.widget.widgetType === SysCustomWidgetType.Cascader) {
if (multiSelect.value) {
// 多选
if (Array.isArray(tempValue)) {
return tempValue
.map(item => {
let temp = findTreeNodePath(dictDataList.value, item);
if (Array.isArray(temp) && temp.length > 0) return temp;
return null;
})
.filter(item => item != null);
} else {
let temp = findTreeNodePath(dictDataList.value, tempValue.toString());
return temp || [];
}
} else {
// 单选
let temp = findTreeNodePath(dictDataList.value, tempValue.toString());
return temp.length > 0 ? temp : tempValue;
}
} else if (pps.widget.widgetType === SysCustomWidgetType.NumberInput) {
return tempValue as number;
} else {
return tempValue;
}
},
set(val) {
console.log('widget .......bindValue..........', val, typeof val);
onValueInput(val as ANY_OBJECT | undefined);
},
});
const isUploadImage = computed(() => {
if (pps.widget.widgetType !== SysCustomWidgetType.Upload) return false;
let column = pps.widget.bindData.column || pps.widget.column;
return column ? column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE : false;
});
const showRightIcon = computed(() => {
return (
[
SysCustomWidgetType.Select,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Date,
SysCustomWidgetType.UserSelect,
SysCustomWidgetType.DeptSelect,
SysCustomWidgetType.Calendar,
].indexOf(pps.widget.widgetType) !== -1
);
});
const getWidgetProps = computed(() => {
let props = {
...(pps.widget.props || {}),
};
// 日期组件根据类型设置format
if (
pps.widget.widgetType === SysCustomWidgetType.Date ||
pps.widget.widgetType === SysCustomWidgetType.DateRange
) {
props['value-format'] = 'YYYY-MM-DD HH:mm:ss';
}
console.log('widget props', SysCustomWidgetType.getValue(pps.widget.widgetType), props);
return {
...props,
clearable: true,
filterable: true,
readOnly:
pps.widget.widgetType === SysCustomWidgetType.Upload
? form().readOnly || props.readOnly
: undefined,
options:
pps.widget.widgetType === SysCustomWidgetType.Cascader ? dictDataList.value : undefined,
props:
pps.widget.widgetType === SysCustomWidgetType.Cascader
? {
label: 'name',
value: 'id',
multiple: multiSelect.value,
checkStrictly: true,
}
: undefined,
operationList: pps.widget.operationList,
multiple: multiSelect.value,
'collapse-tags': multiSelect.value,
dataList: pps.widget.widgetType === SysCustomWidgetType.Tree ? dictDataList.value : undefined,
dictDataList: isMobileFilter.value ? getAllDropdownData.value : undefined,
href: getLinkHerf.value,
widget: pps.widget,
};
});
const getDisabledStatus = () => {
return pps.widget.props.disabled;
};
const parseValue = (val: ValueType): ANY_OBJECT => {
if (pps.widget.widgetType === SysCustomWidgetType.Cascader) {
if (multiSelect.value && Array.isArray(val)) {
return val
.map((item: ANY_OBJECT) => {
return item[item.length - 1];
})
.filter((item: ANY_OBJECT) => item != null);
} else {
return Array.isArray(val) ? val[val.length - 1] : undefined;
}
} else {
return val as ANY_OBJECT;
}
};
const onValueInput = (val: ANY_OBJECT | undefined) => {
let tempValue: ValueType | undefined = undefined;
if (val) {
console.log('widget typeof val', typeof val, val instanceof InputEvent, val instanceof Event);
if (val instanceof InputEvent) {
console.warn('widget input value is InputEvent');
return;
} else if (val instanceof Event) {
console.log('wiget >>>>>>> value >>>>>>', val, val.target instanceof HTMLInputElement);
if (val.target instanceof HTMLInputElement) {
tempValue = val.target.value;
}
} else {
tempValue = parseValue(val);
}
if (multiSelect.value && Array.isArray(tempValue) /*&& tempValue.length > 0*/) {
tempValue = tempValue.join(',') + ',';
}
}
console.log('widget update:value', tempValue);
emit('update:value', tempValue);
emit('update:modelValue', tempValue);
// ElDatePicker(DateRange)有了这个事件才能更新显示
emit('input', tempValue);
};
const onValueChange = (
val: ANY_OBJECT | undefined,
selectRow: ANY_OBJECT | undefined = undefined,
) => {
let tempVal: ValueType | undefined = undefined;
let dictData = null;
console.log('widget onValueChange >>>', val);
if (val) {
tempVal = parseValue(val);
if (multiSelect.value) {
dictData = val
.map((item: string) => {
if (pps.widget.widgetType === SysCustomWidgetType.Cascader) {
if (Array.isArray(item)) {
item = item[item.length - 1];
}
return findTreeNode(dictDataList.value, item, 'id', 'children');
} else {
return findItemFromList(dictDataList.value, item, 'id');
}
})
.filter((item: ANY_OBJECT) => item != null);
} else {
let id = val.toString();
if (pps.widget.widgetType === SysCustomWidgetType.Cascader) {
if (Array.isArray(val)) {
val = val[val.length - 1];
}
dictData = findTreeNode(dictDataList.value, id, 'id', 'children');
} else {
dictData = findItemFromList(dictDataList.value, id, 'id');
}
}
}
// 这个似乎没有起到什么作用
emit('change', tempVal, {
dictData: dictData,
selectRow: selectRow,
});
onValueInput(tempVal);
};
const loadDropdownData = () => {
if (pps.widget == null || !isDictWidget.value) {
return;
}
dictDataList.value = [];
setTimeout(() => {
let dictInfo = (pps.widget.props.dictInfo || {}).dict;
console.log('widget OnlineCustomWidget loadDropdownData', dictInfo, pps);
if (dictInfo && form().getDictDataList) {
let dictCall;
if (form().pageCode != null) {
// 报表字典
dictCall = form().getDictDataList(dictInfo, form().getDropdownParams(pps.widget));
} else {
// 在线表单字典
dictCall = form().getDictDataList(dictInfo, form().getDropdownParams(pps.widget));
}
dictCall
.then((res: ANY_OBJECT[]) => {
console.log('OnlineCustomWidget loadDropdownData', res);
res.forEach((item: ANY_OBJECT) => {
item.id = item.id + '';
if (item.parentId) item.parentId = item.parentId + '';
});
// 级联组件将列表转换成树
if (pps.widget.widgetType === SysCustomWidgetType.Cascader) {
res = treeDataTranslate(res, 'id', 'parentId');
}
dictDataList.value = res;
})
.catch((e: Error) => {
console.log(e);
});
}
}, 30);
};
const getHtml = () => {
console.log('widget call getHtml()');
const refs = (getCurrentInstance()?.refs || {}) as ANY_OBJECT;
console.log('refs', refs);
if (pps.widget.widgetType === SysCustomWidgetType.RichEditor) {
return refs[pps.widget.variableName] ? refs[pps.widget.variableName].getHtml() : undefined;
}
};
const reset = () => {
console.log('widget call reset()');
onValueInput(undefined);
onValueChange(undefined);
nextTick(() => {
loadDropdownData();
});
};
const refresh = () => {
const refs = (getCurrentInstance()?.refs || {}) as ANY_OBJECT;
console.log('widget call refresh() refs:', refs);
if (
refs[pps.widget.variableName] &&
typeof refs[pps.widget.variableName].refresh === 'function'
) {
refs[pps.widget.variableName].refresh();
}
};
defineExpose({
getHtml,
reset,
refresh,
});
watch(
() => pps.widget,
() => {
console.log('props widget change', pps.widget);
if (pps.widget) loadDropdownData();
},
{
deep: true,
immediate: true,
},
);
// watch(
// () => pps.widget.props.dictInfo,
// () => {
// if (pps.widget) loadDropdownData();
// },
// {
// deep: true,
// immediate: true,
// },
// );
// 如果父组件为数据表格或者列表组件,那么子组件绑定表必须跟父组件一致
watch(
() => parentWidget,
(newValue, oldValue) => {
const widget = { ...propsWidget.value };
if (parentWidget != null && propsWidget.value) {
// 如果绑定的数据表改变了,修改子组件绑定的数据表
if (parentWidget.bindData.tableId !== propsWidget.value.bindData.tableId) {
widget.props.bindData = {
...parentWidget.bindData,
};
widget.showName = undefined;
if (widget.widgetType === SysCustomWidgetType.Text) {
widget.props.text = '文本内容';
}
}
} else {
if (newValue == null && oldValue != null) {
widget.bindData = {
dataType: 0,
tableId: undefined,
table: undefined,
columnId: undefined,
column: undefined,
};
}
}
propsWidget.value = widget;
},
{
deep: true,
immediate: true,
},
);
onMounted(() => {
console.log('getComponent', getComponent.value, getWidgetProps.value);
//propsWidget.value.widgetImpl = getCurrentInstance();
//propsWidget.value.parent = parentWidget;
});
</script>

View File

@@ -0,0 +1,496 @@
<template>
<vxe-table
ref="table"
:key="tableKey"
:data="dataList"
:height="height"
show-overflow="title"
show-header-overflow="title"
header-cell-class-name="table-header-gray"
:keep-source="false"
:row-config="{ height: rowHeight, isHover: true }"
:seq-config="{ seqMethod }"
@sort-change="onSortChange"
:sort-config="{ remote: remoteSort }"
>
<vxe-column v-if="tableColumnList.length > 0" type="seq" title="序号" :width="60" />
<vxe-column title="工单编号" show-overflow="tooltip" field="workOrderCode" :width="200" />
<template v-for="tableColumn in tableColumnList" :key="tableColumn.id">
<!-- Boolean类型字段 -->
<vxe-column
v-if="tableColumn.column && tableColumn.column.objectFieldType === 'Boolean'"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<el-tag
:size="layoutStore.defaultFormItemSize"
:type="getObjectValue(row, tableColumn.showFieldName) ? 'success' : 'danger'"
>
{{ getObjectValue(row, tableColumn.showFieldName) ? '是' : '否' }}
</el-tag>
</template>
</vxe-column>
<!-- 图片类型字段 -->
<vxe-column
v-else-if="
tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE
"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<el-image
v-for="item in parseTableUploadData(tableColumn, row)"
:preview-src-list="getTablePictureList(tableColumn, row)"
class="table-cell-image"
:key="item.url"
:src="item.url"
fit="fill"
>
<template v-slot:error>
<div class="table-cell-image">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</template>
</vxe-column>
<!-- 文件下载类型字段 -->
<vxe-column
v-else-if="tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD"
:title="tableColumn.showName"
:width="tableColumn.columnWidth"
>
<template #default="{ row }">
<a
v-for="item in parseTableUploadData(tableColumn, row)"
:key="item.url"
href="javascript:void(0);"
@click="downloadFile(item.url, item.name)"
>
{{ item.name }}
</a>
</template>
</vxe-column>
<!-- 其他类型 -->
<vxe-column
v-else
:title="tableColumn.showName"
:field="tableColumn.showFieldName"
:width="tableColumn.columnWidth"
:sortable="tableColumn.sortable"
/>
</template>
<vxe-column title="当前任务" field="runtimeTaskInfo.taskName" :min-width="100" />
<vxe-column title="流程创建时间" field="createTime" :min-width="180" />
<vxe-column title="流程状态" field="latestApprovalStatusDictMap.name" :min-width="100" />
<vxe-column title="工单状态" field="flowStatusShowName" :min-width="100" />
<vxe-column title="操作" :width="160" fixed="right" :show-overflow="false">
<template #default="{ row }">
<el-button
link
:size="layoutStore.defaultFormItemSize"
v-if="getPrintOperation != null"
:class="getPrintOperation.btnClass"
@click.stop="onPrint(getPrintOperation, row)"
>
{{ getPrintOperation.name || '打印' }}
</el-button>
<el-button
link
type="primary"
:size="layoutStore.defaultFormItemSize"
v-if="(row.initTaskInfo || {}).taskKey !== (row.runtimeTaskInfo || {}).taskKey"
@click.stop="onViewWorkOrder(row)"
>
详情
</el-button>
<el-button
link
type="primary"
:size="layoutStore.defaultFormItemSize"
v-if="(row.initTaskInfo || {}).taskKey === (row.runtimeTaskInfo || {}).taskKey"
@click.stop="onHandlerWorkOrder(row)"
>
办理
</el-button>
<el-button
link
type="primary"
:size="layoutStore.defaultFormItemSize"
@click.stop="onHandlerRemindClick(row)"
>
催办
</el-button>
<el-button
link
type="primary"
:size="layoutStore.defaultFormItemSize"
@click.stop="onCancelWorkOrder(row)"
>
撤销
</el-button>
</template>
</vxe-column>
<template v-slot:empty>
<div class="table-empty unified-font">
<img src="@/assets/img/empty.png" />
<span>暂无数据</span>
</div>
</template>
</vxe-table>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus';
import { Picture } from '@element-plus/icons-vue';
import { VxeTable, VxeColumn } from 'vxe-table';
import { ANY_OBJECT } from '@/types/generic';
import { SortInfo } from '@/common/types/sortinfo';
import { SysOnlineFieldKind } from '@/common/staticDict/online';
import { findItemFromList, getObjectValue } from '@/common/utils';
import { SysCustomWidgetOperationType } from '@/common/staticDict';
import widgetData from '@/online/config/index';
import { downloadBlob, post } from '@/common/http/request';
import { useDownload } from '@/common/hooks/useDownload';
import { useUpload } from '@/common/hooks/useUpload';
import { API_CONTEXT } from '@/api/config';
const emit = defineEmits<{
viewWorkOrder: [ANY_OBJECT, ANY_OBJECT | null];
handlerWorkOrder: [ANY_OBJECT, ANY_OBJECT | null];
handlerRemind: [ANY_OBJECT, ANY_OBJECT | null];
cancelWorkOrder: [ANY_OBJECT, ANY_OBJECT | null];
}>();
interface IProps {
widget: ANY_OBJECT;
dataList?: ANY_OBJECT[];
height?: number | string;
border?: string;
// 是否支持行内编辑
rowEdit?: boolean;
// 是否支持多选
multiSelect?: boolean;
// 表格操作列表
operationList?: ANY_OBJECT[];
// 获取行序号
getTableIndex?: (value: number) => number;
// 排序改变
sortChange?: (value: SortInfo) => void;
// 多选选中改变
onSelectChange?: (values: Array<ANY_OBJECT>) => void;
// 单选中改变
onRadioChange?: (value: ANY_OBJECT) => void;
}
const props = withDefaults(defineProps<IProps>(), {
isEdit: false,
dataList: () => [],
height: 'auto',
border: 'full',
rowEdit: false,
multiSelect: false,
operationList: () => [],
});
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
const form = inject('form', () => {
console.error('OnlineCustomWorkFlowTable: form not injected');
return { isEdit: false } as ANY_OBJECT;
});
const { downloadFile } = useDownload();
const { parseUploadData } = useUpload();
const table = ref();
const sortInfo = ref<SortInfo | null>();
const tableColumnList = computed(() => {
let tempList =
props.widget && props.widget.props && Array.isArray(props.widget.props.tableColumnList)
? props.widget.props.tableColumnList
: [];
tempList.forEach((item: ANY_OBJECT) => {
if (item.fieldType === 0 || item.fieldType == null) {
// 绑定表字段
item.showFieldName =
'masterData.' +
(item.relation ? item.relation.variableName : '') +
(item.column || {}).columnName;
if ((item.column || {}).dictInfo) {
item.showFieldName = item.showFieldName + 'DictMap.name';
}
} else {
// 自定义字段
item.showFieldName = item.customFieldName;
}
});
return tempList;
});
const hasImageColumn = computed(() => {
return (
tableColumnList.value.filter((tableColumn: ANY_OBJECT) => {
return tableColumn.column && tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE;
}).length > 0
);
});
const rowHeight = computed(() => {
return hasImageColumn.value ? 80 : 35;
});
const tableKey = computed(() => {
return (props.widget || {}).variableName + new Date().getTime() + tableColumnList.value.length;
});
const remoteSort = computed(() => {
return false;
});
const getPrintOperation = computed(() => {
let operation = findItemFromList(
form().operationList,
SysCustomWidgetOperationType.PRINT,
'type',
);
return operation && operation.enabled ? operation : null;
});
const buildFlowParam = computed(() => {
let flowParam: ANY_OBJECT = {};
let flowData = form().flowData;
if (flowData) {
if (flowData.processDefinitionKey)
flowParam.processDefinitionKey = flowData.processDefinitionKey;
if (flowData.processInstanceId) flowParam.processInstanceId = flowData.processInstanceId;
if (flowData.taskId) flowParam.taskId = flowData.taskId;
}
return flowParam;
});
const seqMethod = (data: ANY_OBJECT) => {
if (props.getTableIndex) {
return props.getTableIndex(data.seq - 1);
} else {
return data.seq;
}
};
const onSortChange = (data: ANY_OBJECT) => {
if (!props.widget.props.paged) return;
let fieldName = data.property.replace('DictMap.name', '');
let order = data.order;
if (order == null) {
fieldName = undefined;
}
if (order === 'asc') order = 'ascending';
if (
sortInfo.value != null &&
sortInfo.value.prop === fieldName &&
sortInfo.value.order === order
) {
return;
}
sortInfo.value = {
prop: fieldName,
order: order,
};
if (props.sortChange) {
props.sortChange(sortInfo.value);
}
};
const onViewWorkOrder = (row: ANY_OBJECT) => {
emit('viewWorkOrder', row, widgetData.getWidgetAttribute(props.widget.widgetType));
};
const onHandlerWorkOrder = (row: ANY_OBJECT) => {
emit('handlerWorkOrder', row, widgetData.getWidgetAttribute(props.widget.widgetType));
};
const onHandlerRemindClick = (row: ANY_OBJECT) => {
emit('handlerRemind', row, widgetData.getWidgetAttribute(props.widget.widgetType));
};
const onCancelWorkOrder = (row: ANY_OBJECT) => {
emit('cancelWorkOrder', row, widgetData.getWidgetAttribute(props.widget.widgetType));
};
const refreshColumn = () => {
nextTick(() => {
if (table.value) table.value.refreshColumn();
});
};
const getDownloadUrl = (tableColumn: ANY_OBJECT) => {
let downloadUrl = null;
if (form().flowData != null) {
downloadUrl = API_CONTEXT + '/flow/flowOnlineOperation/download';
} else {
if (tableColumn.relationId) {
downloadUrl =
API_CONTEXT +
'/online/onlineOperation/downloadOneToManyRelation/' +
(props.widget.datasource || {}).variableName;
} else {
downloadUrl =
API_CONTEXT +
'/online/onlineOperation/downloadDatasource/' +
(props.widget.datasource || {}).variableName;
}
}
return downloadUrl;
};
const parseTableUploadData = (tableColumn: ANY_OBJECT, row: ANY_OBJECT) => {
let jsonData = getObjectValue(row, tableColumn.showFieldName);
if (jsonData == null) return [];
let downloadParams: ANY_OBJECT = {
...buildFlowParam.value,
datasourceId: props.widget.datasource.datasourceId,
relationId: tableColumn.relationId,
fieldName: tableColumn.column.columnName,
asImage: tableColumn.column.fieldKind === SysOnlineFieldKind.UPLOAD_IMAGE,
};
if (props.widget.primaryColumnName != null) {
downloadParams.dataId = row[props.widget.primaryColumnName] || '';
}
let downloadUrl = getDownloadUrl(tableColumn);
let temp = JSON.parse(jsonData.toString());
temp = Array.isArray(temp)
? temp.map(item => {
return {
...item,
downloadUri: downloadUrl,
};
})
: [];
return parseUploadData(JSON.stringify(temp), downloadParams);
};
const getTablePictureList = (tableColumn: ANY_OBJECT, row: ANY_OBJECT) => {
let temp = parseTableUploadData(tableColumn, row);
if (Array.isArray(temp)) {
return temp.map(item => item.url);
}
};
const getPrintParamItem = (row: ANY_OBJECT, printParamList: ANY_OBJECT[]) => {
let param;
if (Array.isArray(printParamList)) {
param = printParamList
.map(item => {
let columnId = item.paramValue;
if (columnId != null) {
let column = form().columnMap.get(columnId);
let value;
if (column == null) {
value = row[item.paramValue];
} else {
value = (row.masterData || {})[column.columnName];
}
if (item.paramName != null && value != null) {
return {
paramName: item.paramName,
paramValue: value,
};
}
}
return null;
})
.filter(item => item != null);
}
return param;
};
const onPrint = (operation: ANY_OBJECT, row: ANY_OBJECT) => {
if (operation == null || row == null || row.processDefinitionKey == null) return;
let printParam;
let temp = getPrintParamItem(row, operation.printParamList);
printParam = temp ? [temp] : [];
let params = {
printId: operation.printTemplateId,
printParams: printParam,
};
post<string>(
API_CONTEXT + '/flow/flowOnlineOperation/printWorkOrder/' + row.processDefinitionKey,
params,
)
.then(res => {
let downloadUrl = res.data;
downloadBlob(downloadUrl, {})
.then((blobData: Blob) => {
let pdfUrl = window.URL.createObjectURL(blobData);
window.open('./lib/pdfjs/web/viewer.html?file=' + pdfUrl);
})
.catch(e => {
console.log(e);
ElMessage.error(e);
});
})
.catch(e => {
console.warn(e);
});
};
const formatListData = (data: ANY_OBJECT) => {
Object.keys(data).forEach(key => {
let subData = data[key];
if (typeof subData === 'object' && key.indexOf('DictMap') === -1) {
formatListData(subData);
} else {
// 如果是多选字典字段那么把选中的字典值拼接成DictMap去显示
if (key.indexOf('DictMapList') !== -1 && Array.isArray(data[key])) {
let newKey = key.replace('DictMapList', 'DictMap');
data[newKey] = {
id: data[key.replace('DictMapList', '')],
name: data[key].map((subItem: ANY_OBJECT) => subItem.name).join(', '),
};
}
}
});
};
watch(
() => props.dataList,
newVal => {
if (newVal) {
newVal.forEach(item => {
formatListData(item.masterData);
});
}
},
{
deep: true,
immediate: true,
},
);
watch(
() => tableColumnList.value,
() => {
refreshColumn();
},
{
deep: true,
immediate: true,
},
);
// watch(
// () => props.widget,
// newVal => {
// if (newVal) {
// newVal.widgetImpl = getCurrentInstance();
// }
// },
// {
// deep: true,
// immediate: true,
// },
// );
watch(
() => props.widget,
() => {
// if (newVal) {
// newVal.widgetImpl = getCurrentInstance();
// }
refreshColumn();
},
{
deep: true,
immediate: true,
},
);
</script>

View File

@@ -0,0 +1,264 @@
<template>
<el-collapse-item :title="attributeItem.name" class="attribute-collapse">
<template #title>
<el-row class="attribute-item-title">
<span style="padding: 0 16px">{{ attributeItem.name }}</span>
</el-row>
</template>
<el-input
v-if="inputWidgetType === SysCustomWidgetType.Input"
size="default"
style="width: 100%"
clearable
v-model="valStr"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
<el-row v-if="inputWidgetType === SysCustomWidgetType.NumberInput" type="flex">
<el-input-number
size="default"
clearable
style="width: 100%"
controls-position="right"
:min="attributeItem.min"
:max="attributeItem.max"
v-model="valNum"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
</el-row>
<el-row v-if="inputWidgetType === SysCustomWidgetType.Radio" type="flex" align="middle">
<el-radio-group
size="default"
v-model="valNum"
v-bind="attributeProps"
@change="onValueChange"
>
<el-radio-button v-for="item in dropdownData" :key="item.id" :value="item.id">
{{ item.name }}
</el-radio-button>
</el-radio-group>
</el-row>
<el-slider
v-if="inputWidgetType === SysCustomWidgetType.Slider"
style="width: 95%; margin-left: 5px"
size="default"
:min="attributeItem.min"
:max="attributeItem.max"
v-model="valNum"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
<el-row v-if="inputWidgetType === SysCustomWidgetType.Switch" type="flex" align="middle">
<el-select
size="default"
style="width: 100%"
v-model="valBool"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
placeholder=""
>
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
<!--
<el-switch size="default"
:value="value" @input="onValueChange"
@change="$emit('change')"
/>
-->
</el-row>
<el-select
v-if="inputWidgetType === SysCustomWidgetType.Select"
style="width: 100%"
clearable
size="default"
v-model="valNum"
v-bind="attributeProps"
@change="onValueChange"
placeholder=""
>
<el-option v-for="item in dropdownData" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-color-picker
v-if="inputWidgetType === SysCustomWidgetType.ColorPicker"
size="default"
v-model="valStr"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
<component
v-if="attributeItem.customComponent"
:is="components[attributeItem.customComponent.component]"
style="width: 100%"
v-bind="attributeProps"
v-model:value="val"
@input="onValueChange"
/>
</el-collapse-item>
</template>
<script setup lang="ts">
import { Arrayable } from 'element-plus/es/utils';
import { findItemFromList } from '@/common/utils';
import { ANY_OBJECT } from '@/types/generic';
import { SysCustomWidgetType } from '@/common/staticDict';
import OnlineTableColumnSetting from '../components/OnlineTableColumnSetting/index.vue';
import CustomWidgetDictSetting from '../components/CustomWidgetDictSetting/index.vue';
import OnlineTabPanelSetting from '../components/OnlineTabPanelSetting/index.vue';
import OnlineImageUrlInput from '../components/OnlineImageUrlInput.vue';
const components: ANY_OBJECT = {
CustomWidgetDictSetting,
OnlineTabPanelSetting,
OnlineImageUrlInput,
};
const emit = defineEmits<{
'update:value': [string | number | Arrayable<number> | boolean | null | undefined];
input: [string | number | Arrayable<number> | null | undefined];
change: [];
}>();
const Props = defineProps<{
value?: string | number | boolean | ANY_OBJECT;
attributeItem: ANY_OBJECT;
}>();
console.log('AttributeCollapse editWidgetAttribute.props', Props);
const formConfig = inject('formConfig', () => {
console.error('editWidgetAttribute: formConfig not injected');
return {} as ANY_OBJECT;
});
const dropdownData = ref<ANY_OBJECT[]>([]);
const attributeProps = ref<ANY_OBJECT>({});
const val = computed({
get() {
return Props.value;
},
set(value) {
console.log('val change', value);
},
});
const valStr = computed({
get() {
return Props.value as string;
},
set(value) {
console.log('val change str', value);
},
});
const valBool = computed({
get() {
return Props.value as boolean;
},
set(value) {
console.log('val change boolean', value);
},
});
const valNum = computed({
get() {
return Props.value as number;
},
set(value) {
console.log('val change number', value);
},
});
const inputWidgetType = computed(() => {
return Props.attributeItem.widgetType;
});
const currentWidget = computed(() => {
return formConfig().currentWidget;
});
const isWidgetDictSelect = computed(() => {
if (currentWidget.value == null) return false;
let temp =
[
SysCustomWidgetType.Select,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Radio,
SysCustomWidgetType.CheckBox,
].indexOf(currentWidget.value.widgetType) !== -1;
return temp && currentWidget.value && Props.attributeItem.attributeKey === 'dictId';
});
const onValueChange = (val: string | number | Arrayable<number> | null | undefined) => {
console.log('AttributeCollapse editWidgetAttribute.onValueChange', typeof val, val);
if (isWidgetDictSelect.value) {
currentWidget.value.dictInfo = findItemFromList(formConfig().dictList, val as string, 'id');
}
if (val && typeof val === 'object') {
emit('input', (val as ANY_OBJECT).target?.value);
} else {
emit('input', val);
}
if (Props.attributeItem.customComponent) emit('change');
};
const loadDropdownData = () => {
dropdownData.value = [];
if (typeof Props.attributeItem.dropdownList === 'function') {
if (isWidgetDictSelect.value) {
dropdownData.value = Props.attributeItem.dropdownList(formConfig());
} else {
dropdownData.value = Props.attributeItem.dropdownList();
}
} else {
dropdownData.value = Array.isArray(Props.attributeItem.dropdownList)
? Props.attributeItem.dropdownList
: [];
}
if ((dropdownData.value || []).length === 1) onValueChange(dropdownData.value[0].id);
};
const getProps = () => {
let props: ANY_OBJECT = {};
if (Props.attributeItem.customComponent) {
props = Props.attributeItem.customComponent.props || {};
} else {
props = Props.attributeItem.props || {};
}
props.disabled = Props.attributeItem.disabled || props.disabled;
attributeProps.value = Object.keys(props).reduce((retObj: ANY_OBJECT, key: string) => {
let value = props[key];
if (typeof value === 'function') value = value(formConfig());
retObj[key] = value;
return retObj;
}, {});
console.log('>>> attributeProps', attributeProps.value);
};
watch(
() => Props.attributeItem,
() => {
loadDropdownData();
},
{
immediate: true,
},
);
watch(
() => currentWidget.value,
() => {
loadDropdownData();
getProps();
},
{
deep: true,
immediate: true,
},
);
</script>
<style scoped>
.attribute-collapse :deep(.el-textarea__inner) {
min-height: 100px !important;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<el-collapse style="border: none" :accordion="true" class="unified-font" :modelValue="undefined">
<slot />
<EditWidgetAttribute
v-for="attributeItem in attributeList"
:key="attributeItem.attributeKey"
v-model:value="val[attributeItem.attributeKey]"
:attributeItem="attributeItem"
/>
</el-collapse>
</template>
<script setup lang="ts">
import { ANY_OBJECT } from '@/types/generic';
import EditWidgetAttribute from './editWidgetAttribute.vue';
const props = defineProps<{ widget: ANY_OBJECT; value?: ANY_OBJECT }>();
const formConfig = inject('formConfig', () => {
console.error('AttributeCollapse: formConfig not injected');
return {} as ANY_OBJECT;
});
const attributeList = ref<ANY_OBJECT[]>([]);
const val = computed<ANY_OBJECT>({
get() {
console.log('AttributeCollapse value', props.value, currentWidget.value.props);
//return props.value || {};
return currentWidget.value.props || {};
},
set(val) {
console.log('AttributeCollapse value change', val);
},
});
const currentWidget = computed(() => {
return formConfig().currentWidget || {};
});
const getWidgetAttributeConfig = computed(() => {
// props.widget的变化滞后
// console.log(
// 'AttributeCollapse getWidgetAttributeConfig widget',
// props.widget,
// currentWidget.value,
// );
return (formConfig().getWidgetAttribute(currentWidget.value.widgetType) || {}).attribute;
});
const getAttributeItemValue = (attributeItem: ANY_OBJECT) => {
if (typeof attributeItem === 'function') {
return attributeItem(formConfig());
} else {
return attributeItem;
}
};
const buildAttributeList = () => {
const values: ANY_OBJECT[] = [];
const config: ANY_OBJECT = getWidgetAttributeConfig.value;
console.log('AttributeCollapse getWidgetAttributeConfig', config);
if (config) {
Object.keys(config).forEach(attributeKey => {
let attributeItem = config[attributeKey];
if (attributeItem != null) {
let temp = Object.keys(attributeItem).reduce(
(retObj: ANY_OBJECT, subKey: string) => {
retObj[subKey] = getAttributeItemValue(attributeItem[subKey]);
return retObj;
},
{
attributeKey: attributeKey,
},
);
if (temp.visible == null || temp.visible) values.push(temp);
}
});
}
attributeList.value = values;
console.log('AttributeCollapse attributeList', attributeList);
};
watch(
() => currentWidget.value,
() => {
console.log('AttributeCollapse watch widget props', currentWidget.value, {
...currentWidget.value,
});
buildAttributeList();
},
{
immediate: true,
},
);
</script>

View File

@@ -0,0 +1,270 @@
<template>
<el-form-item :label="attributeItem.name" class="widget-attribute-item">
<el-input
v-if="inputWidgetType === SysCustomWidgetType.Input"
style="width: 100%"
clearable
v-model="valStr"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
<el-row v-if="inputWidgetType === SysCustomWidgetType.NumberInput">
<el-input-number
clearable
style="width: 100%"
controls-position="right"
:min="attributeItem.min"
:max="attributeItem.max"
v-model="valNum"
@input="onValueChange"
v-bind="attributeProps"
@change="$emit('change')"
/>
</el-row>
<el-row v-if="inputWidgetType === SysCustomWidgetType.Radio" align="middle">
<el-radio-group
size="default"
v-model="valStr"
v-bind="attributeProps"
@change="onValueChange"
>
<el-radio-button v-for="item in dropdownData" :key="item.id" :value="item.id">{{
item.name
}}</el-radio-button>
</el-radio-group>
</el-row>
<el-slider
v-if="inputWidgetType === SysCustomWidgetType.Slider"
style="width: 95%; margin-left: 5px"
:min="attributeItem.min"
:max="attributeItem.max"
v-model="valNum"
@input="onValueChange"
v-bind="attributeProps"
/>
<el-row v-if="inputWidgetType === SysCustomWidgetType.Switch" align="middle">
<el-select
style="width: 100%"
v-model="valBool"
v-bind="attributeProps"
placeholder=""
@change="onValueChange"
>
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
<!--
<el-switch
:value="value" @input="onValueChange"
/>
-->
</el-row>
<el-select
v-if="inputWidgetType === SysCustomWidgetType.Select"
style="width: 100%"
clearable
v-model="valNum"
@input="onValueChange"
v-bind="attributeProps"
placeholder=""
@change="onValueChange"
>
<el-option v-for="item in dropdownData" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-color-picker
v-if="inputWidgetType === SysCustomWidgetType.ColorPicker"
size="default"
v-model="valStr"
v-bind="attributeProps"
@change="onValueChange"
/>
<component
v-if="attributeItem.customComponent"
:is="components[attributeItem.customComponent.component]"
style="width: 100%"
v-bind="attributeProps"
v-model:value="val"
@change="onValueChange"
/>
</el-form-item>
</template>
<script setup lang="ts">
import { Arrayable } from 'element-plus/es/utils';
import { ANY_OBJECT } from '@/types/generic';
import { findItemFromList } from '@/common/utils';
import { SysCustomWidgetType } from '@/common/staticDict/index';
import OnlineTableColumnSetting from '../components/OnlineTableColumnSetting/index.vue';
import CustomWidgetDictSetting from '../components/CustomWidgetDictSetting/index.vue';
import OnlineTabPanelSetting from '../components/OnlineTabPanelSetting/index.vue';
import OnlineImageUrlInput from '../components/OnlineImageUrlInput.vue';
const components: ANY_OBJECT = {
OnlineTableColumnSetting,
CustomWidgetDictSetting,
OnlineTabPanelSetting,
OnlineImageUrlInput,
};
const emit = defineEmits<{
'update:value': [ANY_OBJECT | string | number | Arrayable<number> | boolean | null | undefined];
input: [ANY_OBJECT | string | number | Arrayable<number> | boolean | null | undefined];
change: [];
}>();
const props = defineProps<{
value?: string | number | Arrayable<number> | boolean | ANY_OBJECT | null;
attributeItem: ANY_OBJECT;
}>();
console.log('>>> >>> attributeItem', props.attributeItem);
const formConfig = inject('formConfig', () => {
console.error('editWidgetAttribute: formConfig not injected');
return {} as ANY_OBJECT;
});
const dropdownData = ref<ANY_OBJECT[]>([]);
const attributeProps = ref<ANY_OBJECT>({});
const val = computed({
get() {
console.log('get val', props.value);
return props.value;
},
set(value) {
console.log('val change', value);
onValueChange(value);
},
});
const valStr = computed({
get() {
console.log('get val as string', props.value);
return props.value as string;
},
set(value) {
console.log('val change str', value);
onValueChange(value);
},
});
const valBool = computed({
get() {
console.log('get val as boolean', props.value, typeof props.value);
return props.value as boolean;
},
set(value) {
console.log('val change boolean', value);
onValueChange(value);
},
});
const valNum = computed({
get() {
console.log('get val as number', props.value);
return props.value as number;
},
set(value) {
console.log('val change number', value);
onValueChange(value);
},
});
const currentWidget = computed(() => {
return formConfig().currentWidget;
});
const inputWidgetType = computed(() => {
return props.attributeItem.widgetType;
});
const isWidgetDictSelect = computed(() => {
if (currentWidget.value == null) return false;
let temp =
[
SysCustomWidgetType.Select,
SysCustomWidgetType.Cascader,
SysCustomWidgetType.Radio,
SysCustomWidgetType.CheckBox,
].indexOf(currentWidget.value.widgetType) !== -1;
return temp && currentWidget.value && props.attributeItem.attributeKey === 'dictId';
});
const onValueChange = (
val: string | number | Arrayable<number> | boolean | ANY_OBJECT | null | undefined,
) => {
if (isWidgetDictSelect.value) {
console.log('isWidgetDictSelect', isWidgetDictSelect.value);
currentWidget.value.dictInfo = findItemFromList(formConfig().dictList, val as string, 'id');
console.log('currentWidget.dictInfo', currentWidget.value.dictInfo);
}
console.log('onValueChange val', val, typeof val);
//emit('input', val);
emit('update:value', val);
if (props.attributeItem.customComponent) emit('change');
};
const loadDropdownData = () => {
dropdownData.value = [];
if (typeof props.attributeItem.dropdownList === 'function') {
if (isWidgetDictSelect.value) {
dropdownData.value = props.attributeItem.dropdownList(formConfig());
} else {
dropdownData.value = props.attributeItem.dropdownList();
}
} else {
dropdownData.value = Array.isArray(props.attributeItem.dropdownList)
? props.attributeItem.dropdownList
: [];
}
//console.log('dropdownData', dropdownData.value);
if ((dropdownData.value || []).length === 1) onValueChange(dropdownData.value[0].id);
};
const getProps = () => {
let pps: ANY_OBJECT = {};
if (props.attributeItem.customComponent) {
pps = props.attributeItem.customComponent.props || {};
} else {
pps = props.attributeItem.props || {};
}
pps.disabled = props.attributeItem.disabled || pps.disabled;
attributeProps.value = Object.keys(pps).reduce((retObj: ANY_OBJECT, key: string) => {
let value = pps[key];
if (typeof value === 'function') value = value(formConfig());
retObj[key] = value;
return retObj;
}, {});
};
watch(
() => props.value,
() => {
console.log('..........', props.value);
},
{
immediate: true,
},
);
watch(
() => props.attributeItem,
() => {
loadDropdownData();
},
{
immediate: true,
deep: true,
},
);
watch(
() => currentWidget.value,
() => {
loadDropdownData();
getProps();
},
{
deep: true,
immediate: true,
},
);
</script>
<style scoped>
.widget-attribute-item :deep(.el-textarea__inner) {
min-height: 100px !important;
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<el-form
class="custom-widget-attribute"
label-position="top"
label-width="115px"
size="default"
@submit.prevent
>
<EditWidgetAttribute
v-for="attributeItem in attributeList"
:key="attributeItem.attributeKey"
v-model:value="modelValue[attributeItem.attributeKey]"
:attributeItem="attributeItem"
/>
</el-form>
</template>
<script setup lang="ts">
import { ANY_OBJECT } from '@/types/generic';
import EditWidgetAttribute from './editWidgetAttribute.vue';
const emit = defineEmits<{ 'update:value': [ANY_OBJECT] }>();
const props = defineProps<{ value: ANY_OBJECT }>();
const formConfig = inject('formConfig', () => {
console.error('AttributeForm: formConfig not injected');
return {} as ANY_OBJECT;
});
const attributeList = ref<ANY_OBJECT[]>([]);
const modelValue = computed({
get() {
return props.value || {};
},
set(val) {
console.log('AttributeForm value change', val);
emit('update:value', props.value);
},
});
const currentWidget = computed(() => {
return formConfig().currentWidget;
});
const getWidgetAttributeConfig = computed(() => {
return (formConfig().getWidgetAttribute(currentWidget.value?.widgetType) || {}).attribute;
});
const getAttributeItemValue = (attributeItem: ANY_OBJECT) => {
if (typeof attributeItem === 'function') {
return attributeItem(formConfig());
} else {
return attributeItem;
}
};
const buildAttributeList = () => {
const list: ANY_OBJECT[] = [];
if (getWidgetAttributeConfig.value != null) {
Object.keys(getWidgetAttributeConfig.value).forEach(attributeKey => {
let attributeItem = getWidgetAttributeConfig.value[attributeKey];
if (attributeItem != null) {
let temp = Object.keys(attributeItem).reduce(
(retObj: ANY_OBJECT, subKey: string) => {
retObj[subKey] = getAttributeItemValue(attributeItem[subKey]);
return retObj;
},
{
attributeKey: attributeKey,
},
);
if (temp.visible == null || temp.visible) list.push(temp);
}
});
}
console.log('AttributeForm attributeList', list);
attributeList.value = list;
};
watch(
() => currentWidget.value,
() => {
buildAttributeList();
},
{
immediate: true,
},
);
</script>

View File

@@ -0,0 +1,188 @@
<template>
<div class="form-single-fragment third-party-dlg" style="position: relative">
<el-form
ref="form"
:model="formData"
class="full-width-input form-box"
:rules="rules"
label-width="100px"
:size="formItemSize"
label-position="right"
@submit.prevent
>
<el-row>
<el-col :span="24">
<el-form-item label="参数名称" prop="dictParamName">
<el-select v-model="formData.dictParamName" placeholder="" :disabled="rowData != null">
<el-option
v-for="item in dialogParams.paramList"
:key="item.dictParamName"
:label="item.dictParamName"
:value="item.dictParamName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="参数值类型" prop="dictValueType">
<el-select
class="input-item"
v-model="formData.dictValueType"
:clearable="true"
placeholder="参数值类型"
@change="onDictValueTypeChange"
>
<el-option
v-for="item in SysOnlineParamValueType.getList()"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.dictValueType === SysOnlineParamValueType.TABLE_COLUMN">
<el-form-item label="参数值" prop="dictValue">
<el-select
class="input-item"
v-model="formData.dictValue"
key="TABLE_COLUMN"
:clearable="true"
placeholder="参数值"
>
<el-option
v-for="item in dialogParams.columnList"
:key="item.columnId"
:label="item.objectFieldName"
:value="item.columnId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.dictValueType === SysOnlineParamValueType.STATIC_DICT">
<el-form-item label="参数值" prop="dictValue">
<el-cascader
v-model="formData.dictValue"
:options="staticData as CascaderOption[]"
:props="staticPops"
/>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.dictValueType === SysOnlineParamValueType.INPUT_VALUE">
<el-form-item label="参数值" prop="dictValue">
<el-input v-model="formData.dictValue" placeholder="请输入参数值" clearable />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row class="no-scroll menu-box" type="flex" justify="end" style="margin-top: 15px">
<el-button :size="formItemSize" :plain="true" @click="onCancel"> 取消 </el-button>
<el-button type="primary" :size="formItemSize" @click="onSubmit"> 保存 </el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { CascaderOption, ElMessage } from 'element-plus';
import combinedDict from '@/common/staticDict/combined';
import { ANY_OBJECT } from '@/types/generic';
import { DialogProp } from '@/components/Dialog/types';
import { SysOnlineParamValueType } from '@/common/staticDict/online';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
interface IProps extends ThirdProps {
rowData: ANY_OBJECT;
paramList: ANY_OBJECT[];
columnList: ANY_OBJECT[];
// 当使用Dialog.show弹出组件时须定义该prop属性以便对dialog进行回调
dialog?: DialogProp<ANY_OBJECT>;
}
const StaticDict = { ...combinedDict };
const props = defineProps<IProps>();
const formItemSize = computed(() => {
return layoutStore.defaultFormItemSize || thirdParams.value.defaultFormItemSize?.value;
});
const { thirdParams, onCloseThirdDialog } = useThirdParty(props);
const form = ref();
const formData = ref<ANY_OBJECT>({
dictParamName: undefined,
dictValue: undefined,
dictValueType: undefined,
});
const staticPops: ANY_OBJECT = {
label: 'name',
value: 'id',
};
const staticData = ref<(ANY_OBJECT | undefined)[]>([]);
const rules: ANY_OBJECT = {
dictParamName: [{ required: true, message: '请选择参数', trigger: 'blur' }],
dictValueType: [{ required: true, message: '请选择参数值类型', trigger: 'blur' }],
dictValue: [{ required: true, message: '请选择参数值', trigger: 'blur' }],
};
const dialogParams = computed(() => {
return {
rowData: props.rowData || thirdParams.value.rowData,
paramList: props.paramList || thirdParams.value.paramList,
columnList: props.columnList || thirdParams.value.columnList,
};
});
const onCancel = () => {
if (props.dialog) {
props.dialog.cancel();
} else {
onCloseThirdDialog(false);
}
};
const onSubmit = () => {
form.value.validate((valid: boolean) => {
if (!valid) return;
if (
formData.value.dictValueType === SysOnlineParamValueType.STATIC_DICT &&
formData.value.dictValue.length !== 2
) {
ElMessage.error('静态字典类型的参数值必须选择静态字典的值!');
return;
}
if (props.dialog) {
props.dialog.submit(formData.value);
} else {
onCloseThirdDialog(true, dialogParams.value.rowData, formData.value);
}
});
};
const onDictValueTypeChange = () => {
formData.value.dictValue = undefined;
form.value.clearValidate();
};
const loadStaticData = () => {
const dictList: ANY_OBJECT = StaticDict;
staticData.value = Object.keys(dictList)
.map((key: string) => {
if (key === 'DictionaryBase') return undefined;
let dictItem = dictList[key];
return {
id: key,
name: dictItem.showName,
children: dictItem.getList(),
} as ANY_OBJECT;
})
.filter(item => !!item);
};
onMounted(() => {
formData.value = {
...formData.value,
...dialogParams.value.rowData,
};
loadStaticData();
});
</script>

View File

@@ -0,0 +1,223 @@
<template>
<el-row>
<el-col :span="24">
<el-select
v-model="dictId"
clearable
placeholder=""
style="width: 100%"
@change="onDictChange"
>
<el-option
v-for="item in validDictList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-col>
<el-col v-if="dictParamList && dictParamList.length > 0" :span="24" style="margin-top: 10px">
<MultiItemBox
:data="(value || {}).paramList"
addText="添加字典过滤参数"
:maxCount="dictParamList ? dictParamList.length : 0"
@add="onEditDictParamValue()"
@edit="onEditDictParamValue"
@delete="onRemoveDictParamValue"
:prop="{
label: 'dictParamName',
value: 'dictParamName',
}"
/>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus';
import MultiItemBox from '@/components/MultiItemBox/index.vue';
import { findItemFromList } from '@/common/utils';
import { ANY_OBJECT } from '@/types/generic';
import { Dialog } from '@/components/Dialog';
import { SysCustomWidgetBindDataType, SysOnlineFormType } from '@/common/staticDict';
import { SysOnlineColumnFilterType } from '@/common/staticDict/online';
import EditDictParamValue from './editDictParamValue.vue';
const props = withDefaults(defineProps<{ value?: ANY_OBJECT }>(), {
value: () => {
return {};
},
});
const formConfig = inject('formConfig', () => {
console.error('CustomWidgetDictSetting: formConfig not injected');
return {} as ANY_OBJECT;
});
console.log('>>> >>> dictSetting', formConfig());
const validDictList = ref<ANY_OBJECT[]>([]);
const currentWidget = computed(() => {
return formConfig().currentWidget;
});
const allDictList = computed(() => {
return formConfig().dictList;
});
const currentDictInfo = computed(() => {
return findItemFromList(allDictList.value, props.value.dictId, 'id');
});
const dictParamList = computed(() => {
return currentDictInfo.value ? (currentDictInfo.value.dictData || {}).paramList : undefined;
});
const dictId = computed({
get() {
return props.value.dictId;
},
set(val) {
onDictChange(val);
},
});
const refreshData = (data: ANY_OBJECT) => {
if (data.path === 'thirdEditDictParamValue' && data.isSuccess) {
handlerEditOperate(data.rowData, data.data);
}
};
defineExpose({ refreshData });
const onDictChange = (val: ANY_OBJECT) => {
emitChange({
dictId: val,
paramList: [],
});
};
const getValidDictList = () => {
validDictList.value = [];
if (currentWidget.value == null || !Array.isArray(allDictList.value)) return;
if (currentWidget.value.bindData.dataType === SysCustomWidgetBindDataType.Column) {
if (
currentWidget.value.bindData.column != null &&
currentWidget.value.bindData.column.dictId != null
) {
validDictList.value = (allDictList.value || []).filter(
item => item.id === currentWidget.value.bindData.column.dictId,
);
}
} else if (currentWidget.value.bindData.dataType === SysCustomWidgetBindDataType.Custom) {
validDictList.value = allDictList.value;
}
};
const emit = defineEmits<{ 'update:value': [ANY_OBJECT] }>();
const emitChange = (info: ANY_OBJECT) => {
nextTick(() => {
emit('update:value', {
...info,
dict: findItemFromList(allDictList.value, (info || {}).dictId, 'id'),
});
});
};
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
if (!row) {
emitChange({
dictId: props.value.dictId,
paramList: [...props.value.paramList, res],
});
} else {
emitChange({
dictId: props.value.dictId,
paramList: props.value.paramList.map((item: ANY_OBJECT) => {
return item.dictParamName === row.dictParamName ? res : item;
}),
});
}
};
const onEditDictParamValue = (row: ANY_OBJECT | null = null) => {
let validParamList = (dictParamList.value || []).filter((item: ANY_OBJECT) => {
let usedParamList = props.value.paramList;
let temp = findItemFromList(usedParamList, item.dictParamName, 'dictParamName');
return temp == null;
});
let columnList = [];
// 报表表单从组件中获取数据,其他表单从数据源字段中获取数据
if (formConfig().form.pageCode != null) {
columnList = (formConfig().allWidgetList || []).map((widget: ANY_OBJECT) => {
return {
columnId: widget.widgetId,
objectFieldName: widget.showName,
};
});
} else {
columnList = formConfig().getMasterTable.columnList.filter((item: ANY_OBJECT) => {
return (
formConfig().form.formType !== SysOnlineFormType.QUERY ||
item.filterType !== SysOnlineColumnFilterType.NONE
);
});
}
Dialog.show<ANY_OBJECT>(
row ? '编辑参数' : '添加参数',
EditDictParamValue,
{
area: '600px',
},
{
rowData: row,
paramList: validParamList,
columnList: columnList,
path: 'thirdEditDictParamValue',
},
{
width: '600px',
height: '400px',
pathName: '/thirdParty/thirdEditDictParamValue',
},
)
.then(res => {
handlerEditOperate(row, res);
})
.catch(e => {
console.warn(e);
});
};
const onRemoveDictParamValue = (row: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此参数?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
emitChange({
dictId: props.value.dictId,
paramList: props.value.paramList.filter(
(item: ANY_OBJECT) => item.dictParamName !== row.dictParamName,
),
});
})
.catch(e => {
console.warn(e);
});
};
watch(
() => currentWidget.value,
() => {
getValidDictList();
},
{
deep: true,
immediate: true,
},
);
watch(
() => validDictList.value,
() => {
if (validDictList.value.length === 1 && !props.value.dictId) {
emitChange({
dictId: validDictList.value[0].id,
paramList: [],
});
}
},
{
immediate: true,
},
);
</script>

View File

@@ -0,0 +1,54 @@
<template>
<el-form
label-position="top"
size="default"
v-if="pagerSetting"
style="padding: 10px 16px 0"
@submit.prevent
>
<el-form-item class="view-attribute-item" label="是否显示">
<el-switch v-model="pagerSetting.show" />
</el-form-item>
<el-form-item class="view-attribute-item" label="尺寸">
<el-select v-model="pagerSetting.size" placeholder="">
<el-option label="默认" value="default" />
<el-option label="小" value="small" />
</el-select>
</el-form-item>
<el-form-item class="view-attribute-item" label="样式">
<el-select v-model="pagerSetting.background" placeholder="">
<el-option label="按钮" value="1" />
<el-option label="文字" value="0" />
</el-select>
</el-form-item>
<el-form-item class="view-attribute-item" label="对齐方式">
<el-radio-group v-model="pagerSetting.align">
<el-radio-button value="left">居左</el-radio-button>
<el-radio-button value="center">居中</el-radio-button>
<el-radio-button value="right">居右</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ANY_OBJECT } from '@/types/generic';
const props = withDefaults(defineProps<{ fieldName?: string }>(), { fieldName: 'pagerSetting' });
const formConfig = inject('formConfig', () => {
console.error('PivotTableTotalSetting: formConfig not injected');
return {} as ANY_OBJECT;
});
const widget = computed(() => {
return formConfig().currentWidget;
});
const pagerSetting = computed(() => {
let pps = (widget.value || {}).props;
if (pps) {
return pps[props.fieldName];
} else {
return {};
}
});
</script>

View File

@@ -0,0 +1,179 @@
<template>
<div class="form-single-fragment third-party-dlg" style="position: relative">
<el-form
ref="form"
:model="formData"
class="full-width-input form-box"
:rules="rules"
style="width: 100%"
label-width="100px"
:size="formItemSize"
label-position="right"
@submit.prevent
>
<el-row>
<el-col :span="24">
<el-form-item label="字段数据表" prop="tableId">
<el-select
class="input-item"
v-model="formData.tableId"
:disabled="dialogParams.rowData != null"
:clearable="true"
placeholder="字段数据表"
@change="onTableChange"
>
<el-option
v-for="(table, index) in dialogParams.tableList"
:key="table.tableId"
:label="table.tableName"
:value="table.tableId"
>
<el-row type="flex" justify="space-between" align="middle">
<span>{{ table.tableName }}</span>
<el-tag
:type="table.relationType == null ? 'success' : 'primary'"
style="margin-left: 30px"
:size="formItemSize"
effect="dark"
>
{{ table.relationType == null || index === 0 ? '主表' : '一对一关联' }}
</el-tag>
</el-row>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="绑定字段" prop="columnId">
<el-select
class="input-item"
v-model="formData.columnId"
:disabled="dialogParams.rowData != null"
:clearable="true"
placeholder="绑定字段"
@change="onColumnChange"
>
<el-option
v-for="column in getTableColumnList"
:key="column.columnId"
:label="column.columnName"
:value="column.columnId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="显示名" prop="showName">
<el-input v-model="formData.showName" clearable />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row class="no-scroll menu-box" type="flex" justify="end">
<el-button type="primary" :size="formItemSize" :plain="true" @click="onCancel">
取消
</el-button>
<el-button type="primary" :size="formItemSize" @click="onSubmit"> 保存 </el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { findItemFromList } from '@/common/utils';
import { DialogProp } from '@/components/Dialog/types';
import { ANY_OBJECT } from '@/types/generic';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
interface IProps extends ThirdProps {
rowData?: ANY_OBJECT;
tableList?: ANY_OBJECT[];
usedColumnList?: ANY_OBJECT[];
// 当使用Dialog.show弹出组件时须定义该prop属性以便对dialog进行回调
dialog?: DialogProp<ANY_OBJECT>;
}
const props = defineProps<IProps>();
const formItemSize = computed(() => {
return layoutStore.defaultFormItemSize || thirdParams.value.defaultFormItemSize?.value;
});
const { thirdParams, onCloseThirdDialog } = useThirdParty(props);
const form = ref();
const formData = ref<ANY_OBJECT>({
id: undefined,
tableId: undefined,
columnId: undefined,
showName: undefined,
});
const oldColumnId = ref<string>();
const rules: ANY_OBJECT = {
showName: [{ required: true, message: '显示名不能为空', trigger: 'blur' }],
tableId: [{ required: true, message: '数据表不能为空', trigger: 'blur' }],
columnId: [{ required: true, message: '绑定字段不能为空', trigger: 'blur' }],
};
const dialogParams = computed(() => {
return {
rowData: props.rowData || thirdParams.value.rowData,
tableList: props.tableList || thirdParams.value.tableList,
usedColumnList: props.usedColumnList || thirdParams.value.usedColumnList || [],
};
});
const getCurrentTable = computed(() => {
return findItemFromList(dialogParams.value.tableList, formData.value.tableId, 'tableId');
});
const getTableColumnList = computed(() => {
if (getCurrentTable.value != null) {
return getCurrentTable.value.columnList.filter((item: ANY_OBJECT) => {
return (
dialogParams.value.usedColumnList.indexOf(item.columnId) === -1 ||
oldColumnId.value === item.columnId
);
});
} else {
return [];
}
});
const onCancel = () => {
if (props.dialog) {
props.dialog.cancel();
} else {
onCloseThirdDialog(false);
}
};
const onSubmit = () => {
form.value.validate((valid: boolean) => {
if (!valid) return;
if (!valid) return;
if (formData.value.id == null) formData.value.id = new Date().getTime();
formData.value.relationId =
(getCurrentTable.value || {}).relationType == null ? undefined : getCurrentTable.value?.id;
if (props.dialog) {
props.dialog?.submit(formData.value);
} else {
onCloseThirdDialog(true, dialogParams.value.rowData, formData.value);
}
});
};
const onTableChange = () => {
formData.value.columnId = undefined;
};
const onColumnChange = () => {
let column = findItemFromList(getTableColumnList.value, formData.value.columnId, 'columnId');
formData.value.showName = column ? column.columnComment : undefined;
};
onMounted(() => {
if (dialogParams.value.rowData != null) {
formData.value = {
...formData.value,
...dialogParams.value.rowData,
};
oldColumnId.value = formData.value.columnId;
}
});
</script>

View File

@@ -0,0 +1,98 @@
<template>
<MultiItemBox
:data="value"
addText="添加排序字段"
:supportSort="true"
@add="onEditTableColumn()"
@edit="onEditTableColumn"
@delete="onRemoveTableColumn"
:prop="{
label: 'showName',
value: 'id',
}"
/>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus';
import MultiItemBox from '@/components/MultiItemBox/index.vue';
import { ANY_OBJECT } from '@/types/generic';
import { Dialog } from '@/components/Dialog';
import { SysOnlineFormType } from '@/common/staticDict';
import EditCustomListOrder from './editCustomListOrder.vue';
const emit = defineEmits<{ 'update:value': [ANY_OBJECT[]] }>();
const props = withDefaults(defineProps<{ value: ANY_OBJECT[] }>(), {});
const formConfig = inject('formConfig', () => {
console.error('OnlineCustomListOrderSetting: formConfig not injected');
return {} as ANY_OBJECT;
});
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
let tempList: ANY_OBJECT[] = [];
if (row == null) {
tempList = props.value || [];
tempList.push(res);
} else {
tempList = (props.value || []).map(item => {
return item.columnId === res.id ? res : item;
});
}
emit('update:value', tempList);
};
const onEditTableColumn = (row: ANY_OBJECT | null = null) => {
let tableList = formConfig().getTableWidgetTableList;
// 非查询页面只能从组件绑定的表中选择
if (formConfig().form.formType !== SysOnlineFormType.QUERY) {
tableList = (tableList || []).filter((table: ANY_OBJECT) => {
return table.tableId === formConfig().currentWidget.bindData.tableId;
});
}
Dialog.show<ANY_OBJECT>(
row ? '编辑排序字段' : '添加排序字段',
EditCustomListOrder,
{
area: '500px',
},
{
rowData: row,
tableList: tableList,
usedColumnList: props.value.map(item => item.columnId),
path: 'thirdEditOnlineListOrder',
},
{
width: '500px',
height: '500px',
pathName: '/thirdParty/thirdEditOnlineListOrder',
},
)
.then(res => {
handlerEditOperate(row, res);
})
.catch(e => {
console.warn(e);
});
};
const onRemoveTableColumn = (row: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此排序字段?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
emit(
'update:value',
(props.value || []).filter(item => item !== row),
);
})
.catch(e => {
console.warn(e);
});
};
const refreshData = (data: ANY_OBJECT) => {
if (data.path === 'thirdEditOnlineListOrder' && data.isSuccess) {
handlerEditOperate(data.rowData, data.data);
}
};
defineExpose({ refreshData });
</script>

View File

@@ -0,0 +1,68 @@
<template>
<div>
<el-input :value="value" :size="size" :disabled="disabled">
<template #append>
<el-button :icon="Upload" @click="onSelect" :disabled="disabled"> </el-button>
</template>
</el-input>
<input
type="file"
ref="fileSelect"
v-show="false"
accept="image/*"
@change.stop="onSelectImageChange"
/>
</div>
</template>
<script setup lang="ts">
import { Upload } from '@element-plus/icons-vue';
import { useUpload } from '@/common/hooks/useUpload';
import { API_CONTEXT } from '@/api/config';
const { getUploadActionUrl, fetchUpload } = useUpload();
withDefaults(
defineProps<{
value: string;
size?: '' | 'default' | 'small' | 'large';
disabled?: boolean;
}>(),
{
size: 'small',
disabled: false,
},
);
const fileSelect = ref();
const onSelect = () => {
fileSelect.value.click();
};
const onSelectImageChange = (e: Event) => {
const el = e.target as HTMLInputElement;
if (el && el.files) {
console.log('files', el.files);
let file = el.files[0];
if (file != null) {
let uploadUrl = getUploadActionUrl(API_CONTEXT + '/commonext/util/uploadImage');
fetchUpload(uploadUrl, {
uploadFile: file,
})
.then(res => {
//console.log('upload file', res);
onValueChange(JSON.stringify(res));
})
.catch((e: Error) => {
console.warn(e);
});
}
}
};
const emit = defineEmits<{ 'update:value': [string] }>();
const onValueChange = (val: string) => {
//console.log('image url change', val);
emit('update:value', val);
};
</script>

View File

@@ -0,0 +1,98 @@
<template>
<div class="form-single-fragment third-party-dlg" style="position: relative">
<el-form
ref="form"
:model="formData"
class="full-width-input form-box"
:rules="rules"
style="width: 100%"
label-width="100px"
:size="formItemSize"
label-position="right"
@submit.prevent
>
<el-form-item label="显示名称" prop="name">
<el-input v-model="formData.name" clearable />
</el-form-item>
<el-form-item label="最小值">
<el-input v-model="formData.min" clearable />
</el-form-item>
<el-form-item label="最大值">
<el-input v-model="formData.max" clearable />
</el-form-item>
</el-form>
<el-row class="no-scroll menu-box" type="flex" justify="end">
<el-button type="primary" :size="formItemSize" :plain="true" @click="onCancel">
取消
</el-button>
<el-button type="primary" :size="formItemSize" @click="onSubmit"> 保存 </el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { DialogProp } from '@/components/Dialog/types';
import { ANY_OBJECT } from '@/types/generic';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
interface IProps extends ThirdProps {
rowData?: ANY_OBJECT;
// 当使用Dialog.show弹出组件时须定义该prop属性以便对dialog进行回调
dialog?: DialogProp<ANY_OBJECT>;
}
const props = defineProps<IProps>();
const formItemSize = computed(() => {
return layoutStore.defaultFormItemSize || thirdParams.value.defaultFormItemSize?.value;
});
const { thirdParams, onCloseThirdDialog } = useThirdParty(props);
const form = ref();
const formData = ref<ANY_OBJECT>({
id: undefined,
name: undefined,
min: undefined,
max: undefined,
});
const rules: ANY_OBJECT = {
name: [{ required: true, message: '请输入显示名称', trugger: 'change' }],
};
const dialogParams = computed(() => {
return {
rowData: props.rowData || thirdParams.value.rowData,
};
});
const onCancel = () => {
if (props.dialog) {
props.dialog.cancel();
} else {
onCloseThirdDialog(false);
}
};
const onSubmit = () => {
form.value.validate((valid: boolean) => {
if (!valid) return;
// 生成唯一ID
if (formData.value.id == null) formData.value.id = new Date().getTime();
if (props.dialog) {
props.dialog?.submit(formData.value);
} else {
onCloseThirdDialog(true, dialogParams.value.rowData, formData.value);
}
});
};
onMounted(() => {
if (dialogParams.value.rowData != null) {
formData.value = {
...formData.value,
...dialogParams.value.rowData,
};
}
});
</script>

View File

@@ -0,0 +1,84 @@
<template>
<MultiItemBox
:data="value"
addText="添加快捷选项"
:supportSort="true"
@add="onEditTableColumn()"
@edit="onEditTableColumn"
@delete="onRemoveTableColumn"
:prop="{
label: 'name',
value: 'id',
}"
/>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus';
import MultiItemBox from '@/components/MultiItemBox/index.vue';
import { ANY_OBJECT } from '@/types/generic';
import { Dialog } from '@/components/Dialog';
import EditNumberRangeQuick from './editNumberRangeQuick.vue';
const emit = defineEmits<{ 'update:value': [ANY_OBJECT[]] }>();
const props = withDefaults(defineProps<{ value: ANY_OBJECT[] }>(), {});
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
let tempList: ANY_OBJECT[] = [];
if (row == null) {
tempList = props.value || [];
tempList.push(res);
} else {
tempList = (props.value || []).map(item => {
return item.id === res.id ? res : item;
});
}
emit('update:value', tempList);
};
const onEditTableColumn = (row: ANY_OBJECT | null = null) => {
Dialog.show<ANY_OBJECT>(
row ? '编辑快捷选项' : '添加快捷选项',
EditNumberRangeQuick,
{
area: '500px',
},
{
rowData: row,
path: 'thirdEditOnlineMobileNumberRangeQuick',
},
{
width: '500px',
height: '500px',
pathName: '/thirdParty/thirdEditOnlineMobileNumberRangeQuick',
},
)
.then(res => {
handlerEditOperate(row, res);
})
.catch(e => {
console.warn(e);
});
};
const onRemoveTableColumn = (row: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此快捷选项?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
emit(
'update:value',
(props.value || []).filter(item => item !== row),
);
})
.catch(e => {
console.warn(e);
});
};
const refreshData = (data: ANY_OBJECT) => {
if (data.path === 'thirdEditOnlineMobileNumberRangeQuick' && data.isSuccess) {
handlerEditOperate(data.rowData, data.data);
}
};
defineExpose({ refreshData });
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div class="form-single-fragment third-party-dlg" style="position: relative">
<el-form
ref="form"
:model="formData"
class="full-width-input form-box"
:rules="rules"
style="width: 100%"
label-width="100px"
:size="formItemSize"
label-position="right"
@submit.prevent
>
<el-row>
<el-col :span="24">
<el-form-item label="标签显示名" prop="showName">
<el-input v-model="formData.showName" clearable />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="标签标识" prop="variableName">
<el-input v-model="formData.variableName" clearable />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row class="no-scroll menu-box" type="flex" justify="end">
<el-button :size="formItemSize" :plain="true" @click="onCancel"> 取消 </el-button>
<el-button type="primary" :size="formItemSize" @click="onSubmit"> 保存 </el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { DialogProp } from '@/components/Dialog/types';
import { ANY_OBJECT } from '@/types/generic';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
interface IProps extends ThirdProps {
rowData?: ANY_OBJECT;
tabPanelList?: ANY_OBJECT[];
// 当使用Dialog.show弹出组件时须定义该prop属性以便对dialog进行回调
dialog?: DialogProp<ANY_OBJECT | ANY_OBJECT[] | undefined>;
}
const props = defineProps<IProps>();
const formItemSize = computed(() => {
return layoutStore.defaultFormItemSize || thirdParams.value.defaultFormItemSize?.value;
});
const { thirdParams, onCloseThirdDialog } = useThirdParty(props);
const form = ref();
const formData = ref<ANY_OBJECT>({
showName: undefined,
variableName: undefined,
});
function func(param: ANY_OBJECT | null = null) {
// empty
}
const checkVarialeName = (rule: ANY_OBJECT | null, value: ANY_OBJECT, callback: typeof func) => {
if (Array.isArray(dialogParams.value.tabPanelList)) {
if (dialogParams.value.tabPanelList.indexOf(value) !== -1) {
callback(new Error('标签页标识不能重复'));
} else {
callback();
}
} else {
callback();
}
};
const rules: ANY_OBJECT = {
showName: [{ required: true, message: '标签页显示名不能为空', trigger: 'blur' }],
variableName: [
{ required: true, message: '标签页标识不能为空', trigger: 'blur' },
{
validator: checkVarialeName,
trigger: 'blur',
},
],
};
const dialogParams = computed(() => {
return {
rowData: props.rowData || thirdParams.value.rowData,
tabPanelList: props.tabPanelList || thirdParams.value.tabPanelList,
};
});
const onCancel = () => {
if (props.dialog) {
props.dialog.cancel();
} else {
onCloseThirdDialog(false);
}
};
const onSubmit = () => {
form.value.validate((valid: boolean) => {
if (!valid) return;
if (props.dialog) {
props.dialog.submit(formData.value);
} else {
onCloseThirdDialog(true, dialogParams.value.rowData, formData.value);
}
});
};
onMounted(() => {
if (dialogParams.value.rowData != null) {
formData.value = {
...formData.value,
...dialogParams.value.rowData,
};
}
});
</script>

View File

@@ -0,0 +1,97 @@
<template>
<MultiItemBox
:data="(formConfig().currentWidget || {}).childWidgetList"
addText="添加标签页"
@add="onEditTabPanel()"
@edit="onEditTabPanel"
@delete="onRemoveTabPanel"
:prop="{
label: 'showName',
value: 'variableName',
}"
/>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus';
import MultiItemBox from '@/components/MultiItemBox/index.vue';
import blockConfig from '@/online/config/customBlock';
import { ANY_OBJECT } from '@/types/generic';
import { Dialog } from '@/components/Dialog';
import EditOnlineTabPanel from './editOnlineTabPanel.vue';
const formConfig = inject('formConfig', () => {
console.error('editWidgetAttribute: formConfig not injected');
return {} as ANY_OBJECT;
});
// TODO refreshData
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
if (row == null) {
res = createTabPanel(res);
formConfig().currentWidget.childWidgetList.push(res);
} else {
formConfig().currentWidget.childWidgetList = formConfig().currentWidget.childWidgetList.map(
(item: ANY_OBJECT) => {
return item.variableName === row.variableName ? res : item;
},
);
}
};
const onEditTabPanel = (row: ANY_OBJECT | null = null) => {
Dialog.show<ANY_OBJECT>(
row ? '编辑标签页' : '添加标签页',
EditOnlineTabPanel,
{
area: '600px',
},
{
rowData: row,
tabPanelList: (formConfig().currentWidget.childWidgetList || [])
.filter((item: ANY_OBJECT) => {
return row != null ? item.variableName !== row.variableName : true;
})
.map((item: ANY_OBJECT) => item.variableName),
path: 'thirdEditOnlineTabPanel',
},
{
width: '600px',
height: '500px',
pathName: '/thirdParty/thirdEditOnlineTabPanel',
},
)
.then(res => {
handlerEditOperate(row, res);
})
.catch(e => {
console.log(e);
});
};
const onRemoveTabPanel = (row: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此标签页?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
formConfig().currentWidget.childWidgetList =
formConfig().currentWidget.childWidgetList.filter((item: ANY_OBJECT) => {
return item.variableName !== row.variableName;
});
})
.catch((e: Error) => {
console.warn(e);
});
};
const createTabPanel = (res: ANY_OBJECT) => {
let temp = formConfig().getWidgetObject(blockConfig);
temp.showName = res.showName;
temp.variableName = res.variableName;
temp.relation = undefined;
temp.datasource = undefined;
temp.column = undefined;
return temp;
};
</script>

View File

@@ -0,0 +1,227 @@
<template>
<div class="form-single-fragment third-party-dlg">
<el-form
ref="form"
:model="formData"
class="full-width-input form-box"
:rules="rules"
style="width: 100%"
label-width="100px"
:size="formItemSize"
label-position="right"
@submit.prevent
>
<el-row>
<el-col :span="24">
<el-form-item label="绑定类型">
<el-radio-group
v-model="formData.fieldType"
:disabled="dialogParams.rowData != null"
@change="onBindTypeChange"
>
<el-radio-button :value="0">表字段</el-radio-button>
<el-radio-button :value="1">自定义</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.fieldType === 1">
<el-form-item label="字段名" prop="customFieldName">
<el-input v-model="formData.customFieldName" clearable />
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.fieldType === 0">
<el-form-item label="字段数据表" prop="tableId">
<el-select
class="input-item"
v-model="formData.tableId"
:disabled="dialogParams.rowData != null"
:clearable="true"
placeholder="字段数据表"
@change="onTableChange"
>
<el-option
v-for="(table, index) in dialogParams.tableList"
:key="table.tableId"
:label="table.tableName"
:value="table.tableId"
>
<el-row type="flex" justify="space-between" align="middle">
<span>{{ table.tableName }}</span>
<el-tag
:type="table.relationType == null ? 'success' : 'primary'"
style="margin-left: 30px"
:size="formItemSize"
effect="dark"
>
{{ table.relationType == null || index === 0 ? '主表' : '一对一关联' }}
</el-tag>
</el-row>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.fieldType === 0">
<el-form-item label="绑定列字段" prop="columnId">
<el-select
class="input-item"
v-model="formData.columnId"
:disabled="dialogParams.rowData != null"
:clearable="true"
placeholder="字段数据表"
@change="onColumnChange"
>
<el-option
v-for="column in getTableColumnList"
:key="column.columnId"
:label="column.columnName"
:value="column.columnId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="表格列名" prop="showName">
<el-input
class="input-item"
v-model="formData.showName"
:clearable="true"
placeholder="表格列名"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="表格列宽">
<el-input-number
class="input-item"
v-model="formData.columnWidth"
placeholder="表格列宽"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="支持排序">
<el-switch v-model="formData.sortable" :disabled="formData.fieldType == 1" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row class="no-scroll menu-box" type="flex" justify="end">
<el-button :size="formItemSize" :plain="true" @click="onCancel"> 取消 </el-button>
<el-button type="primary" :size="formItemSize" @click="onSubmit"> 保存 </el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { findItemFromList } from '@/common/utils';
import { DialogProp } from '@/components/Dialog/types';
import { ANY_OBJECT } from '@/types/generic';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
interface IProps extends ThirdProps {
rowData?: ANY_OBJECT;
tableList?: ANY_OBJECT[];
usedColumnList?: ANY_OBJECT[];
// 当使用Dialog.show弹出组件时须定义该prop属性以便对dialog进行回调
dialog?: DialogProp<ANY_OBJECT>;
}
const props = defineProps<IProps>();
const formItemSize = computed(() => {
return layoutStore.defaultFormItemSize || thirdParams.value.defaultFormItemSize?.value;
});
const { thirdParams, onCloseThirdDialog } = useThirdParty(props);
const form = ref();
const formData = ref<ANY_OBJECT>({
columnId: undefined,
tableId: undefined,
showName: undefined,
sortable: false,
columnWidth: undefined,
fieldType: 0,
customFieldName: undefined,
});
const oldColumnId = ref<string>();
const rules: ANY_OBJECT = {
showName: [{ required: true, message: '表格列名不能为空', trigger: 'blur' }],
tableId: [{ required: true, message: '表格列绑定数据表不能为空', trigger: 'blur' }],
columnId: [{ required: true, message: '表格列绑定字段不能为空', trigger: 'blur' }],
customFieldName: [{ required: true, message: '自定义字段名不能为空', trigger: 'blur' }],
};
const dialogParams = computed(() => {
return {
rowData: props.rowData || thirdParams.value.rowData,
tableList: props.tableList || thirdParams.value.tableList || [],
usedColumnList: props.usedColumnList || thirdParams.value.usedColumnList || [],
};
});
const getCurrentTable = computed(() => {
return findItemFromList(dialogParams.value.tableList, formData.value.tableId, 'tableId');
});
const getTableColumnList = computed(() => {
if (getCurrentTable.value != null) {
return getCurrentTable.value.columnList.filter((item: ANY_OBJECT) => {
return (
dialogParams.value.usedColumnList.indexOf(item.columnId) === -1 ||
oldColumnId.value === item.columnId
);
});
} else {
return [];
}
});
const onCancel = () => {
if (props.dialog) {
props.dialog.cancel();
} else {
onCloseThirdDialog(false);
}
};
const onSubmit = () => {
form.value.validate((valid: boolean) => {
if (!valid) return;
formData.value.table = getCurrentTable.value;
formData.value.relationId =
(getCurrentTable.value || {}).relationType == null ? undefined : getCurrentTable.value?.id;
if (props.dialog) {
props.dialog?.submit(formData.value);
} else {
onCloseThirdDialog(true, dialogParams.value.rowData, formData.value);
}
});
};
const onTableChange = () => {
formData.value.columnId = undefined;
};
const onColumnChange = () => {
formData.value.column = findItemFromList(
getTableColumnList.value,
formData.value.columnId,
'columnId',
);
formData.value.showName = formData.value.column ? formData.value.column.columnComment : undefined;
};
const onBindTypeChange = () => {
formData.value.customFieldName = undefined;
formData.value.tableId = undefined;
formData.value.columnId = undefined;
formData.value.sortable = false;
if (form.value) form.value.clearValidate();
};
onMounted(() => {
if (dialogParams.value.rowData != null) {
formData.value = {
...formData.value,
...dialogParams.value.rowData,
};
oldColumnId.value = formData.value.columnId;
}
});
</script>

View File

@@ -0,0 +1,101 @@
<template>
<MultiItemList
:data="value"
label="表格字段"
:supportSort="true"
@add="onEditTableColumn()"
@edit="onEditTableColumn"
@delete="onRemoveTableColumn"
:prop="{
label: 'showName',
value: 'columnId',
}"
>
</MultiItemList>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus';
import MultiItemList from '@/components/MultiItemList/index.vue';
import { ANY_OBJECT } from '@/types/generic';
import { Dialog } from '@/components/Dialog';
import { SysOnlineFormType } from '@/common/staticDict';
import EditOnlineTableColumn from './editOnlineTableColumn.vue';
const emit = defineEmits<{ 'update:value': [ANY_OBJECT[]] }>();
const props = withDefaults(defineProps<{ value: ANY_OBJECT[] }>(), {});
const formConfig = inject('formConfig', () => {
console.error('OnlineTableColumnSetting: formConfig not injected');
return {} as ANY_OBJECT;
});
const handlerEditOperate = (row: ANY_OBJECT | null, res: ANY_OBJECT) => {
let tempList: ANY_OBJECT[] = [];
if (row == null) {
tempList = props.value || [];
tempList.push(res);
} else {
tempList = (props.value || []).map(item => {
return item.columnId === res.columnId ? res : item;
});
}
emit('update:value', tempList);
};
const onEditTableColumn = (row: ANY_OBJECT | null = null) => {
let tableList = formConfig().getTableWidgetTableList;
// 非查询页面只能从组件绑定的表中选择
if (formConfig().form.formType !== SysOnlineFormType.QUERY) {
tableList = (tableList || []).filter((table: ANY_OBJECT) => {
return table.tableId === formConfig().currentWidget.bindData.tableId;
});
}
Dialog.show<ANY_OBJECT>(
row ? '编辑字段' : '添加字段',
EditOnlineTableColumn,
{
area: '600px',
},
{
rowData: row,
tableList: tableList,
usedColumnList: props.value.map(item => item.columnId),
path: 'thirdEditOnlineTableColumn',
},
{
width: '600px',
height: '500px',
pathName: '/thirdParty/thirdEditOnlineTableColumn',
},
)
.then(res => {
handlerEditOperate(row, res);
})
.catch(e => {
console.warn(e);
});
};
const onRemoveTableColumn = (row: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此表格列?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
emit(
'update:value',
(props.value || []).filter(item => item !== row),
);
})
.catch(e => {
console.warn(e);
});
};
const refreshData = (data: ANY_OBJECT) => {
if (data.path === 'thirdEditOnlineTableColumn' && data.isSuccess) {
handlerEditOperate(data.rowData, data.data);
}
};
defineExpose({ refreshData });
</script>

View File

@@ -0,0 +1,52 @@
import { ElMessageBox } from 'element-plus';
import { ANY_OBJECT } from '@/types/generic';
import { WidgetProps, WidgetEmit } from '../types/widget';
export const useWidget = (props: WidgetProps, emit: WidgetEmit) => {
const propsWidget = computed<ANY_OBJECT>({
get() {
return props.widget;
},
set(value: ANY_OBJECT) {
console.log('widget change', value);
emit('update:widget', value);
},
});
const childWidgetList = computed<ANY_OBJECT[]>({
get() {
return props.widget.childWidgetList || [];
},
set(values: ANY_OBJECT[]) {
console.log('childWidgetList change', values);
const widget = props.widget;
widget.childWidgetList = values;
emit('update:widget', widget);
},
});
const onWidgetClick = (widget: ANY_OBJECT | null = null) => {
emit('widgetClick', widget);
};
const onDeleteWidget = (widget: ANY_OBJECT) => {
ElMessageBox.confirm('是否删除此组件?', '', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
childWidgetList.value = childWidgetList.value.filter((item: ANY_OBJECT) => item !== widget);
onWidgetClick(null);
})
.catch(e => {
console.warn(e);
});
};
const onCopyWidget = (widget: ANY_OBJECT) => {
const childWidgetList = props.widget.childWidgetList;
childWidgetList.push(widget);
};
return { propsWidget, childWidgetList, onWidgetClick, onDeleteWidget, onCopyWidget };
};

View File

@@ -0,0 +1,11 @@
import { ANY_OBJECT } from '@/types/generic';
export interface WidgetProps {
widget: ANY_OBJECT;
isEdit?: boolean;
}
export interface WidgetEmit {
(event: 'widgetClick', value: ANY_OBJECT | null): void;
(event: 'update:widget', value: ANY_OBJECT | ANY_OBJECT[]): void;
}

View File

@@ -0,0 +1,54 @@
import { SysCustomWidgetType, OnlineFormEventType } from '@/common/staticDict/index';
const card = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
min: 1,
max: 24,
},
shadow: {
name: '阴影显示',
widgetType: SysCustomWidgetType.Select,
value: 'never',
dropdownList: [
{
id: 'never',
name: '不显示',
},
{
id: 'hover',
name: '悬浮显示',
},
{
id: 'always',
name: '一直显示',
},
],
},
padding: {
name: '内部边距',
widgetType: SysCustomWidgetType.NumberInput,
value: 20,
min: 0,
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
};
const baseCardConfig = {
widgetType: SysCustomWidgetType.Card,
icon: 'online-icon icon-card3',
attribute: card,
allowEventList: [OnlineFormEventType.VISIBLE],
operationList: [],
supportBindTable: false,
supportBindColumn: false,
};
export default baseCardConfig;

View File

@@ -0,0 +1,80 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const cascader = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
dictInfo: {
name: '下拉字典',
value: {},
customComponent: {
component: 'CustomWidgetDictSetting',
},
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const cascaderConfig = {
widgetType: SysCustomWidgetType.Cascader,
icon: 'online-icon icon-cascader',
attribute: cascader,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
OnlineFormEventType.DROPDOWN_CHANGE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default cascaderConfig;

View File

@@ -0,0 +1,75 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const checkbox = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
dictInfo: {
name: '下拉字典',
value: {},
customComponent: {
component: 'CustomWidgetDictSetting',
},
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const checkboxConfig = {
widgetType: SysCustomWidgetType.CheckBox,
icon: 'online-icon icon-checkbox',
attribute: checkbox,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
OnlineFormEventType.DROPDOWN_CHANGE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default checkboxConfig;

View File

@@ -0,0 +1,28 @@
import { SysCustomWidgetType, OnlineFormEventType } from '@/common/staticDict/index';
const block = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
min: 1,
max: 24,
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
};
const customBlockConfig = {
widgetType: SysCustomWidgetType.Block,
icon: 'online-icon icon-block',
attribute: block,
allowEventList: [OnlineFormEventType.VISIBLE],
supportBindTable: false,
supportBindColumn: false,
};
export default customBlockConfig;

View File

@@ -0,0 +1,100 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const date = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
type: {
name: '显示类型',
widgetType: SysCustomWidgetType.Select,
dropdownList: [
{
id: 'date',
name: '日',
},
{
id: 'week',
name: '周',
},
{
id: 'month',
name: '月',
},
{
id: 'year',
name: '年',
},
{
id: 'datetime',
name: '时间',
},
],
value: 'date',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const dateConfig = {
widgetType: SysCustomWidgetType.Date,
icon: 'online-icon icon-date',
attribute: date,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
OnlineFormEventType.DISABLED_DATE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default dateConfig;

View File

@@ -0,0 +1,97 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const dateRange = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
'start-placeholder': {
name: '开始日期提示',
widgetType: SysCustomWidgetType.Input,
value: '',
},
'end-placeholder': {
name: '结束日期提示',
widgetType: SysCustomWidgetType.Input,
value: '',
},
type: {
name: '显示类型',
widgetType: SysCustomWidgetType.Select,
dropdownList: [
{
id: 'daterange',
name: '日',
},
{
id: 'monthrange',
name: '月',
},
{
id: 'datetimerange',
name: '时间',
},
],
value: 'daterange',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const dateRangeConfig = {
widgetType: SysCustomWidgetType.DateRange,
icon: 'online-icon icon-date-range',
attribute: dateRange,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
OnlineFormEventType.DISABLED_DATE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default dateRangeConfig;

View File

@@ -0,0 +1,72 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const deptSelect = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const deptSelectConfig = {
widgetType: SysCustomWidgetType.DeptSelect,
icon: 'online-icon icon-dept',
attribute: deptSelect,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default deptSelectConfig;

View File

@@ -0,0 +1,118 @@
import {
SysCustomWidgetType,
OnlineFormEventType,
SysCustomWidgetBindDataType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const image = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
min: 1,
max: 24,
},
fit: {
name: '裁切方式',
widgetType: SysCustomWidgetType.Select,
value: 'fill',
dropdownList: [
{
id: 'fill',
name: 'fill',
},
{
id: 'contain',
name: 'contain',
},
{
id: 'cover',
name: 'cover',
},
{
id: 'none',
name: 'none',
},
{
id: 'scale-down',
name: 'scale-down',
},
],
},
align: {
name: '图片位置',
widgetType: SysCustomWidgetType.Select,
value: 'start',
dropdownList: [
{
id: 'start',
name: '左侧',
},
{
id: 'center',
name: '居中',
},
{
id: 'end',
name: '右侧',
},
],
},
width: {
name: '图片宽度',
value: '100px',
widgetType: SysCustomWidgetType.Input,
},
height: {
name: '图片高度',
value: '100px',
widgetType: SysCustomWidgetType.Input,
},
radius: {
name: '圆角宽度',
value: 3,
widgetType: SysCustomWidgetType.Slider,
min: 0,
},
round: {
name: '圆形图片',
value: false,
widgetType: SysCustomWidgetType.Switch,
},
src: {
name: '图片地址',
value: '',
customComponent: {
component: 'OnlineImageUrlInput',
props: {
disabled: function (formConfig: ANY_OBJECT) {
// 表单为非报表,并且绑定在字段上,那么图片不可输入
return (
formConfig &&
formConfig.currentWidget &&
formConfig.currentWidget.bindData?.dataType !== SysCustomWidgetBindDataType.Fixed &&
formConfig.form?.pageCode == null
);
},
},
},
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
};
const imageConfig = {
widgetType: SysCustomWidgetType.Image,
icon: 'online-icon icon-image',
attribute: image,
allowEventList: [OnlineFormEventType.VISIBLE],
supportBindTable: true,
supportBindColumn: true,
};
export default imageConfig;

View File

@@ -0,0 +1,282 @@
import { ANY_OBJECT } from '@/types/generic';
import { SysCustomWidgetType } from '@/common/staticDict/index';
import blockConfig from './customBlock';
import baseCardConfig from './baseCard';
import tabsConfig from './tabs';
import textConfig from './text';
import imageConfig from './image';
import labelConfig from './label';
import inputConfig from './input';
import numberInputConfig from './numberInput';
import numberRangeConfig from './numberRange';
import switchConfig from './switch';
import radioConfig from './radio';
import checkboxConfig from './checkbox';
import selectConfig from './select';
import cascaderConfig from './cascader';
import dateConfig from './date';
import dateRangeConfig from './dateRange';
import userSelectConfig from './userSelect';
import deptSelectConfig from './deptSelect';
import uploadConfig from './upload';
import richEditorConfig from './richEditor';
import tableConfig from './table';
import linkConfig from './link';
import treeConfig from './tree';
const formWidgetGroupList: ANY_OBJECT = {
pc: [
{
id: 'layout',
groupName: '布局组件',
widgetList: [blockConfig, baseCardConfig, tabsConfig, textConfig, imageConfig],
},
{
id: 'filter',
groupName: '过滤组件',
widgetList: [
labelConfig,
inputConfig,
numberInputConfig,
numberRangeConfig,
switchConfig,
radioConfig,
checkboxConfig,
selectConfig,
cascaderConfig,
dateConfig,
dateRangeConfig,
userSelectConfig,
deptSelectConfig,
],
},
{
id: 'base',
groupName: '基础组件',
widgetList: [
labelConfig,
inputConfig,
numberInputConfig,
numberRangeConfig,
switchConfig,
radioConfig,
checkboxConfig,
selectConfig,
cascaderConfig,
dateConfig,
dateRangeConfig,
uploadConfig,
richEditorConfig,
tableConfig,
linkConfig,
],
},
{
id: 'advance',
groupName: '高级组件',
widgetList: [userSelectConfig, deptSelectConfig],
},
],
};
function getDefaultVariableName(widgetType: number) {
const tempTime = new Date().getTime();
switch (widgetType) {
case SysCustomWidgetType.Label:
return 'label' + tempTime;
case SysCustomWidgetType.Input:
return 'input' + tempTime;
case SysCustomWidgetType.NumberInput:
return 'numberInput' + tempTime;
case SysCustomWidgetType.NumberRange:
return 'numberRange' + tempTime;
case SysCustomWidgetType.Switch:
return 'switch' + tempTime;
case SysCustomWidgetType.Slider:
return 'slider' + tempTime;
case SysCustomWidgetType.Radio:
return 'radio' + tempTime;
case SysCustomWidgetType.CheckBox:
return 'checkBox' + tempTime;
case SysCustomWidgetType.Select:
return 'select' + tempTime;
case SysCustomWidgetType.Cascader:
return 'cascader' + tempTime;
case SysCustomWidgetType.Date:
return 'date' + tempTime;
case SysCustomWidgetType.DateRange:
return 'dateRange' + tempTime;
case SysCustomWidgetType.Upload:
return 'upload' + tempTime;
case SysCustomWidgetType.RichEditor:
return 'richEditor' + tempTime;
case SysCustomWidgetType.Divider:
return 'divider' + tempTime;
case SysCustomWidgetType.Text:
return 'text' + tempTime;
case SysCustomWidgetType.Image:
return 'image' + tempTime;
case SysCustomWidgetType.ImageCard:
return 'imageCard' + tempTime;
case SysCustomWidgetType.Table:
return 'table' + tempTime;
case SysCustomWidgetType.PivotTable:
return 'pivotTable' + tempTime;
case SysCustomWidgetType.LineChart:
return 'lineChart' + tempTime;
case SysCustomWidgetType.BarChart:
return 'barChart' + tempTime;
case SysCustomWidgetType.PieChart:
return 'pieChart' + tempTime;
case SysCustomWidgetType.ScatterChart:
return 'scatterChart' + tempTime;
case SysCustomWidgetType.Block:
return 'block' + tempTime;
case SysCustomWidgetType.Link:
return 'link' + tempTime;
case SysCustomWidgetType.UserSelect:
return 'userSelect' + tempTime;
case SysCustomWidgetType.DeptSelect:
return 'deptSelect' + tempTime;
case SysCustomWidgetType.DataSelect:
return 'dataSelect' + tempTime;
case SysCustomWidgetType.Card:
return 'baseCard' + tempTime;
case SysCustomWidgetType.Tabs:
return 'tabs' + tempTime;
case SysCustomWidgetType.Tree:
return 'tree' + tempTime;
case SysCustomWidgetType.TableContainer:
return 'tableContainer' + tempTime;
case SysCustomWidgetType.List:
return 'baseList' + tempTime;
case SysCustomWidgetType.Rate:
return 'rate' + tempTime;
case SysCustomWidgetType.Stepper:
return 'stepper' + tempTime;
case SysCustomWidgetType.Calendar:
return 'calendar' + tempTime;
case SysCustomWidgetType.CellGroup:
return 'group' + tempTime;
case SysCustomWidgetType.MobileRadioFilter:
return 'mbileRadioFilter' + tempTime;
case SysCustomWidgetType.MobileCheckBoxFilter:
return 'mobileCheckBoxFilter' + tempTime;
case SysCustomWidgetType.MobileInputFilter:
return 'mobileInputFilter' + tempTime;
case SysCustomWidgetType.MobileSwitchFilter:
return 'mobileSwitchFilter' + tempTime;
case SysCustomWidgetType.MobileDateRangeFilter:
return 'mobileDateRangeFilter' + tempTime;
case SysCustomWidgetType.MobileNumberRangeFilter:
return 'mobileNumberRangeFilter' + tempTime;
}
}
function getWidgetAttribute(widgetType: number): ANY_OBJECT | null {
switch (widgetType) {
case SysCustomWidgetType.Label:
return labelConfig;
case SysCustomWidgetType.Text:
return textConfig;
case SysCustomWidgetType.Image:
return imageConfig;
case SysCustomWidgetType.Input:
return inputConfig;
case SysCustomWidgetType.NumberInput:
return numberInputConfig;
case SysCustomWidgetType.NumberRange:
return numberRangeConfig;
case SysCustomWidgetType.Switch:
return switchConfig;
case SysCustomWidgetType.Radio:
return radioConfig;
case SysCustomWidgetType.CheckBox:
return checkboxConfig;
case SysCustomWidgetType.Select:
return selectConfig;
case SysCustomWidgetType.Cascader:
return cascaderConfig;
case SysCustomWidgetType.Date:
return dateConfig;
case SysCustomWidgetType.DateRange:
return dateRangeConfig;
case SysCustomWidgetType.Upload:
return uploadConfig;
case SysCustomWidgetType.RichEditor:
return richEditorConfig;
case SysCustomWidgetType.Table:
return tableConfig;
case SysCustomWidgetType.Block:
return blockConfig;
case SysCustomWidgetType.Link:
return linkConfig;
case SysCustomWidgetType.UserSelect:
return userSelectConfig;
case SysCustomWidgetType.DeptSelect:
return deptSelectConfig;
case SysCustomWidgetType.Card:
return baseCardConfig;
case SysCustomWidgetType.Tabs:
return tabsConfig;
case SysCustomWidgetType.Tree:
return treeConfig;
default:
return null;
}
}
function getWidgetObject(widget: ANY_OBJECT): ANY_OBJECT {
const temp = {
// ...widget,
widgetType: widget.widgetType,
bindData: {
//...bindDataConfig,
defaultValue: {
//...bindDataConfig.defaultValue,
},
},
operationList: widget.operationList
? JSON.parse(JSON.stringify(widget.operationList))
: undefined,
showName: SysCustomWidgetType.getValue(widget.widgetType),
variableName: getDefaultVariableName(widget.widgetType),
props: Object.keys(widget.attribute).reduce((retObj: ANY_OBJECT, key) => {
let tempValue;
if (typeof widget.attribute[key].value === 'function') {
tempValue = widget.attribute[key].value();
} else {
tempValue = widget.attribute[key].value;
}
if (Array.isArray(tempValue) || tempValue instanceof Object) {
retObj[key] = JSON.parse(JSON.stringify(tempValue));
} else {
retObj[key] = tempValue;
}
return retObj;
}, {}),
eventList: [],
childWidgetList: [],
style: {},
supportOperation: widget.supportOperation == null ? false : widget.supportOperation,
};
return temp;
}
function supportBindTable(widget: ANY_OBJECT) {
const widgetInfo = getWidgetAttribute(widget.widgetType);
return widgetInfo ? widgetInfo.supportBindTable : false;
}
function supportBindColumn(widget: ANY_OBJECT) {
const widgetInfo = getWidgetAttribute(widget.widgetType);
return widgetInfo ? widgetInfo.supportBindColumn : false;
}
export default {
formWidgetGroupList,
getWidgetObject,
getWidgetAttribute,
supportBindTable,
supportBindColumn,
};

View File

@@ -0,0 +1,129 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const input = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return (
formConfig &&
formConfig.form.formType !== SysOnlineFormType.QUERY &&
formConfig.activeMode === 'pc'
);
},
disabled: false,
min: 1,
max: 24,
},
type: {
name: '输入框类型',
value: 'text',
widgetType: SysCustomWidgetType.Select,
dropdownList: function (formConfig: ANY_OBJECT) {
return [
{
id: 'text',
name: '单行文本',
},
{
id: 'textarea',
name: '多行文本',
},
];
},
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
'show-password': {
name: '是否密码',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '是',
},
{
id: false,
name: '否',
},
],
},
'show-word-limit': {
name: '是否显示字数统计',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
maxlength: {
name: '最大字符数',
widgetType: SysCustomWidgetType.NumberInput,
value: undefined,
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const inputConfig = {
widgetType: SysCustomWidgetType.Input,
icon: 'online-icon icon-input',
attribute: input,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default inputConfig;

View File

@@ -0,0 +1,30 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const label = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
};
const labelConfig = {
widgetType: SysCustomWidgetType.Label,
icon: 'online-icon icon-text',
attribute: label,
allowEventList: [OnlineFormEventType.VISIBLE],
supportBindTable: true,
supportBindColumn: true,
};
export default labelConfig;

View File

@@ -0,0 +1,105 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const input = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
disabled: false,
min: 1,
max: 24,
},
type: {
name: '显示类型',
value: 'primary',
widgetType: SysCustomWidgetType.Select,
dropdownList: [
{
id: 'primary',
name: 'primary',
},
{
id: 'success',
name: 'success',
},
{
id: 'warning',
name: 'warning',
},
{
id: 'danger',
name: 'danger',
},
{
id: 'info',
name: 'info',
},
],
},
href: {
name: '链接地址',
widgetType: SysCustomWidgetType.Input,
value: undefined,
},
showText: {
name: '链接显示',
widgetType: SysCustomWidgetType.Input,
value: undefined,
},
underline: {
name: '下划线',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '下划线',
},
{
id: false,
name: '无下划线',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const linkConfig = {
widgetType: SysCustomWidgetType.Link,
icon: 'online-icon icon-link',
attribute: input,
allowEventList: [
OnlineFormEventType.LINK_HERF,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: false,
supportBindColumn: false,
};
export default linkConfig;

View File

@@ -0,0 +1,122 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const numberInput = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
min: {
name: '最小值',
widgetType: SysCustomWidgetType.NumberInput,
value: undefined,
},
max: {
name: '最大值',
widgetType: SysCustomWidgetType.NumberInput,
value: undefined,
},
step: {
name: '步长',
widgetType: SysCustomWidgetType.NumberInput,
value: 1,
},
precision: {
name: '精度',
widgetType: SysCustomWidgetType.NumberInput,
value: undefined,
},
controls: {
name: '控制按钮',
widgetType: SysCustomWidgetType.Switch,
value: true,
dropdownList: [
{
id: true,
name: '显示',
},
{
id: false,
name: '隐藏',
},
],
},
'controls-position': {
name: '按钮位置',
widgetType: SysCustomWidgetType.Radio,
value: undefined,
dropdownList: [
{
id: undefined,
name: '默认',
},
{
id: 'right',
name: '右侧',
},
],
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const numberInputConfig = {
widgetType: SysCustomWidgetType.NumberInput,
icon: 'online-icon icon-input-number',
attribute: numberInput,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default numberInputConfig;

View File

@@ -0,0 +1,77 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const numberRange = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
'start-placeholder': {
name: '最小值提示',
widgetType: SysCustomWidgetType.Input,
value: '',
},
'end-placeholder': {
name: '最大值提示',
widgetType: SysCustomWidgetType.Input,
value: '',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const numberRangeConfig = {
widgetType: SysCustomWidgetType.NumberRange,
icon: 'online-icon icon-number-range',
attribute: numberRange,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default numberRangeConfig;

View File

@@ -0,0 +1,88 @@
import { ANY_OBJECT } from '@/types/generic';
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
const radio = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
dictInfo: {
name: '下拉字典',
value: {},
customComponent: {
component: 'CustomWidgetDictSetting',
},
},
supportAll: {
name: '全部选项',
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '显示',
},
{
id: false,
name: '隐藏',
},
],
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const radioConfig = {
widgetType: SysCustomWidgetType.Radio,
icon: 'online-icon icon-radio',
attribute: radio,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default radioConfig;

View File

@@ -0,0 +1,45 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const richEditor = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 24,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
};
const richEditorConfig = {
widgetType: SysCustomWidgetType.RichEditor,
icon: 'online-icon icon-richeditor',
attribute: richEditor,
eventList: [OnlineFormEventType.VISIBLE],
supportBindTable: true,
supportBindColumn: true,
};
export default richEditorConfig;

View File

@@ -0,0 +1,80 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const select = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
dictInfo: {
name: '下拉字典',
value: {},
customComponent: {
component: 'CustomWidgetDictSetting',
},
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const selectConfig = {
widgetType: SysCustomWidgetType.Select,
icon: 'online-icon icon-select',
attribute: select,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
OnlineFormEventType.DROPDOWN_CHANGE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default selectConfig;

View File

@@ -0,0 +1,77 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const switchAttribute = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
'active-color': {
name: '打开背景色',
widgetType: SysCustomWidgetType.ColorPicker,
value: '#1989fa',
},
'inactive-color': {
name: '关闭背景色',
widgetType: SysCustomWidgetType.ColorPicker,
value: '#E8E8E8',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const switchConfig = {
widgetType: SysCustomWidgetType.Switch,
icon: 'online-icon icon-switch',
attribute: switchAttribute,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default switchConfig;

View File

@@ -0,0 +1,155 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
SysCustomWidgetOperationType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const table = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 24,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
height: {
name: '表格高度',
widgetType: SysCustomWidgetType.NumberInput,
value: 300,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 100,
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
paged: {
name: '支持分页',
widgetType: SysCustomWidgetType.Switch,
value: true,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType === SysOnlineFormType.QUERY;
},
},
pageSize: {
name: '每页条数',
widgetType: SysCustomWidgetType.Select,
value: 10,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType === SysOnlineFormType.QUERY;
},
dropdownList: [
{
id: 10,
name: 10,
},
{
id: 20,
name: 20,
},
{
id: 50,
name: 50,
},
{
id: 100,
name: 100,
},
],
},
operationColumnWidth: {
name: '操作列宽度',
widgetType: SysCustomWidgetType.NumberInput,
value: 160,
},
tableColumnList: {
name: '表格字段',
showLabel: false,
value: [],
customComponent: {
component: 'OnlineTableColumnSetting',
},
},
};
const tableConfig = {
widgetType: SysCustomWidgetType.Table,
icon: 'online-icon icon-table',
attribute: table,
allowEventList: [
OnlineFormEventType.VISIBLE,
OnlineFormEventType.BEFORE_LOAD_TABLE_DATA,
OnlineFormEventType.AFTER_LOAD_TABLE_DATA,
],
operationList: [
{
id: 1,
type: SysCustomWidgetOperationType.BATCH_DELETE,
name: SysCustomWidgetOperationType.getValue(SysCustomWidgetOperationType.BATCH_DELETE),
enabled: false,
builtin: true,
rowOperation: false,
btnType: 'danger',
plain: true,
formId: undefined,
readOnly: false,
showOrder: 0,
eventList: [],
},
{
id: 2,
type: SysCustomWidgetOperationType.ADD,
name: SysCustomWidgetOperationType.getValue(SysCustomWidgetOperationType.ADD),
enabled: false,
builtin: true,
rowOperation: false,
btnType: 'primary',
plain: false,
formId: undefined,
readOnly: false,
showOrder: 1,
eventList: [],
},
{
id: 3,
type: SysCustomWidgetOperationType.EDIT,
name: SysCustomWidgetOperationType.getValue(SysCustomWidgetOperationType.EDIT),
enabled: false,
builtin: true,
rowOperation: true,
btnClass: 'table-btn success',
formId: undefined,
readOnly: false,
showOrder: 10,
eventList: [],
},
{
id: 4,
type: SysCustomWidgetOperationType.DELETE,
name: SysCustomWidgetOperationType.getValue(SysCustomWidgetOperationType.DELETE),
enabled: false,
builtin: true,
rowOperation: true,
btnClass: 'table-btn delete',
formId: undefined,
readOnly: false,
showOrder: 15,
eventList: [],
},
],
supportOperate: true,
supportBindTable: true,
supportBindColumn: false,
supportOperation: true,
};
export default tableConfig;

View File

@@ -0,0 +1,51 @@
import { SysCustomWidgetType, OnlineFormEventType } from '@/common/staticDict/index';
const tabs = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 24,
min: 1,
max: 24,
},
type: {
name: '风格类型',
widgetType: SysCustomWidgetType.Radio,
value: undefined,
dropdownList: [
{
id: undefined,
name: '默认',
},
{
id: 'border-card',
name: '卡片',
},
],
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
tabPanelList: {
name: '标签页设置',
value: [],
customComponent: {
component: 'OnlineTabPanelSetting',
},
},
};
const tabsConfig = {
widgetType: SysCustomWidgetType.Tabs,
icon: 'online-icon icon-tabs2',
attribute: tabs,
allowEventList: [OnlineFormEventType.VISIBLE],
supportOperate: false,
supportBindTable: false,
supportBindColumn: false,
};
export default tabsConfig;

View File

@@ -0,0 +1,132 @@
import {
SysCustomWidgetType,
OnlineFormEventType,
SysCustomWidgetBindDataType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const text = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
min: 1,
max: 24,
},
padding: {
name: '内部边距',
widgetType: SysCustomWidgetType.NumberInput,
value: 2,
min: 0,
},
paddingBottom: {
name: '底部距离',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
height: {
name: '组件高度',
widgetType: SysCustomWidgetType.NumberInput,
value: 25,
min: 0,
},
textIndent: {
name: '首行缩进',
widgetType: SysCustomWidgetType.NumberInput,
value: 0,
min: 0,
},
align: {
name: '水平对齐',
value: 'left',
widgetType: SysCustomWidgetType.Select,
dropdownList: [
{
id: 'left',
name: '左对齐',
},
{
id: 'center',
name: '居中',
},
{
id: 'right',
name: '右对齐',
},
],
},
valign: {
name: '垂直对齐',
value: 'center',
widgetType: SysCustomWidgetType.Select,
dropdownList: [
{
id: 'flex-start',
name: '顶部',
},
{
id: 'center',
name: '居中',
},
{
id: 'flex-end',
name: '底部',
},
],
},
fontSize: {
name: '字号',
value: 14,
widgetType: SysCustomWidgetType.Slider,
min: 10,
max: 50,
},
fontColor: {
name: '字体颜色',
widgetType: SysCustomWidgetType.ColorPicker,
value: '#383838',
},
bgColor: {
name: '背景色',
widgetType: SysCustomWidgetType.ColorPicker,
value: undefined,
},
fontBold: {
name: '粗体',
widgetType: SysCustomWidgetType.Switch,
value: false,
},
fontItalic: {
name: '斜体',
widgetType: SysCustomWidgetType.Switch,
value: false,
},
text: {
name: '内容',
value: '文本内容',
widgetType: SysCustomWidgetType.Input,
props: {
type: 'textarea',
disabled: function (formConfig: ANY_OBJECT) {
// 表单为非报表,并且绑定在字段上,那么内容不可输入
return (
formConfig &&
formConfig.currentWidget?.bindData?.dataType !== SysCustomWidgetBindDataType.Fixed &&
formConfig.form?.pageCode == null
);
},
},
},
};
const textConfig = {
widgetType: SysCustomWidgetType.Text,
icon: 'online-icon icon-text',
attribute: text,
allowEventList: [OnlineFormEventType.VISIBLE],
supportBindTable: true,
supportBindColumn: true,
};
export default textConfig;

View File

@@ -0,0 +1,83 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const tree = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 24,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
height: {
name: '组件高度',
widgetType: SysCustomWidgetType.NumberInput,
value: 300,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 100,
},
dictInfo: {
name: '下拉字典',
value: {},
customComponent: {
component: 'CustomWidgetDictSetting',
},
},
required: {
name: '是否必填',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const treeConfig = {
widgetType: SysCustomWidgetType.Tree,
icon: 'online-icon icon-table',
attribute: tree,
allowEventList: [OnlineFormEventType.VISIBLE],
supportOperate: false,
supportBindTable: true,
supportBindColumn: true,
};
export default treeConfig;

View File

@@ -0,0 +1,134 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
import { API_CONTEXT } from '@/api/config';
const upload = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
fileFieldName: {
name: '文件字段名',
widgetType: SysCustomWidgetType.Input,
value: 'uploadFile',
},
actionUrl: {
name: '上传地址',
widgetType: SysCustomWidgetType.Input,
value: function (formConfig: ANY_OBJECT) {
if (formConfig == null) return;
const form = formConfig.form;
const widget = formConfig.currentWidget;
if (form == null || widget == null || widget.datasource == null) return '';
if (
form.formType === SysOnlineFormType.FLOW ||
form.formType === SysOnlineFormType.FLOW_SLAVE_EDIT
) {
return API_CONTEXT + '/flow/flowOnlineOperation/upload';
} else {
return (
API_CONTEXT +
'/online/onlineOperation/' +
(widget.relation ? 'uploadOneToManyRelation/' : 'uploadDatasource/') +
widget.datasource.variableName
);
}
},
},
downloadUrl: {
name: '下载地址',
widgetType: SysCustomWidgetType.Input,
value: function (formConfig: ANY_OBJECT) {
if (formConfig == null) return;
const form = formConfig.form;
const widget = formConfig.currentWidget;
if (form == null || widget == null || widget.datasource == null) return '';
if (
form.formType === SysOnlineFormType.FLOW ||
form.formType === SysOnlineFormType.FLOW_SLAVE_EDIT
) {
return API_CONTEXT + '/flow/flowOnlineOperation/download';
} else {
return (
API_CONTEXT +
'/online/onlineOperation/' +
(widget.relation ? 'downloadOneToManyRelation/' : 'downloadDatasource/') +
widget.datasource.variableName
);
}
},
},
readOnly: {
name: '是否只读',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '是',
},
{
id: false,
name: '否',
},
],
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const uploadConfig = {
widgetType: SysCustomWidgetType.Upload,
icon: 'online-icon icon-upload',
attribute: upload,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default uploadConfig;

View File

@@ -0,0 +1,72 @@
import {
SysCustomWidgetType,
SysOnlineFormType,
OnlineFormEventType,
} from '@/common/staticDict/index';
import { ANY_OBJECT } from '@/types/generic';
const userSelect = {
span: {
name: '组件宽度',
widgetType: SysCustomWidgetType.Slider,
value: 12,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
min: 1,
max: 24,
},
placeholder: {
name: '占位文本',
widgetType: SysCustomWidgetType.Input,
value: '',
},
required: {
name: '是否必填',
value: false,
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: true,
name: '必填',
},
{
id: false,
name: '非必填',
},
],
},
disabled: {
name: '是否禁用',
value: false,
visible: function (formConfig: ANY_OBJECT) {
return formConfig && formConfig.form.formType !== SysOnlineFormType.QUERY;
},
widgetType: SysCustomWidgetType.Switch,
dropdownList: [
{
id: false,
name: '启用',
},
{
id: true,
name: '禁用',
},
],
},
};
const userSelectConfig = {
widgetType: SysCustomWidgetType.UserSelect,
icon: 'online-icon icon-user',
attribute: userSelect,
allowEventList: [
OnlineFormEventType.CHANGE,
OnlineFormEventType.DISABLE,
OnlineFormEventType.VISIBLE,
],
supportBindTable: true,
supportBindColumn: true,
};
export default userSelectConfig;

View File

@@ -0,0 +1,32 @@
import { SysCustomWidgetType, OnlineFormEventType } from '@/common/staticDict/index';
const workOrderList = {
card: {
name: '显示组件',
widgetType: SysCustomWidgetType.Select,
value: SysCustomWidgetType.ImageCard,
dropdownList: [
{
id: SysCustomWidgetType.ImageCard,
name: SysCustomWidgetType.getValue(SysCustomWidgetType.ImageCard),
},
],
props: {
clearable: false,
},
},
};
const workOrderListConfig = {
widgetType: SysCustomWidgetType.WorkOrderList,
icon: 'online-icon icon-card',
attribute: workOrderList,
allowEventList: [OnlineFormEventType.VISIBLE],
operationList: [],
supportOperate: false,
supportBindTable: true,
supportBindColumn: false,
supportOperation: false,
};
export default workOrderListConfig;