---初始化后台管理web页面项目
This commit is contained in:
5
src/components/Designer/index.ts
Normal file
5
src/components/Designer/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import designForm from './src/Designer.vue';
|
||||
|
||||
export * from './src/types';
|
||||
export const DesignForm = withInstall(designForm);
|
||||
739
src/components/Designer/src/Designer.vue
Normal file
739
src/components/Designer/src/Designer.vue
Normal file
@ -0,0 +1,739 @@
|
||||
<template>
|
||||
<div class="fc-style">
|
||||
<a-layout class="fc-container">
|
||||
<a-layout-content class="fc-main">
|
||||
<a-layout>
|
||||
<a-layout-sider theme="light" :width="250" class="cg-container">
|
||||
<div class="components">
|
||||
<a-collapse
|
||||
v-model:activeKey="componentGroupKey"
|
||||
:bordered="false"
|
||||
expand-icon-position="right"
|
||||
ghost
|
||||
>
|
||||
<template #expandIcon="{ isActive }">
|
||||
<double-right-outlined :rotate="isActive ? -90 : 90" style="color: #ccc" />
|
||||
</template>
|
||||
<a-collapse-panel
|
||||
v-for="(item, index) in componentList"
|
||||
:key="index + 1"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{item.title}}</span>
|
||||
<span class="components-readme" v-if="item.title == '布局型组件'" @click="openCompReadme">注意事项</span>
|
||||
</template>
|
||||
<ComponentGroup :fields="item.fields" :list="item.list" :isDisabled="!dbValue" />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<a-layout class="center-container">
|
||||
<AntdHeader
|
||||
v-bind="$props"
|
||||
@preview="handlerPreview"
|
||||
@upload-json="uploadJsonVisible = true"
|
||||
@generate-json="handleGenerateJson"
|
||||
@generate-code="handleGenerateCode"
|
||||
@clearable="handleClearable"
|
||||
>
|
||||
<slot name="header">
|
||||
<a-row class="float-left text-left w-[240px]" align="middle" v-if="isShowSelectDb">
|
||||
<a-col :span="8" align="right">
|
||||
<span style="color: red">*</span>
|
||||
数据库:
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<DbSelect
|
||||
:placeholder="t('请选择数据库')"
|
||||
@change="handleDbChange"
|
||||
:disabled="isDbDisabled"
|
||||
v-model:value="dbValue"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</slot>
|
||||
</AntdHeader>
|
||||
<a-layout-content id="layoutId" :class="{ 'widget-empty': widgetForm.list }">
|
||||
<AntdWidgetForm
|
||||
ref="widgetFormRef"
|
||||
v-model:widgetForm="widgetForm"
|
||||
v-model:widgetFormSelect="widgetFormSelect"
|
||||
/>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
<a-layout-sider theme="light" class="widget-config-container" :width="320">
|
||||
<a-layout class="layout-height">
|
||||
<!-- <a-layout-header class="right-tab"> -->
|
||||
<a-tabs
|
||||
v-model:activeKey="configTab"
|
||||
centered
|
||||
style="margin-bottom: 8px; background: #fff; height: 100%"
|
||||
>
|
||||
<a-tab-pane key="widget" :tab="t('组件属性')">
|
||||
<a-collapse
|
||||
v-model:activeKey="chooseTab"
|
||||
ghost
|
||||
expandIconPosition="right"
|
||||
v-if="widgetFormSelect"
|
||||
style="margin: 5px 0 10px 10px"
|
||||
>
|
||||
<a-collapse-panel key="property" :header="t('属性设置')">
|
||||
<TableLayoutOption
|
||||
v-if="widgetFormSelect.type == 'table-layout'"
|
||||
v-model:select="widgetFormSelect"
|
||||
:widgetForm="widgetForm"
|
||||
></TableLayoutOption>
|
||||
<PropertyOption
|
||||
v-else
|
||||
v-model:select="widgetFormSelect"
|
||||
:widgetForm="widgetForm"
|
||||
/>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel
|
||||
key="event"
|
||||
:header="t('触发事件')"
|
||||
v-if="!widgetFormSelect.isSubFormChild && !widgetFormSelect.isSingleFormChild"
|
||||
>
|
||||
<div
|
||||
class="widget-none"
|
||||
v-if="noConfigComponentEvent.includes(widgetFormSelect.type)"
|
||||
>
|
||||
<SvgIcon name="exclamation" :size="44" />
|
||||
<p>{{ t('当前组件无法配置触发组件') }}</p>
|
||||
</div>
|
||||
<ComponentEvent
|
||||
v-model:select="widgetFormSelect"
|
||||
:widgetForm="widgetForm"
|
||||
v-else
|
||||
/>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="regular" :header="t('正则校验')">
|
||||
<div
|
||||
class="widget-none"
|
||||
v-if="noConfigRegularSetting.includes(widgetFormSelect.type)"
|
||||
>
|
||||
<SvgIcon name="exclamation" :size="44" />
|
||||
<p>{{ t('当前组件无法配置正则检验') }}</p>
|
||||
</div>
|
||||
<RegularSetting v-model:select="widgetFormSelect" v-else />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="widget-none" v-else>
|
||||
<SvgIcon name="tool" :size="44" />
|
||||
<p>{{ t('请先拖入组件后再查看组件属性') }}</p>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="form" :tab="t('表单属性')">
|
||||
<AntdFormConfig v-model:config="widgetForm.config" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="hidden" :tab="t('隐藏组件')">
|
||||
<HiddenComponent v-model:component="widgetForm.hiddenComponent" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-layout>
|
||||
</a-layout-sider>
|
||||
</a-layout>
|
||||
</a-layout-content>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="uploadJsonVisible"
|
||||
:title="t('导入JSON')"
|
||||
:width="800"
|
||||
@ok="handleUploadJson"
|
||||
:okText="t('确认')"
|
||||
:cancelText="t('取消')"
|
||||
>
|
||||
<a-alert
|
||||
type="info"
|
||||
:message="t('JSON格式如下,直接复制生成的json覆盖此处代码点击确定即可')"
|
||||
style="margin-bottom: 10px"
|
||||
/>
|
||||
<CodeEditor v-model:value="jsonEg" language="json" />
|
||||
</a-modal>
|
||||
|
||||
<PreviewDrawer @register="registerDrawer" />
|
||||
|
||||
<a-modal
|
||||
v-model:visible="generateJsonVisible"
|
||||
:title="t('生成JSON')"
|
||||
:okText="t('复制')"
|
||||
:cancelText="t('取消')"
|
||||
:width="800"
|
||||
@ok="handleCopyClick(generateJsonTemplate)"
|
||||
>
|
||||
<CodeEditor :value="generateJsonTemplate" language="json" readonly />
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="dataJsonVisible"
|
||||
:title="t('获取数据')"
|
||||
:okText="t('复制')"
|
||||
:cancelText="t('取消')"
|
||||
:width="800"
|
||||
@ok="handleCopyClick(dataJsonTemplate)"
|
||||
>
|
||||
<CodeEditor :value="dataJsonTemplate" language="json" readonly />
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="dataCodeVisible"
|
||||
:title="t('生产代码')"
|
||||
:okText="t('复制')"
|
||||
:cancelText="t('取消')"
|
||||
:width="800"
|
||||
@ok="handleCopyClick(dataCodeTemplate)"
|
||||
>
|
||||
<a-tabs type="card" v-model:activeKey="codeLanguage" :tabBarStyle="{ margin: 0 }">
|
||||
<a-tab-pane tab="Vue Component" :key="codeType.Vue">
|
||||
<CodeEditor :value="dataCodeTemplate" language="html" readonly />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="HTML" :key="codeType.Html">
|
||||
<CodeEditor :value="dataCodeTemplate" language="html" readonly />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
<a-modal v-model:visible="showCompReadme" title="注意事项" @ok="closeCompReadme">
|
||||
<div class="comp-readme-content">
|
||||
因为组件封装的限制,当使用表格布局、选项卡、卡片布局、栅格布局时,会带来一些问题,包括二开拆分不完全、移动端生成代码无法适配等。<br/>
|
||||
无特殊需求时,应该优先使用设计器提供的响应式布局,或者进行源码二开。<br/>
|
||||
源码二开可以复用设计器生成的代码,调整布局、显隐、权限更加方便。
|
||||
</div>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
PropType,
|
||||
toRefs,
|
||||
watchEffect,
|
||||
provide,
|
||||
watch,
|
||||
inject,
|
||||
ref,
|
||||
onMounted,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { cloneDeep, merge, debounce } from 'lodash-es';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import ComponentGroup from './components/ComponentGroup.vue';
|
||||
import AntdHeader from './components/AntdHeader.vue';
|
||||
import AntdWidgetForm from './components/AntdWidgetForm.vue';
|
||||
import PropertyOption from './components/componentProperty/PropertyOption.vue';
|
||||
import TableLayoutOption from './components/componentProperty/TableLayoutOption.vue';
|
||||
import RegularSetting from './components/componentProperty/RegularSetting.vue';
|
||||
import ComponentEvent from './components/componentProperty/ComponentEvent.vue';
|
||||
import AntdFormConfig from './components/AntdFormConfig.vue';
|
||||
import HiddenComponent from './components/HiddenComponent.vue';
|
||||
import { DbSelect } from '/@/components/DbSelect';
|
||||
import * as antd from './types';
|
||||
import { copy } from '/@/utils';
|
||||
import generateCode from './util/generateCode';
|
||||
import { WidgetForm, CodeType, PlatformType, upperFieldDb, TableCell, TableTh } from './types';
|
||||
import PreviewDrawer from './components/PreviewDrawer.vue';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
|
||||
import { noConfigComponentEvent, noConfigRegularSetting, noHaveTableAndField } from './types';
|
||||
import { DoubleRightOutlined } from '@ant-design/icons-vue';
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
export default defineComponent({
|
||||
name: 'AntdDesignForm',
|
||||
components: {
|
||||
AntdHeader,
|
||||
ComponentGroup,
|
||||
CodeEditor,
|
||||
AntdWidgetForm,
|
||||
PreviewDrawer,
|
||||
AntdFormConfig,
|
||||
PropertyOption,
|
||||
RegularSetting,
|
||||
ComponentEvent,
|
||||
HiddenComponent,
|
||||
DoubleRightOutlined,
|
||||
SvgIcon,
|
||||
DbSelect,
|
||||
TableLayoutOption,
|
||||
},
|
||||
props: {
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
generateJson: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
uploadJson: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
basicFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => [
|
||||
'input',
|
||||
'textarea',
|
||||
'password',
|
||||
'number',
|
||||
'richtext-editor',
|
||||
'auto-code',
|
||||
'auto-complete',
|
||||
],
|
||||
},
|
||||
advanceFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => [
|
||||
'select',
|
||||
'cascader',
|
||||
'associate-select',
|
||||
'multiple-popup',
|
||||
'associate-popup',
|
||||
'area',
|
||||
'checkbox',
|
||||
'radio',
|
||||
'switch',
|
||||
'slider',
|
||||
'time',
|
||||
'time-range',
|
||||
'date',
|
||||
'date-range',
|
||||
'rate',
|
||||
'picker-color',
|
||||
'upload',
|
||||
'image',
|
||||
'map',
|
||||
'qrcode',
|
||||
],
|
||||
},
|
||||
buttonFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => ['button'],
|
||||
},
|
||||
layoutFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => [
|
||||
'form',
|
||||
|
||||
'one-for-one',
|
||||
'form-view',
|
||||
|
||||
'title',
|
||||
'text',
|
||||
'grid',
|
||||
|
||||
'tab',
|
||||
|
||||
'card',
|
||||
'iframe',
|
||||
|
||||
'divider',
|
||||
,
|
||||
'table-layout',
|
||||
],
|
||||
},
|
||||
workFlowFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => ['opinion'],
|
||||
},
|
||||
infoFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => ['info', 'organization', 'user'],
|
||||
},
|
||||
financeFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => ['computational', 'money-chinese'],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const state = reactive({
|
||||
antd,
|
||||
codeType: CodeType,
|
||||
widgetForm: undefined as any,
|
||||
widgetFormSelect: undefined as any,
|
||||
generateFormRef: null as any,
|
||||
configTab: 'widget',
|
||||
chooseTab: 'property',
|
||||
previewVisible: false,
|
||||
uploadJsonVisible: false,
|
||||
dataJsonVisible: false,
|
||||
dataCodeVisible: false,
|
||||
generateJsonVisible: false,
|
||||
generateCodeVisible: false,
|
||||
generateJsonTemplate: JSON.stringify(antd.widgetForm, null, 2),
|
||||
dataJsonTemplate: '',
|
||||
dataCodeTemplate: '',
|
||||
codeLanguage: CodeType.Vue,
|
||||
jsonEg: JSON.stringify(antd.widgetForm, null, 2),
|
||||
componentGroupKey: ['1', '2', '3', '4', '5', '6', '7'],
|
||||
componentList: [
|
||||
{
|
||||
title: t('输入型组件'),
|
||||
fields: props.basicFields,
|
||||
list: antd.basicComponents,
|
||||
},
|
||||
{
|
||||
title: t('选择型组件'),
|
||||
fields: props.advanceFields,
|
||||
list: antd.advanceComponents,
|
||||
},
|
||||
{
|
||||
title: t('按钮型组件'),
|
||||
fields: props.buttonFields,
|
||||
list: antd.buttonComponents,
|
||||
},
|
||||
{
|
||||
title: t('布局型组件'),
|
||||
fields: props.layoutFields,
|
||||
list: antd.layoutComponents,
|
||||
},
|
||||
{
|
||||
title: t('工作流组件'),
|
||||
fields: props.workFlowFields,
|
||||
list: antd.workFlowComponents,
|
||||
},
|
||||
{
|
||||
title: t('组织架构'),
|
||||
fields: props.infoFields,
|
||||
list: antd.infoComponents,
|
||||
},
|
||||
{
|
||||
title: t('财务组件'),
|
||||
fields: props.financeFields,
|
||||
list: antd.financeComponents,
|
||||
},
|
||||
],
|
||||
});
|
||||
state.widgetForm = inject('widgetForm');
|
||||
const generatorConfig = inject<GeneratorConfig>('generatorConfig') as GeneratorConfig;
|
||||
const designType = inject<string>('designType');
|
||||
let isFieldUpper = inject<Ref<boolean>>('isFieldUpper', ref(false));
|
||||
let mainTableName = inject<Ref<string>>('mainTableName', ref(''));
|
||||
|
||||
const { notification } = useMessage();
|
||||
|
||||
const isDbDisabled = ref(false);
|
||||
const dbValue = ref();
|
||||
const isShowSelectDb = ref(false);
|
||||
const tableCell = ref<TableCell>();
|
||||
const tableTh = ref<TableTh>();
|
||||
const showCompReadme = ref(false);
|
||||
provide('tableCell', tableCell);
|
||||
provide('tableTh', tableTh);
|
||||
|
||||
watch(
|
||||
() => generatorConfig.databaseId,
|
||||
(val) => {
|
||||
if (val) {
|
||||
dbValue.value = val;
|
||||
if (state.widgetForm?.list?.length) {
|
||||
isDbDisabled.value = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
isShowSelectDb.value = designType === 'code';
|
||||
});
|
||||
|
||||
//注入数据
|
||||
provide('state', state);
|
||||
|
||||
const handleUploadJson = () => {
|
||||
try {
|
||||
setJson(JSON.parse(state.jsonEg));
|
||||
state.uploadJsonVisible = false;
|
||||
message.success(t('上传成功'));
|
||||
} catch (error) {
|
||||
message.error(t('上传失败'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyClick = (text: string) => {
|
||||
copy(text);
|
||||
message.success(t('Copy成功'));
|
||||
};
|
||||
|
||||
const handleGetData = () => {
|
||||
state.generateFormRef.getData().then((res: any) => {
|
||||
state.dataJsonTemplate = JSON.stringify(res, null, 2);
|
||||
state.dataJsonVisible = true;
|
||||
});
|
||||
};
|
||||
|
||||
const handleGenerateJson = () => {
|
||||
(state.generateJsonTemplate = JSON.stringify(state.widgetForm, null, 2)) &&
|
||||
(state.generateJsonVisible = true);
|
||||
};
|
||||
|
||||
const handleGenerateCode = () => {
|
||||
state.codeLanguage = CodeType.Vue;
|
||||
state.dataCodeVisible = true;
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (state.dataCodeVisible) {
|
||||
state.dataCodeTemplate = generateCode(
|
||||
state.widgetForm,
|
||||
state.codeLanguage,
|
||||
PlatformType.Antd,
|
||||
)!;
|
||||
}
|
||||
});
|
||||
|
||||
const handleClearable = () => {
|
||||
state.widgetForm.list = [];
|
||||
merge(state.widgetForm, JSON.parse(JSON.stringify(antd.widgetForm)));
|
||||
state.widgetFormSelect = undefined;
|
||||
};
|
||||
|
||||
const handleReset = () => state.generateFormRef.reset();
|
||||
|
||||
const getJson = () => state.widgetForm;
|
||||
|
||||
const setJson = (json: WidgetForm) => {
|
||||
state.widgetForm.list = [];
|
||||
merge(state.widgetForm, json);
|
||||
if (json.list?.length) {
|
||||
state.widgetFormSelect = json.list[0];
|
||||
}
|
||||
};
|
||||
const setWidgetFormSelect = (json: WidgetForm) => {
|
||||
if (json.list?.length) {
|
||||
state.widgetFormSelect = json.list[0];
|
||||
}
|
||||
};
|
||||
const handlerPreview = () => {
|
||||
if (designType === 'data' && hasNoBindTableOrField(state.widgetForm.list)) {
|
||||
notification.error({
|
||||
message: t('提示'),
|
||||
description: t('请先将组件绑定表和字段'),
|
||||
}); //提示消息
|
||||
} else {
|
||||
openDrawer(true, getJson());
|
||||
}
|
||||
};
|
||||
|
||||
const hasNoBindTableOrField = (component) => {
|
||||
const hasSubComponent = ['tab', 'grid', 'card', 'one-for-one', 'form', 'table-layout'];
|
||||
return component?.some((info) => {
|
||||
if (hasSubComponent.includes(info.type!)) {
|
||||
if (info.type === 'form' || info.type === 'one-for-one') {
|
||||
return hasNoBindTableOrField(info.children);
|
||||
} else if (info.type === 'table-layout') {
|
||||
return info.layout?.some((childInfo) => {
|
||||
return childInfo.list?.some((child) => {
|
||||
return hasNoBindTableOrField(child.children);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return info.layout?.some((childInfo) => {
|
||||
return hasNoBindTableOrField(childInfo.list);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (info.type === 'time-range' || info.type === 'date-range') {
|
||||
return !info.bindTable || !info.bindStartTime || !info.bindEndTime;
|
||||
}
|
||||
if (info.type === 'input' && info.options.isSave) {
|
||||
return false;
|
||||
}
|
||||
return noHaveTableAndField.includes(info.type)
|
||||
? false
|
||||
: !info.bindTable || !info.bindField;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDbChange = (value, option) => {
|
||||
generatorConfig.databaseId = value;
|
||||
if (option) isFieldUpper.value = upperFieldDb.includes(option.dbType);
|
||||
mainTableName.value = isFieldUpper.value
|
||||
? mainTableName.value.toUpperCase()
|
||||
: mainTableName.value;
|
||||
};
|
||||
|
||||
const getTemplate = (codeType: CodeType) =>
|
||||
generateCode(state.widgetForm, codeType, PlatformType.Antd);
|
||||
|
||||
const clear = () => handleClearable();
|
||||
|
||||
//这里使用了防抖 包裹 因为监听 state.widgetForm 会执行2次
|
||||
watch(
|
||||
() => state.widgetForm,
|
||||
debounce((val) => {
|
||||
isDbDisabled.value = !!val?.list?.length || !!val?.hiddenComponent?.length;
|
||||
generatorConfig.formJson = cloneDeep(val);
|
||||
}, 50),
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function openCompReadme(e) {
|
||||
e.stopPropagation();
|
||||
showCompReadme.value = true;
|
||||
}
|
||||
|
||||
function closeCompReadme() {
|
||||
showCompReadme.value = false;
|
||||
}
|
||||
//注册抽屉 获取外部操作抽屉得方法
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
handleUploadJson,
|
||||
handleCopyClick,
|
||||
handleGetData,
|
||||
handleGenerateJson,
|
||||
handleGenerateCode,
|
||||
handleClearable,
|
||||
handleReset,
|
||||
getJson,
|
||||
setJson,
|
||||
setWidgetFormSelect,
|
||||
getTemplate,
|
||||
clear,
|
||||
noConfigComponentEvent,
|
||||
noConfigRegularSetting,
|
||||
handlerPreview,
|
||||
registerDrawer,
|
||||
t,
|
||||
handleDbChange,
|
||||
isDbDisabled,
|
||||
isShowSelectDb,
|
||||
dbValue,
|
||||
tableCell,
|
||||
tableTh,
|
||||
showCompReadme,
|
||||
openCompReadme,
|
||||
closeCompReadme
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.fc-style {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-height {
|
||||
height: 100%;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
padding-left: 10px !important;
|
||||
margin: 10px 0;
|
||||
height: 24px;
|
||||
align-items: center !important;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-content-box) {
|
||||
padding: 0 0 5px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 10px 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content-top) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-radio-group) {
|
||||
display: flex;
|
||||
|
||||
.ant-radio-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: 3px 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-none {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
margin: 10px;
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-radio-group-small .ant-radio-button-wrapper) {
|
||||
padding: 3px 10px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.cg-container {
|
||||
margin-bottom: 8px;
|
||||
overflow-y: scroll;
|
||||
padding-left: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fc-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-layout.ant-layout-has-sider {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.center-container {
|
||||
height: calc(100% - 8px);
|
||||
background-color: #fff;
|
||||
padding-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
border-left: 8px solid #f0f2f5;
|
||||
border-right: 8px solid #f0f2f5;
|
||||
|
||||
.center-header {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.components-readme {
|
||||
position: absolute;
|
||||
right: 38px;
|
||||
color: #c00;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.comp-readme-content {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
117
src/components/Designer/src/components/AntdFormConfig.vue
Normal file
117
src/components/Designer/src/components/AntdFormConfig.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="form-config-container">
|
||||
<a-form v-bind="layout">
|
||||
<a-form-item :label="t('表单形式')">
|
||||
<a-radio-group v-model:value="data.formType" button-style="solid" size="small">
|
||||
<a-radio-button value="modal">{{ t('弹窗') }}</a-radio-button>
|
||||
<a-radio-button value="drawer">{{ t('抽屉') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('表单布局')">
|
||||
<a-radio-group v-model:value="data.layout" button-style="solid" size="small">
|
||||
<a-radio-button value="horizontal">{{ t('水平排列') }}</a-radio-button>
|
||||
<a-radio-button value="vertical">{{ t('垂直排列') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('标签对齐')">
|
||||
<a-radio-group v-model:value="data.labelAlign" button-style="solid" size="small">
|
||||
<a-radio-button value="left">{{ t('左对齐') }}</a-radio-button>
|
||||
<a-radio-button value="right">{{ t('右对齐') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="标签模式">
|
||||
<a-radio-group v-model:value="data.labelCol.labelWidthMode" button-style="solid" size="small">
|
||||
<a-radio-button value="flex">{{ t('百分比') }}</a-radio-button>
|
||||
<a-radio-button value="fix">{{ t('定宽') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="标签宽度">
|
||||
<a-input-number v-model:value="data.labelCol.span" :max="24" :min="0" addonAfter="/ 24" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-show="data.labelCol.labelWidthMode === 'fix'" label="标签定宽">
|
||||
<a-input-number v-model:value="data.labelCol.labelFixWidth" :max="200" :min="40" addonAfter="px" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="标签边距">
|
||||
<a-input-number v-model:value="data.labelCol.offset" :max="24" :min="0" addonAfter="/ 24" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('组件尺寸')">
|
||||
<a-radio-group v-model:value="data.size" button-style="solid" size="small">
|
||||
<a-radio-button value="default">{{ t('默认') }}</a-radio-button>
|
||||
<a-radio-button value="large">{{ t('大') }}</a-radio-button>
|
||||
<a-radio-button value="small">{{ t('小') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="表单宽度">
|
||||
<a-input-number v-model:value="data.formWidth" addonAfter="px" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { WidgetForm } from '../types';
|
||||
import { defineComponent, PropType, ref, watch } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
export default defineComponent({
|
||||
name: 'AntdFormConfig',
|
||||
props: {
|
||||
config: {
|
||||
type: Object as PropType<WidgetForm['config']>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['update:config'],
|
||||
setup(props, context) {
|
||||
const data = ref(props.config);
|
||||
const layout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 18 }
|
||||
};
|
||||
watch(data, () => context.emit('update:config', data));
|
||||
|
||||
return {
|
||||
data,
|
||||
layout,
|
||||
t
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.fc-style .widget-config-container .config-content .ant-form-item,
|
||||
.fc-style .widget-config-container .config-content .el-form-item,
|
||||
.fc-style .widget-config-container .config-content h4 {
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px dashed #e1e1e1;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-config-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// :deep(.ant-radio-group) {
|
||||
// display: flex;
|
||||
|
||||
// .ant-radio-button-wrapper {
|
||||
// flex: 1;
|
||||
// text-align: center;
|
||||
// padding: 3px 0 !important;
|
||||
// }
|
||||
// }
|
||||
// @import '/@/assets/style/designer/index.css';
|
||||
</style>
|
||||
83
src/components/Designer/src/components/AntdHeader.vue
Normal file
83
src/components/Designer/src/components/AntdHeader.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="ah-container">
|
||||
<a-layout-header class="btn-bar">
|
||||
<slot></slot>
|
||||
<a-button v-if="$attrs.uploadJson" type="link" size="small" @click="$emit('uploadJson')">
|
||||
<template #icon>
|
||||
<div class="btn-icon">
|
||||
<SvgIcon name="import" :size="14" />
|
||||
</div>
|
||||
</template>
|
||||
{{ t('导入JSON') }}
|
||||
</a-button>
|
||||
<a-button v-if="$attrs.generateJson" type="link" size="small" @click="$emit('generateJson')">
|
||||
<template #icon>
|
||||
<div class="btn-icon">
|
||||
<SvgIcon name="printer" :size="14" />
|
||||
</div>
|
||||
</template>
|
||||
{{ t('生成JSON') }}
|
||||
</a-button>
|
||||
<a-button v-if="$attrs.clearable" type="link" size="small" @click="$emit('clearable')">
|
||||
<template #icon>
|
||||
<div class="btn-icon">
|
||||
<SvgIcon name="clearable" :size="14" />
|
||||
</div>
|
||||
</template>
|
||||
{{ t('清空') }}
|
||||
</a-button>
|
||||
<a-button v-if="$attrs.preview" type="link" size="small" @click="$emit('preview')">
|
||||
<template #icon>
|
||||
<div class="btn-icon">
|
||||
<SvgIcon name="action" :size="14" />
|
||||
</div>
|
||||
</template>
|
||||
{{ t('预览') }}
|
||||
</a-button>
|
||||
|
||||
<a-button v-if="$attrs.generateCode" type="link" size="small" @click="$emit('generateCode')">
|
||||
<template #icon>
|
||||
<div class="btn-icon">
|
||||
<SvgIcon name="generate-code" />
|
||||
</div>
|
||||
</template>
|
||||
{{ t('生成代码') }}
|
||||
</a-button>
|
||||
</a-layout-header>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
defineEmits(['uploadJson', 'clearable', 'preview', 'generateJson', 'generateCode']);
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.ah-container {
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.btn-bar {
|
||||
line-height: 46px;
|
||||
height: 46px;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
text-align: right;
|
||||
|
||||
.btn-icon {
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-btn) > span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
// @import '/@/assets/style/designer/index.css';
|
||||
</style>
|
||||
493
src/components/Designer/src/components/AntdWidgetForm.vue
Normal file
493
src/components/Designer/src/components/AntdWidgetForm.vue
Normal file
@ -0,0 +1,493 @@
|
||||
<template>
|
||||
<div class="awf-container-center">
|
||||
<div class="widget-form-container">
|
||||
<div v-if="!widgetForm.list" class="form-empty">从左侧拖拽来添加字段</div>
|
||||
<a-form
|
||||
:layout="widgetForm.config.layout"
|
||||
:labelAlign="widgetForm.config.labelAlign"
|
||||
:labelCol="widgetForm.config.labelCol"
|
||||
:disabled="true"
|
||||
>
|
||||
<Draggable
|
||||
class="widget-form-list"
|
||||
item-key="key"
|
||||
ghostClass="ghost"
|
||||
handle=".drag-widget"
|
||||
:animation="200"
|
||||
:group="{ name: 'people' }"
|
||||
:list="widgetForm.list"
|
||||
@add="handleMoveAdd"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<transition-group name="fade" tag="div">
|
||||
<AntdWidgetFormItem
|
||||
v-if="element.key"
|
||||
:key="element.key"
|
||||
class="drag-widget"
|
||||
:element="element"
|
||||
:config="widgetForm.config"
|
||||
:selectWidget="widgetFormSelect"
|
||||
@click="handleItemClick(element)"
|
||||
@copy="handleCopyClick(index, widgetForm.list)"
|
||||
@delete="handleDeleteClick(index, widgetForm.list)"
|
||||
@addRow="handleAddClick(element, 'row')"
|
||||
@addCol="handleAddClick(element, 'col')"
|
||||
/>
|
||||
</transition-group>
|
||||
</template>
|
||||
</Draggable>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick, PropType, toRaw, inject, Ref, ref } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import AntdWidgetFormItem from './AntdWidgetFormItem.vue';
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import {
|
||||
WidgetForm,
|
||||
gridComponents,
|
||||
TableInfo,
|
||||
noHaveTableAndField,
|
||||
noHaveField,
|
||||
TableCell,
|
||||
TableTh,
|
||||
} from '../types';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { error } from '/@/utils/log';
|
||||
import { changeToPinyin } from '/@/utils/event/design';
|
||||
import { random } from 'lodash-es';
|
||||
|
||||
const handleListInsert = (key: string, list: any[], obj: any) => {
|
||||
const newList: any[] = [];
|
||||
list.forEach((item) => {
|
||||
if (item.key === key) {
|
||||
newList.push(item);
|
||||
newList.push(obj);
|
||||
} else {
|
||||
if (item.columns) {
|
||||
item.columns = item.columns.map((col: any) => ({
|
||||
...col,
|
||||
list: handleListInsert(key, col.list, obj),
|
||||
}));
|
||||
}
|
||||
newList.push(item);
|
||||
}
|
||||
});
|
||||
return newList;
|
||||
};
|
||||
|
||||
const handleListDelete = (key: string, list: any[]) => {
|
||||
const newList: any[] = [];
|
||||
list.forEach((item) => {
|
||||
if (item.key !== key) {
|
||||
if (item.columns) {
|
||||
item.columns = item.columns.map((col: any) => ({
|
||||
...col,
|
||||
list: handleListDelete(key, col.list),
|
||||
}));
|
||||
}
|
||||
newList.push(item);
|
||||
}
|
||||
});
|
||||
return newList;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AntdWidgetForm',
|
||||
components: {
|
||||
SvgIcon,
|
||||
Draggable,
|
||||
AntdWidgetFormItem,
|
||||
},
|
||||
props: {
|
||||
widgetForm: {
|
||||
type: Object as PropType<WidgetForm>,
|
||||
required: true,
|
||||
},
|
||||
widgetFormSelect: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
emits: ['update:widgetForm', 'update:widgetFormSelect'],
|
||||
setup(props, context) {
|
||||
const state = inject('state') as any;
|
||||
const tableInfo = inject<Ref<TableInfo[]>>('tableInfo') as Ref<TableInfo[]>;
|
||||
const isFieldUpper = inject<Ref<boolean>>('isFieldUpper', ref(false));
|
||||
const designType = inject('designType') as string;
|
||||
const tablecell = inject<Ref<TableCell>>('tableCell');
|
||||
const tableth = inject<Ref<TableTh>>('tableTh');
|
||||
let mainTableName;
|
||||
if (designType !== 'data') {
|
||||
mainTableName = inject<string>('mainTableName');
|
||||
}
|
||||
|
||||
const handleItemClick = (row: any) => {
|
||||
state.widgetFormSelect = row;
|
||||
if (row.type === 'table-layout') {
|
||||
if (tablecell) tablecell.value = {};
|
||||
if (tableth) tableth.value = {};
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddClick = (row: any, type: string) => {
|
||||
let tdMax = 0;
|
||||
row.layout[0].list.forEach((o) => {
|
||||
if (o.colspan) {
|
||||
tdMax = tdMax + o.colspan;
|
||||
} else {
|
||||
tdMax += 1;
|
||||
}
|
||||
});
|
||||
if (type == 'row') {
|
||||
let lastRow: any = [];
|
||||
for (let j = 0; j < tdMax; j++) {
|
||||
lastRow.push({
|
||||
width: '',
|
||||
height: '',
|
||||
class: 'tableLayoutTd',
|
||||
children: [],
|
||||
position: [row.layout.length, j],
|
||||
});
|
||||
}
|
||||
row.layout.push({ list: lastRow });
|
||||
} else {
|
||||
row.layout.forEach((o, i) => {
|
||||
o.list.push({
|
||||
width: '',
|
||||
height: '',
|
||||
class: 'tableLayoutTd',
|
||||
children: [],
|
||||
position: [i, tdMax],
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleCopyClick = (index: number, list: any[]) => {
|
||||
const key = buildUUID().replaceAll('-', '');
|
||||
const oldList = JSON.parse(JSON.stringify(props.widgetForm.list));
|
||||
let copyData = {
|
||||
...list[index],
|
||||
key,
|
||||
model: `${list[index].type}_${key}`,
|
||||
rules: list[index].rules ?? [],
|
||||
bindField: '',
|
||||
};
|
||||
|
||||
if (
|
||||
list[index].type === 'radio' ||
|
||||
list[index].type === 'checkbox' ||
|
||||
list[index].type === 'select'
|
||||
) {
|
||||
copyData = {
|
||||
...copyData,
|
||||
options: {
|
||||
...copyData.options,
|
||||
staticOptions: copyData.options.staticOptions.map((item: any) => ({ ...item })),
|
||||
},
|
||||
};
|
||||
}
|
||||
copyLayoutData([copyData], mainTableName);
|
||||
|
||||
context.emit('update:widgetForm', {
|
||||
...props.widgetForm,
|
||||
list: handleListInsert(list[index].key, oldList, copyData),
|
||||
});
|
||||
inject('widgetFormSelect', copyData);
|
||||
context.emit('update:widgetFormSelect', copyData);
|
||||
};
|
||||
|
||||
const copyLayoutData = (data, tableName) => {
|
||||
data.map((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
item.bindTable = '';
|
||||
item.key = buildUUID().replaceAll('-', '');
|
||||
addBindTableAndField(item, tableName);
|
||||
for (const child of item.layout!) {
|
||||
copyLayoutData(child.list, tableName);
|
||||
}
|
||||
} else if (item.type === 'table-layout') {
|
||||
item.bindTable = '';
|
||||
item.key = buildUUID().replaceAll('-', '');
|
||||
addBindTableAndField(item, tableName);
|
||||
for (const child of item.layout!) {
|
||||
for (const list of child.list!) {
|
||||
copyLayoutData(list.children, tableName);
|
||||
}
|
||||
}
|
||||
} else if (['form', 'one-for-one'].includes(item.type)) {
|
||||
item.key = buildUUID().replaceAll('-', '');
|
||||
addBindTableAndField(item);
|
||||
copyLayoutData(item.children, item.bindTable);
|
||||
} else {
|
||||
item.bindField = '';
|
||||
item.bindTable = tableName;
|
||||
item.key = buildUUID().replaceAll('-', '');
|
||||
addBindTableAndField(item, tableName);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteClick = async (index: number, list: any[]) => {
|
||||
await deleteAdoptedCalc(index, list);
|
||||
const oldList = JSON.parse(JSON.stringify(props.widgetForm.list));
|
||||
|
||||
if (list.length - 1 === index) {
|
||||
if (index === 0) {
|
||||
nextTick(() => context.emit('update:widgetFormSelect', null));
|
||||
} else {
|
||||
context.emit('update:widgetFormSelect', list[index - 1]);
|
||||
}
|
||||
} else {
|
||||
context.emit('update:widgetFormSelect', list[index + 1]);
|
||||
}
|
||||
|
||||
context.emit('update:widgetForm', {
|
||||
...props.widgetForm,
|
||||
list: handleListDelete(list[index].key, oldList),
|
||||
});
|
||||
};
|
||||
|
||||
const handleTabGridDeleteClick = (index: number, list: any[]) => {
|
||||
list.splice(index, 1);
|
||||
};
|
||||
|
||||
const handleMoveAdd = (event: any) => {
|
||||
const list = JSON.parse(JSON.stringify(props.widgetForm.list));
|
||||
const { newIndex } = event;
|
||||
|
||||
//判断如果是单表 不支持子表单组件 (仅数据优先时)
|
||||
if (tableInfo.value.length < 2 && list[newIndex].type === 'form' && designType === 'data') {
|
||||
list.splice(newIndex, 2); //必须2
|
||||
context.emit('update:widgetForm', { ...props.widgetForm, list });
|
||||
error('单表不能使用子表单组件!');
|
||||
return;
|
||||
}
|
||||
const key = buildUUID().replaceAll('-', '');
|
||||
|
||||
list[newIndex] = {
|
||||
...toRaw(list[newIndex]),
|
||||
key,
|
||||
model: `${list[newIndex].type}_${key}`,
|
||||
rules: [],
|
||||
};
|
||||
|
||||
if (
|
||||
list[newIndex].type === 'radio' ||
|
||||
list[newIndex].type === 'checkbox' ||
|
||||
list[newIndex].type === 'select'
|
||||
) {
|
||||
list[newIndex] = {
|
||||
...list[newIndex],
|
||||
options: {
|
||||
...list[newIndex].options,
|
||||
staticOptions: list[newIndex].options.staticOptions.map((item: any) => ({
|
||||
...item,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (list[newIndex].type === 'grid') {
|
||||
list[newIndex].layout.map((item: any) => (item.list = []));
|
||||
}
|
||||
|
||||
if (list[newIndex].type === 'form') {
|
||||
list[newIndex].children.map((item: any) => (item.list = []));
|
||||
}
|
||||
|
||||
if (list[newIndex].type === 'tab') {
|
||||
list[newIndex].layout.map((item: any) => (item.list = []));
|
||||
}
|
||||
//从子表拖入主表时
|
||||
list[newIndex].isSubFormChild = false;
|
||||
list[newIndex].isSingleFormChild = false;
|
||||
|
||||
addBindTableAndField(list[newIndex]);
|
||||
|
||||
context.emit('update:widgetForm', { ...props.widgetForm, list });
|
||||
context.emit('update:widgetFormSelect', list[newIndex]);
|
||||
};
|
||||
|
||||
const handleColMoveAdd = (event: any, row: any, index: string | number | symbol) => {
|
||||
const { newIndex, oldIndex, item } = event;
|
||||
if (row.type === 'form') {
|
||||
const list = JSON.parse(JSON.stringify(props.widgetForm.list));
|
||||
|
||||
if (item.className.includes('data-grid')) {
|
||||
item.tagName === 'DIV' && list.splice(oldIndex, 0, row.children[newIndex]);
|
||||
row.children[index].splice(newIndex, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = buildUUID().replaceAll('-', '');
|
||||
|
||||
row.children[newIndex] = {
|
||||
...toRaw(row.children[index]),
|
||||
key,
|
||||
model: `${row.children[newIndex].type}_${key}`,
|
||||
rules: [],
|
||||
};
|
||||
|
||||
if (
|
||||
row.children[newIndex].type === 'radio' ||
|
||||
row.children[newIndex].type === 'checkbox' ||
|
||||
row.children[newIndex].type === 'select'
|
||||
) {
|
||||
row.children[newIndex] = {
|
||||
...row.children[newIndex],
|
||||
options: {
|
||||
...row.children[newIndex].options,
|
||||
staticOptions: row.children[newIndex].options.staticOptions.map((item: any) => ({
|
||||
...item,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
context.emit('update:widgetFormSelect', row.children[newIndex]);
|
||||
} else {
|
||||
const list = JSON.parse(JSON.stringify(props.widgetForm.list));
|
||||
|
||||
if (item.className.includes('data-grid')) {
|
||||
item.tagName === 'DIV' && list.splice(oldIndex, 0, row.layout[index].list[newIndex]);
|
||||
row.layout[index].list.splice(newIndex, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = buildUUID().replaceAll('-', '');
|
||||
|
||||
row.layout[index].list[newIndex] = {
|
||||
...toRaw(row.layout[index].list[newIndex]),
|
||||
key,
|
||||
model: `${row.layout[index].list[newIndex].type}_${key}`,
|
||||
rules: [],
|
||||
};
|
||||
|
||||
if (
|
||||
row.layout[index].list[newIndex].type === 'radio' ||
|
||||
row.layout[index].list[newIndex].type === 'checkbox' ||
|
||||
row.layout[index].list[newIndex].type === 'select'
|
||||
) {
|
||||
row.layout[index].list[newIndex] = {
|
||||
...row.layout[index].list[newIndex],
|
||||
options: {
|
||||
...row.layout[index].list[newIndex].options,
|
||||
staticOptions: row.layout[index].list[newIndex].options.staticOptions.map(
|
||||
(item: any) => ({
|
||||
...item,
|
||||
}),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
context.emit('update:widgetFormSelect', row.layout[index].list[newIndex]);
|
||||
}
|
||||
};
|
||||
const addBindTableAndField = (component: any, table?) => {
|
||||
//界面优先和简易模板
|
||||
if (designType !== 'data') {
|
||||
//需要绑定字段、组件没有绑定字段的情况下新增绑定字段
|
||||
const rangeComponents = ['time-range', 'date-range'];
|
||||
if (rangeComponents.includes(component.type)) {
|
||||
component.bindStartTime =
|
||||
changeToPinyin(component.label, isFieldUpper.value) + random(1000, 9999);
|
||||
component.bindEndTime =
|
||||
changeToPinyin(component.label, isFieldUpper.value) + random(1000, 9999);
|
||||
} else if (!noHaveField.includes(component.type) && !component.bindField) {
|
||||
component.bindField =
|
||||
changeToPinyin(component.label, isFieldUpper.value) + random(1000, 9999);
|
||||
}
|
||||
if (component.type === 'form' || component.type === 'one-for-one') {
|
||||
component.bindTable = isFieldUpper.value
|
||||
? `${mainTableName.value}_CHILD_${random(1000, 9999)}`
|
||||
: `${mainTableName.value}_child_${random(1000, 9999)}`;
|
||||
} else if (!noHaveTableAndField.includes(component.type)) {
|
||||
component.bindTable = table || mainTableName.value;
|
||||
}
|
||||
}
|
||||
if (component.isGridChild) component.options.span = 7;
|
||||
};
|
||||
|
||||
//删除引用已删除的财务组件的计算式配置
|
||||
const deleteAdoptedCalc = (index: number, list: any[]) => {
|
||||
const component = list[index];
|
||||
if (['computational', 'money-chinese'].includes(component.type)) {
|
||||
component.options.beAdoptedComponent?.map((key) => {
|
||||
getLayoutComponent(list, key);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getLayoutComponent = (list, key) => {
|
||||
list?.map((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getLayoutComponent(child.list, key);
|
||||
}
|
||||
} else if (item.type === 'table-layout') {
|
||||
for (const child of item.layout!) {
|
||||
for (const list of child.list) {
|
||||
getLayoutComponent(list.children, key);
|
||||
}
|
||||
}
|
||||
} else if (item.type === 'form') {
|
||||
item.children.map((child: any) => {
|
||||
if (['computational', 'money-chinese'].includes(child.type) && child.key === key) {
|
||||
child.options.computationalConfig = [];
|
||||
child.options.computationalConfigValue = '== 请填写计算式配置 ==';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (['computational', 'money-chinese'].includes(item.type) && item.key === key) {
|
||||
item.options.computationalConfig = [];
|
||||
item.options.computationalConfigValue = '== 请填写计算式配置 ==';
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
handleItemClick,
|
||||
handleAddClick,
|
||||
handleCopyClick,
|
||||
handleDeleteClick,
|
||||
handleTabGridDeleteClick,
|
||||
handleMoveAdd,
|
||||
handleColMoveAdd,
|
||||
gridComponents,
|
||||
addBindTableAndField,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
/* @import '/@/assets/style/designer/index.css'; */
|
||||
.awf-container-center {
|
||||
width: 100%;
|
||||
height: calc(100%);
|
||||
padding: 1px 10px 10px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.widget-form-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
padding: 10px 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.widget-form-list {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fc-style .widget-form-container .widget-form-list {
|
||||
background: #fff;
|
||||
// border: 1px dashed #999;
|
||||
min-height: calc(100vh - 305px);
|
||||
}
|
||||
</style>
|
||||
1205
src/components/Designer/src/components/AntdWidgetFormItem.vue
Normal file
1205
src/components/Designer/src/components/AntdWidgetFormItem.vue
Normal file
File diff suppressed because it is too large
Load Diff
106
src/components/Designer/src/components/ComponentGroup.vue
Normal file
106
src/components/Designer/src/components/ComponentGroup.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<Draggable
|
||||
tag="ul"
|
||||
item-key="type"
|
||||
ghostClass="ghost"
|
||||
:group="{ name: 'people', pull: 'clone', put: false }"
|
||||
:sort="false"
|
||||
:list="list"
|
||||
class="cg-list-container"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<li
|
||||
v-if="fields.includes(element.type)"
|
||||
class="form-edit-widget-label cg-list"
|
||||
:class="{ 'no-put': element.type === 'divider' }"
|
||||
@click="handleClick(element)"
|
||||
>
|
||||
<a>
|
||||
<SvgIcon :name="element.type ?? element.icon" />
|
||||
<span>{{ element.label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</Draggable>
|
||||
<div class="clear"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
fields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array as PropType<Array<any>>,
|
||||
required: true,
|
||||
},
|
||||
isDisabled: Boolean,
|
||||
});
|
||||
const emit = defineEmits(['copy']);
|
||||
const handleClick = (element: any) => {
|
||||
emit('copy', element);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.left-title {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
padding-left: 10px;
|
||||
box-sizing: border-box;
|
||||
margin: 15px 0;
|
||||
border-left: 6px solid #5e95ff;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cg-list-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cg-list {
|
||||
float: left;
|
||||
width: 44%;
|
||||
margin-right: 6%;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
border: 1px solid #e4e4e4;
|
||||
}
|
||||
|
||||
.cg-list:hover {
|
||||
border: 1px dashed #eef4ff;
|
||||
background-color: #eef4ff;
|
||||
cursor: v-bind("props.isDisabled ? 'not-allowed' : 'pointer'");
|
||||
}
|
||||
|
||||
.cg-list:hover a {
|
||||
color: #0960bd;
|
||||
cursor: v-bind("props.isDisabled ? 'not-allowed' : 'pointer'");
|
||||
}
|
||||
|
||||
.cg-list a {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cg-list a svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
// @import '/@/assets/style/designer/index.css';
|
||||
</style>
|
||||
63
src/components/Designer/src/components/EventEditModal.vue
Normal file
63
src/components/Designer/src/components/EventEditModal.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit">
|
||||
<div>
|
||||
<CodeEditor v-model:value="defaultValue" language="js" />
|
||||
</div>
|
||||
<div>
|
||||
<CodeEditor :value="tipContent" language="js" readonly />
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref } from 'vue';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
|
||||
const eventType = ref('');
|
||||
|
||||
const tipContent = computed(() => {
|
||||
return `
|
||||
//事件方法参数 一共有3个
|
||||
{
|
||||
schema: FormSchema; //当前表单组件的schema
|
||||
formActionType: FormActionType; // 操作当前表单的各种方法
|
||||
formModel: Recordable<any>; //当前表单数据对象
|
||||
}
|
||||
//例子
|
||||
function(){
|
||||
if(formModel.numOne === 1){
|
||||
formModel.sum = formModel.numOne + formModel.numTwo;
|
||||
}
|
||||
else{
|
||||
formModel.sum = formModel.numOne * formModel.numTwo;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
});
|
||||
|
||||
const defaultValue = ref(``);
|
||||
|
||||
const title = computed(() => {
|
||||
return `编辑${eventType.value}事件`;
|
||||
});
|
||||
|
||||
const [registerModal, { closeModal }] = useModalInner(async (data) => {
|
||||
eventType.value = data.type;
|
||||
//如果已经设置过当前事件代码 则显示已经设置的值 反之 显示默认值
|
||||
if (data.content) {
|
||||
defaultValue.value = data.content;
|
||||
} else {
|
||||
defaultValue.value = `
|
||||
console.log(schema,formModel,formActionType);
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
emit('success', unref(eventType), unref(defaultValue));
|
||||
closeModal();
|
||||
}
|
||||
</script>
|
||||
155
src/components/Designer/src/components/HiddenComponent.vue
Normal file
155
src/components/Designer/src/components/HiddenComponent.vue
Normal file
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="box-top">
|
||||
<div class="title">{{ t('隐藏组件列表') }}</div>
|
||||
<a-button type="primary" size="small" @click="addComponent">{{ t('添加组件') }}</a-button>
|
||||
</div>
|
||||
<a-form
|
||||
v-for="(item, index) in hiddenComponent"
|
||||
:key="item.key"
|
||||
:labelCol="{ span: 8 }"
|
||||
:wrapperCol="{ span: 16 }"
|
||||
style="margin-top: 20px; position: relative"
|
||||
@mouseenter="showDeleteIndex = index"
|
||||
@mouseleave="showDeleteIndex = -1"
|
||||
>
|
||||
<a-form-item :label="t('组件编码')" required>
|
||||
<a-input v-model:value="item.code" :placeholder="t('请输入组件编码')" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('组件名称')" required>
|
||||
<a-input
|
||||
v-model:value="item.label"
|
||||
:placeholder="t('请输入组件名称')"
|
||||
@change="handleChangeLabel(item)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('组件值')" required>
|
||||
<a-input v-model:value="item.value" :placeholder="t('请输入组件值')" />
|
||||
</a-form-item>
|
||||
<template v-if="designType === 'data'">
|
||||
<a-form-item :label="t('绑定表')" required>
|
||||
<a-select
|
||||
v-model:value="item.bindTable"
|
||||
:placeholder="t('请选择数据表')"
|
||||
size="mini"
|
||||
@change="changeTable"
|
||||
>
|
||||
<a-select-option v-for="(table, idx) in mainTable" :value="table.name" :key="idx" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('绑定字段')" required>
|
||||
<a-select v-model:value="item.bindField" :placeholder="t('请选择表字段')" size="mini">
|
||||
<a-select-option v-for="(field, idx) in tableField" :value="field.name" :key="idx" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<div class="icon-delete" v-show="index === showDeleteIndex">
|
||||
<SvgIcon name="delete" @click.stop="deleteHiddenComponent(index)" class="svg-delete" />
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, inject, Ref } from 'vue';
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import { TableInfo, FieldInfo, HiddenComponentInfo } from '../types';
|
||||
import { changeToPinyin } from '/@/utils/event/design';
|
||||
import { random } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: Array as PropType<HiddenComponentInfo[]>,
|
||||
},
|
||||
});
|
||||
|
||||
const tableInfo = inject<Ref<TableInfo[]>>('tableInfo') as Ref<TableInfo[]>;
|
||||
|
||||
const designType = inject<string>('designType');
|
||||
const isFieldUpper = inject<Ref<boolean>>('isFieldUpper', ref(false));
|
||||
|
||||
const hiddenComponent = ref<HiddenComponentInfo[]>(props.component!);
|
||||
const mainTable = ref<TableInfo[]>([]);
|
||||
const tableField = ref<FieldInfo[]>([]);
|
||||
const showDeleteIndex = ref<number>(-1);
|
||||
let mainTableName;
|
||||
if (designType !== 'data') {
|
||||
mainTableName = inject<string>('mainTableName');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => tableInfo,
|
||||
() => {
|
||||
mainTable.value = tableInfo.value?.filter((item: any) => item.isMain); //隐藏组件只能选择主表的字段进行绑定
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
const addComponent = () => {
|
||||
const key = `hiddenComponent${Date.now()}`;
|
||||
const com: HiddenComponentInfo = {
|
||||
key,
|
||||
type: 'hiddenComponent',
|
||||
code: '',
|
||||
label: '',
|
||||
value: '',
|
||||
bindTable: '',
|
||||
bindField: '',
|
||||
};
|
||||
if (designType !== 'data') {
|
||||
com.bindTable = mainTableName.value!;
|
||||
}
|
||||
hiddenComponent.value.push(com);
|
||||
};
|
||||
|
||||
const handleChangeLabel = (info) => {
|
||||
if (designType !== 'data') {
|
||||
info.bindField = changeToPinyin(info.label, isFieldUpper.value) + random(1000, 9999);
|
||||
}
|
||||
};
|
||||
const deleteHiddenComponent = (index: number) => {
|
||||
hiddenComponent.value.splice(index, 1);
|
||||
};
|
||||
const changeTable = (val: string) => {
|
||||
const chooseTable = mainTable.value.find((table: any) => table.name === val)!;
|
||||
tableField.value = chooseTable?.fields;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 5px 0 10px 10px;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-delete {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: -5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
border: 1px solid #f64c4c;
|
||||
color: #f64c4c;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-delete {
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
59
src/components/Designer/src/components/PreviewDrawer.vue
Normal file
59
src/components/Designer/src/components/PreviewDrawer.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicDrawer
|
||||
v-bind="$attrs"
|
||||
size="large"
|
||||
@register="registerDrawer"
|
||||
@close="handleCloser"
|
||||
:title="t('表单预览')"
|
||||
>
|
||||
<div>
|
||||
<SimpleForm ref="formRef" :formProps="state.formProps" :formModel="state.formModel">
|
||||
</SimpleForm>
|
||||
</div>
|
||||
</BasicDrawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, inject } from 'vue';
|
||||
import SimpleForm from '/@/components/SimpleForm/src/SimpleForm.vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { buildOption } from '/@/utils/helper/designHelper';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const state = reactive({
|
||||
formProps: {} as any,
|
||||
formModel: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const isCustomForm = inject<boolean>('isCustomForm', false);
|
||||
|
||||
//使用表单钩子 注册表单 获取到 操作表单的方法
|
||||
|
||||
//使用抽屉内部钩子 获取到 操作抽屉的方法
|
||||
const [registerDrawer, { setDrawerProps }] = useDrawerInner(async (option) => {
|
||||
//每次进来置空
|
||||
setDrawerProps({
|
||||
destroyOnClose: true,
|
||||
width: option?.config?.formWidth || 800,
|
||||
canFullscreen: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
formRef.value.setProps({
|
||||
...buildOption(option, !isCustomForm),
|
||||
showResetButton: true,
|
||||
showSubmitButton: true,
|
||||
});
|
||||
formRef.value.setDefaultValue();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
//关闭方法
|
||||
const handleCloser = () => {
|
||||
formRef.value!.resetFields(); //重置表单
|
||||
state.formProps = {};
|
||||
};
|
||||
</script>
|
||||
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div v-for="(item, index) in render" :key="item.type">
|
||||
<div :class="index == render.length - 1 ? 'mb-0' : 'event-box'" v-if="isShowEvent(item.name)">
|
||||
<div class="event-title">{{ item.name }}</div>
|
||||
<div>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-col :offset="1" :span="6">
|
||||
<span>{{ t('配置脚本:') }}</span>
|
||||
</a-col>
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
:placeholder="t('点击配置脚本')"
|
||||
:value="configTxt(item.type)"
|
||||
@click="showConfigModal(item.type)"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ScriptConfig @register="registerModal" @success="submitConfig" :disabled="disabled" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import ScriptConfig from './settings/ScriptConfig.vue';
|
||||
import {
|
||||
noBlurComponentEvent,
|
||||
noChangeComponentEvent,
|
||||
noClickComponentEvent,
|
||||
noHaveTableAndField,
|
||||
} from '../../types';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
select: { type: Object, default: {} },
|
||||
widgetForm: { type: Object, default: {} },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
const render = [
|
||||
{
|
||||
name: t('失焦事件'),
|
||||
type: 'blur',
|
||||
},
|
||||
{
|
||||
name: t('点击事件'),
|
||||
type: 'click',
|
||||
},
|
||||
{
|
||||
name: t('值改变事件'),
|
||||
type: 'change',
|
||||
},
|
||||
];
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
const getMainTableComponents = (list, mainTableComponent) => {
|
||||
const rangeComponents = ['time-range', 'date-range'];
|
||||
list.forEach((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getMainTableComponents(child.list, mainTableComponent);
|
||||
}
|
||||
} else if (rangeComponents.includes(item.type)) {
|
||||
//时间范围、日期范围需改为两个字段
|
||||
mainTableComponent.push({
|
||||
label: t(`{name}开始时间`, { name: item.label }),
|
||||
bindField: item.bindStartTime,
|
||||
});
|
||||
mainTableComponent.push({
|
||||
label: t(`{name}结束时间`, { name: item.label }),
|
||||
bindField: item.bindEndTime,
|
||||
});
|
||||
} else if (
|
||||
item.type !== 'form' &&
|
||||
item.type !== 'one-for-one' &&
|
||||
!noHaveTableAndField.includes(item.type)
|
||||
) {
|
||||
//除去不绑定表和字段的组件 以及表格组件
|
||||
mainTableComponent.push(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isShowEvent = (name) => {
|
||||
const type = props.select?.type;
|
||||
switch (name) {
|
||||
case t('失焦事件'):
|
||||
return !noBlurComponentEvent.includes(type);
|
||||
case t('点击事件'):
|
||||
return !noClickComponentEvent.includes(type);
|
||||
case t('值改变事件'):
|
||||
return !noChangeComponentEvent.includes(type);
|
||||
}
|
||||
};
|
||||
const showConfigModal = (val) => {
|
||||
const mainTableComponent = ref<any[]>([]);
|
||||
|
||||
getMainTableComponents(props.widgetForm?.list, mainTableComponent.value);
|
||||
openModal(true, {
|
||||
type: val,
|
||||
content: props.select?.options.events[val] || '',
|
||||
list: mainTableComponent.value, //这样传才能每次点击都更新
|
||||
});
|
||||
};
|
||||
|
||||
const configTxt = (type) => {
|
||||
return props.select.options.events[type] ? t('已配置') : '';
|
||||
};
|
||||
const submitConfig = (type, value) => {
|
||||
if (props.select?.options.events) {
|
||||
props.select.options.events[type] = value;
|
||||
} else {
|
||||
props.select.options.events = {
|
||||
[type]: value,
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.event-box {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
margin: 5px 0 10px 10px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="top-box">
|
||||
<div class="regular-title">{{ t('正则校验列表') }}</div>
|
||||
<a-button type="primary" size="small" @click="addRegular" class="add-btn">
|
||||
{{ t('添加正则') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<a-form
|
||||
layout="vertical"
|
||||
v-for="(item, index) in data.options?.rules"
|
||||
:key="index"
|
||||
class="reg-item"
|
||||
@mouseenter="showDeleteIndex = index"
|
||||
@mouseleave="showDeleteIndex = -1"
|
||||
>
|
||||
<a-form-item :label="t('正则表达式')">
|
||||
<a-textarea
|
||||
:placeholder="t('请输入正则表达式')"
|
||||
v-model:value="item.pattern"
|
||||
size="small"
|
||||
@blur="checkPattern(item.pattern)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('错误提示')">
|
||||
<a-textarea :placeholder="t('请输入错误提示')" v-model:value="item.message" size="small" />
|
||||
</a-form-item>
|
||||
<div class="delete-btn" v-show="index === showDeleteIndex">
|
||||
<SvgIcon name="delete" @click.stop="deleteRegList(index)" class="svg-delete" />
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { watch, ref } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
select: { type: Object },
|
||||
});
|
||||
const emit = defineEmits(['update:select']);
|
||||
const data = ref<any>();
|
||||
const showDeleteIndex = ref<number>(-1);
|
||||
watch(
|
||||
() => props.select,
|
||||
(val) => {
|
||||
data.value = val;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
data,
|
||||
(val) => {
|
||||
emit('update:select', val);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
const addRegular = () => {
|
||||
data.value.options.rules.push({
|
||||
pattern: '',
|
||||
message: '',
|
||||
});
|
||||
data.value.options.required = true;
|
||||
};
|
||||
const deleteRegList = (index) => {
|
||||
data.value.options.rules.splice(index, 1);
|
||||
};
|
||||
const checkPattern = (pattern) => {
|
||||
if (!pattern) return;
|
||||
const start = pattern.substr(0, 1);
|
||||
const end = pattern.substr(-1);
|
||||
if (start !== '/' || end !== '/') {
|
||||
message.error(t('正则表达式不合法,提醒:是否表达式前后带上了/'));
|
||||
return;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.top-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 5px 0 10px 10px;
|
||||
|
||||
.regular-title {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
}
|
||||
|
||||
.reg-item {
|
||||
position: relative;
|
||||
padding: 5px 0 5px 15px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
:deep(.ant-form-item-label) > label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #f64c4c;
|
||||
color: #f64c4c;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-delete {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// .add-btn {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
|
||||
// /deep/.app-iconify {
|
||||
// display: block !important;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="awc-containter">
|
||||
<a-form
|
||||
v-bind="layout"
|
||||
v-if="tablecell && Object.keys(tablecell).length > 0"
|
||||
:key="1"
|
||||
size="small"
|
||||
>
|
||||
<a-form-item label="类型">
|
||||
<a-input v-model:value="tablecell.class" size="mini" />
|
||||
</a-form-item>
|
||||
<a-form-item label="高度">
|
||||
<a-input-number v-model:value="tablecell.height" size="mini" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form v-bind="layout" v-if="tableth && Object.keys(tableth).length > 0" :key="2" size="small">
|
||||
<a-form-item label="类型">
|
||||
<a-input v-model:value="tableth.class" size="mini" />
|
||||
</a-form-item>
|
||||
<a-form-item label="宽度">
|
||||
<a-input-number v-model:value="tableth.width" size="mini" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form v-bind="layout" v-if="showData" :key="3" size="small">
|
||||
<a-form-item :label="t('标题')">
|
||||
<a-input v-model:value="data.label" size="mini" />
|
||||
</a-form-item>
|
||||
<a-form-item label="边框宽度">
|
||||
<a-input-number v-model:value="data.options.borderWidth" size="mini" />
|
||||
</a-form-item>
|
||||
<a-form-item label="边框颜色">
|
||||
<ColorPicker v-model:value="data.options.borderColor" />
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义类">
|
||||
<a-input v-model:value="data.options.class" size="mini" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('是否显示')">
|
||||
<a-switch v-model:checked="data.options.isShow" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, ref, Ref, watch } from 'vue';
|
||||
|
||||
import Draggable from 'vuedraggable';
|
||||
import { SvgIcon, IconPicker, Icon } from '/@/components/Icon';
|
||||
|
||||
import { ColorPicker } from '/@/components/ColorPicker';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { TableCell, TableTh } from '../../types';
|
||||
|
||||
const { t } = useI18n();
|
||||
export default defineComponent({
|
||||
name: 'PropertyOption',
|
||||
components: {
|
||||
Draggable,
|
||||
SvgIcon,
|
||||
IconPicker,
|
||||
Icon,
|
||||
ColorPicker,
|
||||
},
|
||||
emits: ['update:select'],
|
||||
props: {
|
||||
//所选组件配置
|
||||
select: {
|
||||
type: Object,
|
||||
},
|
||||
widgetForm: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const showData = ref(false);
|
||||
const data = ref<any>();
|
||||
const tablecell = inject<Ref<TableCell>>('tableCell');
|
||||
const tableth = inject<Ref<TableTh>>('tableTh');
|
||||
const layout = {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
};
|
||||
watch(
|
||||
() => props.select,
|
||||
(val) => {
|
||||
data.value = val;
|
||||
if (
|
||||
(tableth?.value && Object.keys(tableth.value).length > 0) ||
|
||||
(tablecell?.value && Object.keys(tablecell.value).length > 0)
|
||||
) {
|
||||
showData.value = false;
|
||||
} else {
|
||||
showData.value = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => tablecell?.value,
|
||||
(val) => {
|
||||
if (val && Object.keys(val).length > 0) {
|
||||
showData.value = false;
|
||||
} else if (tableth?.value && Object.keys(tableth.value).length <= 0) {
|
||||
showData.value = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => tableth?.value,
|
||||
(val) => {
|
||||
if (val && Object.keys(val).length > 0) {
|
||||
showData.value = false;
|
||||
} else if (tablecell?.value && Object.keys(tablecell.value).length <= 0) {
|
||||
showData.value = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
data,
|
||||
(val) => {
|
||||
context.emit('update:select', val);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
layout,
|
||||
tablecell,
|
||||
tableth,
|
||||
showData,
|
||||
data,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
*,
|
||||
:deep(.ant-input) {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
:deep(.ant-row) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:maskClosable="false"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
:bodyStyle="{ padding: '0 10px 10px' }"
|
||||
>
|
||||
<div class="list-title">{{ t('字段列表') }}</div>
|
||||
<a-row type="flex" align="middle" :gutter="12" style="margin: 15px 0">
|
||||
<a-col flex="auto" class="text-right">{{ t('调用接口:') }}</a-col>
|
||||
<a-col flex="75%">
|
||||
<a-input v-model:value="apiConfigInfo.path" disabled />
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-button key="submit" type="primary" @click="viewInterfaceInfo">{{
|
||||
t('查看接口信息')
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table
|
||||
:dataSource="apiConfigInfo.outputParams"
|
||||
:columns="apiColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: '400px' }"
|
||||
>
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'show'">
|
||||
{{ t('是否在弹层列表显示') }}
|
||||
<a-checkbox v-model:checked="checkedAll" @change="handleChecked" />
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<a-input
|
||||
v-model:value="record.name"
|
||||
:placeholder="t('请填写字段名')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'component'">
|
||||
<a-select
|
||||
v-model:value="record.component"
|
||||
style="width: 100%"
|
||||
:placeholder="t('请选择填充组件')"
|
||||
@change="(value) => selectBindField(value, record)"
|
||||
allowClear
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-select-option :value="val.key" v-for="(val, idx) in selectedList" :key="idx">
|
||||
{{ val.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tableTitle'">
|
||||
<a-input v-model:value="record.tableTitle" :placeholder="t('请填写表头名称')" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'show'">
|
||||
<a-checkbox v-model:checked="record.show" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'width'">
|
||||
<a-input
|
||||
v-model:value="record.width"
|
||||
:disabled="!record.show"
|
||||
:placeholder="t('请填写列表宽度')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<DeleteTwoTone two-tone-color="#ff8080" @click="deleteParamas(index)" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-button type="dashed" block @click="addOutPutParamas" :disabled="disabled">
|
||||
<PlusOutlined />
|
||||
{{ t('新增') }}
|
||||
</a-button>
|
||||
</a-modal>
|
||||
<InterfaceInfoModal @register="registerModal" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, ref, reactive } from 'vue';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import { PlusOutlined, DeleteTwoTone } from '@ant-design/icons-vue';
|
||||
import InterfaceInfoModal from './InterfaceInfoModal.vue';
|
||||
import { getInterfaceInfo } from '/@/api/system/interface/index';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
apiAssoDia: { type: Boolean },
|
||||
apiConfig: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
selectedList: { type: Array as any },
|
||||
type: { type: String },
|
||||
disabled: { type: Boolean, default: () => false },
|
||||
});
|
||||
const emit = defineEmits(['update:apiAssoDia', 'update:apiConfig']);
|
||||
const visible = ref<boolean>(props.apiAssoDia);
|
||||
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
const apiConfigInfo = ref();
|
||||
const apiColumns = ref<ColumnProps[]>([]);
|
||||
const title = ref<string>('');
|
||||
const checkedAll = ref<boolean>(false);
|
||||
const multiplePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('接口返回参数字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('是否在弹层中显示'),
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('列表宽度'),
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
|
||||
const associatePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('接口返回参数字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('填充组件'),
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('是否在弹层列表显示'),
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('列表宽度'),
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
|
||||
const associateSelectColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('接口返回参数字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('填充组件'),
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('填充组件绑定字段'),
|
||||
dataIndex: 'bindField',
|
||||
key: 'bindField',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
const preloadColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('接口返回参数字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
watch(
|
||||
() => props.type,
|
||||
(val) => {
|
||||
switch (val) {
|
||||
case 'button':
|
||||
case 'associate-popup':
|
||||
apiColumns.value = associatePopupColumns;
|
||||
title.value = t('联想数据配置-API');
|
||||
break;
|
||||
case 'multiple-popup':
|
||||
apiColumns.value = multiplePopupColumns;
|
||||
title.value = t('多选数据配置-API');
|
||||
break;
|
||||
case 'associate-select':
|
||||
apiColumns.value = associateSelectColumns;
|
||||
title.value = t('联想数据配置-API');
|
||||
break;
|
||||
case 'preload-title':
|
||||
apiColumns.value = preloadColumns;
|
||||
title.value = t('表头配置-API');
|
||||
break;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.apiConfig,
|
||||
(val) => {
|
||||
apiConfigInfo.value = cloneDeep(val);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => apiConfigInfo.value,
|
||||
(val) => {
|
||||
checkedAll.value = val?.outputParams?.every((item: any) => {
|
||||
return item.show;
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function selectBindField(value, record) {
|
||||
if (!value || !isNaN(value)) {
|
||||
message.error(t('请先选择该组件的绑定表及绑定字段'));
|
||||
record.bindField = null;
|
||||
record.bindTable = undefined;
|
||||
} else {
|
||||
let obj = props.selectedList.find((o) => o.key == value);
|
||||
if (obj) {
|
||||
record.bindField = obj.bindField;
|
||||
if (obj.isSingleFormChild || obj.isSubFormChild) {
|
||||
record.bindTable = obj.bindTable;
|
||||
} else {
|
||||
record.bindTable = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleClose() {
|
||||
emit('update:apiAssoDia', false);
|
||||
}
|
||||
function handleChecked(e) {
|
||||
apiConfigInfo.value.outputParams.map((item: any) => (item.show = e.target.checked));
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
const bindFieldArr = <any>[];
|
||||
apiConfigInfo.value.outputParams?.map((item: any) => {
|
||||
if (item.bindField) {
|
||||
bindFieldArr.push(item.bindField);
|
||||
}
|
||||
});
|
||||
if (new Set(bindFieldArr).size !== bindFieldArr.length) {
|
||||
message.error(t('组件存在重复绑定,请更改'));
|
||||
return;
|
||||
}
|
||||
emit('update:apiConfig', apiConfigInfo.value);
|
||||
emit('update:apiAssoDia', false);
|
||||
}
|
||||
|
||||
const viewInterfaceInfo = async () => {
|
||||
//在弹窗内调接口会格式不对 所以在此处调接口传进去
|
||||
const info = await getInterfaceInfo({ id: props.apiConfig.apiId });
|
||||
openModal(true, {
|
||||
script: info?.script || '',
|
||||
});
|
||||
};
|
||||
|
||||
const addOutPutParamas = () => {
|
||||
if (!apiConfigInfo.value.outputParams) {
|
||||
apiConfigInfo.value.outputParams = [];
|
||||
}
|
||||
let configObj = {};
|
||||
switch (props.type) {
|
||||
case 'button':
|
||||
case 'associate-popup':
|
||||
configObj = {
|
||||
name: '',
|
||||
tableTitle: '',
|
||||
bindField: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
};
|
||||
break;
|
||||
case 'multiple-popup':
|
||||
configObj = {
|
||||
name: '',
|
||||
tableTitle: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
};
|
||||
break;
|
||||
case 'associate-select':
|
||||
configObj = {
|
||||
name: '',
|
||||
bindField: '',
|
||||
};
|
||||
break;
|
||||
}
|
||||
apiConfigInfo.value.outputParams.push(configObj);
|
||||
};
|
||||
const deleteParamas = (index) => {
|
||||
if (props.disabled) return;
|
||||
apiConfigInfo.value.outputParams.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-title {
|
||||
margin: 10px 0 5px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
:height="550"
|
||||
v-model:visible="dialog"
|
||||
:title="t('计算式配置')"
|
||||
:maskClosable="false"
|
||||
@ok="submitDialog"
|
||||
@cancel="closeDialog"
|
||||
style="overflow: auto"
|
||||
>
|
||||
<a-layout class="config-container" hasSider>
|
||||
<a-layout-sider width="300">
|
||||
<div class="component-list">
|
||||
<a-collapse v-model:activeKey="activeKey" expand-icon-position="right">
|
||||
<a-collapse-panel
|
||||
v-for="(item, index) in selectedComponents"
|
||||
:key="index"
|
||||
:header="item.label"
|
||||
>
|
||||
<draggable
|
||||
v-model="item.children"
|
||||
item-key="item.key"
|
||||
:group="{ name: 'computation', pull: 'clone', put: false }"
|
||||
:sort="false"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<a-tag
|
||||
:color="tagColor(item, element)"
|
||||
:class="{ 'disabled-tag': disabledCom(element.key) }"
|
||||
>
|
||||
{{ element.label }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</draggable>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<a-layout-content class="bgcWhite">
|
||||
<div class="title">{{ t('计算公式') }}</div>
|
||||
<div class="btn-list">
|
||||
<div>
|
||||
<draggable
|
||||
v-model="operatingBtnList"
|
||||
:group="{ name: 'computation', pull: 'clone', put: false }"
|
||||
item-key="item.label"
|
||||
:sort="false"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<a-tag>
|
||||
{{ element.label }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="input-number">
|
||||
<span>{{ t('数字:') }}</span>
|
||||
<a-input-number v-model:value="number" />
|
||||
<a-button type="primary" @click="addNumber">{{ t('确定') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="computational-box">
|
||||
<div v-if="!computationalList.length" class="computational-text">
|
||||
<p style="font-size: 24px; color: #606266">{{ t('拖拽左侧到此处进行添加') }}</p>
|
||||
<p>{{ t('变量之间可通过计算公式进行运算(例:销售单价 * 销售数量)') }}</p>
|
||||
</div>
|
||||
<draggable
|
||||
v-model="computationalList"
|
||||
group="computation"
|
||||
item-key="key"
|
||||
@add="addComputationalList"
|
||||
class="draggable"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div @mouseenter="mouseenter(index)" @mouseleave="isMouseenter = false">
|
||||
<a-select
|
||||
v-model:value="element.computationalMethod"
|
||||
style="width: 85px"
|
||||
v-if="element.computationalMethod"
|
||||
>
|
||||
<a-select-option value="sum">{{ t('总和') }}</a-select-option>
|
||||
<a-select-option value="mean">{{ t('平均数') }}</a-select-option>
|
||||
<a-select-option value="max">{{ t('最大值') }}</a-select-option>
|
||||
<a-select-option value="min">{{ t('最小值') }}</a-select-option>
|
||||
</a-select>
|
||||
<a-tag color="blue" closable style="padding: 5px" @close="delTag(index)">
|
||||
<span>{{ element.label }}</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, inject, reactive, onMounted } from 'vue';
|
||||
import { ComputationalConfig } from '/@/components/Designer/src/types';
|
||||
import draggable from 'vuedraggable';
|
||||
import { cloneDeep, random } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
computationalDialog: { type: Boolean },
|
||||
computationalConfig: {
|
||||
type: Array as PropType<ComputationalConfig[]>,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
const { widgetForm, widgetFormSelect } = inject('state') as any; //整个表单json配置
|
||||
const operatingBtnList = reactive([
|
||||
{
|
||||
key: 1,
|
||||
operation: '+',
|
||||
label: '+',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
operation: '-',
|
||||
label: '-',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
operation: '×',
|
||||
label: '*',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
operation: '÷',
|
||||
label: '/',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 5,
|
||||
operation: '(',
|
||||
label: '(',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
operation: ')',
|
||||
label: ')',
|
||||
type: 'operation',
|
||||
},
|
||||
{
|
||||
key: 7,
|
||||
operation: '.',
|
||||
label: '.',
|
||||
type: 'operation',
|
||||
},
|
||||
]);
|
||||
const number = ref<string>('');
|
||||
const isMouseenter = ref<Boolean>(false);
|
||||
const computationalList = ref<Array<any>>([]);
|
||||
const mouseenterIndex = ref<number>();
|
||||
const activeKey = ref([0]);
|
||||
const selectedComponents = ref<Array<any>>([]);
|
||||
const dialog = ref(props.computationalDialog);
|
||||
onMounted(() => {
|
||||
if (widgetForm.list.length) {
|
||||
getTableComponents(widgetForm.list, t('主表组件列表'));
|
||||
}
|
||||
if (props.computationalConfig.length) {
|
||||
computationalList.value = cloneDeep(props.computationalConfig);
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:computationalDialog',
|
||||
'setComputationalConfigValue',
|
||||
'update:computationalConfig',
|
||||
]);
|
||||
|
||||
const getTableComponents = (list, tableName, key?) => {
|
||||
list.forEach((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getTableComponents(child.list, tableName);
|
||||
}
|
||||
} else if (item.type === 'table-layout') {
|
||||
for (const child of item.layout!) {
|
||||
for (const list of child.list) {
|
||||
getTableComponents(list.children, key);
|
||||
}
|
||||
}
|
||||
} else if (item.type === 'one-for-one') {
|
||||
getTableComponents(item.children, `${item.label}-${item.key}`, item.key);
|
||||
} else if (item.type === 'form') {
|
||||
const hasMethods = item.children.some((child) => {
|
||||
return child.key === widgetFormSelect.key;
|
||||
});
|
||||
const itemChildren = item.children.filter((child) => {
|
||||
child.computationalMethod = hasMethods ? undefined : 'sum';
|
||||
return child.type === 'computational' || child.type === 'money-chinese';
|
||||
});
|
||||
|
||||
selectedComponents.value.push({
|
||||
label: `${item.label}-${item.key}`,
|
||||
key: item.key,
|
||||
children: itemChildren,
|
||||
});
|
||||
} else if (item.type === 'computational' || item.type === 'money-chinese') {
|
||||
const index = selectedComponents.value.findIndex((x) => x.label === tableName);
|
||||
if (index !== -1) {
|
||||
selectedComponents.value[index].children.push(item);
|
||||
} else {
|
||||
const tableChildren = [item];
|
||||
if (tableName === t('主表组件列表')) {
|
||||
selectedComponents.value.unshift({
|
||||
label: tableName,
|
||||
key: '1',
|
||||
children: tableChildren,
|
||||
});
|
||||
} else {
|
||||
selectedComponents.value.push({
|
||||
label: tableName,
|
||||
key: key,
|
||||
children: tableChildren,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const tagColor = (tableItem, componentItem) => {
|
||||
if (widgetFormSelect.key === componentItem.key) return '#f4f4f5';
|
||||
const currentItem = tableItem.children.some((item: any) => {
|
||||
return item.key === widgetFormSelect.key;
|
||||
});
|
||||
return currentItem ? 'green' : 'blue';
|
||||
};
|
||||
|
||||
const disabledCom = (currentKey) => {
|
||||
if (widgetFormSelect.key === currentKey) return true;
|
||||
const isDisabled = widgetFormSelect.options.beAdoptedComponent.some((key: string) => {
|
||||
return key === currentKey;
|
||||
});
|
||||
return isDisabled;
|
||||
};
|
||||
|
||||
const setBeAdoptedComponent = (list, key) => {
|
||||
list.forEach((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
setBeAdoptedComponent(child.list, key);
|
||||
}
|
||||
} else if (item.type === 'form') {
|
||||
setBeAdoptedComponent(item.children, key);
|
||||
} else if (item.key === key) {
|
||||
if (!item.options.beAdoptedComponent.includes(widgetFormSelect.key)) {
|
||||
if (!item.options.beAdoptedComponent) item.options.beAdoptedComponent = [];
|
||||
//被引用的组件不能引用当前组件
|
||||
item.options.beAdoptedComponent.push(widgetFormSelect.key);
|
||||
if (widgetFormSelect.options?.beAdoptedComponent.length) {
|
||||
item.options.beAdoptedComponent.push(...widgetFormSelect.options?.beAdoptedComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//编辑时 去掉已经删除的组件引用
|
||||
const deleteBeAdoptedComponent = (list, keys) => {
|
||||
keys.forEach((key) => {
|
||||
list.forEach((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
deleteBeAdoptedComponent(child.list, keys);
|
||||
}
|
||||
} else if (item.type === 'form') {
|
||||
deleteBeAdoptedComponent(item.children, keys);
|
||||
} else if (item.key === key) {
|
||||
//被引用的组件不能引用当前组件
|
||||
item.options.beAdoptedComponent = item.options.beAdoptedComponent.filter(
|
||||
(x) => x !== widgetFormSelect.key,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const submitDialog = () => {
|
||||
let configValue = '';
|
||||
const computationalConfig: ComputationalConfig[] = [];
|
||||
computationalList.value.map((item: any) => {
|
||||
const text = item.computationalMethod
|
||||
? `${item.computationalMethod}(${item.label})`
|
||||
: item.label;
|
||||
let isMainForm;
|
||||
if (item.hasOwnProperty('isMainForm')) {
|
||||
isMainForm = item.isMainForm;
|
||||
} else {
|
||||
if (item.type === 'computational' || item.type === 'money-chinese') {
|
||||
isMainForm = !(item?.isSubFormChild || item?.isSingleFormChild);
|
||||
} else {
|
||||
isMainForm = undefined;
|
||||
}
|
||||
}
|
||||
configValue = configValue.concat(text);
|
||||
computationalConfig.push({
|
||||
$index: item.$index,
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
key: item.key,
|
||||
bindTable: item.bindTable,
|
||||
bindField: item.bindField,
|
||||
computationalMethod: item?.computationalMethod,
|
||||
isMainForm: isMainForm,
|
||||
});
|
||||
setBeAdoptedComponent(widgetForm.list, item.key);
|
||||
});
|
||||
const currentConfigKeys = computationalConfig.map((x) => x.bindTable && x.key);
|
||||
const oldConfigKeys = props.computationalConfig.map((x) => x.bindTable && x.key);
|
||||
|
||||
// 被删除的引用组件
|
||||
const needDeleteKeys = oldConfigKeys.filter((old) => {
|
||||
return old && currentConfigKeys.indexOf(old) === -1;
|
||||
});
|
||||
|
||||
deleteBeAdoptedComponent(widgetForm.list, needDeleteKeys);
|
||||
emit('setComputationalConfigValue', configValue);
|
||||
emit('update:computationalConfig', computationalConfig);
|
||||
emit('update:computationalDialog', false);
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
emit('update:computationalDialog', false);
|
||||
};
|
||||
const addComputationalList = (e) => {
|
||||
computationalList.value[e.newIndex] = cloneDeep(computationalList.value[e.newIndex]);
|
||||
computationalList.value[e.newIndex].$index = random(100, 999);
|
||||
};
|
||||
const addNumber = () => {
|
||||
if (number.value) {
|
||||
computationalList.value.push({ label: number.value, type: 'operation' });
|
||||
}
|
||||
};
|
||||
const mouseenter = (index: number) => {
|
||||
mouseenterIndex.value = index;
|
||||
isMouseenter.value = true;
|
||||
};
|
||||
const delTag = (index) => {
|
||||
computationalList.value.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.bgcWhite {
|
||||
background: #fff;
|
||||
|
||||
.btn-list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
|
||||
:deep(.ant-tag) {
|
||||
font-size: 18px !important;
|
||||
padding: 5px 15px !important;
|
||||
margin-bottom: 0;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.input-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.computational-box {
|
||||
height: 100%;
|
||||
padding: 5px 10px;
|
||||
min-height: 300px;
|
||||
|
||||
.draggable {
|
||||
font-size: 13px;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
> span:nth-of-type(1) {
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
p:nth-child(1) {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
p:nth-child(2) {
|
||||
color: #9e9d9d;
|
||||
}
|
||||
}
|
||||
|
||||
.computational-text {
|
||||
position: absolute;
|
||||
left: 45%;
|
||||
top: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-container {
|
||||
margin: 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
|
||||
.component-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
:deep(.ant-collapse) {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tag) {
|
||||
padding: 8px;
|
||||
font-size: 13px;
|
||||
cursor: move;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 15px 10px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.disabled-tag {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
border: none;
|
||||
color: #c3c5ca;
|
||||
}
|
||||
|
||||
:deep(.ant-layout-sider) {
|
||||
background: #fff;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
v-model:visible="visible"
|
||||
:title="state.title"
|
||||
:maskClosable="false"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="list-title">字段列表</div>
|
||||
<a-table
|
||||
:dataSource="configDataSource"
|
||||
:columns="state.configColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: '300px' }"
|
||||
>
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'show'">
|
||||
是否在弹层列表显示
|
||||
<a-checkbox v-model:checked="state.checkedAll" @change="handleChecked" />
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'component'">
|
||||
<a-select
|
||||
v-model:value="record.bindField"
|
||||
style="width: 100%"
|
||||
placeholder="请选择填充组件"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="val.bindField"
|
||||
v-for="(val, index) in selectedList"
|
||||
:key="index"
|
||||
>
|
||||
{{ val.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tableTitle'">
|
||||
<a-input v-model:value="record.tableTitle" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'show'">
|
||||
<a-checkbox v-model:checked="record.show" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'width'">
|
||||
<a-input v-model:value="record.width" :disabled="!record.show" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, reactive, ref } from 'vue';
|
||||
import { DataSourceConfig } from '../../../types';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const props = defineProps({
|
||||
dataSourceAssoDia: { type: Boolean },
|
||||
type: { type: String },
|
||||
selectedList: { type: Array as any },
|
||||
dataSourceOptions: { type: Array as PropType<DataSourceConfig[]> },
|
||||
});
|
||||
const emit = defineEmits(['update:dataSourceAssoDia', 'update:dataSourceOptions']);
|
||||
const state = reactive({
|
||||
title: '',
|
||||
checkedAll: false as boolean,
|
||||
configColumns: [] as ColumnProps[],
|
||||
});
|
||||
const visible = ref<boolean>(props.dataSourceAssoDia);
|
||||
const configDataSource = ref<DataSourceConfig[]>([]);
|
||||
const multiplePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字段名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '表头名称',
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '是否在弹层列表显示',
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '列表宽度',
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
]);
|
||||
|
||||
const associatePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字段名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '填充组件',
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: '表头名称',
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '是否在弹层列表显示',
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '列表宽度',
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
]);
|
||||
|
||||
const associateSelectColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字段名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '填充组件',
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '填充组件绑定字段',
|
||||
dataIndex: 'bindField',
|
||||
key: 'bindField',
|
||||
align: 'center',
|
||||
},
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => props.type,
|
||||
(val) => {
|
||||
switch (val) {
|
||||
case 'associate-popup':
|
||||
state.configColumns = associatePopupColumns;
|
||||
state.title = '联想数据配置-数据源';
|
||||
break;
|
||||
case 'multiple-popup':
|
||||
state.configColumns = multiplePopupColumns;
|
||||
state.title = '多选数据配置-数据源';
|
||||
break;
|
||||
case 'associate-select':
|
||||
state.configColumns = associateSelectColumns;
|
||||
state.title = '联想数据配置-数据源';
|
||||
break;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.dataSourceOptions,
|
||||
(val) => {
|
||||
if (!val?.length) return;
|
||||
configDataSource.value = cloneDeep(val)!;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
configDataSource.value,
|
||||
(val) => {
|
||||
state.checkedAll = val!.every((item: any) => {
|
||||
return item.show;
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function handleClose() {
|
||||
emit('update:dataSourceAssoDia', false);
|
||||
}
|
||||
function handleChecked(e: any) {
|
||||
configDataSource.value.map((item: any) => (item.show = e.target.checked));
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
const bindFieldArr = [] as any;
|
||||
configDataSource.value?.map((item: any) => {
|
||||
if (item.bindField) {
|
||||
bindFieldArr.push(item.bindField);
|
||||
}
|
||||
});
|
||||
|
||||
if (new Set(bindFieldArr).size !== bindFieldArr.length) {
|
||||
message.error('组件存在重复绑定,请更改');
|
||||
return;
|
||||
}
|
||||
|
||||
emit('update:dataSourceAssoDia', false);
|
||||
emit('update:dataSourceOptions', configDataSource.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-title {
|
||||
margin: 10px 0 5px 15px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<a-form-item :label="t('数据来源')">
|
||||
<a-radio-group
|
||||
button-style="solid"
|
||||
v-model:value="info.options.datasourceType"
|
||||
size="small"
|
||||
:disabled="disabled"
|
||||
@change="handleDatasourceChange"
|
||||
>
|
||||
<a-radio-button
|
||||
value="staticData"
|
||||
v-if="
|
||||
info.type === 'radio' ||
|
||||
info.type === 'checkbox' ||
|
||||
info.type === 'select' ||
|
||||
info.type === 'auto-complete'
|
||||
"
|
||||
>
|
||||
{{ t('静态数据') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="api">API</a-radio-button>
|
||||
<!-- <a-radio-button value="datasource">数据源</a-radio-button> -->
|
||||
<a-radio-button value="dic">{{ t('数据字典') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<div v-if="info.options.datasourceType === 'staticData'">
|
||||
<StaticData
|
||||
:disabled="disabled"
|
||||
:options="info.options"
|
||||
@handleOptionsRemove="
|
||||
(index) => {
|
||||
emit('handleOptionsRemove', index);
|
||||
}
|
||||
"
|
||||
@handleInsertOption="handleInsertOption"
|
||||
@handleDefaultSelect="
|
||||
(val) => {
|
||||
info.options.defaultSelect = val;
|
||||
}
|
||||
"
|
||||
:type="info.type === 'checkbox' ? 'checkbox' : 'radio'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="info.options.datasourceType === 'datasource'">
|
||||
<a-form-item :label="t('数据选项')">
|
||||
<DatasourceSelect v-model:value="info.options.datasourceId" @change="handleFieldChange" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('显示字段')">
|
||||
<a-select v-model:value="info.options.labelField" :placeholder="t('请选择显示字段')">
|
||||
<a-select-option v-for="(item, idx) in fieldOptions" :value="item" :key="idx" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('保存字段')">
|
||||
<a-select v-model:value="info.options.valueField" :placeholder="t('请选择保存字段')">
|
||||
<a-select-option v-for="(item, idx) in fieldOptions" :value="item" :key="idx" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div v-if="info.options.datasourceType === 'api'">
|
||||
<a-form-item :label="t('接口配置')">
|
||||
<a-input
|
||||
v-model:value="info.options.apiConfig.path"
|
||||
:placeholder="t('点击进行接口配置')"
|
||||
:disabled="disabled"
|
||||
@click="apiConfigDialog = true"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div v-if="info.options.datasourceType === 'dic'">
|
||||
<a-form-item :label="t('数据选项')">
|
||||
<DicTreeSelect
|
||||
v-model:value="info.options.itemId"
|
||||
@change="handleDicChange"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item
|
||||
:label="formLabel"
|
||||
v-if="
|
||||
info.type === 'associate-popup' ||
|
||||
info.type === 'multiple-popup' ||
|
||||
info.type === 'associate-select' ||
|
||||
(info.type === 'button' && info.options.isSpecial && info.options.buttonType == 1)
|
||||
"
|
||||
>
|
||||
<a-input :value="configText" :placeholder="t('点击进行配置')" @click="showConfigDialog">
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<DataSourceAssoConfig
|
||||
v-if="dataSourceAssoDia"
|
||||
v-model:dataSourceAssoDia="dataSourceAssoDia"
|
||||
v-model:dataSourceOptions="info.options.dataSourceOptions"
|
||||
:selectedList="list"
|
||||
:type="info.type"
|
||||
/>
|
||||
<ApiAssoConfig
|
||||
v-if="apiAssoDia"
|
||||
:disabled="disabled"
|
||||
v-model:apiAssoDia="apiAssoDia"
|
||||
v-model:apiConfig="info.options.apiConfig"
|
||||
:selectedList="list"
|
||||
:type="info.type"
|
||||
/>
|
||||
<DicAssoConfig
|
||||
v-if="dicAssoDia"
|
||||
v-model:dicAssoDia="dicAssoDia"
|
||||
v-model:dicOptions="info.options.dicOptions"
|
||||
:selectedList="list"
|
||||
:type="info.type"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<ApiConfig
|
||||
v-if="apiConfigDialog"
|
||||
:isSubForm="info.isSubFormChild"
|
||||
v-model:apiConfigDialog="apiConfigDialog"
|
||||
v-model:apiConfig="info.options.apiConfig"
|
||||
:formItem="info"
|
||||
:title="t('API配置')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, ref, inject, computed } from 'vue';
|
||||
import DataSourceAssoConfig from './DataSourceAssoConfig.vue';
|
||||
import ApiAssoConfig from './ApiAssoConfig.vue';
|
||||
import DicAssoConfig from './DicAssoConfig.vue';
|
||||
import StaticData from './StaticData.vue';
|
||||
import { getDatasourceColumn } from '/@/api/system/datasource';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { DatasourceSelect } from '/@/components/DataSourceSelect';
|
||||
import { ApiConfig } from '/@/components/ApiConfig';
|
||||
import { DicTreeSelect } from '/@/components/DicTreeSelect';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
data: { type: Object },
|
||||
disabled: { type: Boolean, default: () => false },
|
||||
});
|
||||
const { widgetForm, widgetFormSelect } = inject('state') as any; //整个表单json配置
|
||||
|
||||
const emit = defineEmits(['update:data', 'handleOptionsRemove']);
|
||||
const info = ref<any>(props.data);
|
||||
const dataSourceAssoDia = ref<boolean>(false);
|
||||
const apiAssoDia = ref<boolean>(false);
|
||||
const dicAssoDia = ref<boolean>(false);
|
||||
const apiConfigDialog = ref<boolean>(false);
|
||||
const list = ref<any[]>([]);
|
||||
const selectedList = ref<any[]>([]);
|
||||
const selectedSingleList = ref<any[]>([]); //单表组件内部引用列表
|
||||
|
||||
const fieldOptions = ref<string[]>([]);
|
||||
const notBindFieldConfig = ['select', 'radio', 'checkbox', 'associate-select'];
|
||||
const noAssociateComponents = [
|
||||
'multiple-popup',
|
||||
'associate-popup',
|
||||
'associate-select',
|
||||
'form',
|
||||
'tab',
|
||||
'card',
|
||||
'grid',
|
||||
'one-for-one',
|
||||
'button',
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(val) => {
|
||||
info.value = val;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
info.value,
|
||||
(val) => {
|
||||
emit('update:data', val);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
const getSelectedList = (list, isOneForOne = false) => {
|
||||
list?.map((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getSelectedList(child.list, isOneForOne);
|
||||
}
|
||||
} else if (item.type == 'table-layout') {
|
||||
for (const child of item.layout!) {
|
||||
for (const item of child.list) {
|
||||
getSelectedList(item.children, isOneForOne);
|
||||
}
|
||||
}
|
||||
} else if (item.type === 'one-for-one') {
|
||||
getSelectedList(item.children, true);
|
||||
} else if (item.type === 'form') {
|
||||
if (item.bindTable === widgetFormSelect.bindTable) {
|
||||
selectedList.value = item.children.filter((child: any) => {
|
||||
return !noAssociateComponents.includes(child.type);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!noAssociateComponents.includes(item.type)) {
|
||||
if (isOneForOne) item.isSingleFormChild = true;
|
||||
selectedList.value.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => widgetForm.list,
|
||||
() => {
|
||||
if (widgetForm.list.length) {
|
||||
selectedSingleList.value = [];
|
||||
selectedList.value = [];
|
||||
getSelectedList(widgetForm.list);
|
||||
list.value = selectedList.value;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
const configText = computed(() => {
|
||||
const datasourceType = info.value.options.datasourceType;
|
||||
const dicOptions = info.value.options.dicOptions;
|
||||
const outputParams = info.value.options.apiConfig?.outputParams;
|
||||
if (
|
||||
(datasourceType === 'dic' && dicOptions?.length) ||
|
||||
(datasourceType === 'api' && outputParams?.length)
|
||||
) {
|
||||
return t('已配置');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const formLabel = computed(() => {
|
||||
return info.value?.type === 'multiple-popup' ? t('显示配置') : t('联想配置');
|
||||
});
|
||||
|
||||
const handleDatasourceChange = () => {
|
||||
info.value.options.params = null;
|
||||
info.value.options.labelField = 'label';
|
||||
info.value.options.valueField = 'value';
|
||||
|
||||
if (info.value.options.datasourceType === 'dic') {
|
||||
info.value.options.params = info.value.options.itemId
|
||||
? { itemId: info.value.options.itemId }
|
||||
: null;
|
||||
info.value.options.labelField = 'name';
|
||||
}
|
||||
if (info.value.options.datasourceType !== 'staticData') {
|
||||
if (info.value.type == 'multiple-popup' || info.value.type == 'checkbox') {
|
||||
info.value.options.defaultTempValue = [];
|
||||
}
|
||||
info.value.options.defaultSelect = null;
|
||||
}
|
||||
};
|
||||
const handleInsertOption = () => {
|
||||
const index = info.value.options.staticOptions.length + 1;
|
||||
info.value.options.staticOptions.push({
|
||||
key: index,
|
||||
label: `Option ${index}`,
|
||||
value: `Option ${index}`,
|
||||
});
|
||||
};
|
||||
const handleFieldChange = async (id: string) => {
|
||||
if (!id) return;
|
||||
info.value.options.labelField = undefined;
|
||||
info.value.options.valueField = undefined;
|
||||
info.value.options.dataSourceOptions = [];
|
||||
info.value.options.params = id;
|
||||
fieldOptions.value = await getDatasourceColumn(id);
|
||||
|
||||
if (fieldOptions.value.length) {
|
||||
fieldOptions.value.map((item, index) => {
|
||||
let options = {};
|
||||
if (notBindFieldConfig.includes(info.value.type)) {
|
||||
options = { key: index + 1, name: item };
|
||||
} else {
|
||||
options = {
|
||||
key: index + 1,
|
||||
name: item,
|
||||
tableTitle: item,
|
||||
bindField: '',
|
||||
show: true,
|
||||
width: '150',
|
||||
};
|
||||
}
|
||||
info.value.options.dataSourceOptions.push(options);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDicChange = (val) => {
|
||||
info.value.options.params = { itemId: val };
|
||||
};
|
||||
const showConfigDialog = () => {
|
||||
if (info.value.options.datasourceType === 'datasource') {
|
||||
if (info.value.type === 'associate-popup' && !selectedList.value.length) {
|
||||
message.error(t('请保证存在一个或一个以上可联想绑定数据的组件'));
|
||||
} else {
|
||||
dataSourceAssoDia.value = true;
|
||||
}
|
||||
} else if (info.value.options.datasourceType === 'api') {
|
||||
apiAssoDia.value = true;
|
||||
} else if (info.value.options.datasourceType === 'dic') {
|
||||
dicAssoDia.value = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="1000"
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:maskClosable="false"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
:bodyStyle="{ padding: '0 10px 10px' }"
|
||||
>
|
||||
<div class="list-title">{{ t('字段列表') }}</div>
|
||||
<a-table
|
||||
:dataSource="dicInfo"
|
||||
:columns="dicColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: '400px' }"
|
||||
>
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'show'">
|
||||
{{ t('是否在弹层列表显示') }}
|
||||
<a-checkbox v-model:checked="checkedAll" @change="handleChecked" />
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'component'">
|
||||
<a-select
|
||||
v-model:value="record.component"
|
||||
style="width: 100%"
|
||||
:placeholder="t('请选择填充组件')"
|
||||
@change="(value) => selectBindField(value, record)"
|
||||
allowClear
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-select-option :value="val.key" v-for="(val, idx) in selectedList" :key="idx">
|
||||
{{ val.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tableTitle'">
|
||||
<a-input v-model:value="record.tableTitle" :placeholder="t('请填写表头名称')" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'show'">
|
||||
<a-checkbox v-model:checked="record.show" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'width'">
|
||||
<a-input
|
||||
v-model:value="record.width"
|
||||
:disabled="!record.show"
|
||||
:placeholder="t('请填写列表宽度')"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, onMounted } from 'vue';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dicAssoDia: { type: Boolean },
|
||||
dicOptions: { type: Object as PropType<any> },
|
||||
selectedList: { type: Array as any },
|
||||
type: { type: String },
|
||||
disabled: { type: Boolean, default: () => false },
|
||||
});
|
||||
const emit = defineEmits(['update:dicAssoDia', 'update:dicOptions']);
|
||||
const dicInfo = ref<any[]>([]);
|
||||
const title = ref<string>('');
|
||||
const checkedAll = ref<boolean>(false);
|
||||
let dicColumns;
|
||||
const visible = ref<boolean>(props.dicAssoDia);
|
||||
|
||||
watch(
|
||||
() => props.dicOptions,
|
||||
(val) => {
|
||||
if (val && val.length) {
|
||||
dicInfo.value = cloneDeep(val);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => dicInfo.value,
|
||||
(val) => {
|
||||
checkedAll.value = val.every((item: any) => {
|
||||
return item.show;
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (!dicInfo.value.length) {
|
||||
switch (props.type) {
|
||||
case 'button':
|
||||
case 'associate-popup':
|
||||
dicInfo.value = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: 'name',
|
||||
name: 'name',
|
||||
bindField: '',
|
||||
tableTitle: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
title: 'value',
|
||||
name: 'value',
|
||||
bindField: '',
|
||||
tableTitle: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 'multiple-popup':
|
||||
dicInfo.value = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: 'name',
|
||||
name: 'name',
|
||||
tableTitle: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
title: 'value',
|
||||
name: 'value',
|
||||
tableTitle: '',
|
||||
show: true,
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 'associate-select':
|
||||
dicInfo.value = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: 'name',
|
||||
name: 'name',
|
||||
bindField: '',
|
||||
},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
title: 'value',
|
||||
name: 'value',
|
||||
bindField: '',
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 'preload-title':
|
||||
dicInfo.value = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: 'name',
|
||||
name: 'name',
|
||||
tableTitle: 'name',
|
||||
},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
title: 'value',
|
||||
name: 'value',
|
||||
tableTitle: 'value',
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
const multiplePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('显示'),
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('列表宽度'),
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
]);
|
||||
|
||||
const associatePopupColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('填充组件'),
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('是否在弹层列表显示'),
|
||||
dataIndex: 'show',
|
||||
key: 'show',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('列表宽度'),
|
||||
dataIndex: 'width',
|
||||
key: 'width',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
]);
|
||||
|
||||
const associateSelectColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('填充组件'),
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('填充组件绑定字段'),
|
||||
dataIndex: 'bindField',
|
||||
key: 'bindField',
|
||||
align: 'center',
|
||||
},
|
||||
]);
|
||||
|
||||
const preloadColumns = reactive<ColumnProps[]>([
|
||||
{
|
||||
title: '#',
|
||||
align: 'center',
|
||||
customRender: ({ index }) => `${index + 1}`, // 显示每一行的序号
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('字段名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('表头名称'),
|
||||
dataIndex: 'tableTitle',
|
||||
key: 'tableTitle',
|
||||
align: 'center',
|
||||
},
|
||||
]);
|
||||
watch(
|
||||
() => props.type,
|
||||
(val) => {
|
||||
console.log('props.type', val);
|
||||
switch (val) {
|
||||
case 'button':
|
||||
case 'associate-popup':
|
||||
dicColumns = associatePopupColumns;
|
||||
title.value = t('联想数据配置-数据字典');
|
||||
break;
|
||||
case 'multiple-popup':
|
||||
dicColumns = multiplePopupColumns;
|
||||
title.value = t('多选数据配置-数据字典');
|
||||
break;
|
||||
case 'associate-select':
|
||||
dicColumns = associateSelectColumns;
|
||||
title.value = t('联想数据配置-数据字典');
|
||||
break;
|
||||
case 'preload-title':
|
||||
dicColumns = preloadColumns;
|
||||
title.value = t('表头配置-数据字典');
|
||||
break;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const selectBindField = (value, record) => {
|
||||
if (!value || !isNaN(value)) {
|
||||
message.error(t('请先选择该组件的绑定表及绑定字段'));
|
||||
record.bindField = null;
|
||||
record.bindTable = undefined;
|
||||
} else {
|
||||
let obj = props.selectedList.find((o) => o.key == value);
|
||||
if (obj) {
|
||||
record.bindField = obj.bindField;
|
||||
if (obj.isSingleFormChild || obj.isSubFormChild) {
|
||||
record.bindTable = obj.bindTable;
|
||||
} else {
|
||||
record.bindTable = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChecked = (e: any) => {
|
||||
dicInfo.value.map((item: any) => (item.show = e.target.checked));
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:dicAssoDia', false);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
const bindFieldArr = [] as any;
|
||||
dicInfo.value?.map((item: any) => {
|
||||
if (item.bindField) {
|
||||
bindFieldArr.push(item.bindField);
|
||||
}
|
||||
});
|
||||
if (new Set(bindFieldArr).size !== bindFieldArr.length) {
|
||||
message.error(t('组件存在重复绑定,请更改'));
|
||||
return;
|
||||
}
|
||||
emit('update:dicAssoDia', false);
|
||||
emit('update:dicOptions', dicInfo.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-title {
|
||||
margin: 10px 0 5px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="t('查看接口信息')" @ok="closeModal">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" :tab="t('接口信息')">
|
||||
<CodeEditor :value="script" language="json" readonly />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" :tab="t('接口出参示例')">
|
||||
<CodeEditor :value="infoExample" language="json" readonly />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const activeKey = ref<string>('1');
|
||||
const script = ref<string>('');
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
script.value = data.script;
|
||||
setModalProps({
|
||||
confirmLoading: false,
|
||||
draggable: false,
|
||||
showOkBtn: false,
|
||||
showCancelBtn: false,
|
||||
destroyOnClose: true,
|
||||
width: 800,
|
||||
});
|
||||
});
|
||||
|
||||
const infoExample = JSON.stringify({
|
||||
code: 0,
|
||||
msg: t('提示信息'),
|
||||
data: [
|
||||
{
|
||||
label: t('选项一'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: t('选项二'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: t('选项三'),
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="registerModal"
|
||||
:title="t('脚本配置')"
|
||||
@ok="handleSubmit"
|
||||
@visible-change="visibleChange"
|
||||
width="70%"
|
||||
>
|
||||
<div class="config-box">
|
||||
<div>
|
||||
<a-tabs
|
||||
v-model:activeKey="configTab"
|
||||
centered
|
||||
style="margin-bottom: 8px; background: #fff; height: 100%"
|
||||
>
|
||||
<a-tab-pane key="widget" :tab="t('脚本示例')">
|
||||
<div>
|
||||
<div
|
||||
v-for="(item, index) in example"
|
||||
:key="index"
|
||||
:class="['script-name', { 'active-name': activeIndex === index }]"
|
||||
@click="activeIndex = index"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="component" :tab="t('选择组件')">
|
||||
<div class="overflow-y-scroll">
|
||||
<Draggable
|
||||
v-model="widgetFormList"
|
||||
:group="{ name: 'script', pull: false, put: false }"
|
||||
item-key="key"
|
||||
@end="dragEnd"
|
||||
:sort="false"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<a-tag color="blue">{{ `${element.label}(${element.bindField})` }}</a-tag>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- <div class="box-top">
|
||||
<div class="title">{{ t('脚本示例') }}</div>
|
||||
<a-button type="primary" size="small" @click="componentVisible = true">{{
|
||||
t('选择组件')
|
||||
}}</a-button>
|
||||
</div> -->
|
||||
</div>
|
||||
<div style="max-width: 500px">
|
||||
<div class="box-top">
|
||||
<div class="title">{{ t('脚本示例代码') }}</div>
|
||||
</div>
|
||||
<div style="height: calc(100% - 40px)">
|
||||
<CodeEditor :value="example[activeIndex].code" language="js" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="box-top">
|
||||
<div class="title">{{ t('自定义脚本') }}</div>
|
||||
</div>
|
||||
<div style="height: calc(100% - 40px)">
|
||||
<CodeEditor v-model:value="definedScript" language="js" :readonly="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-drawer
|
||||
:title="t('组件列表(可将组件拖入自定义脚本当中)')"
|
||||
placement="left"
|
||||
:visible="componentVisible"
|
||||
:get-container="false"
|
||||
:style="{ position: 'absolute' }"
|
||||
:mask="false"
|
||||
width="50%"
|
||||
:closable="false"
|
||||
>
|
||||
<Draggable
|
||||
v-model="widgetFormList"
|
||||
:group="{ name: 'script', pull: false, put: false }"
|
||||
item-key="key"
|
||||
@end="dragEnd"
|
||||
:sort="false"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<a-tag color="blue">{{ `${element.label}(${element.bindField})` }}</a-tag>
|
||||
</template>
|
||||
</Draggable>
|
||||
<template #extra>
|
||||
<CloseOutlined @click="componentVisible = false" />
|
||||
</template>
|
||||
</a-drawer>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, inject } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { camelCaseString } from '/@/utils/event/design';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
defineProps({
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const eventType = ref('');
|
||||
const definedScript = ref(``);
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const activeIndex = ref(0);
|
||||
const componentVisible = ref<boolean>(false);
|
||||
const configIndex = ref();
|
||||
const configTab = ref('widget');
|
||||
|
||||
const widgetFormList = ref([]); //整个表单json配置
|
||||
const isCustomForm = inject<boolean>('isCustomForm', false);
|
||||
const example = reactive([
|
||||
{
|
||||
title: t('读写组件值'),
|
||||
code: `const value = formModel.bindField //获取组件值
|
||||
formModel.bindField = 1 //修改组件值
|
||||
|
||||
// bindField: 组件的绑定字段`,
|
||||
},
|
||||
{
|
||||
title: t('ajax加载服务器数据'),
|
||||
code: `formActionType.httpRequest({
|
||||
requestType: 'get', //请求方式有: get、post、put、delete
|
||||
requestUrl: '/system/dictionary-detail', //请求地址
|
||||
params: {
|
||||
itemId: '1419276800524423168'
|
||||
},//请求参数
|
||||
errorMessageMode: 'none' //错误提示方式,默认为none
|
||||
})
|
||||
// errorMessageMode='message' 错误提示为消息提示
|
||||
// errorMessageMode='modal' 显示modal错误弹窗,用于一些比较重要的错误
|
||||
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
|
||||
`,
|
||||
},
|
||||
{
|
||||
title: t('修改组件样式'),
|
||||
code: `formActionType.changeStyle(schema, { border:'2px solid #5e95ff' },'bindField')
|
||||
|
||||
// schema:必须要写
|
||||
// 中间对象为需要修改的样式
|
||||
// bindField:需要修改组件的绑定字段(字符串),如修改本组件 则不填`,
|
||||
},
|
||||
{
|
||||
title: t('弹出对话框'),
|
||||
code: `formActionType.showModal({
|
||||
title: '我是标题', // 对话框标题
|
||||
content: '我是内容', // 对话框内容
|
||||
onOk() {
|
||||
console.log('onOk') // 点击确定按钮的处理逻辑
|
||||
},
|
||||
onCancel() {
|
||||
console.log('onCancel') // 点击取消按钮的处理逻辑
|
||||
},
|
||||
});`,
|
||||
},
|
||||
{
|
||||
title: t('if判断'),
|
||||
code: `const value = 3
|
||||
if(value === 3){ /* 括号内为判断条件 */
|
||||
console.log('value为3')
|
||||
/* 处理逻辑 */
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t('循环'),
|
||||
code: `for(let i=0; i<3; i++){ /* 括号内为循环条件 */
|
||||
console.log('i的值为', i)
|
||||
/* 处理逻辑 */
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t('正则校验'),
|
||||
code: `formActionType.regTest({
|
||||
regExpression: /^[0-9]/,// 正则表达式
|
||||
testValue: 123,// 需要进行校验的值
|
||||
successMessage: '校验成功',// 校验成功的提示信息
|
||||
errorMessage: '校验失败'// 校验失败的提示信息
|
||||
})`,
|
||||
},
|
||||
{
|
||||
title: t('刷新API'),
|
||||
code: `formActionType.refreshAPI('bindField')
|
||||
|
||||
// bindField:需要刷新API组件的绑定字段(字符串)`,
|
||||
},
|
||||
]);
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
widgetFormList.value = data.list;
|
||||
eventType.value = data.type;
|
||||
//如果已经设置过当前事件代码 则显示已经设置的值 反之 显示默认值
|
||||
definedScript.value = data.content;
|
||||
configIndex.value = data.index;
|
||||
setModalProps({
|
||||
fixedHeight: true,
|
||||
});
|
||||
});
|
||||
|
||||
const dragEnd = ({ item }) => {
|
||||
const component = item._underlying_vm_;
|
||||
const str = `${component.label}(${component.bindField})`;
|
||||
const replaceStr = isCustomForm
|
||||
? component.bindField
|
||||
: `${camelCaseString(component.bindField)}`;
|
||||
definedScript.value = definedScript.value.replace(str, replaceStr);
|
||||
};
|
||||
const handleSubmit = () => {
|
||||
emit('success', eventType.value, definedScript.value, configIndex.value);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const visibleChange = (visible: boolean) => {
|
||||
if (!visible) componentVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.config-box {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
div:nth-child(1) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
div {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.box-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 30px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin: 0 0 10px 10px;
|
||||
}
|
||||
|
||||
.script-name {
|
||||
cursor: pointer;
|
||||
padding: 10px 0 10px 15px;
|
||||
|
||||
&:hover {
|
||||
background: #eef4ff;
|
||||
}
|
||||
}
|
||||
|
||||
.active-name {
|
||||
background: #eef4ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-drawer-title),
|
||||
.title {
|
||||
font-size: 16px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
|
||||
:deep(.ant-tag) {
|
||||
padding: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 7px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab) {
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="static-box">
|
||||
<div class="static-top">
|
||||
<div class="static-title">{{ t('静态数据列表') }}</div>
|
||||
<div>
|
||||
<a-button
|
||||
:disabled="disabled"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleReset"
|
||||
class="mr-0.5"
|
||||
>
|
||||
取消选中
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="disabled"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="emits('handleInsertOption')"
|
||||
>
|
||||
{{ t('添加数据') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
:dataSource="options?.staticOptions"
|
||||
:row-selection="{
|
||||
type: type,
|
||||
selectedRowKeys: selectedRowKey,
|
||||
onChange: onChange,
|
||||
}"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'label'">
|
||||
<a-input
|
||||
v-model:value="record[column.key]"
|
||||
:disabled="disabled"
|
||||
:placeholder="t('请输入')"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'value'">
|
||||
<a-input
|
||||
v-model:value="record[column.key]"
|
||||
:disabled="disabled"
|
||||
:placeholder="t('请输入')"
|
||||
@change="(val) => changeValue(val, record.key)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<DeleteTwoTone two-tone-color="#ff8080" @click="emits('handleOptionsRemove', index)" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, onMounted } from 'vue';
|
||||
import { DeleteTwoTone } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
options: Object,
|
||||
type: String,
|
||||
disabled: { type: Boolean, default: () => false },
|
||||
});
|
||||
const emits = defineEmits(['handleOptionsRemove', 'handleInsertOption', 'handleDefaultSelect']);
|
||||
const selectedRowKey = ref<string[] | number[]>([1]);
|
||||
const selectedRow = ref<any[]>([]);
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
title: t('显示值'),
|
||||
dataIndex: 'label',
|
||||
key: 'label',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('保存值'),
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(val) => {
|
||||
if (props.type === 'radio') {
|
||||
//单选组件、下拉选择
|
||||
if (!!val?.defaultSelect) {
|
||||
val.staticOptions.map((item: any, index: number) => {
|
||||
if (item.value === val?.defaultSelect) {
|
||||
selectedRowKey.value = [index + 1];
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//多选组件
|
||||
if (val?.defaultSelect) {
|
||||
selectedRowKey.value = [];
|
||||
val.staticOptions.map((item: any, index: number) => {
|
||||
val?.defaultSelect.split(',').map((select: [string, number]) => {
|
||||
if (item.value === select) {
|
||||
(selectedRowKey.value as number[]).push(index + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
selectedRowKey.value = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
selectedRowKey.value = [props.options?.staticOptions[0].key];
|
||||
selectedRow.value = [props.options?.staticOptions[0]];
|
||||
emits('handleDefaultSelect', props.options?.staticOptions[0].value);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => selectedRow.value,
|
||||
(val) => {
|
||||
const selected = val?.map((x) => x.value).join(',');
|
||||
emits('handleDefaultSelect', selected);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const onChange = (selectedRowKeys: string[] | number[], selectedRows: any[]) => {
|
||||
if (props.disabled) return;
|
||||
selectedRowKey.value = selectedRowKeys;
|
||||
selectedRow.value = selectedRows;
|
||||
let selectedValue;
|
||||
if (props.type === 'radio') {
|
||||
selectedValue = selectedRows[0].value;
|
||||
} else {
|
||||
selectedValue = selectedRows.map((item) => item.value).join(',');
|
||||
}
|
||||
emits('handleDefaultSelect', selectedValue);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
selectedRowKey.value = [];
|
||||
selectedRow.value = [];
|
||||
emits('handleDefaultSelect', '');
|
||||
};
|
||||
|
||||
const changeValue = (e, key) => {
|
||||
selectedRow.value.map((row) => {
|
||||
if (row.key === key) {
|
||||
row.value = e.target.value;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.static-box {
|
||||
padding: 5px 0 10px 20px;
|
||||
|
||||
:deep(.ant-table) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody tr td) {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-table .ant-table-thead tr th) {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.static-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
|
||||
.static-title {
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
347
src/components/Designer/src/components/data.ts
Normal file
347
src/components/Designer/src/components/data.ts
Normal file
@ -0,0 +1,347 @@
|
||||
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
|
||||
import { ColumnType, TableInfo } from '/@/model/generator/tableInfo';
|
||||
|
||||
/**
|
||||
* 单表 数据库表结构json
|
||||
*/
|
||||
export const generatorConfig: GeneratorConfig = {
|
||||
databaseId: 'master',
|
||||
formJson: {
|
||||
list: [],
|
||||
config: {},
|
||||
hiddenComponent: [],
|
||||
},
|
||||
listConfig: {
|
||||
buttonConfigs: [],
|
||||
columnConfigs: [
|
||||
{
|
||||
label: '字符串',
|
||||
alignType: 'center',
|
||||
columnName: 'field_string',
|
||||
columnWidth: '20',
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
alignType: 'center',
|
||||
columnName: 'field_long',
|
||||
columnWidth: '20',
|
||||
},
|
||||
{
|
||||
label: '整型',
|
||||
alignType: 'center',
|
||||
columnName: 'field_int',
|
||||
columnWidth: '20',
|
||||
},
|
||||
{
|
||||
label: '浮点',
|
||||
alignType: 'center',
|
||||
columnName: 'field_double',
|
||||
columnWidth: '20',
|
||||
},
|
||||
{
|
||||
label: '时间',
|
||||
alignType: 'center',
|
||||
columnName: 'field_datetime',
|
||||
columnWidth: '20',
|
||||
},
|
||||
],
|
||||
isLeftMenu: true,
|
||||
isPage: true,
|
||||
leftMenuConfig: {
|
||||
childIcon: '',
|
||||
dataSourceId: '0',
|
||||
datasourceType: 'api',
|
||||
dictionaryItemId: '0',
|
||||
fieldName: '',
|
||||
isDictionary: true,
|
||||
// isTree: true,
|
||||
leftWidth: 1,
|
||||
menuName: '',
|
||||
parentFiledName: '',
|
||||
parentIcon: '',
|
||||
showFieldName: '',
|
||||
apiConfig: {},
|
||||
staticData: [],
|
||||
},
|
||||
orderBy: '',
|
||||
orderType: 'desc',
|
||||
queryConfigs: [
|
||||
{
|
||||
fieldName: 'field_string',
|
||||
label: 'q1',
|
||||
},
|
||||
{
|
||||
fieldName: 'field_datetime',
|
||||
label: 'q2',
|
||||
},
|
||||
{
|
||||
fieldName: 'field_int',
|
||||
label: 'q3',
|
||||
},
|
||||
],
|
||||
totalConfigs: [],
|
||||
},
|
||||
menuConfig: {
|
||||
code: 'xx',
|
||||
icon: 'xx',
|
||||
name: 'xx',
|
||||
system: '',
|
||||
parentId: '',
|
||||
remark: 'xxx',
|
||||
sortCode: 0,
|
||||
},
|
||||
outputConfig: {
|
||||
className: 'demo',
|
||||
comment: 'xx',
|
||||
creator: 'xx',
|
||||
onlyInterface: true,
|
||||
onlyFront: true,
|
||||
outputArea: 'bi',
|
||||
isApp: true,
|
||||
isMenu: false,
|
||||
},
|
||||
tableConfigs: [
|
||||
{
|
||||
isMain: true,
|
||||
pkField: 'id',
|
||||
relationField: '',
|
||||
tableName: 'xjr_demo',
|
||||
relationTableField: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* 单表 数据库表结构json
|
||||
*/
|
||||
export const tableInfo: TableInfo[] = [
|
||||
{
|
||||
tableName: 'xjr_demo',
|
||||
columnInfo: [
|
||||
{
|
||||
columnName: 'id',
|
||||
columnType: ColumnType.NUMBER,
|
||||
columnLength: '',
|
||||
isPrimaryKey: true,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
columnName: 'field_string',
|
||||
columnType: ColumnType.STRING,
|
||||
columnLength: '50',
|
||||
isPrimaryKey: false,
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
columnName: 'field_datetime',
|
||||
columnType: ColumnType.STRING,
|
||||
columnLength: '50',
|
||||
isPrimaryKey: false,
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
columnName: 'field_double',
|
||||
columnType: ColumnType.NUMBER,
|
||||
columnLength: '50',
|
||||
isPrimaryKey: false,
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
columnName: 'field_int',
|
||||
columnType: ColumnType.NUMBER,
|
||||
columnLength: '50',
|
||||
isPrimaryKey: false,
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
columnName: 'field_long',
|
||||
columnType: ColumnType.NUMBER,
|
||||
columnLength: '50',
|
||||
isPrimaryKey: false,
|
||||
isNullable: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// /**
|
||||
// * 多表 代码生成器配置json
|
||||
// */
|
||||
// export const generatorConfig: GeneratorConfig = {
|
||||
// databaseId: 'master',
|
||||
// formJson: {
|
||||
// list: [],
|
||||
// config: {},
|
||||
// },
|
||||
// listConfig: {
|
||||
// buttonConfigs: [],
|
||||
// columnConfigs: [
|
||||
// {
|
||||
// label: '字符串',
|
||||
// alignType: 'center',
|
||||
// columnName: 'field_string',
|
||||
// columnWidth: '20',
|
||||
// },
|
||||
// {
|
||||
// label: '数字',
|
||||
// alignType: 'center',
|
||||
// columnName: 'field_long',
|
||||
// columnWidth: '20',
|
||||
// },
|
||||
// {
|
||||
// label: '整型',
|
||||
// alignType: 'center',
|
||||
// columnName: 'field_int',
|
||||
// columnWidth: '20',
|
||||
// },
|
||||
// ],
|
||||
// isLeftMenu: true,
|
||||
// isPage: true,
|
||||
// leftMenuConfig: {
|
||||
// childIcon: '',
|
||||
// dataSourceId: '0',
|
||||
// dictionaryItemId: '0',
|
||||
// fieldName: '',
|
||||
// isDictionary: true,
|
||||
// isTree: true,
|
||||
// leftWidth: 1,
|
||||
// menuName: '',
|
||||
// parentFiledName: '',
|
||||
// parentIcon: '',
|
||||
// showFieldName: '',
|
||||
// },
|
||||
// orderBy: '',
|
||||
// orderType: 'desc',
|
||||
// queryConfigs: [
|
||||
// {
|
||||
// fieldName: 'field_string',
|
||||
// isDate: false,
|
||||
// label: 'q1',
|
||||
// },
|
||||
// {
|
||||
// fieldName: 'field_long',
|
||||
// isDate: false,
|
||||
// label: 'q2',
|
||||
// },
|
||||
// {
|
||||
// fieldName: 'field_int',
|
||||
// isDate: false,
|
||||
// label: 'q3',
|
||||
// },
|
||||
// ],
|
||||
// totalConfigs: [],
|
||||
// },
|
||||
// menuConfig: {
|
||||
// code: 'xx',
|
||||
// icon: 'xx',
|
||||
// name: 'xx',
|
||||
// parentId: '',
|
||||
// remark: 'xxx',
|
||||
// sortCode: 0,
|
||||
// },
|
||||
// outputConfig: {
|
||||
// className: 'demo',
|
||||
// comment: 'xx',
|
||||
// creator: 'xx',
|
||||
// onlyInterface: true,
|
||||
// onlyFront: true,
|
||||
// outputArea: 'bi',
|
||||
// isApp: true,
|
||||
// isMenu: false,
|
||||
// },
|
||||
// tableConfigs: [
|
||||
// {
|
||||
// isMain: true,
|
||||
// pkField: 'id',
|
||||
// relationField: '',
|
||||
// tableName: 'xjr_parent',
|
||||
// relationTableField: '',
|
||||
// },
|
||||
// {
|
||||
// isMain: false,
|
||||
// pkField: 'id',
|
||||
// relationField: 'parent_id',
|
||||
// tableName: 'xjr_child',
|
||||
// relationTableField: 'id',
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * 多表 数据库表结构json
|
||||
// */
|
||||
// export const tableInfo: TableInfo[] = [
|
||||
// {
|
||||
// tableName: 'xjr_parent',
|
||||
// columnInfo: [
|
||||
// {
|
||||
// columnName: 'id',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '',
|
||||
// isPrimaryKey: true,
|
||||
// isNullable: false,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_string',
|
||||
// columnType: ColumnType.STRING,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_int',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_long',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// tableName: 'xjr_child',
|
||||
// columnInfo: [
|
||||
// {
|
||||
// columnName: 'id',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '',
|
||||
// isPrimaryKey: true,
|
||||
// isNullable: false,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_string',
|
||||
// columnType: ColumnType.STRING,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_int',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'field_long',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// {
|
||||
// columnName: 'parent_id',
|
||||
// columnType: ColumnType.NUMBER,
|
||||
// columnLength: '50',
|
||||
// isPrimaryKey: false,
|
||||
// isNullable: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="registerModal"
|
||||
title="批量设置权限所属人"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<div class="table-title">数据列表</div>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.componentType === 'switch'">
|
||||
<a-switch
|
||||
v-model:checked="record[column.dataIndex]"
|
||||
:unCheckedValue="0"
|
||||
:checkedValue="1"
|
||||
:disabled="true"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.componentType === 'picker-color'">
|
||||
<ColorPicker v-model:value="record[column.dataIndex]" :disabled="true" />
|
||||
</template>
|
||||
<template v-if="column.componentType === 'money-chinese'">
|
||||
{{
|
||||
!isNaN(record[column.dataIndex]) && record[column.dataIndex] !== null
|
||||
? nzhcn.encodeB(record[column.dataIndex]) + '元'
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<SelectUser
|
||||
class="select-box"
|
||||
:selectedIds="state.userIds"
|
||||
:multiple="false"
|
||||
@change="getRuleUserIds"
|
||||
@changeNames="getRuleUserNames"
|
||||
>
|
||||
<a-button type="primary"> 设置权限所属人 </a-button>
|
||||
</SelectUser>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { SelectUser } from '/@/components/SelectOrganizational/index';
|
||||
import { getAllUserList } from '/@/api/system/user';
|
||||
import { ColorPicker } from '/@/components/ColorPicker';
|
||||
import nzhcn from 'nzh/cn';
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
|
||||
const state = reactive({
|
||||
rowKey: '',
|
||||
dataSource: [] as any[],
|
||||
columns: [] as BasicColumn[],
|
||||
userIds: [],
|
||||
setDataAuthApi: null as any,
|
||||
apiParams: null,
|
||||
ruleUserIdName: 'ruleUserId',
|
||||
});
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
state.rowKey = data.rowKey;
|
||||
state.columns = cloneDeep(data.columns);
|
||||
state.dataSource = cloneDeep(data.dataSource);
|
||||
state.setDataAuthApi = data.setDataAuthApi;
|
||||
state.apiParams = data.params;
|
||||
state.ruleUserIdName = !!data.isCustomForm ? 'rule_user_id' : 'ruleUserId';
|
||||
|
||||
setModalProps({
|
||||
destroyOnClose: true,
|
||||
maskClosable: false,
|
||||
fixedHeight: true,
|
||||
width: 800,
|
||||
});
|
||||
if (state.columns?.length && state.columns[0].dataIndex !== state.ruleUserIdName) {
|
||||
state.columns.unshift({
|
||||
dataIndex: state.ruleUserIdName,
|
||||
title: '当前权限人',
|
||||
});
|
||||
}
|
||||
|
||||
const userInfo = await getAllUserList();
|
||||
|
||||
state.dataSource.map((item) => {
|
||||
if (item[state.ruleUserIdName]) {
|
||||
const userIdsArr = item[state.ruleUserIdName].split(',');
|
||||
const userNamesArr = [] as string[];
|
||||
userInfo.map((user) => {
|
||||
if (userIdsArr.includes(user.id)) {
|
||||
userNamesArr.push(user.name);
|
||||
}
|
||||
});
|
||||
item[state.ruleUserIdName] = userNamesArr.join(',');
|
||||
}
|
||||
});
|
||||
setTableData(state.dataSource);
|
||||
setColumns(state.columns);
|
||||
});
|
||||
|
||||
const [registerTable, { setTableData, setColumns }] = useTable({
|
||||
rowKey: state.rowKey,
|
||||
columns: state.columns,
|
||||
dataSource: state.dataSource,
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const params = {
|
||||
userIdList: state.userIds,
|
||||
dataIdList: state.dataSource.map((x) => x[state.rowKey]),
|
||||
};
|
||||
if (isFunction(state.setDataAuthApi)) {
|
||||
!!state.apiParams
|
||||
? await state.setDataAuthApi(state.apiParams, params)
|
||||
: await state.setDataAuthApi(params);
|
||||
}
|
||||
closeModal();
|
||||
emit('success');
|
||||
state.userIds = [];
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
state.userIds = [];
|
||||
}
|
||||
|
||||
function getRuleUserIds(ids) {
|
||||
state.userIds = ids;
|
||||
}
|
||||
|
||||
function getRuleUserNames(names) {
|
||||
state.dataSource.map((item) => {
|
||||
item[state.ruleUserIdName] = names;
|
||||
});
|
||||
setTableData(state.dataSource);
|
||||
}
|
||||
</script>
|
||||
<style lange="less" scoped>
|
||||
.select-box {
|
||||
text-align: right;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
</style>
|
||||
1460
src/components/Designer/src/types/index.ts
Normal file
1460
src/components/Designer/src/types/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
112
src/components/Designer/src/util/generateCode.ts
Normal file
112
src/components/Designer/src/util/generateCode.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { CodeType, PlatformType } from '../types';
|
||||
|
||||
export default function (widgetForm: any, codeType: CodeType, platformType: PlatformType) {
|
||||
if (codeType === CodeType.Vue) {
|
||||
return `<template>
|
||||
${
|
||||
platformType === PlatformType.Antd
|
||||
? `<antd-generate-form ref="generateFormRef" :data="widgetForm">
|
||||
</antd-generate-form>
|
||||
<a-button type="primary" @click="handleSubmit">提交</a-button>`
|
||||
: `<el-generate-form ref="generateFormRef" :data="widgetForm">
|
||||
</el-generate-form>
|
||||
<el-button type="primary" @click="handleSubmit">提交</el-button>`
|
||||
}
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
generateFormRef: null,
|
||||
widgetForm: ${JSON.stringify(widgetForm)}
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
state.generateFormRef.getData().then(data => {
|
||||
console.log(data)
|
||||
// data success
|
||||
// data 表单数据
|
||||
}).catch(error => {
|
||||
// data failed
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
handleSubmit
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
if (codeType === CodeType.Html) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
${
|
||||
platformType === PlatformType.Antd
|
||||
? '<link rel="stylesheet" href="https://unpkg.com/ant-design-vue@next/dist/antd.min.css" />'
|
||||
: '<link rel="stylesheet" href="https://unpkg.com/element-plus/lib/theme-chalk/index.css" />'
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
${
|
||||
platformType === PlatformType.Antd
|
||||
? `<antd-generate-form ref="generateFormRef" :data="widgetForm">
|
||||
</antd-generate-form>
|
||||
<a-button type="primary" @click="handleSubmit">提交</a-button>`
|
||||
: `<el-generate-form ref="generateFormRef" :data="widgetForm">
|
||||
</el-generate-form>
|
||||
<el-button type="primary" @click="handleSubmit">提交</el-button>`
|
||||
}
|
||||
</div>
|
||||
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
|
||||
<script src="https://unpkg.com/vue-form-create/dist/formCreate.umd.min.js"></script>
|
||||
<script src="https://unpkg.com/ace-builds/src-noconflict/ace.js"></script>
|
||||
${
|
||||
platformType === PlatformType.Antd
|
||||
? `<script src="https://unpkg.com/moment/moment.js"></script>
|
||||
<script src="https://unpkg.com/ant-design-vue@next/dist/antd.min.js"></script>`
|
||||
: '<script src="https://unpkg.com/element-plus/lib/index.full.js"></script>'
|
||||
}
|
||||
<script>
|
||||
const { createApp, reactive, toRefs } = Vue
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
generateFormRef: null,
|
||||
widgetForm: ${JSON.stringify(widgetForm)}
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
state.generateFormRef.getData().then(data => {
|
||||
console.log(data)
|
||||
// data success
|
||||
// data 表单数据
|
||||
}).catch(error => {
|
||||
// data failed
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
handleSubmit
|
||||
}
|
||||
}
|
||||
})
|
||||
.use(${platformType === PlatformType.Antd ? 'antd' : 'ElementPlus'})
|
||||
.use(formCreate)
|
||||
.mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user