IdAsValueDropdown.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <template>
  2. <a-select
  3. :value="valueV"
  4. :mode="multiple ? 'multiple' : 'combobox'"
  5. :allowClear="allowClear"
  6. :showSearch="showSearch"
  7. :disabled="disabled"
  8. :placeholder="placeholder"
  9. :default-active-first-option="false"
  10. :notFoundContent="notFoundContent"
  11. :options="data"
  12. :filterOption="showSearch && filterDirectly ? filterOption : false"
  13. @update:value="handleChange"
  14. @search="handleSearch"
  15. v-bind="customProps"
  16. style="min-width: 150px"
  17. >
  18. <template v-if="renderOption" #option="data">
  19. <VNodeRenderer :render="renderOption" :data="data" />
  20. </template>
  21. <a-select-option v-if="showNull" :value="null">(空)</a-select-option>
  22. </a-select>
  23. </template>
  24. <script lang="ts">
  25. import VNodeRenderer from "@/components/VNodeRenderer.vue";
  26. import { type SelectProps } from "ant-design-vue";
  27. import { defineComponent, markRaw, type PropType, type VNode } from "vue";
  28. import { debounce } from 'lodash-es';
  29. import type { DropdownValues, LoadDataFun } from "./IdAsValueDropdown";
  30. import type { DataModel } from "@imengyu/js-request-transform";
  31. import CommonUtils from "@/common/utils/CommonUtils";
  32. /**
  33. * IdAsValueDropdown 的公共接口
  34. */
  35. export interface IdAsValueDropdownInterface {
  36. /**
  37. * 获取某个ID的Lablel
  38. * @param value 要获取的ID
  39. */
  40. getLableByValue(value: number): string;
  41. /**
  42. * 重新加载数据
  43. * @param clearValue 是否需要清除选中数据,默认否
  44. */
  45. reload(clearValue?: boolean): void;
  46. }
  47. /**
  48. * IdAsValueDropdown 的公共接口
  49. */
  50. export interface IdAsValueDropdownProps<T extends DataModel> {
  51. /**
  52. * 允许清除
  53. */
  54. allowClear?: boolean,
  55. /**
  56. * 显示空?
  57. */
  58. showNull?: boolean,
  59. /**
  60. * 禁用
  61. */
  62. disabled?: boolean,
  63. /**
  64. * 多选?
  65. */
  66. multiple?: boolean,
  67. /**
  68. * 允许搜索
  69. */
  70. showSearch?: boolean,
  71. placeholder?: string,
  72. /**
  73. * 未找到数据时的文案
  74. */
  75. notFoundContent?: string,
  76. /**
  77. * 初始化时加载数据
  78. */
  79. loadAtStart?: boolean,
  80. /**
  81. * 不使用后端筛选数据而是前端直接筛选
  82. */
  83. filterDirectly?: boolean,
  84. /**
  85. * 初始化时的搜索数据
  86. */
  87. intitialSearchValue?: Record<string, unknown>,
  88. /**
  89. * 加载数据回调
  90. */
  91. loadData: LoadDataFun<T>,
  92. /**
  93. * a-select 其他自定义参数
  94. */
  95. customProps?: SelectProps,
  96. /**
  97. * 是否自定义渲染option插槽
  98. */
  99. renderOption?: RenderOption<T>;
  100. }
  101. type RenderOption<T> = (data: {
  102. value: unknown,
  103. label: string,
  104. raw: T
  105. }) => VNode;
  106. /**
  107. * 使用数据的ID作为value的下拉框包装
  108. */
  109. export default defineComponent({
  110. name: "IdAsValueDropdown",
  111. data() {
  112. return {
  113. valueV: null,
  114. data: [] as DropdownValues<DataModel>[],
  115. lastLoadValue: null,
  116. handleSearch: markRaw(debounce((val: string) => {
  117. if (!this.filterDirectly)
  118. this.doLoadData(val);
  119. }, 500)),
  120. };
  121. },
  122. emits: [
  123. "update:value",
  124. "change",
  125. "loaded",
  126. ],
  127. props: {
  128. showNull: {
  129. default: false,
  130. type: Boolean
  131. },
  132. renderOption: {
  133. default: null,
  134. type: Function as PropType<RenderOption<DataModel>>
  135. },
  136. allowClear: {
  137. default: false,
  138. type: Boolean
  139. },
  140. multiple: {
  141. default: false,
  142. type: Boolean
  143. },
  144. disabled: {
  145. default: false,
  146. type: Boolean
  147. },
  148. showSearch: {
  149. default: true,
  150. type: Boolean
  151. },
  152. placeholder: {
  153. default: "输入可进行搜索",
  154. type: String
  155. },
  156. notFoundContent: {
  157. default: "未找到数据,请换个搜索词再试",
  158. type: String
  159. },
  160. loadAtStart: {
  161. default: true,
  162. type: Boolean
  163. },
  164. filterDirectly: {
  165. default: true,
  166. type: Boolean
  167. },
  168. value: {
  169. default: null,
  170. },
  171. intitialSearchValue: {
  172. default: null,
  173. type: String
  174. },
  175. loadData: {
  176. type: Function as PropType<LoadDataFun<DataModel>>,
  177. default: null,
  178. },
  179. /**
  180. * a-select 其他自定义参数
  181. */
  182. customProps: {
  183. type: Object as PropType<SelectProps>,
  184. default: null,
  185. },
  186. },
  187. methods: {
  188. handleChange(value: unknown) {
  189. this.$emit("change", value);
  190. this.$emit("update:value", value);
  191. },
  192. doLoadData(val: string | null) {
  193. if (typeof this.loadData === "function") {
  194. const oldValue = this.valueV;
  195. this.valueV = null;
  196. (this.loadData as LoadDataFun<DataModel>)(val).then((d) => {
  197. this.data = d;
  198. setTimeout(() => {
  199. this.valueV = oldValue;
  200. this.$emit("loaded");
  201. }, 30);
  202. });
  203. }
  204. },
  205. filterOption(input: string, option: {
  206. label: string;
  207. }) {
  208. return !this.filterDirectly || option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
  209. },
  210. getLableByValue(value: number) {
  211. for (let i = 0; i < this.data.length; i++) {
  212. if (this.data[i].value == value) {
  213. return this.data[i].label;
  214. }
  215. }
  216. return "";
  217. },
  218. reload(clearValue = false) {
  219. if (clearValue) {
  220. this.valueV = null;
  221. this.handleChange(null);
  222. }
  223. this.data = [];
  224. this.doLoadData(this.intitialSearchValue as string);
  225. },
  226. },
  227. watch: {
  228. loadData() {
  229. this.doLoadData(this.intitialSearchValue);
  230. },
  231. intitialSearchValue(v) {
  232. if (!this.filterDirectly && !CommonUtils.isNullOrEmpty(v)) {
  233. this.doLoadData(v);
  234. }
  235. },
  236. value(v) {
  237. this.valueV = v;
  238. },
  239. },
  240. mounted() {
  241. this.valueV = this.value;
  242. setTimeout(() => {
  243. if (this.loadAtStart) {
  244. this.doLoadData(this.intitialSearchValue as string);
  245. }
  246. }, 300);
  247. },
  248. components: { VNodeRenderer }
  249. });
  250. </script>