cwg-input.vue 14 KB

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