| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- <template>
- <div class="form-group">
- <label class="form-label" v-if="label"
- ><span>{{ required ? '*' : '' }}</span> {{ label }}
- </label>
- <template v-if="type === 'text' || type == 'password'">
- <van-field
- class="form-input"
- v-model="inputValueDoc"
- :type="type"
- :placeholder="placeholder"
- :readonly="readonly"
- :disabled="disabled"
- :clearable="clearable"
- :rules="[...rules]"
- :maxlength="maxlength"
- autocomplete="off"
- :error-message="errorMessage"
- @blur="handleBlur"
- @focus="handleFocus"
- @clear="handleClear"
- >
- <template #left-icon> <slot name="left-icon1"></slot> </template>
- <template #right-icon> <slot name="right-icon1"></slot> </template>
- </van-field>
- </template>
- <template v-if="type === 'number'">
- <van-field
- class="form-input"
- v-model="inputValueDoc"
- type="number"
- :placeholder="placeholder"
- :readonly="readonly"
- :disabled="disabled"
- :clearable="clearable"
- autocomplete="off"
- :rules="[...rules]"
- :maxlength="maxlength"
- :error-message="errorMessage"
- @blur="handleBlur"
- @focus="handleFocus"
- @clear="handleClear"
- ><slot></slot
- ></van-field>
- </template>
- <template v-if="type === 'select'">
- <van-field
- class="form-input"
- v-model="inputValueDoc"
- :placeholder="placeholder"
- :readonly="true"
- :disabled="disabled"
- :clearable="clearable"
- :rules="[...rules]"
- :error-message="errorMessage"
- is-link
- @click="!disabled && (showPicker = true)"
- @clear="handleClear"
- ><slot></slot
- ></van-field>
- <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
- <van-field
- v-if="showSearch"
- class="form-input search-field"
- v-model="searchText"
- :clearable="clearable"
- autocomplete="off"
- :placeholder="t('eur-remit.search')"
- clearable
- />
- <van-picker :columns="filteredColumns" v-model="selectedValue" :loading="loading" @cancel="showPicker = false" @confirm="onConfirm" />
- </van-popup>
- </template>
- <template v-if="type === 'date'">
- <van-field
- class="form-input"
- v-model="inputValueDoc"
- :placeholder="placeholder"
- :readonly="true"
- :disabled="disabled"
- :rules="[...rules]"
- :clearable="clearable"
- :error-message="errorMessage"
- is-link
- @click="!disabled && (showPicker = true)"
- @clear="handleClear"
- />
- <van-calendar
- v-model:show="showPicker"
- :show-mark="false"
- :title="t('cards.selectDateRange')"
- :subtitle="t('cards.selectDate')"
- color="var(--main-yellow)"
- :min-date="minDate"
- :max-date="maxDate"
- :show-confirm="false"
- @cancel="showPicker = false"
- @confirm="onDateConfirm"
- />
- </template>
- <template v-if="type === 'upload'">
- <van-field name="uploader" class="form-input uploader" :rules="[...rules]">
- <template #input>
- <van-uploader v-model="uploader" :max-count="1" :after-read="afterRead" />
- </template>
- </van-field>
- </template>
- </div>
- </template>
- <script setup>
- import dayjs from 'dayjs'
- import { useI18n } from 'vue-i18n'
- import { uploadApi } from '@/api/upload'
- import { showLoadingToast, closeToast, showToast } from 'vant'
- const { t } = useI18n()
- const isUploading = ref(false)
- const uploader = ref([])
- const afterRead = async (file) => {
- isUploading.value = true
- showLoadingToast({
- message: '上传中...',
- forbidClick: true,
- })
- try {
- const result = await uploadApi.uploadFile(file.file)
- closeToast()
- inputValueDoc.value = result.data
- showToast('上传成功')
- setTimeout(()=>{isUploading.value = false},100)
- } catch (error) {
- showToast('上传失败')
- }
- }
- const props = defineProps({
- type: { type: String, default: 'text', validator: (v) => ['text', 'password', 'number', 'select', 'date'].includes(v) },
- label: String,
- fkey: String,
- showSearch: { type: Boolean, default: false },
- value: { type: [String, Number] },
- placeholder: { type: String, default: '请输入' },
- disabled: Boolean,
- readonly: Boolean,
- required: Boolean,
- clearable: { type: Boolean, default: true },
- columns: { type: Array, default: () => [] },
- rules: { type: Array, default: () => [] },
- maxlength: Number,
- errorMessage: String,
- minDate: { type: Date, default: () => new Date(1920, 0, 1) },
- maxDate: { type: Date, default: () => new Date(2050, 11, 31) },
- dateFormatter: { type: Function, default: (type, val) => val },
- displayFormatter: { type: Function, default: (val) => dayjs(val).format('YYYY-MM-DD') },
- })
- const emit = defineEmits(['update:value', 'blur', 'focus', 'clear', 'confirm', 'change'])
- const inputValueDoc = ref('')
- const selectedValue = ref([])
- const inputValue = ref('')
- const showPicker = ref(false)
- const loading = ref(false)
- const searchText = ref('')
- const filteredColumns = computed(() => {
- if (!searchText.value) {
- return props.columns
- }
- return props.columns.filter((item) => item.text.toLowerCase().includes(searchText.value.toLowerCase()))
- })
- watch(
- () => inputValueDoc.value,
- (newVal) => {
- if (!newVal) return
- if (props.type === 'text' || props.type === 'number') {
- inputValue.value = newVal
- emit('update:value', newVal)
- emit('change', { value: newVal, key: props.fkey })
- } else if (props.type === 'select') {
- const matched = props.columns.find((opt) => opt.text === newVal)
- inputValue.value = matched?.value || ''
- emit('update:value', matched?.value || '')
- emit('change', { value: matched?.value || '', key: props.fkey })
- } else if (props.type === 'date') {
- inputValue.value = newVal
- emit('update:value', newVal)
- emit('change', { value: newVal, key: props.fkey })
- } else if (props.type === 'upload') {
- inputValue.value = newVal
- emit('update:value', newVal)
- emit('change', { value: newVal, key: props.fkey })
- }
- },
- )
- watch(
- () => props.value,
- (newVal) => {
- if (!newVal || isUploading.value) return
- if (props.type === 'date') {
- inputValueDoc.value = newVal ? dayjs(newVal).format('YYYY-MM-DD') : ''
- inputValue.value = newVal
- } else if (props.type === 'select') {
- const matched = props.columns.find((opt) => opt.value === newVal)
- inputValueDoc.value = matched?.text || ''
- inputValue.value = matched?.value || ''
- selectedValue.value = [matched?.value]
- } else if (props.type === 'upload') {
- uploader.value = [{ url: props.value }]
- inputValueDoc.value = props.value
- } else {
- inputValueDoc.value = newVal
- inputValue.value = newVal
- }
- },
- { immediate: true },
- )
- const handleBlur = (event) => {
- emit('blur', event)
- }
- const handleFocus = (event) => {
- emit('focus', event)
- }
- const handleClear = () => {
- inputValueDoc.value = ''
- inputValue.value = ''
- emit('update:value', '')
- emit('clear')
- }
- const onConfirm = (value) => {
- const selectedValue = value.selectedValues?.[0]
- const selectedText = value.selectedOptions?.[0]?.text || ''
- inputValueDoc.value = selectedText
- searchText.value = ''
- showPicker.value = false
- emit('update:value', selectedValue)
- emit('change', selectedValue)
- }
- const onDateConfirm = (value) => {
- const formatted = dayjs(value).format('YYYY-MM-DD')
- inputValueDoc.value = formatted
- showPicker.value = false
- emit('update:value', formatted)
- }
- </script>
- <style scoped lang="scss">
- .form-group {
- width: 100%;
- margin-bottom: 8px;
- }
- .form-label {
- font-family: 'Inter';
- font-style: normal;
- font-size: var(--font-size-14);
- line-height: 22px;
- letter-spacing: 1px;
- color: var(--lable);
- span {
- color: red;
- }
- }
- .form-input {
- width: 100%;
- border: 1px solid var(--border) !important;
- border-radius: 10px !important;
- font-size: 16px !important;
- margin-top: 4px;
- ::v-deep .van-field__control {
- &::placeholder {
- color: var(--lable) !important;
- }
- }
- }
- .search-field {
- background: #181818 !important;
- border: none !important;
- padding-left: 20px !important;
- }
- .uploader {
- border: none !important;
- background: transparent !important;
- padding-left: 0;
- }
- ::v-deep .van-popup {
- background: #181818 !important;
- }
- ::v-deep .van-field__right-icon {
- color: var(--black1) !important;
- }
- ::v-deep .van-field {
- background: transparent;
- color: var(--white);
- }
- ::v-deep .van-field__control {
- color: var(--white);
- }
- ::v-deep .van-uploader__upload {
- /* background: #181818; */
- }
- ::v-deep .van-calendar {
- background: var(--action-bg);
- }
- ::v-deep .van-picker {
- background: var(--action-bg);
- }
- ::v-deep .van-picker__columns {
- background: var(--action-bg);
- }
- ::v-deep .van-calendar__month-mark {
- display: none;
- }
- ::v-deep .van-calendar__header-subtitle {
- color: var(--white);
- }
- ::v-deep .van-calendar__header-title {
- color: var(--white);
- }
- ::v-deep .van-calendar__month-title {
- color: var(--main-yellow);
- }
- ::v-deep {
- .van-picker {
- background: #181818;
- .van-picker__toolbar {
- background: #181818;
- border-bottom: 1px solid #333;
- height: 44px;
- padding: 0 16px;
- .van-picker__title {
- color: #fff;
- font-size: var(--font-size-16);
- font-weight: 500;
- }
- .van-picker__cancel,
- .van-picker__confirm {
- color: var(--main-yellow);
- font-size: var(--font-size-14);
- padding: 0 8px;
- height: 28px;
- line-height: 28px;
- border-radius: 14px;
- transition: all 0.3s ease;
- &:active {
- opacity: 0.8;
- background: rgba(255, 193, 7, 0.1);
- }
- }
- }
- .van-picker-column {
- color: #fff;
- .van-picker-column__item {
- color: #fff;
- font-size: var(--font-size-14);
- padding: 0 16px;
- height: 44px;
- line-height: 44px;
- transition: all 0.3s ease;
- &--selected {
- color: var(--main-yellow);
- font-weight: 500;
- font-size: var(--font-size-16);
- }
- &:active {
- background: rgba(255, 255, 255, 0.05);
- }
- }
- .van-picker-column__wrapper {
- &::after {
- border-color: #333;
- }
- }
- }
- .van-picker__mask {
- background-image: linear-gradient(180deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4)),
- linear-gradient(0deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4));
- }
- .van-picker__indicator {
- height: 44px;
- background: rgba(255, 193, 7, 0.05);
- border-top: 1px solid rgba(255, 193, 7, 0.1);
- border-bottom: 1px solid rgba(255, 193, 7, 0.1);
- }
- }
- }
- </style>
|