DySearchSelect.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <script setup lang="ts">
  2. import type { AxiosRequestConfig } from "axios"
  3. import Rs from "@/core/services/RequestService"
  4. export interface SelectOptionProp {
  5. key: string | number
  6. value: string | number
  7. label?: string | number //若不设置则默认与value相同
  8. disabled?: boolean
  9. }
  10. export interface SelectProp {
  11. value: string | number | Array<string> | Array<number>
  12. name?: string
  13. url?: string
  14. method?: string
  15. configs?: AxiosRequestConfig
  16. formatFunction?: (data: any) => SelectOptionProp
  17. staticOptions?: Array<SelectOptionProp>
  18. data?: Array<any>
  19. placeholder?: string
  20. disabled?: boolean
  21. isSearch?: boolean
  22. autocomplete?: string
  23. size?: "large" | "default" | "small" //输入框尺寸
  24. effect?: "dark " | "light" //Tooltip 主题,内置了 dark / light 两种
  25. clearable?: boolean //是否可以清空选项
  26. clearIcon?: string //是否可以清空选项
  27. multiple?: boolean //是否多选
  28. multipleLimit?: number //为0时不限制
  29. placement?:
  30. | "top"
  31. | "top-start"
  32. | "top-end"
  33. | "bottom"
  34. | "bottom-start"
  35. | "bottom-end"
  36. | "left"
  37. | "left-start"
  38. | "left-end"
  39. | "right"
  40. | "right-start"
  41. | "right-end" //下拉框出现的位置
  42. filterable?: boolean //Select 组件是否可筛选
  43. filterMethod?: (v: string, o: Array<SelectOptionProp>) => any //自定义筛选方法
  44. remote?: boolean //其中的选项是否从服务器远程加载
  45. remoteShowSuffix?: boolean //远程搜索方法显示后缀图标
  46. remoteMethod?: (v: string, o: Array<SelectOptionProp>) => any //远程搜索方法显示后缀图标
  47. formatRemoteData?: (v: any) => any
  48. loading?: boolean //是否正在从远程获取数据
  49. loadingText?: string //从服务器加载内容时显示的文本
  50. noMatchText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
  51. noDataText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
  52. popperClass?: string //选择器下拉菜单的自定义类名
  53. suffixIcon?: string //后缀图标组件
  54. reserveKeyword?: boolean //当 multiple 和 filter被设置为 true 时,是否在选中一个选项后保留当前的搜索关键词
  55. defaultFirstOption?: boolean //是否在输入框按下回车时,选择第一个匹配项。 需配合 filterable 或 remote 使用
  56. popperAppendToBody?: boolean //是否将弹出框插入至 body 元素 当弹出框的位置出现问题时,你可以尝试将该属性设置为false
  57. teleported?: boolean //该下拉菜单是否使用teleport插入body元素
  58. persistent?: boolean //当下拉选择器未被激活并且persistent设置为false,选择器会被删除。
  59. automatiDropdown?: boolean //对于不可过滤的 Select 组件,此属性决定是否在输入框获得焦点后自动弹出选项菜单
  60. fitInputWidth?: boolean //下拉框的宽度是否与输入框相同
  61. validateEvent?: boolean //是否触发表单验证
  62. }
  63. const props = withDefaults(defineProps<SelectProp>(), {
  64. value: "",
  65. url: "",
  66. method: "get",
  67. configs: undefined,
  68. multiple: false,
  69. multipleLimit: 0,
  70. filterable: false,
  71. remote: false,
  72. formatRemoteData: (v: any) => {
  73. return v
  74. },
  75. loading: false,
  76. loadingText: "加载中...",
  77. noMatchText: "没有匹配项",
  78. noDataText: "未查询到数据",
  79. isSearch: false,
  80. disabled: false,
  81. clearable: false,
  82. clearIcon: "CircleClose",
  83. effect: "light",
  84. size: "default",
  85. placement: "bottom-start",
  86. autocomplete: "off",
  87. placeholder: "请选择",
  88. popperClass: "",
  89. suffixIcon: "ArrowDown",
  90. defaultFirstOption: false,
  91. reserveKeyword: true,
  92. popperAppendToBody: true,
  93. teleported: true,
  94. persistent: true,
  95. automatiDropdown: false,
  96. fitInputWidth: false,
  97. validateEvent: false,
  98. })
  99. const emits = defineEmits<{
  100. (e: "update:value", v: string | number | Array<string> | Array<number>): void
  101. (e: "change", v: string | number | Array<string> | Array<number>): void
  102. (e: "visible-change", v: boolean): void //下拉框出现/隐藏时触发 出现则为 true,隐藏则为 false
  103. (e: "remove-tag", v: string | number | Array<string> | Array<number>): void
  104. (e: "clear"): void
  105. (e: "blur", v: FocusEvent): void
  106. (e: "focus", v: FocusEvent): void
  107. }>()
  108. const { value, url } = toRefs(props)
  109. const loading = ref(props.loading)
  110. const remote = ref(props.remote)
  111. const filterable = ref(props.filterable)
  112. const _staticOptions = ref<Array<SelectOptionProp>>()
  113. const options = ref<Array<SelectOptionProp>>(_staticOptions.value ?? [])
  114. let remoteFunction: (v: string) => void
  115. function init() {
  116. _staticOptions.value = Object.assign([], props.staticOptions || [], formatSelectData(props.data) ?? [])
  117. if (props.remoteMethod) {
  118. filterable.value = true
  119. remote.value = true
  120. remoteFunction = (v: string) => {
  121. props.remoteMethod && props.remoteMethod(v, options.value)
  122. loading.value = false
  123. }
  124. } else if (props.url) {
  125. filterable.value = true
  126. remote.value = true
  127. const configs = Object.assign({}, { url: url.value, method: props.method, successAlert: false }, props.configs)
  128. remoteFunction = (query: string) => {
  129. options.value = Object.assign([], _staticOptions.value ?? [])
  130. console.log("======>", options.value, "=", _staticOptions.value)
  131. if (!props.isSearch || query) {
  132. loading.value = true
  133. Rs.request(configs).then((res: any) => {
  134. processData(res.data)
  135. })
  136. // if (props.method.toLowerCase() == "get") {
  137. // Rs.get(configs).then((res: any) => {
  138. // processData(res.data)
  139. // })
  140. // } else if (props.method.toLowerCase() == "post") {
  141. // Rs.get(configs).then((res: any) => {
  142. // processData(res.data)
  143. // })
  144. // }
  145. }
  146. function processData(data: any) {
  147. let result = formatSelectData(data)
  148. if (query) {
  149. result = result.filter((v: SelectOptionProp) => {
  150. return (
  151. (v.label as string).toLowerCase().includes(query.toLowerCase()) ||
  152. (v.value as string).toLowerCase().includes(query.toLowerCase())
  153. )
  154. })
  155. }
  156. options.value.push(...result)
  157. loading.value = false
  158. }
  159. }
  160. }
  161. }
  162. function formatSelectData(v: any) {
  163. const result: Array<SelectOptionProp> = []
  164. const data = props.formatRemoteData && typeof props.formatRemoteData == "function" ? props.formatRemoteData(v) : v
  165. if (data && data.length) {
  166. data.forEach((v: any) => {
  167. let item = {} as SelectOptionProp
  168. if (props.formatFunction) {
  169. item = props.formatFunction(v)
  170. } else {
  171. item = Object.assign({}, v)
  172. if ("value" in v) {
  173. item.key = v.value + ""
  174. item.value = v.value + ""
  175. } else if ("code" in v) {
  176. item.key = v.code + ""
  177. item.value = v.code + ""
  178. }
  179. if ("label" in v) {
  180. item.label = v.label + ""
  181. } else if ("name" in v) {
  182. item.label = v.name + ""
  183. } else if ("title" in v) {
  184. item.label = v.title + ""
  185. }
  186. }
  187. result.push(item)
  188. })
  189. }
  190. return result
  191. }
  192. function onChange(val: string | number | Array<string> | Array<number>) {
  193. emits("update:value", val)
  194. emits("change", val)
  195. }
  196. function onRemoveTag(val: string | number | Array<string> | Array<number>) {
  197. emits("remove-tag", val)
  198. }
  199. function onVisibleChange(val: boolean) {
  200. //下拉框出现/隐藏时触发 出现则为 true,隐藏则为 false
  201. emits("visible-change", val)
  202. }
  203. function onBlur(v: FocusEvent) {
  204. emits("blur", v)
  205. }
  206. function onFocus(v: FocusEvent) {
  207. emits("focus", v)
  208. }
  209. function onClear() {
  210. emits("clear")
  211. }
  212. onMounted(() => {
  213. init()
  214. })
  215. </script>
  216. <template>
  217. <el-select
  218. v-model="value"
  219. :placeholder="placeholder"
  220. :disabled="disabled"
  221. :multiple="multiple"
  222. :multiple-limit="multipleLimit"
  223. :clearable="clearable"
  224. :clear-icon="clearIcon"
  225. :suffix-icon="suffixIcon"
  226. :popper-class="popperClass"
  227. :autocomplete="autocomplete"
  228. :size="size"
  229. :effect="effect"
  230. :placement="placement"
  231. :filterable="filterable"
  232. :remote="remote"
  233. :remote-method="remoteFunction"
  234. :remote-show-suffix="remoteShowSuffix"
  235. :loading="loading"
  236. :loading-text="loadingText"
  237. :no-match-text="noMatchText"
  238. :no-data-text="noDataText"
  239. :default-first-option="defaultFirstOption"
  240. :popper-append-to-body="popperAppendToBody"
  241. :reserve-keyword="reserveKeyword"
  242. :teleported="teleported"
  243. :persistent="persistent"
  244. :automati-dropdown="automatiDropdown"
  245. :fit-input-width="fitInputWidth"
  246. :validate-event="validateEvent"
  247. @change="onChange"
  248. @remove-tag="onRemoveTag"
  249. @visible-change="onVisibleChange"
  250. @blur="onBlur"
  251. @focus="onFocus"
  252. @clear="onClear"
  253. >
  254. <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  255. </el-select>
  256. </template>