444 lines
10 KiB
Vue
444 lines
10 KiB
Vue
<script lang="ts" setup>
|
|
import { useI18n } from 'vue-i18n';
|
|
import { PropType } from 'vue';
|
|
import { IconQuestionCircleFill } from '@arco-design/web-vue/es/icon';
|
|
import SelectPro from '../SelectPro/index.vue';
|
|
import QuantityInput from '../QuantityInput/index.vue';
|
|
import TagsAdd from '../TagsAdd/index.vue';
|
|
import Timezone from '../Timezone/index.vue';
|
|
|
|
const { t } = useI18n();
|
|
|
|
defineOptions({
|
|
name: 'FormPro',
|
|
components: {
|
|
SelectPro,
|
|
QuantityInput,
|
|
TagsAdd,
|
|
Timezone,
|
|
},
|
|
});
|
|
type itemProps = {
|
|
comp: string;
|
|
field: string;
|
|
label: string;
|
|
required?: boolean;
|
|
span?: number;
|
|
labelAlign?: 'left' | 'right';
|
|
hideColon?: boolean;
|
|
hideLabel?: boolean;
|
|
ifShow?: any;
|
|
tooltip?: string;
|
|
rules?: any[];
|
|
compProps: {
|
|
[key: string]: any;
|
|
};
|
|
};
|
|
const formRef = ref();
|
|
const props = defineProps({
|
|
itemList: {
|
|
type: Array<itemProps>,
|
|
default: () => [],
|
|
},
|
|
labelColProps: {
|
|
type: Object,
|
|
default: () => ({ span: 5 }),
|
|
},
|
|
wrapperColProps: {
|
|
type: Object,
|
|
default: () => ({ span: 19 }),
|
|
},
|
|
buttonConfig: {
|
|
type: Object as PropType<{
|
|
showBtn: boolean;
|
|
okText: string;
|
|
resetText: string;
|
|
showReset: boolean;
|
|
showOk: boolean;
|
|
span?: number;
|
|
className?: string;
|
|
}>,
|
|
default: () => ({
|
|
showBtn: false,
|
|
okText: 'common.search',
|
|
resetText: 'common.reset',
|
|
showReset: true,
|
|
showOk: true,
|
|
span: 6,
|
|
className: '',
|
|
}),
|
|
},
|
|
rowGutter: {
|
|
type: Number,
|
|
default: 20,
|
|
},
|
|
defaultSpan: {
|
|
type: Number,
|
|
default: 6,
|
|
},
|
|
formLayout: {
|
|
type: String as PropType<'horizontal' | 'vertical' | 'inline'>,
|
|
default: 'horizontal',
|
|
},
|
|
formClassName: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
rowClassName: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
density: {
|
|
type: String as PropType<'default' | 'compact' | 'dense'>,
|
|
default: 'default',
|
|
},
|
|
hideColon: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
labelAlign: {
|
|
type: String as PropType<'left' | 'right'>,
|
|
default: undefined,
|
|
},
|
|
autoLabelWidth: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
size: {
|
|
type: String as PropType<'mini' | 'small' | 'medium' | 'large'>,
|
|
default: 'medium',
|
|
},
|
|
layout: {
|
|
type: String as PropType<'default' | 'inline'>,
|
|
default: 'default',
|
|
},
|
|
});
|
|
const emits = defineEmits(['submit', 'reset']);
|
|
|
|
const formValue = reactive<{
|
|
[key: string]: any;
|
|
}>({});
|
|
|
|
const submitFn = async () => {
|
|
emits('submit', formValue);
|
|
};
|
|
|
|
const resetFn = async () => {
|
|
formRef.value.resetFields();
|
|
emits('reset', formValue);
|
|
};
|
|
|
|
const getValues = () => {
|
|
return formValue;
|
|
};
|
|
|
|
const setValues = (values: any) => {
|
|
Object.assign(formValue, values);
|
|
};
|
|
const validate = async () => {
|
|
return formRef.value.validate();
|
|
};
|
|
|
|
// 添加设置表单值的方法
|
|
const setFormValue = (key, value) => {
|
|
// 如果第一个参数是对象,则批量设置值
|
|
if (typeof key === 'object' && key !== null) {
|
|
const values = key;
|
|
|
|
// 批量设置值
|
|
Object.keys(values).forEach((k) => {
|
|
// 无论字段是否已存在,都设置值
|
|
formValue[k] = values[k];
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// 单个字段设置,无论字段是否已存在
|
|
formValue[key] = value;
|
|
};
|
|
|
|
defineExpose({
|
|
getValues,
|
|
formRef,
|
|
validate,
|
|
setValues,
|
|
setFormValue,
|
|
});
|
|
|
|
// 计算表单类名
|
|
const formClasses = computed(() => {
|
|
const classes = [props.formClassName];
|
|
|
|
if (props.density === 'compact') {
|
|
classes.push('form-pro-compact');
|
|
} else if (props.density === 'dense') {
|
|
classes.push('form-pro-dense');
|
|
}
|
|
|
|
if (props.labelAlign === 'right') {
|
|
classes.push('form-pro-aligned-right');
|
|
} else if (props.labelAlign === 'left') {
|
|
classes.push('form-pro-aligned-left');
|
|
}
|
|
|
|
if (props.hideColon) {
|
|
classes.push('form-pro-no-colon');
|
|
}
|
|
|
|
if (props.autoLabelWidth) {
|
|
classes.push('form-pro-auto-label-width');
|
|
}
|
|
|
|
return classes;
|
|
});
|
|
const getItemList = computed(() => {
|
|
return props.itemList
|
|
.map((i) => {
|
|
return {
|
|
...i,
|
|
ifShow: getShow(i.ifShow),
|
|
compProps: getCompProps(i.compProps, i.field),
|
|
};
|
|
})
|
|
.filter((i) => i.ifShow);
|
|
});
|
|
const getShow = (ifShow) => {
|
|
if (ifShow === undefined) {
|
|
return true;
|
|
}
|
|
if (typeof ifShow === 'function') {
|
|
return ifShow(formValue);
|
|
}
|
|
return ifShow;
|
|
};
|
|
const getCompProps = (compProps, field) => {
|
|
if (typeof compProps === 'function') {
|
|
return compProps({ formModel: formValue || {}, field });
|
|
}
|
|
return compProps;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<a-form
|
|
ref="formRef"
|
|
v-model="formValue"
|
|
:label-col-props="labelColProps"
|
|
:layout="formLayout"
|
|
:model="formValue"
|
|
:size="size"
|
|
:wrapper-col-props="wrapperColProps"
|
|
class="bg-#ffffff"
|
|
v-bind="$attrs"
|
|
>
|
|
<a-row
|
|
v-if="layout === 'default' || formLayout === 'vertical'"
|
|
:class="rowClassName"
|
|
:gutter="rowGutter"
|
|
>
|
|
<a-col
|
|
v-for="i in getItemList"
|
|
:key="i.field"
|
|
:span="i.span || defaultSpan"
|
|
>
|
|
<a-form-item
|
|
:field="i.field"
|
|
:hide-colon="i.hideColon"
|
|
:hide-label="i.hideLabel"
|
|
:label-align="i.labelAlign"
|
|
:rules="i.rules"
|
|
>
|
|
<template #label>
|
|
<div class="label-container">
|
|
<span :class="{ 'required-label': i.required }">{{
|
|
$t(i.label) || i.label
|
|
}}</span>
|
|
<a-tooltip v-if="i.tooltip" :content="i.tooltip">
|
|
<icon-question-circle-fill class="tooltip-icon" />
|
|
</a-tooltip>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-if="i.comp === 'slot'">
|
|
<slot
|
|
:name="i.field"
|
|
v-bind="{
|
|
model: formValue,
|
|
schema: i,
|
|
}"
|
|
/>
|
|
</template>
|
|
<component
|
|
:is="i.comp"
|
|
v-else
|
|
v-model="formValue[i.field]"
|
|
v-bind="getCompProps(i.compProps)"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col
|
|
v-if="props.buttonConfig.showBtn"
|
|
:span="props.buttonConfig.span || defaultSpan"
|
|
>
|
|
<a-space>
|
|
<a-button
|
|
v-if="props.buttonConfig.showOk"
|
|
type="primary"
|
|
@click="submitFn"
|
|
>
|
|
{{ $t(props.buttonConfig.okText) }}
|
|
</a-button>
|
|
<a-button v-if="props.buttonConfig.showReset" @click="resetFn"
|
|
>{{ $t(props.buttonConfig.resetText) }}
|
|
</a-button>
|
|
</a-space>
|
|
</a-col>
|
|
</a-row>
|
|
<a-row
|
|
v-if="layout === 'inline'"
|
|
:gutter="rowGutter"
|
|
class="form-pro-inline-row"
|
|
>
|
|
<a-col
|
|
v-for="i in getItemList"
|
|
:key="i.field"
|
|
:span="i.span || defaultSpan"
|
|
>
|
|
<a-form-item
|
|
:field="i.field"
|
|
:hide-colon="i.hideColon"
|
|
:hide-label="i.hideLabel"
|
|
:label-align="i.labelAlign"
|
|
:label-col-style="{
|
|
width: 'auto',
|
|
flex: 'none',
|
|
}"
|
|
:rules="i.rules"
|
|
:wrapper-col-style="{
|
|
width: 'auto',
|
|
flex: 1,
|
|
}"
|
|
style="border: 1px solid #ccc"
|
|
>
|
|
<template #label>
|
|
<div class="label-container">
|
|
<span :class="{ 'required-label': i.required }">{{
|
|
$t(i.label) || i.label
|
|
}}</span>
|
|
<a-tooltip v-if="i.tooltip" :content="i.tooltip">
|
|
<icon-question-circle-fill class="tooltip-icon" />
|
|
</a-tooltip>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-if="i.comp === 'slot'">
|
|
<slot
|
|
:name="i.field"
|
|
v-bind="{
|
|
model: formValue,
|
|
schema: i,
|
|
}"
|
|
/>
|
|
</template>
|
|
<component
|
|
:is="i.comp"
|
|
v-else
|
|
v-model="formValue[i.field]"
|
|
v-bind="getCompProps(i.compProps)"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col
|
|
v-if="props.buttonConfig.showBtn"
|
|
:class="props.buttonConfig.className"
|
|
:span="props.buttonConfig.span || defaultSpan"
|
|
>
|
|
<a-space class="mt-4px" :class="props.buttonConfig.className">
|
|
<a-button
|
|
v-if="props.buttonConfig.showOk"
|
|
type="primary"
|
|
@click="submitFn"
|
|
>
|
|
{{ $t(props.buttonConfig.okText) }}
|
|
</a-button>
|
|
<a-button v-if="props.buttonConfig.showReset" @click="resetFn"
|
|
>{{ $t(props.buttonConfig.resetText) }}
|
|
</a-button>
|
|
</a-space>
|
|
</a-col>
|
|
</a-row>
|
|
</a-form>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
:deep(.arco-form-item-content) {
|
|
align-items: right !important;
|
|
}
|
|
:deep(.arco-form-item) {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
:deep(.arco-form-item-wrapper-col) {
|
|
min-height: 28px;
|
|
}
|
|
|
|
:deep(.arco-form-item-label) {
|
|
display: flex;
|
|
font-size: 12px; /* 标签文字 */
|
|
}
|
|
|
|
// 确保 vertical 布局下 label 显示
|
|
:deep(.arco-form-layout-vertical) {
|
|
.arco-form-item-label-col {
|
|
display: block !important;
|
|
width: 100% !important;
|
|
margin-bottom: 4px;
|
|
}
|
|
.arco-form-item-label {
|
|
display: block !important;
|
|
margin-bottom: 4px;
|
|
font-weight: 500;
|
|
color: var(--color-text-1);
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
|
|
.form-pro-inline-row {
|
|
|
|
:deep(.arco-form-item-label-col) {
|
|
padding-right: 0px;
|
|
padding-left: 16px;
|
|
}
|
|
|
|
:deep(.arco-input-wrapper) {
|
|
background: #ffffff !important;
|
|
}
|
|
|
|
:deep(.arco-picker) {
|
|
background: #ffffff !important;
|
|
width: 100%;
|
|
}
|
|
|
|
:deep(.arco-select) {
|
|
background: #ffffff !important;
|
|
|
|
}
|
|
:deep(.arco-form-item-content-wrapper) {
|
|
.arco-input.arco-input-size-medium,
|
|
.arco-form-item-content,
|
|
.arco-select-view-value,
|
|
.arco-select-view-input {
|
|
font-size: 12px !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// .btn {
|
|
// display: flex;
|
|
// justify-content: end;
|
|
// }
|
|
</style>
|