cwg-input.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <template>
  2. <view class="form-group" :ref="setItemRef">
  3. <view v-if="label" class="form-label"><text class="required-mark">{{ required ? "*" : "" }}</text> {{ label }}
  4. </view>
  5. <u-form-item :prop="rulesKey">
  6. <template v-if="type === 'text' || type === 'password'">
  7. <up-input v-model="inputValueDoc" class="form-input" :type="type"
  8. :placeholder="placeholder ? placeholder : t('common.input')" :readonly="readonly" :disabled="disabled"
  9. :clearable="clearable" :maxlength="maxlength" :errorMessage="errorMessage" @blur="handleBlur"
  10. @focus="handleFocus" @clear="handleClear">
  11. <template #prefix>
  12. <slot name="left-icon1"></slot>
  13. </template>
  14. <template #suffix>
  15. <slot name="right-icon1"></slot>
  16. </template>
  17. </up-input>
  18. </template>
  19. <template v-if="type === 'number'">
  20. <up-input v-model="inputValueDoc" class="form-input" type="number"
  21. :placeholder="placeholder ? placeholder : t('common.input')" :readonly="readonly" :disabled="disabled"
  22. :clearable="clearable" :maxlength="maxlength" :errorMessage="errorMessage" @blur="handleBlur"
  23. @focus="handleFocus" @clear="handleClear">
  24. <slot></slot>
  25. </up-input>
  26. </template>
  27. <template v-if="type === 'dropdown'">
  28. <view class="input-wrapper" @click.stop="handleInputClick">
  29. <view class="input-bg"></view>
  30. <up-input v-model="inputValueDoc" class="form-input"
  31. :placeholder="placeholder ? placeholder : t('common.choose')" :readonly="true" :disabled="disabled"
  32. :clearable="clearable" :errorMessage="errorMessage" @clear="handleClear">
  33. <template #suffix>
  34. <up-icon name="arrow-down" size="14"></up-icon>
  35. </template>
  36. <slot></slot>
  37. </up-input>
  38. </view>
  39. <up-action-sheet :show="filteredColumns.length && showPicker" :actions="filteredColumns"
  40. @select="onActionSheetConfirm" @close="showPicker = false" />
  41. </template>
  42. <template v-if="type === 'select'">
  43. <view class="input-wrapper" @click.stop="handleInputClick">
  44. <view class="input-bg"></view>
  45. <up-input v-model="inputValueDoc" class="form-input"
  46. :placeholder="placeholder ? placeholder : t('common.choose')" :readonly="true" :disabled="disabled"
  47. :clearable="clearable" :errorMessage="errorMessage" @clear="handleClear">
  48. <template #suffix>
  49. <up-icon name="arrow-down" size="14"></up-icon>
  50. </template>
  51. <slot></slot>
  52. </up-input>
  53. </view>
  54. <!-- :showSearch="false" :options="selectPopup.options" v-model="selectPopup.show" @select="onSelectConfirm" -->
  55. <cwg-more-select v-if="showPicker" :showSearch="showSearch" :input-value="inputValueDoc"
  56. :options="filteredColumns" v-model="showPicker" @select="onConfirm" />
  57. </template>
  58. <template v-if="type === 'date'">
  59. <view class="input-wrapper" @click="handleInputClick">
  60. <view class="input-bg"></view>
  61. <up-input v-model="inputValueDoc" class="form-input"
  62. :placeholder="placeholder ? placeholder : t('common.choose')" :readonly="true" :disabled="disabled"
  63. :clearable="clearable" :errorMessage="errorMessage" @clear="handleClear">
  64. <template #suffix>
  65. <up-icon name="calendar" size="14"></up-icon>
  66. </template>
  67. </up-input>
  68. </view>
  69. <cwg-date-picker v-model:show="showPicker" v-model="inputValueDoc" mode="date" @confirm="onDateConfirm"
  70. :minDate="minDate" :maxDate="maxDate" />
  71. </template>
  72. <template v-if="type === 'upload'">
  73. <view class="form-input uploader">
  74. <up-upload v-if="!isUploadD" :fileList="uploader" :disabled="disabled" :deletable="!disabled" :accept="accept"
  75. :maxCount="1" @afterRead="afterRead" @delete="handleDelete"></up-upload>
  76. <up-upload v-if="isUploadD" :fileList="uploader" :disabled="disabled" :deletable="!disabled" :maxCount="1"
  77. @afterRead="afterRead" @delete="handleDelete">
  78. <slot></slot>
  79. </up-upload>
  80. </view>
  81. </template>
  82. </u-form-item>
  83. </view>
  84. </template>
  85. <script setup>
  86. import { ref, onMounted, watch, computed, defineProps } from "vue";
  87. import dayjs from "dayjs";
  88. import { upload } from "@/utils/request";
  89. import { uploadApi } from "@/api/upload";
  90. import config from "@/config";
  91. import { useI18n } from "vue-i18n";
  92. const { t } = useI18n();
  93. const props = defineProps({
  94. type: {
  95. type: String,
  96. default: "text",
  97. validator: (v) =>
  98. [
  99. "text",
  100. "password",
  101. "number",
  102. "select",
  103. "date",
  104. "dropdown",
  105. "upload",
  106. ].includes(v),
  107. },
  108. label: String,
  109. fkey: String,
  110. rulesKey: String,
  111. accept: String,
  112. showSearch: Boolean,
  113. isUploadD: { type: Boolean, default: false },
  114. value: { type: [String, Number] },
  115. selectedValueDoc: { type: [String, Number] },
  116. placeholder: String,
  117. direction: { type: String, default: "down" },
  118. disabled: Boolean,
  119. readonly: Boolean,
  120. required: Boolean,
  121. max: Number,
  122. clearable: { type: Boolean, default: true },
  123. columns: { type: Array, default: () => [] },
  124. rules: { type: Array, default: () => [] },
  125. maxlength: Number,
  126. errorMessage: String,
  127. minDate: { type: Date, default: () => new Date(1920, 0, 1).getTime() },
  128. maxDate: { type: Date, default: () => new Date().getTime() },
  129. dateFormatter: { type: Function, default: (_type, val) => val },
  130. displayFormatter: {
  131. type: Function,
  132. default: (val) => dayjs(val).format("YYYY-MM-DD"),
  133. },
  134. });
  135. // console.log(props, 'props');
  136. const itemRef = ref(null)
  137. const setItemRef = el => {
  138. if (el) itemRef.value = el
  139. }
  140. const emit = defineEmits([
  141. "update:value",
  142. "blur",
  143. "focus",
  144. "clear",
  145. "confirm",
  146. "change",
  147. "change1",
  148. ]);
  149. const isUploading = ref(false);
  150. const uploader = ref([]);
  151. const inputValueDoc = ref("");
  152. const selectedValueDoc = ref([]);
  153. const showPicker = ref(false);
  154. const onConfirm = (value) => {
  155. let selectedText = value.text || ''
  156. if (props.fkey == 'areaCode') {
  157. selectedText = selectedText
  158. }
  159. inputValueDoc.value = selectedText
  160. showPicker.value = false
  161. emit("update:selectedValueDoc", selectedText);
  162. }
  163. // 处理输入框点击事件
  164. function handleInputClick() {
  165. if (props.disabled || props.readonly) return;
  166. showPicker.value = true;
  167. }
  168. function absoluteUrl(url) {
  169. if (!url) {
  170. return "";
  171. }
  172. if (/^https?:\/\//.test(url)) {
  173. return url;
  174. }
  175. const prefix = config.Host85 || "";
  176. if (!prefix) {
  177. return url;
  178. }
  179. return `${prefix}${url.startsWith("/") ? url : `/${url}`}`;
  180. }
  181. async function afterRead(file) {
  182. isUploading.value = true;
  183. uni.showLoading({
  184. title: "上传中...",
  185. mask: true,
  186. });
  187. try {
  188. // console.log(file.file, file);
  189. const result = await uploadApi.uploadFile(file.file);
  190. // console.log(500, result);
  191. uni.hideLoading();
  192. if (result.code === 200) {
  193. inputValueDoc.value = result.data;
  194. uploader.value = [{ url: absoluteUrl(result.data) }];
  195. uni.showToast({
  196. title: "上传成功",
  197. icon: "success",
  198. });
  199. setTimeout(() => {
  200. isUploading.value = false;
  201. }, 100);
  202. } else {
  203. uni.showToast({
  204. title: result.message || "上传失败",
  205. icon: "none",
  206. });
  207. isUploading.value = false;
  208. inputValueDoc.value = "";
  209. uploader.value = [];
  210. }
  211. } catch (_error) {
  212. uni.hideLoading();
  213. uni.showToast({
  214. title: _error.message || "上传失败",
  215. icon: "none",
  216. });
  217. }
  218. }
  219. async function afterRead1(event) {
  220. // console.log(event, 198);
  221. isUploading.value = true;
  222. uni.showLoading({
  223. title: "上传中...",
  224. mask: true,
  225. });
  226. try {
  227. // uview-plus 的 upload 组件返回的是 { file, fileList }
  228. // file 对象包含 url(临时路径)或 path 属性
  229. const file = event.file || (Array.isArray(event) ? event[0] : event);
  230. const filePath = file.url || file.path || file.tempFilePath;
  231. if (!filePath) {
  232. throw new Error("文件路径不存在");
  233. }
  234. // 使用 uni-app 的 upload 方法,需要传入 filePath
  235. const result = await upload({
  236. url: "/wasabi/api/upload/file",
  237. filePath: filePath,
  238. });
  239. uni.hideLoading();
  240. // console.log(500, result);
  241. if (result.code === 200) {
  242. inputValueDoc.value = result.data;
  243. uploader.value = [{ url: absoluteUrl(result.data) }];
  244. uni.showToast({
  245. title: "上传成功",
  246. icon: "success",
  247. });
  248. setTimeout(() => {
  249. isUploading.value = false;
  250. }, 100);
  251. } else {
  252. uni.showToast({
  253. title: result.message || "上传失败",
  254. icon: "none",
  255. });
  256. isUploading.value = false;
  257. inputValueDoc.value = "";
  258. uploader.value = [];
  259. }
  260. } catch (_error) {
  261. uni.hideLoading();
  262. uni.showToast({
  263. title: _error.message || "上传失败",
  264. icon: "none",
  265. });
  266. isUploading.value = false;
  267. uploader.value = [];
  268. }
  269. }
  270. function handleDelete(event) {
  271. const index = event.index;
  272. uploader.value.splice(index, 1);
  273. inputValueDoc.value = "";
  274. emit("update:value", "");
  275. emit("change", { value: "", key: props.fkey });
  276. }
  277. const filteredColumns = computed(() => {
  278. if (props.type === "dropdown") {
  279. return props.columns.map((item) => {
  280. return { ...item, name: item.text };
  281. });
  282. }
  283. return props.columns;
  284. });
  285. watch(
  286. () => inputValueDoc.value,
  287. (newVal) => {
  288. // if (!newVal) return
  289. if (
  290. props.type === "text" ||
  291. props.type === "number" ||
  292. props.type === "password"
  293. ) {
  294. emit("update:value", newVal);
  295. emit("change", { value: newVal, key: props.fkey });
  296. } else if (props.type === "select") {
  297. const matched = props.columns.find((opt) => opt.text === newVal);
  298. emit("update:value", matched?.value || "");
  299. emit("change", { value: matched?.value || "", key: props.fkey });
  300. } else if (props.type === "date") {
  301. emit("update:value", newVal);
  302. emit("change", { value: newVal, key: props.fkey });
  303. } else if (props.type === "upload") {
  304. emit("update:value", newVal);
  305. emit("change", { value: newVal, key: props.fkey });
  306. } else if (props.type === "dropdown") {
  307. const matched = props.columns.find((opt) => opt.text === newVal);
  308. emit("update:value", matched?.value || "");
  309. emit("change", { value: matched?.value || "", key: props.fkey });
  310. }
  311. }
  312. );
  313. watch(
  314. () => props.value,
  315. (newVal) => {
  316. if (isUploading.value) return;
  317. if (props.type === "date") {
  318. inputValueDoc.value = newVal ? dayjs(newVal).format("YYYY-MM-DD") : "";
  319. } else if (props.type === "select") {
  320. const matched = props.columns.find((opt) => opt.value === newVal);
  321. inputValueDoc.value = matched?.text || "";
  322. selectedValueDoc.value = matched ? [matched.value] : [];
  323. } else if (props.type === "upload") {
  324. uploader.value = props.value
  325. ? [{ url: absoluteUrl(String(props.value)) }]
  326. : [];
  327. // console.log(uploader.value, 198);
  328. inputValueDoc.value = props.value || "";
  329. } else if (props.type === "dropdown") {
  330. const matched = props.columns.find((opt) => opt.value === newVal);
  331. inputValueDoc.value = matched?.text || "";
  332. selectedValueDoc.value = matched ? [matched.value] : [];
  333. } else {
  334. inputValueDoc.value = newVal || "";
  335. }
  336. },
  337. { immediate: true }
  338. );
  339. function handleBlur(event) {
  340. emit("blur", event);
  341. }
  342. function handleFocus(event) {
  343. emit("focus", event);
  344. }
  345. function handleClear() {
  346. inputValueDoc.value = "";
  347. emit("update:value", "");
  348. emit("clear");
  349. }
  350. function onActionSheetConfirm(value) {
  351. const selectedText = value.text || value.name || "";
  352. inputValueDoc.value = selectedText;
  353. showPicker.value = false;
  354. const matched = props.columns.find(
  355. (opt) => opt.text === selectedText || opt.name === selectedText
  356. );
  357. emit("update:value", matched?.value || "");
  358. emit("change", { value: matched?.value || "", key: props.fkey });
  359. }
  360. function onPickerConfirm(event) {
  361. const { value } = event;
  362. const selectedItem =
  363. Array.isArray(value) && value.length > 0 ? value[0] : value;
  364. const matched = props.columns.find(
  365. (opt) => opt.value === selectedItem || opt.value === selectedItem?.value
  366. );
  367. if (matched) {
  368. inputValueDoc.value = matched.text;
  369. emit("update:value", matched.value);
  370. emit("change", { value: matched.value, key: props.fkey });
  371. }
  372. showPicker.value = false;
  373. }
  374. function onDateConfirm(event) {
  375. const { formatted } = event;
  376. inputValueDoc.value = formatted;
  377. showPicker.value = false;
  378. emit("update:value", formatted);
  379. emit("change", { value: formatted, key: props.fkey });
  380. }
  381. </script>
  382. <style scoped lang="scss">
  383. @import "@/uni.scss";
  384. .form-group {
  385. width: 100%;
  386. margin-bottom: px2rpx(12);
  387. transform: none;
  388. }
  389. .form-label {
  390. font-size: var(--font-size-16);
  391. line-height: px2rpx(24);
  392. letter-spacing: px2rpx(1);
  393. color: #474747;
  394. margin: px2rpx(16) 0 px2rpx(8) 0;
  395. display: flex;
  396. align-self: start;
  397. .required-mark {
  398. color: red;
  399. }
  400. }
  401. .form-input {
  402. width: 100%;
  403. border: 1px solid var(--bs-border-color) !important;
  404. border-radius: 10px !important;
  405. font-size: px2rpx(31) !important;
  406. margin-top: px2rpx(4) !important;
  407. padding: px2rpx(10) px2rpx(12) !important;
  408. box-sizing: border-box;
  409. }
  410. .search-field {
  411. background: var(--black);
  412. border: none !important;
  413. padding-left: px2rpx(10) !important;
  414. }
  415. .uploader {
  416. border: none !important;
  417. background: transparent !important;
  418. padding-left: 0;
  419. padding-right: 0;
  420. width: 100%;
  421. }
  422. .input-wrapper {
  423. position: relative;
  424. width: 100%;
  425. .input-bg {
  426. position: absolute;
  427. top: 0;
  428. left: 0;
  429. width: 100%;
  430. height: 100%;
  431. z-index: 100;
  432. }
  433. }
  434. // uview-plus 组件样式覆盖
  435. :deep(.u-input) {
  436. border-radius: 10px !important;
  437. font-size: px2rpx(32) !important;
  438. padding: px2rpx(12);
  439. background: transparent;
  440. color: var(--white);
  441. &::after {
  442. border: none !important;
  443. }
  444. }
  445. :deep(.up-input__content__field-wrapper__field) {
  446. color: var(--white);
  447. line-height: px2rpx(22);
  448. &::placeholder {
  449. color: var(--lable) !important;
  450. }
  451. }
  452. :deep(.up-icon) {
  453. color: var(--black1) !important;
  454. }
  455. :deep(.u-form-item__body__right__message) {
  456. position: relative;
  457. top: px2rpx(4);
  458. }
  459. :deep(.u-upload__wrap) {
  460. width: 100%;
  461. >view {
  462. width: 100% !important;
  463. }
  464. .u-upload__wrap__preview__image {
  465. width: 100% !important;
  466. height: px2rpx(160) !important;
  467. border-radius: px2rpx(4);
  468. }
  469. .u-upload__deletable {
  470. width: px2rpx(24) !important;
  471. height: px2rpx(24) !important;
  472. background-color: rgba(0, 0, 0, 0.6) !important;
  473. }
  474. .u-icon__icon {
  475. width: px2rpx(18) !important;
  476. height: px2rpx(18) !important;
  477. font-size: px2rpx(18) !important;
  478. }
  479. }
  480. </style>