123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- <template>
- <el-col
- class="czr-form-column"
- :class="{
- transparent: transparent,
- 'has-width': !!width,
- }"
- :span="span"
- :offset="offset"
- ref="ref_czrFormColumn"
- >
- <el-form-item
- :label="label"
- :label-width="labelWidthCpt"
- :class="{
- ['link_' + link + ($attrs.type ? '_' + $attrs.type : '')]: true,
- required: required !== false,
- 'no-label': labelWidth === '0px' && !isValue(label) && !$slots.label,
- 'no-label-fit': !labelFit,
- 'is-error': !!state.errorMessage,
- [`layout-${layout || formLayout}`]: true,
- }"
- :error="state.errorMessage"
- :required="required"
- >
- <template #label>
- <slot name="label" />
- </template>
- <slot name="czr" :handleValidate="handleValidate">
- <template v-if="link === 'input'">
- <InputCom
- ref="ref_el"
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- @emitEnter="handleEnter"
- >
- <template v-if="$slots.prefix" #prefix>
- <slot name="prefix" />
- </template>
- <template v-if="$slots.suffix" #suffix>
- <slot name="suffix" />
- </template>
- <template v-if="$slots.prepend" #prepend>
- <slot name="prepend" />
- </template>
- <template v-if="$slots.append" #append>
- <slot name="append" />
- </template>
- </InputCom>
- </template>
- <template v-else-if="link === 'select'">
- <SelectCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- <slot />
- <template #row="{ row }">
- <slot name="row" :row="row" />
- </template>
- </SelectCom>
- </template>
- <template v-else-if="link === 'date'">
- <DateCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- <template #default="{ cell }">
- <slot v-bind="cell" />
- </template>
- </DateCom>
- </template>
- <template v-else-if="link === 'datetime'">
- <DateTimeCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- <template #default="{ cell }">
- <slot v-bind="cell" />
- </template>
- </DateTimeCom>
- </template>
- <template v-else-if="link === 'time'">
- <TimeCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </TimeCom>
- </template>
- <template v-else-if="link === 'cascader'">
- <CascaderCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- <template v-if="$slots.default" #default="{ node, data }">
- <slot v-bind="{ node, data }" />
- </template>
- </CascaderCom>
- </template>
- <template v-else-if="link === 'switch'">
- <SwitchCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </SwitchCom>
- </template>
- <template v-else-if="link === 'radio'">
- <RadioCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </RadioCom>
- </template>
- <template v-else-if="link === 'checkbox'">
- <CheckboxCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </CheckboxCom>
- </template>
- <template v-else-if="link === 'number'">
- <NumberCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- @emitEnter="handleEnter"
- >
- <template v-if="$slots.prefix" #prefix>
- <slot name="prefix" />
- </template>
- <template v-if="$slots.suffix" #suffix>
- <slot name="suffix" />
- </template>
- <template v-if="$slots.prepend" #prepend>
- <slot name="prepend" />
- </template>
- <template v-if="$slots.append" #append>
- <slot name="append" />
- </template>
- </NumberCom>
- </template>
- <template v-else-if="link === 'input-number'">
- <InputNumberCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- @emitEnter="handleEnter"
- >
- <template v-if="$slots.prefix" #prefix>
- <slot name="prefix" />
- </template>
- <template v-if="$slots.suffix" #suffix>
- <slot name="suffix" />
- </template>
- </InputNumberCom>
- </template>
- <template v-else-if="link === 'tree-select'">
- <TreeSelectCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- <template v-if="$slots.default" #default="prop">
- <slot name="default" v-bind="prop" />
- </template>
- </TreeSelectCom>
- </template>
- <template v-else-if="link === 'upload'">
- <UploadCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- />
- </template>
- <template v-else-if="link === 'rich'">
- <RichCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </RichCom>
- </template>
- <template v-else-if="link === 'markdown'">
- <MarkdownCom
- v-bind="$attrs"
- :label="labelCpt"
- :param="param"
- @emitParam="
- (val) => {
- $emit('update:param', val), handleValidate(val)
- }
- "
- >
- </MarkdownCom>
- </template>
- </slot>
- <div v-if="unit" class="unit">{{ unit }}</div>
- <slot name="unit" />
- </el-form-item>
- </el-col>
- </template>
- <script setup lang="ts">
- defineOptions({
- name: 'CzrFormColumn',
- })
- import {
- computed,
- onMounted,
- ref,
- reactive,
- watch,
- getCurrentInstance,
- ComponentInternalInstance,
- inject,
- onBeforeUnmount,
- } from 'vue'
- import InputCom from './czr-form-link/input.vue'
- import InputNumberCom from './czr-form-link/input-number.vue'
- import SelectCom from './czr-form-link/select.vue'
- import DateCom from './czr-form-link/date.vue'
- import DateTimeCom from './czr-form-link/datetime.vue'
- import TimeCom from './czr-form-link/time.vue'
- import CascaderCom from './czr-form-link/cascader.vue'
- import SwitchCom from './czr-form-link/switch.vue'
- import RadioCom from './czr-form-link/radio.vue'
- import CheckboxCom from './czr-form-link/checkbox.vue'
- import NumberCom from './czr-form-link/number.vue'
- import TreeSelectCom from './czr-form-link/tree-select.vue'
- import UploadCom from './czr-form-link/upload.vue'
- import RichCom from './czr-form-link/rich.vue'
- import MarkdownCom from './czr-form-link/markdown.vue'
- import { v4 } from 'uuid'
- const props = defineProps({
- span: { type: Number, default: 6 }, // 栅格
- offset: { type: Number, default: 0 }, // 栅格
- param: {}, // 绑定值
- label: { type: String, default: '' }, // 标题
- required: { default: false }, // 必填项
- labelWidth: { type: String, default: '' }, // 标题宽度
- link: {
- type: String,
- default: 'input',
- validator(val: string) {
- return [
- 'cascader',
- 'checkbox',
- 'date',
- 'datetime',
- 'input',
- 'radio',
- 'select',
- 'switch',
- 'dept',
- 'time',
- 'upload',
- 'number',
- 'input-number',
- 'tree-select',
- 'rich',
- 'markdown',
- ].includes(val)
- },
- }, // 类型,为了避免与原生type字段重复
- rules: { type: Array, default: () => [] }, // 自定义规则
- maxLength: { type: Number, default: null }, // 最大长度
- minLength: { type: Number, default: null }, // 最小长度
- defaultErrorMsg: { default: null }, // 默认校验错误提示
- unit: { default: '', type: String }, // 单位
- otherInfo: {}, // 其他信息
- layout: {},
- transparent: { default: false },
- width: { default: '' },
- labelFit: { default: true },
- })
- const attrs = (getCurrentInstance() as ComponentInternalInstance).attrs
- const state = reactive({
- errorMessage: null,
- uuid: '',
- })
- const ref_el = ref()
- const ref_czrFormColumn: any = ref(null)
- const isValue = (val: any) => {
- if (
- val === null ||
- val === undefined ||
- (typeof val === 'string' && val.trim() === '') ||
- (typeof val === 'object' && val?.length === 0)
- ) {
- return false
- }
- return true
- }
- const labelCpt = computed(() => {
- return props.label.replace(/[::]([^::]*)$/, '')
- })
- const rulesCpt = computed(() => {
- const r = [...props.rules]
- if (isValue(props.minLength)) {
- r.unshift({
- handle: (val: any) =>
- !isValue(val) ||
- (isValue(val) && String(val).length >= props.minLength),
- message: `内容过短,字数需大于等于${props.minLength}`,
- })
- }
- if (isValue(props.maxLength)) {
- r.unshift({
- handle: (val: any) =>
- !isValue(val) ||
- (isValue(val) && String(val).length <= props.maxLength),
- message: `内容过长,字数需小于等于${props.maxLength}`,
- })
- } else if (
- props.link === 'input' &&
- attrs.type === 'textarea' &&
- isValue(attrs.maxlength)
- ) {
- r.unshift({
- handle: (val: any) =>
- !isValue(val) ||
- (isValue(val) && String(val).length <= Number(attrs.maxlength)),
- message: `内容过长,字数需小于等于${attrs.maxlength}`,
- })
- }
- const doStr = [
- 'input',
- 'number',
- 'input-number',
- 'rich',
- 'markdown',
- ].includes(props.link)
- ? '输入'
- : '选择'
- if (
- props.required !== false &&
- !props.rules.some((v: any) => v.type === 'default')
- ) {
- r.unshift({
- handle: (val: any) => isValue(val),
- message: props.defaultErrorMsg ?? `请${doStr}${labelCpt.value}`,
- })
- }
- return r
- })
- const handleValidate = (val: any = undefined, val2: any = undefined) => {
- state.errorMessage = null
- for (let i = 0; i < rulesCpt.value.length; i++) {
- const item: any = rulesCpt.value[i]
- if (!item.handle(val === undefined ? props.param : val)) {
- state.errorMessage = item.message
- break
- }
- }
- return state.errorMessage
- }
- const formLayout = inject('form-layout', 'x')
- const handleEnterFunc = inject('handle-enter', () => {})
- const formLabelWidth = inject('form-label-width', '')
- const labelWidthCpt = computed(() => {
- if (props.labelWidth) {
- return props.labelWidth
- }
- if (formLabelWidth) {
- return formLabelWidth
- }
- return 'auto'
- })
- const handleEnter = () => {
- handleEnterFunc?.()
- }
- const reset = () => {
- state.errorMessage = null
- }
- watch(
- () => state.errorMessage,
- (n) => {
- const p =
- ref_czrFormColumn.value?.$el?.getElementsByClassName('el-form-item')?.[0]
- if (n) {
- setTimeout(() => {
- const e = p
- ?.getElementsByClassName('el-form-item__content')?.[0]
- ?.getElementsByClassName('el-form-item__error')?.[0]
- if (e?.clientHeight) {
- p.style.marginBottom = `${e.clientHeight + 4}px`
- }
- }, 200)
- } else {
- p.style.marginBottom = props.transparent ? 0 : '18px'
- }
- },
- )
- onMounted(() => {
- state.uuid = v4()
- const formChildrenMap: any = inject('czr-form-children-map', new Map())
- let flag = true
- const deep = (dom) => {
- if (dom.className === 'hidden-columns') {
- flag = false
- }
- if (dom.parentElement) {
- deep(dom.parentElement)
- }
- }
- deep(ref_czrFormColumn.value.$parent.$el)
- if (flag) {
- formChildrenMap.set(state.uuid, ref_czrFormColumn.value.$parent)
- }
- })
- onBeforeUnmount(() => {
- const formChildrenMap: any = inject('czr-form-children-map', new Map())
- formChildrenMap.delete(state.uuid)
- })
- defineExpose({
- reset,
- handleValidate,
- handleEnter,
- focus: () => ref_el.value.focus(),
- })
- </script>
- <style scoped lang="scss">
- .czr-form-column {
- &.has-width {
- width: v-bind(width);
- max-width: v-bind(width);
- flex: unset;
- }
- &.transparent {
- :deep(.el-form-item) {
- margin-bottom: 0;
- .el-input__wrapper {
- background-color: transparent;
- box-shadow: none;
- .el-input__count-inner {
- background-color: transparent;
- }
- }
- }
- }
- :deep(.el-form-item) {
- min-height: 2.25rem;
- &.link_time {
- margin-top: 1px;
- }
- &.no-label {
- .el-form-item__label {
- display: none;
- }
- }
- &.no-label-fit {
- .el-form-item__label-wrap {
- margin-left: 0 !important;
- }
- }
- &.layout-y {
- .el-form-item__label {
- justify-content: flex-start;
- padding-left: 0;
- }
- }
- &.layout-x {
- display: flex;
- }
- $textColor: #576275;
- .el-form-item__label {
- line-height: 1;
- text-align: right;
- display: flex;
- align-items: center;
- padding-left: 0.25rem;
- color: $textColor;
- font-weight: normal;
- }
- .el-form-item__content {
- flex-wrap: unset;
- > div:first-child {
- flex: 1;
- height: 100%;
- }
- .unit {
- margin-left: 6px;
- font-size: 14px;
- font-family:
- PingFang SC-Regular,
- PingFang SC;
- font-weight: 400;
- color: #606266;
- }
- .is-disabled {
- .el-select__selected-item:not(.is-transparent),
- .el-input__inner,
- .el-radio__label,
- .el-checkbox__label,
- .el-range-input,
- .el-range-separator,
- .el-textarea__inner {
- color: $textColor;
- -webkit-text-fill-color: $textColor;
- }
- }
- .el-input__wrapper,
- .el-textarea__inner {
- border-radius: 0.25rem;
- }
- }
- }
- }
- </style>
|