cwg-input.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <template>
  2. <view class="form-group">
  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 emit = defineEmits([
  137. "update:value",
  138. "blur",
  139. "focus",
  140. "clear",
  141. "confirm",
  142. "change",
  143. "change1",
  144. ]);
  145. const isUploading = ref(false);
  146. const uploader = ref([]);
  147. const inputValueDoc = ref("");
  148. const selectedValueDoc = ref([]);
  149. const showPicker = ref(false);
  150. const onConfirm = (value) => {
  151. let selectedText = value.text || ''
  152. if (props.fkey == 'areaCode') {
  153. selectedText = selectedText
  154. }
  155. inputValueDoc.value = selectedText
  156. showPicker.value = false
  157. emit("update:selectedValueDoc", selectedText);
  158. }
  159. // 处理输入框点击事件
  160. function handleInputClick() {
  161. if (props.disabled || props.readonly) return;
  162. showPicker.value = true;
  163. }
  164. function absoluteUrl(url) {
  165. if (!url) {
  166. return "";
  167. }
  168. if (/^https?:\/\//.test(url)) {
  169. return url;
  170. }
  171. const prefix = config.Host85 || "";
  172. if (!prefix) {
  173. return url;
  174. }
  175. return `${prefix}${url.startsWith("/") ? url : `/${url}`}`;
  176. }
  177. async function afterRead(file) {
  178. isUploading.value = true;
  179. uni.showLoading({
  180. title: "上传中...",
  181. mask: true,
  182. });
  183. try {
  184. console.log(file.file, file);
  185. const result = await uploadApi.uploadFile(file.file);
  186. console.log(500, result);
  187. uni.hideLoading();
  188. if (result.code === 200) {
  189. inputValueDoc.value = result.data;
  190. uploader.value = [{ url: absoluteUrl(result.data) }];
  191. uni.showToast({
  192. title: "上传成功",
  193. icon: "success",
  194. });
  195. setTimeout(() => {
  196. isUploading.value = false;
  197. }, 100);
  198. } else {
  199. uni.showToast({
  200. title: result.message || "上传失败",
  201. icon: "none",
  202. });
  203. isUploading.value = false;
  204. inputValueDoc.value = "";
  205. uploader.value = [];
  206. }
  207. } catch (_error) {
  208. uni.hideLoading();
  209. uni.showToast({
  210. title: _error.message || "上传失败",
  211. icon: "none",
  212. });
  213. }
  214. }
  215. async function afterRead1(event) {
  216. console.log(event, 198);
  217. isUploading.value = true;
  218. uni.showLoading({
  219. title: "上传中...",
  220. mask: true,
  221. });
  222. try {
  223. // uview-plus 的 upload 组件返回的是 { file, fileList }
  224. // file 对象包含 url(临时路径)或 path 属性
  225. const file = event.file || (Array.isArray(event) ? event[0] : event);
  226. const filePath = file.url || file.path || file.tempFilePath;
  227. if (!filePath) {
  228. throw new Error("文件路径不存在");
  229. }
  230. // 使用 uni-app 的 upload 方法,需要传入 filePath
  231. const result = await upload({
  232. url: "/wasabi/api/upload/file",
  233. filePath: filePath,
  234. });
  235. uni.hideLoading();
  236. console.log(500, result);
  237. if (result.code === 200) {
  238. inputValueDoc.value = result.data;
  239. uploader.value = [{ url: absoluteUrl(result.data) }];
  240. uni.showToast({
  241. title: "上传成功",
  242. icon: "success",
  243. });
  244. setTimeout(() => {
  245. isUploading.value = false;
  246. }, 100);
  247. } else {
  248. uni.showToast({
  249. title: result.message || "上传失败",
  250. icon: "none",
  251. });
  252. isUploading.value = false;
  253. inputValueDoc.value = "";
  254. uploader.value = [];
  255. }
  256. } catch (_error) {
  257. uni.hideLoading();
  258. uni.showToast({
  259. title: _error.message || "上传失败",
  260. icon: "none",
  261. });
  262. isUploading.value = false;
  263. uploader.value = [];
  264. }
  265. }
  266. function handleDelete(event) {
  267. const index = event.index;
  268. uploader.value.splice(index, 1);
  269. inputValueDoc.value = "";
  270. emit("update:value", "");
  271. emit("change", { value: "", key: props.fkey });
  272. }
  273. const filteredColumns = computed(() => {
  274. if (props.type === "dropdown") {
  275. return props.columns.map((item) => {
  276. return { ...item, name: item.text };
  277. });
  278. }
  279. return props.columns;
  280. });
  281. watch(
  282. () => inputValueDoc.value,
  283. (newVal) => {
  284. // if (!newVal) return
  285. if (
  286. props.type === "text" ||
  287. props.type === "number" ||
  288. props.type === "password"
  289. ) {
  290. emit("update:value", newVal);
  291. emit("change", { value: newVal, key: props.fkey });
  292. } else if (props.type === "select") {
  293. const matched = props.columns.find((opt) => opt.text === newVal);
  294. emit("update:value", matched?.value || "");
  295. emit("change", { value: matched?.value || "", key: props.fkey });
  296. } else if (props.type === "date") {
  297. emit("update:value", newVal);
  298. emit("change", { value: newVal, key: props.fkey });
  299. } else if (props.type === "upload") {
  300. emit("update:value", newVal);
  301. emit("change", { value: newVal, key: props.fkey });
  302. } else if (props.type === "dropdown") {
  303. const matched = props.columns.find((opt) => opt.text === newVal);
  304. emit("update:value", matched?.value || "");
  305. emit("change", { value: matched?.value || "", key: props.fkey });
  306. }
  307. }
  308. );
  309. watch(
  310. () => props.value,
  311. (newVal) => {
  312. if (isUploading.value) return;
  313. if (props.type === "date") {
  314. inputValueDoc.value = newVal ? dayjs(newVal).format("YYYY-MM-DD") : "";
  315. } else if (props.type === "select") {
  316. const matched = props.columns.find((opt) => opt.value === newVal);
  317. inputValueDoc.value = matched?.text || "";
  318. selectedValueDoc.value = matched ? [matched.value] : [];
  319. } else if (props.type === "upload") {
  320. uploader.value = props.value
  321. ? [{ url: absoluteUrl(String(props.value)) }]
  322. : [];
  323. console.log(uploader.value, 198);
  324. inputValueDoc.value = props.value || "";
  325. } else if (props.type === "dropdown") {
  326. const matched = props.columns.find((opt) => opt.value === newVal);
  327. inputValueDoc.value = matched?.text || "";
  328. selectedValueDoc.value = matched ? [matched.value] : [];
  329. } else {
  330. inputValueDoc.value = newVal || "";
  331. }
  332. },
  333. { immediate: true }
  334. );
  335. function handleBlur(event) {
  336. emit("blur", event);
  337. }
  338. function handleFocus(event) {
  339. emit("focus", event);
  340. }
  341. function handleClear() {
  342. inputValueDoc.value = "";
  343. emit("update:value", "");
  344. emit("clear");
  345. }
  346. function onActionSheetConfirm(value) {
  347. const selectedText = value.text || value.name || "";
  348. inputValueDoc.value = selectedText;
  349. showPicker.value = false;
  350. const matched = props.columns.find(
  351. (opt) => opt.text === selectedText || opt.name === selectedText
  352. );
  353. emit("update:value", matched?.value || "");
  354. emit("change", { value: matched?.value || "", key: props.fkey });
  355. }
  356. function onPickerConfirm(event) {
  357. const { value } = event;
  358. const selectedItem =
  359. Array.isArray(value) && value.length > 0 ? value[0] : value;
  360. const matched = props.columns.find(
  361. (opt) => opt.value === selectedItem || opt.value === selectedItem?.value
  362. );
  363. if (matched) {
  364. inputValueDoc.value = matched.text;
  365. emit("update:value", matched.value);
  366. emit("change", { value: matched.value, key: props.fkey });
  367. }
  368. showPicker.value = false;
  369. }
  370. function onDateConfirm(event) {
  371. const { formatted } = event;
  372. inputValueDoc.value = formatted;
  373. showPicker.value = false;
  374. emit("update:value", formatted);
  375. emit("change", { value: formatted, key: props.fkey });
  376. }
  377. </script>
  378. <style scoped lang="scss">
  379. @import "@/uni.scss";
  380. .form-group {
  381. width: 100%;
  382. margin-bottom: px2rpx(12);
  383. transform: none;
  384. }
  385. .form-label {
  386. font-size: var(--font-size-16);
  387. line-height: px2rpx(44);
  388. letter-spacing: px2rpx(1);
  389. color: #474747;
  390. margin-bottom: px2rpx(4);
  391. overflow: hidden;
  392. text-overflow: ellipsis;
  393. white-space: nowrap;
  394. display: flex;
  395. align-items: center;
  396. .required-mark {
  397. color: red;
  398. }
  399. }
  400. .form-input {
  401. width: 100%;
  402. border: 1px solid var(--border) !important;
  403. border-radius: 10px !important;
  404. font-size: px2rpx(31) !important;
  405. margin-top: px2rpx(4) !important;
  406. padding: px2rpx(10) px2rpx(12) !important;
  407. box-sizing: border-box;
  408. }
  409. .search-field {
  410. background: var(--black);
  411. border: none !important;
  412. padding-left: px2rpx(10) !important;
  413. }
  414. .uploader {
  415. border: none !important;
  416. background: transparent !important;
  417. padding-left: 0;
  418. padding-right: 0;
  419. width: 100%;
  420. }
  421. .input-wrapper {
  422. position: relative;
  423. width: 100%;
  424. .input-bg {
  425. position: absolute;
  426. top: 0;
  427. left: 0;
  428. width: 100%;
  429. height: 100%;
  430. z-index: 100;
  431. }
  432. }
  433. // uview-plus 组件样式覆盖
  434. :deep(.u-input) {
  435. border-radius: 10px !important;
  436. font-size: px2rpx(31) !important;
  437. padding: px2rpx(12);
  438. background: transparent;
  439. color: var(--white);
  440. &::after {
  441. border: none !important;
  442. }
  443. }
  444. :deep(.up-input__content__field-wrapper__field) {
  445. color: var(--white);
  446. line-height: px2rpx(22);
  447. &::placeholder {
  448. color: var(--lable) !important;
  449. }
  450. }
  451. :deep(.up-icon) {
  452. color: var(--black1) !important;
  453. }
  454. :deep(.u-upload__wrap) {
  455. width: 100%;
  456. >view {
  457. width: 100% !important;
  458. }
  459. .u-upload__wrap__preview__image {
  460. width: 100% !important;
  461. height: px2rpx(160) !important;
  462. border-radius: px2rpx(4);
  463. }
  464. .u-upload__deletable {
  465. width: px2rpx(24) !important;
  466. height: px2rpx(24) !important;
  467. background-color: rgba(0, 0, 0, 0.6) !important;
  468. }
  469. .u-icon__icon {
  470. width: px2rpx(18) !important;
  471. height: px2rpx(18) !important;
  472. font-size: px2rpx(18) !important;
  473. }
  474. }
  475. </style>