RemitInput.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <template>
  2. <div class="form-group">
  3. <label class="form-label" v-if="label"
  4. ><span>{{ required ? '*' : '' }}</span> {{ label }}
  5. </label>
  6. <template v-if="type === 'text' || type == 'password'">
  7. <van-field
  8. class="form-input"
  9. v-model="inputValueDoc"
  10. :type="type"
  11. :placeholder="placeholder"
  12. :readonly="readonly"
  13. :disabled="disabled"
  14. :clearable="clearable"
  15. :rules="[...rules]"
  16. :maxlength="maxlength"
  17. autocomplete="off"
  18. :error-message="errorMessage"
  19. @blur="handleBlur"
  20. @focus="handleFocus"
  21. @clear="handleClear"
  22. >
  23. <template #left-icon> <slot name="left-icon1"></slot> </template>
  24. <template #right-icon> <slot name="right-icon1"></slot> </template>
  25. </van-field>
  26. </template>
  27. <template v-if="type === 'number'">
  28. <van-field
  29. class="form-input"
  30. v-model="inputValueDoc"
  31. type="number"
  32. :placeholder="placeholder"
  33. :readonly="readonly"
  34. :disabled="disabled"
  35. :clearable="clearable"
  36. autocomplete="off"
  37. :rules="[...rules]"
  38. :maxlength="maxlength"
  39. :error-message="errorMessage"
  40. @blur="handleBlur"
  41. @focus="handleFocus"
  42. @clear="handleClear"
  43. ><slot></slot
  44. ></van-field>
  45. </template>
  46. <template v-if="type === 'select'">
  47. <van-field
  48. class="form-input"
  49. v-model="inputValueDoc"
  50. :placeholder="placeholder"
  51. :readonly="true"
  52. :disabled="disabled"
  53. :clearable="clearable"
  54. :rules="[...rules]"
  55. :error-message="errorMessage"
  56. is-link
  57. @click="!disabled && (showPicker = true)"
  58. @clear="handleClear"
  59. ><slot></slot
  60. ></van-field>
  61. <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
  62. <van-field
  63. v-if="showSearch"
  64. class="form-input search-field"
  65. v-model="searchText"
  66. :clearable="clearable"
  67. autocomplete="off"
  68. :placeholder="t('eur-remit.search')"
  69. clearable
  70. />
  71. <van-picker :columns="filteredColumns" v-model="selectedValue" :loading="loading" @cancel="showPicker = false" @confirm="onConfirm" />
  72. </van-popup>
  73. </template>
  74. <template v-if="type === 'date'">
  75. <van-field
  76. class="form-input"
  77. v-model="inputValueDoc"
  78. :placeholder="placeholder"
  79. :readonly="true"
  80. :disabled="disabled"
  81. :rules="[...rules]"
  82. :clearable="clearable"
  83. :error-message="errorMessage"
  84. is-link
  85. @click="!disabled && (showPicker = true)"
  86. @clear="handleClear"
  87. />
  88. <van-calendar
  89. v-model:show="showPicker"
  90. :show-mark="false"
  91. :title="t('cards.selectDateRange')"
  92. :subtitle="t('cards.selectDate')"
  93. color="var(--main-yellow)"
  94. :min-date="minDate"
  95. :max-date="maxDate"
  96. :show-confirm="false"
  97. @cancel="showPicker = false"
  98. @confirm="onDateConfirm"
  99. />
  100. </template>
  101. <template v-if="type === 'upload'">
  102. <van-field name="uploader" class="form-input uploader" :rules="[...rules]">
  103. <template #input>
  104. <van-uploader v-model="uploader" :max-count="1" :after-read="afterRead" />
  105. </template>
  106. </van-field>
  107. </template>
  108. </div>
  109. </template>
  110. <script setup>
  111. import dayjs from 'dayjs'
  112. import { useI18n } from 'vue-i18n'
  113. import { uploadApi } from '@/api/upload'
  114. import { showLoadingToast, closeToast, showToast } from 'vant'
  115. const { t } = useI18n()
  116. const isUploading = ref(false)
  117. const uploader = ref([])
  118. const afterRead = async (file) => {
  119. isUploading.value = true
  120. showLoadingToast({
  121. message: '上传中...',
  122. forbidClick: true,
  123. })
  124. try {
  125. const result = await uploadApi.uploadFile(file.file)
  126. closeToast()
  127. inputValueDoc.value = result.data
  128. showToast('上传成功')
  129. setTimeout(()=>{isUploading.value = false},100)
  130. } catch (error) {
  131. showToast('上传失败')
  132. }
  133. }
  134. const props = defineProps({
  135. type: { type: String, default: 'text', validator: (v) => ['text', 'password', 'number', 'select', 'date'].includes(v) },
  136. label: String,
  137. fkey: String,
  138. showSearch: { type: Boolean, default: false },
  139. value: { type: [String, Number] },
  140. placeholder: { type: String, default: '请输入' },
  141. disabled: Boolean,
  142. readonly: Boolean,
  143. required: Boolean,
  144. clearable: { type: Boolean, default: true },
  145. columns: { type: Array, default: () => [] },
  146. rules: { type: Array, default: () => [] },
  147. maxlength: Number,
  148. errorMessage: String,
  149. minDate: { type: Date, default: () => new Date(1920, 0, 1) },
  150. maxDate: { type: Date, default: () => new Date(2050, 11, 31) },
  151. dateFormatter: { type: Function, default: (type, val) => val },
  152. displayFormatter: { type: Function, default: (val) => dayjs(val).format('YYYY-MM-DD') },
  153. })
  154. const emit = defineEmits(['update:value', 'blur', 'focus', 'clear', 'confirm', 'change'])
  155. const inputValueDoc = ref('')
  156. const selectedValue = ref([])
  157. const inputValue = ref('')
  158. const showPicker = ref(false)
  159. const loading = ref(false)
  160. const searchText = ref('')
  161. const filteredColumns = computed(() => {
  162. if (!searchText.value) {
  163. return props.columns
  164. }
  165. return props.columns.filter((item) => item.text.toLowerCase().includes(searchText.value.toLowerCase()))
  166. })
  167. watch(
  168. () => inputValueDoc.value,
  169. (newVal) => {
  170. if (!newVal) return
  171. if (props.type === 'text' || props.type === 'number') {
  172. inputValue.value = newVal
  173. emit('update:value', newVal)
  174. emit('change', { value: newVal, key: props.fkey })
  175. } else if (props.type === 'select') {
  176. const matched = props.columns.find((opt) => opt.text === newVal)
  177. inputValue.value = matched?.value || ''
  178. emit('update:value', matched?.value || '')
  179. emit('change', { value: matched?.value || '', key: props.fkey })
  180. } else if (props.type === 'date') {
  181. inputValue.value = newVal
  182. emit('update:value', newVal)
  183. emit('change', { value: newVal, key: props.fkey })
  184. } else if (props.type === 'upload') {
  185. inputValue.value = newVal
  186. emit('update:value', newVal)
  187. emit('change', { value: newVal, key: props.fkey })
  188. }
  189. },
  190. )
  191. watch(
  192. () => props.value,
  193. (newVal) => {
  194. if (!newVal || isUploading.value) return
  195. if (props.type === 'date') {
  196. inputValueDoc.value = newVal ? dayjs(newVal).format('YYYY-MM-DD') : ''
  197. inputValue.value = newVal
  198. } else if (props.type === 'select') {
  199. const matched = props.columns.find((opt) => opt.value === newVal)
  200. inputValueDoc.value = matched?.text || ''
  201. inputValue.value = matched?.value || ''
  202. selectedValue.value = [matched?.value]
  203. } else if (props.type === 'upload') {
  204. uploader.value = [{ url: props.value }]
  205. inputValueDoc.value = props.value
  206. } else {
  207. inputValueDoc.value = newVal
  208. inputValue.value = newVal
  209. }
  210. },
  211. { immediate: true },
  212. )
  213. const handleBlur = (event) => {
  214. emit('blur', event)
  215. }
  216. const handleFocus = (event) => {
  217. emit('focus', event)
  218. }
  219. const handleClear = () => {
  220. inputValueDoc.value = ''
  221. inputValue.value = ''
  222. emit('update:value', '')
  223. emit('clear')
  224. }
  225. const onConfirm = (value) => {
  226. const selectedValue = value.selectedValues?.[0]
  227. const selectedText = value.selectedOptions?.[0]?.text || ''
  228. inputValueDoc.value = selectedText
  229. searchText.value = ''
  230. showPicker.value = false
  231. emit('update:value', selectedValue)
  232. emit('change', selectedValue)
  233. }
  234. const onDateConfirm = (value) => {
  235. const formatted = dayjs(value).format('YYYY-MM-DD')
  236. inputValueDoc.value = formatted
  237. showPicker.value = false
  238. emit('update:value', formatted)
  239. }
  240. </script>
  241. <style scoped lang="scss">
  242. .form-group {
  243. width: 100%;
  244. margin-bottom: 8px;
  245. }
  246. .form-label {
  247. font-family: 'Inter';
  248. font-style: normal;
  249. font-size: var(--font-size-14);
  250. line-height: 22px;
  251. letter-spacing: 1px;
  252. color: var(--lable);
  253. span {
  254. color: red;
  255. }
  256. }
  257. .form-input {
  258. width: 100%;
  259. border: 1px solid var(--border) !important;
  260. border-radius: 10px !important;
  261. font-size: 16px !important;
  262. margin-top: 4px;
  263. ::v-deep .van-field__control {
  264. &::placeholder {
  265. color: var(--lable) !important;
  266. }
  267. }
  268. }
  269. .search-field {
  270. background: #181818 !important;
  271. border: none !important;
  272. padding-left: 20px !important;
  273. }
  274. .uploader {
  275. border: none !important;
  276. background: transparent !important;
  277. padding-left: 0;
  278. }
  279. ::v-deep .van-popup {
  280. background: #181818 !important;
  281. }
  282. ::v-deep .van-field__right-icon {
  283. color: var(--black1) !important;
  284. }
  285. ::v-deep .van-field {
  286. background: transparent;
  287. color: var(--white);
  288. }
  289. ::v-deep .van-field__control {
  290. color: var(--white);
  291. }
  292. ::v-deep .van-uploader__upload {
  293. /* background: #181818; */
  294. }
  295. ::v-deep .van-calendar {
  296. background: var(--action-bg);
  297. }
  298. ::v-deep .van-picker {
  299. background: var(--action-bg);
  300. }
  301. ::v-deep .van-picker__columns {
  302. background: var(--action-bg);
  303. }
  304. ::v-deep .van-calendar__month-mark {
  305. display: none;
  306. }
  307. ::v-deep .van-calendar__header-subtitle {
  308. color: var(--white);
  309. }
  310. ::v-deep .van-calendar__header-title {
  311. color: var(--white);
  312. }
  313. ::v-deep .van-calendar__month-title {
  314. color: var(--main-yellow);
  315. }
  316. ::v-deep {
  317. .van-picker {
  318. background: #181818;
  319. .van-picker__toolbar {
  320. background: #181818;
  321. border-bottom: 1px solid #333;
  322. height: 44px;
  323. padding: 0 16px;
  324. .van-picker__title {
  325. color: #fff;
  326. font-size: var(--font-size-16);
  327. font-weight: 500;
  328. }
  329. .van-picker__cancel,
  330. .van-picker__confirm {
  331. color: var(--main-yellow);
  332. font-size: var(--font-size-14);
  333. padding: 0 8px;
  334. height: 28px;
  335. line-height: 28px;
  336. border-radius: 14px;
  337. transition: all 0.3s ease;
  338. &:active {
  339. opacity: 0.8;
  340. background: rgba(255, 193, 7, 0.1);
  341. }
  342. }
  343. }
  344. .van-picker-column {
  345. color: #fff;
  346. .van-picker-column__item {
  347. color: #fff;
  348. font-size: var(--font-size-14);
  349. padding: 0 16px;
  350. height: 44px;
  351. line-height: 44px;
  352. transition: all 0.3s ease;
  353. &--selected {
  354. color: var(--main-yellow);
  355. font-weight: 500;
  356. font-size: var(--font-size-16);
  357. }
  358. &:active {
  359. background: rgba(255, 255, 255, 0.05);
  360. }
  361. }
  362. .van-picker-column__wrapper {
  363. &::after {
  364. border-color: #333;
  365. }
  366. }
  367. }
  368. .van-picker__mask {
  369. background-image: linear-gradient(180deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4)),
  370. linear-gradient(0deg, rgba(24, 24, 24, 0.9), rgba(24, 24, 24, 0.4));
  371. }
  372. .van-picker__indicator {
  373. height: 44px;
  374. background: rgba(255, 193, 7, 0.05);
  375. border-top: 1px solid rgba(255, 193, 7, 0.1);
  376. border-bottom: 1px solid rgba(255, 193, 7, 0.1);
  377. }
  378. }
  379. }
  380. </style>