272 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div :class="['m-fields-text', { 'm-fields-text--static': config.static }]">
<div v-if="config.disabled"></div>
<!-- 新增静态展示文本样式 -->
<template v-if="config.static">
<span v-if="config.prepend" class="m-fields-text__prepend">{{ config.prepend }}</span>
<span class="m-fields-text__value">{{ value }}</span>
<template v-if="appendConfig">
<TMagicButton
v-if="appendConfig.type === 'button'"
style="color: #0056ea"
:size="size"
@click.prevent="buttonClickHandler"
>
{{ appendConfig.text }}
</TMagicButton>
<img
v-else-if="appendConfig.type === 'icon'"
class="m-fields-text__icon"
:src="appendConfig.text"
@click="buttonClickHandler"
/>
<span v-else>{{ appendConfig.text }}</span>
</template>
</template>
<TMagicInput
v-else
v-model="value"
ref="input"
:clearable="config.clearable ?? true"
:size="size"
:placeholder="config.placeholder"
:disabled="disabled"
@change="changeHandler"
@input="inputHandler"
@keyup="keyUpHandler($event)"
>
<template #prepend v-if="config.prepend">
<span>{{ config.prepend }}</span>
</template>
<template #append v-if="appendConfig">
<TMagicButton
v-if="appendConfig.type === 'button'"
style="color: #0056ea"
:size="size"
@click.prevent="buttonClickHandler"
>
{{ appendConfig.text }}
</TMagicButton>
<img v-else-if="appendConfig.type === 'icon'" :src="appendConfig.text" @click="buttonClickHandler" />
<span v-else>{{ appendConfig.text }}</span>
</template>
</TMagicInput>
<Teleport to="body" v-if="!config.static">
<div v-if="popoverVisible" class="tmagic-form-text-popper m-form-item__content" ref="popoverEl">
<div class="m-form-validate__warning">输入内容前后有空格是否移除空格</div>
<div style="display: flex; justify-content: flex-end">
<TMagicButton link size="small" @click="popoverVisible = false">保持原样</TMagicButton>
<TMagicButton type="primary" size="small" @click="confirmTrimHandler">移除空格</TMagicButton>
</div>
<span class="tmagic-form-text-popper-arrow" data-popper-arrow></span>
</div>
</Teleport>
</div>
</template>
<script lang="ts" setup>
import { computed, inject, readonly, ref, shallowRef, watch } from 'vue';
import type { Instance } from '@popperjs/core';
import { createPopper } from '@popperjs/core';
import { debounce } from 'lodash-es';
import { TMagicButton, TMagicInput } from '@tmagic/design';
import { isNumber } from '@tmagic/utils';
import type { ChangeRecord, ContainerChangeEventData, FieldProps, FormState, TextConfig } from '../schema';
import { useAddField } from '../utils/useAddField';
defineOptions({
name: 'MFormText',
});
const props = defineProps<FieldProps<TextConfig>>();
const emit = defineEmits<{
change: [value: string, eventData?: ContainerChangeEventData];
input: [value: string];
}>();
useAddField(props.prop);
const mForm = inject<FormState | undefined>('mForm');
const value = ref('');
watch(
() => props.model[props.name],
(v) => {
value.value = v;
},
{
immediate: true,
},
);
const appendConfig = computed(() => {
const { append } = props.config;
// 字符串形态:纯文本 append。
if (typeof append === 'string') {
return {
type: 'text',
text: append,
handler: undefined,
};
}
// 对象形态:按 schema 定义,`type` 可为 `'button' | 'icon'``handler` 可选。
//
// 旧逻辑里只要带 handler 就强制覆盖为 `type: 'button'`,导致 schema 中显式传入的
// `type: 'icon'` 在到达模板前被抹掉(参见 `@editor/utils/props.ts` 中 ID 字段
// 的复制按钮配置)。这里直接返回原对象,模板按 `type` 分支渲染 button / icon / text。
if (typeof append === 'object' && append) {
// `value === 0` 沿用旧约定:作为「隐藏 append」的开关。
if (append.value === 0) return false;
return append;
}
return false;
});
const popoverVisible = ref(false);
const confirmTrimHandler = () => {
emit('change', props.model[props.name].trim() || '');
popoverVisible.value = false;
};
const checkWhiteSpace = debounce((value: unknown) => {
if (typeof value === 'string' && !props.config.trim) {
popoverVisible.value = value.trim() !== value;
}
}, 300);
const changeHandler = (value: string) => {
emit('change', value);
};
const inputHandler = (v: string) => {
checkWhiteSpace(v);
emit('input', v);
mForm?.$emit('field-input', props.prop, v);
};
const buttonClickHandler = async () => {
if (!appendConfig.value) return;
if (typeof appendConfig.value.handler === 'function') {
const newChangeRecords: ChangeRecord[] = [];
const setModel = (key: string, value: any) => {
newChangeRecords.push({ propPath: props.prop.replace(`${props.name}`, key), value });
};
const setFormValue = (key: string, value: any) => {
newChangeRecords.push({ propPath: key, value });
};
await appendConfig.value.handler(mForm, {
model: props.model,
values: mForm ? readonly(mForm.initValues) : null,
formValue: props.values || {},
setModel,
setFormValue,
});
if (newChangeRecords.length > 0) {
emit('change', props.model[props.name], {
changeRecords: newChangeRecords,
});
}
}
};
const keyUpHandler = ($event: KeyboardEvent) => {
if (!props.model) return;
if (!props.name) return;
const arrowUp = $event.key === 'ArrowUp';
const arrowDown = $event.key === 'ArrowDown';
if (!arrowUp && !arrowDown) {
return;
}
const value = props.model[props.name];
let num;
let unit;
if (isNumber(value)) {
num = +value;
} else {
value.replace(/^([0-9.]+)([a-z%]+)$/, ($0: string, $1: string, $2: string) => {
num = +$1;
unit = $2;
});
}
if (num === undefined) {
return;
}
const ctrl = navigator.platform.match('Mac') ? $event.metaKey : $event.ctrlKey;
const shift = $event.shiftKey;
const alt = $event.altKey;
if (arrowUp) {
if (ctrl) {
num += 100;
} else if (alt) {
num = (num * 10000 + 1000) / 10000;
} else if (shift) {
num = num + 10;
} else {
num += 1;
}
} else if (arrowDown) {
if (ctrl) {
num -= 100;
} else if (alt) {
num = (num * 10000 - 1000) / 10000;
} else if (shift) {
num -= 10;
} else {
num -= 1;
}
}
props.model[props.name] = `${num}${unit || ''}`;
emit('change', props.model[props.name]);
};
const popoverEl = ref<HTMLDivElement>();
const input = ref<InstanceType<typeof TMagicInput>>();
const instanceRef = shallowRef<Instance | undefined>();
watch(popoverEl, (el) => {
destroyPopover();
if (!input.value?.$el || !el) return;
instanceRef.value = createPopper(input.value.$el, el, {
placement: props.config.tooltip ? 'top' : 'bottom',
strategy: 'absolute',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10],
},
},
],
});
});
const destroyPopover = () => {
if (!instanceRef.value) return;
instanceRef.value.destroy();
instanceRef.value = undefined;
};
</script>