| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- <script setup lang="ts">
- import { h } from 'vue';
- import { NumberOutlined } from '@ant-design/icons-vue';
- import { Button, Flex } from 'ant-design-vue';
- import { Sender } from 'ant-design-x-vue';
- import { useMessagesContext } from '@/modules/chat/composables';
- import { useGuideStore } from '@/stores';
- import type { GuideUser } from '@/stores/guide.store.ts';
- import type { MessageRendererEmits, MessageRendererProps } from '@/modules/chat/renderer/index.ts';
- import type { BasicInfoPickerKey, BasicInfoPickerModel } from '@/modules/chat/types';
- import type { BasicInfoPickerValue } from '@/modules/chat/config';
- import { basicInfoPickerGroup } from '@/modules/chat/config';
- const STRING_SEPARATOR = ', ';
- defineOptions({ inheritAttrs: false });
- interface Props extends MessageRendererProps {
- group?: Array<BasicInfoPickerKey | BasicInfoPickerValue>;
- }
- type Emits = MessageRendererEmits;
- const { group = [], complete } = defineProps<Props>();
- const emits = defineEmits<Emits>();
- const open = defineModel('open', { default: false });
- const senderInstance = useTemplateRef<AntXSenderInstance>('sender-ref');
- const pickers = shallowRef<BasicInfoPickerValue[]>([]);
- const pickerIndex = ref(0);
- const picker = computed(() => pickers.value.at(pickerIndex.value));
- watchEffect(() => {
- for (let item of group) {
- if (typeof item === 'string') item = Object.assign({ key: item }, basicInfoPickerGroup[item]);
- if (item) pickers.value.push(item);
- }
- triggerRef(pickers);
- pickerIndex.value = 0;
- });
- const pending = ref(false);
- watchEffect(() => {
- pending.value = !complete;
- if (complete) {
- onTrigger(true);
- nextTick(() => senderInstance.value?.focus({ cursor: 'end' }));
- }
- });
- const guide = useGuideStore();
- const { append } = useMessagesContext();
- const { loading, model } = toRefs(reactive({ loading: false, model: {} as BasicInfoPickerModel }));
- function onTrigger(key?: BasicInfoPickerKey | boolean) {
- if (key == null) open.value = !open.value;
- else if (typeof key === 'boolean') open.value = key;
- else {
- open.value = true;
- pickerIndex.value = pickers.value.findIndex((picker) => picker.key === key);
- }
- }
- const getDisplayValue = (values: string[] | string | undefined, columns: VantPickerColumn): string => {
- if (!Array.isArray(values)) values = values ? values.split(STRING_SEPARATOR) : [];
- return values
- .map((value, index) => {
- const column = Array.isArray(columns[index]) ? columns[index] : columns;
- return column.find((col) => col.value === value)?.text;
- })
- .join(STRING_SEPARATOR);
- };
- const displayValue = ref();
- const pickerInstance = useTemplateRef<VantPickerInstance>('picker-ref');
- const pickerProps = computed(() => {
- const { key, title, confirmButtonText = '确定', columns = [], defaultValue, clickOnConfirm = false, onConfirm } = picker.value ?? {};
- const value = model.value[key!] ?? defaultValue;
- displayValue.value = getDisplayValue(value, columns);
- return {
- title: typeof title === 'function' ? title(model.value) : title,
- columns,
- confirmButtonText,
- showToolbar: false,
- loading: pending.value,
- modelValue: value ? value.split(STRING_SEPARATOR) : [],
- 'onUpdate:modelValue'(value) {
- displayValue.value = getDisplayValue(value, columns);
- },
- onClickOption(event) {
- if (clickOnConfirm) setTimeout(() => unref(pickerProps).onConfirm(event), 0);
- },
- async onConfirm(event) {
- model.value[key!] = event.selectedValues.join(STRING_SEPARATOR);
- if (await onConfirm?.(model)) {
- open.value = false;
- senderProps.value.onSubmit();
- } else {
- const index = pickerIndex.value + 1;
- if (index === pickers.value.length) {
- open.value = false;
- senderProps.value.onSubmit();
- } else pickerIndex.value = index;
- }
- },
- } satisfies VantPickProps;
- });
- const senderProps = computed(() => {
- return {
- readOnly: true,
- value: open.value ? displayValue.value : ' ',
- loading: loading.value,
- disabled: pending.value,
- onSubmit() {
- if (open.value) return pickerInstance.value?.confirm();
- const key = pickers.value.find((picker) => model.value[picker.key] == null)?.key;
- if (key) onTrigger(key);
- else {
- pickerIndex.value = pickers.value.length - 1;
- open.value = false;
- onSubmit(pickers.value.map((picker) => getDisplayValue(model.value[picker.key], picker.columns!)).join(STRING_SEPARATOR));
- }
- },
- } satisfies AntXSenderProps;
- });
- const senderHeaderProps = computed(() => {
- const { title, confirmButtonText } = pickerProps.value;
- return {
- open: open.value,
- title: title
- ? h(Flex, { justify: 'space-between' }, () => [
- h('span', null, title),
- !!confirmButtonText &&
- h(
- Button,
- {
- type: 'text',
- size: 'small',
- style: { color: '#1677ff' },
- onClick() {
- pickerInstance.value?.confirm();
- },
- },
- () => confirmButtonText,
- ),
- ])
- : void 0,
- };
- });
- const prefixProps = computed(() => {
- return {
- type: 'text',
- icon: h(NumberOutlined),
- disabled: pending.value,
- } satisfies AntButtonProps;
- });
- async function onSubmit(content: string | VNode) {
- guide.updateUser(<GuideUser>model.value);
- append({ role: 'user', content });
- emits('next');
- }
- </script>
- <template>
- <Sender ref="sender-ref" v-bind="senderProps">
- <template #header>
- <Sender.Header v-bind="senderHeaderProps">
- <van-picker ref="picker-ref" v-bind="pickerProps" :key="picker?.key" />
- </Sender.Header>
- </template>
- <template #prefix>
- <a-flex>
- <template v-for="(picker, i) in pickers" :key="picker.key">
- <a-button v-if="i <= pickerIndex" :class="{ active: open && pickerIndex === i }" v-bind="prefixProps" @click="onTrigger(picker.key)">
- {{ open && i === pickerIndex ? '' : getDisplayValue(model[picker.key], picker.columns!) }}
- </a-button>
- </template>
- </a-flex>
- </template>
- </Sender>
- </template>
- <style scoped lang="scss">
- :deep(.ant-sender-prefix) {
- .ant-btn-text {
- &.active {
- color: #1677ff;
- }
- }
- }
- :deep(.ant-sender-header-content) {
- --van-picker-background: transparent;
- --van-picker-mask-color: transparent;
- --van-picker-group-background: transparent;
- --van-picker-loading-mask-color: transparent;
- .van-picker-column__item {
- &--selected {
- color: #1677ff;
- }
- }
- }
- </style>
|