---初始化后台管理web页面项目

This commit is contained in:
2025-08-20 14:39:30 +08:00
parent ad49711a7e
commit 87545a8baf
2057 changed files with 282864 additions and 213 deletions

View File

@ -0,0 +1,5 @@
import { withInstall } from '/@/utils';
import designForm from './src/Designer.vue';
export * from './src/types';
export const DesignForm = withInstall(designForm);

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

View 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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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,
// },
// ],
// },
// ];

View File

@ -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>

File diff suppressed because it is too large Load Diff

View 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>
`;
}
}