IdAsValueTreeDropdown.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <template>
  2. <div v-if="showDisplayValue" class="display-value" @click="handleDisplayValueClick">
  3. <span>{{displayValue}}</span>
  4. </div>
  5. <a-tree-select
  6. v-else
  7. ref="selectRef"
  8. style="min-width: 150px"
  9. :defaultOpen="true"
  10. :value="valueV"
  11. :dropdown-style="dropdownStyle"
  12. :notFoundContent="notFoundContent"
  13. :tree-data="treeData"
  14. :load-data="handleLoadData"
  15. :treeDataSimpleMode="true"
  16. :placeholder="placeholder"
  17. :allow-clear="allowClear"
  18. :multiple="multiple"
  19. v-bind="customProps"
  20. @blur="handleSelectBlur"
  21. @update:value="handleChange"
  22. />
  23. </template>
  24. <script lang="ts">
  25. import { TreeDataItem } from "@/models/ui/TreeCommon";
  26. import { SelectProps } from "ant-design-vue";
  27. import { defineComponent, PropType } from "vue";
  28. export type LoadDataFun = (pid: string|number, level: number) => Promise<TreeDataItem[]>;
  29. export type CheckClickableFun = (item: TreeDataItem) => Promise<boolean>;
  30. export type GetDiaplayValue = (ref: IdAsValueTreeDropdownInterface) => string;
  31. export type GetRef = (ref: IdAsValueTreeDropdownInterface) => void;
  32. /**
  33. * IdAsValueTreeDropdown 的公共接口
  34. */
  35. export interface IdAsValueTreeDropdownInterface {
  36. /**
  37. * 获取某个ID的树(正排列)
  38. * @param value 要获取的ID
  39. */
  40. getTree(value: number) : Array<TreeDataItem>;
  41. /**
  42. * 获取某个ID的Lablel
  43. * @param value 要获取的ID
  44. */
  45. getLableByValue(value: number) : string;
  46. /**
  47. * 重新加载数据
  48. */
  49. reload(): void;
  50. }
  51. /**
  52. * IdAsValueTreeDropdown 的公共接口
  53. */
  54. export interface IdAsValueTreeDropdownProps {
  55. /**
  56. * 允许清除
  57. */
  58. allowClear?: boolean,
  59. /**
  60. * 多选?
  61. */
  62. multiple?: boolean,
  63. dropdownStyle?: Record<string, unknown>,
  64. disabled?: boolean,
  65. placeholder?: string,
  66. /**
  67. * 未找到数据时的文案
  68. */
  69. notFoundContent?: string,
  70. /**
  71. * 初始化时加载数据
  72. */
  73. loadAtStart?: boolean,
  74. /**
  75. * 加载数据
  76. */
  77. loadData?: LoadDataFun,
  78. /**
  79. * 自定义检查条目是否可点击回调
  80. */
  81. checkClickable?: CheckClickableFun,
  82. /**
  83. * 获取显示数据回调
  84. */
  85. getDisplayValue?: GetDiaplayValue,
  86. /**
  87. * 是否在非激活时显示临时字符串(防止树形数据没有加载,而无法显示当前值)
  88. */
  89. showDisplayValueBeforeEdit?: boolean,
  90. /**
  91. * 子数据最大层级
  92. */
  93. maxLevel?: number,
  94. /**
  95. * 是否只有最后一级可以点击
  96. */
  97. onlyLastLevelClickable?: boolean,
  98. /**
  99. * a-select 其他自定义参数
  100. */
  101. customProps?: SelectProps,
  102. }
  103. /**
  104. * 使用数据的ID作为value的下拉框包装
  105. */
  106. export default defineComponent({
  107. name: "IdAsValueTreeDropdown",
  108. emits: [
  109. 'update:value',
  110. 'change',
  111. 'blur',
  112. ],
  113. props: {
  114. allowClear: {
  115. default: true,
  116. type: Boolean
  117. },
  118. multiple: {
  119. default: false,
  120. type: Boolean
  121. },
  122. dropdownStyle: {
  123. type: Object,
  124. default: () => { return { maxHeight: '400px', overflow: 'auto' } }
  125. },
  126. disabled: {
  127. default: false,
  128. type: Boolean
  129. },
  130. placeholder: {
  131. default: '请选择,输入可进行搜索',
  132. type: String
  133. },
  134. notFoundContent: {
  135. default: '未找到数据,请换个搜索词再试',
  136. type: String
  137. },
  138. loadAtStart: {
  139. default: true,
  140. type: Boolean
  141. },
  142. value: {
  143. default: null,
  144. },
  145. loadData: {
  146. type: Function as PropType<LoadDataFun>,
  147. default: null,
  148. },
  149. checkClickable: {
  150. type: Function as PropType<CheckClickableFun>,
  151. default: null,
  152. },
  153. getDisplayValue: {
  154. type: Function as PropType<GetDiaplayValue>,
  155. default: null,
  156. },
  157. defaultDisplayValue: {
  158. type: String,
  159. default: '',
  160. },
  161. showDisplayValueBeforeEdit: {
  162. default: false,
  163. type: Boolean
  164. },
  165. maxLevel: {
  166. default: 0,
  167. type: Number,
  168. },
  169. onlyLastLevelClickable: {
  170. default: false,
  171. type: Boolean
  172. },
  173. /**
  174. * a-select 其他自定义参数
  175. */
  176. customProps: {
  177. type: Object as PropType<SelectProps>,
  178. default: null,
  179. },
  180. },
  181. computed: {
  182. displayValue() : string {
  183. if (this.valueV != null && this.valueV != 0 && this.defaultDisplayValue != '')
  184. return this.defaultDisplayValue;
  185. if (this.getDisplayValue)
  186. return (this.getDisplayValue as GetDiaplayValue)(this as IdAsValueTreeDropdownInterface);
  187. return '';
  188. },
  189. },
  190. methods: {
  191. handleChange(value: unknown) {
  192. this.$nextTick(() => {
  193. if(value != this.value) {
  194. this.$emit('update:value', value);
  195. this.$emit('change', value);
  196. }
  197. })
  198. },
  199. handleLoadData(treeNode: { dataRef: TreeDataItem }) {
  200. return new Promise((resolve: (value?: unknown) => void) => {
  201. const { id, level } = treeNode.dataRef;
  202. this.doLoadData(id, level as number).then(() => resolve()).catch(() => resolve());
  203. });
  204. },
  205. handleDisplayValueClick() {
  206. this.showDisplayValue = false;
  207. setTimeout(() => {
  208. (this.$refs.selectRef as {
  209. focus: () => void
  210. }).focus();
  211. }, 200);
  212. },
  213. handleSelectBlur() {
  214. if(this.showDisplayValueBeforeEdit) {
  215. if(this.valueV != null && this.valueV != 0 && this.defaultDisplayValue != '')
  216. this.showDisplayValue = true;
  217. else if (this.getLableByValue(this.valueV as number) === '') //只有没有在列表中搜索到数据时,才显示临时数据
  218. this.showDisplayValue = true;
  219. }
  220. },
  221. doLoadData(pid: string|number|null, level: number) {
  222. const loadData = this.loadData;
  223. if(typeof loadData === 'function') {
  224. return (loadData as LoadDataFun)(pid as string, level).then((d) => {
  225. for(let i = this.treeData.length - 1; i >= 0; i--)
  226. if(this.treeData[i].pId == pid)
  227. this.treeData.splice(i, 1);
  228. d.forEach(h => {
  229. h.level = level + 1;
  230. if(this.maxLevel > 0 && h.level >= this.maxLevel)
  231. h.isLeaf = true;
  232. if(typeof this.checkClickable === 'function')
  233. this.checkClickable(h).then((v: boolean) => h.selectable = v);
  234. else if(this.maxLevel > 0) {
  235. if(h.level >= this.maxLevel)
  236. h.selectable = false;
  237. if(this.onlyLastLevelClickable)
  238. h.selectable = (h.level == this.maxLevel);
  239. }
  240. this.treeData.push(h)
  241. });
  242. });
  243. } else
  244. return Promise.resolve();
  245. },
  246. /**
  247. * 获取某个ID的树(正排列)
  248. */
  249. getTree(value: number) {
  250. const result = new Array<TreeDataItem>();
  251. let child : TreeDataItem|null = this.treeData.find((v) => v.id == value) as TreeDataItem;
  252. while(child) {
  253. result.unshift(child);
  254. if(child.pId == 0) child = null;
  255. else child = this.treeData.find((v) => v.id == (child as TreeDataItem).pId) as TreeDataItem;
  256. }
  257. return result;
  258. },
  259. /**
  260. * 获取某个ID的Lablel
  261. */
  262. getLableByValue(value: number) {
  263. const data = this.treeData;
  264. for (let i = 0; i < data.length; i++) {
  265. if(data[i].value == value) {
  266. return data[i].title;
  267. }
  268. }
  269. return '';
  270. },
  271. /**
  272. * 重新加载数据
  273. */
  274. reload() {
  275. this.treeData = [];
  276. this.doLoadData(0, 0)
  277. },
  278. },
  279. watch: {
  280. value(v) {
  281. this.valueV = v;
  282. },
  283. showDisplayValueBeforeEdit(v, old) {
  284. if(!old && v) {
  285. this.showDisplayValue = true;
  286. }
  287. },
  288. },
  289. data() {
  290. return {
  291. showDisplayValue: false,
  292. valueV: null as null|number|string,
  293. treeData: [] as TreeDataItem[],
  294. }
  295. },
  296. mounted() {
  297. this.valueV = this.value;
  298. if(this.showDisplayValueBeforeEdit)
  299. this.showDisplayValue = true;
  300. setTimeout(() => {
  301. if(this.loadAtStart) {
  302. this.treeData = [];
  303. this.doLoadData(0, 0) ;
  304. }
  305. } , 300);
  306. }
  307. });
  308. </script>
  309. <style lang="scss" scoped>
  310. .display-value {
  311. min-width: 150px;
  312. padding: 4px 11px;
  313. color: rgba(0, 0, 0, 0.85);
  314. font-size: 14px;
  315. line-height: 1.5715;
  316. background-color: #fff;
  317. background-image: none;
  318. border: 1px solid #d9d9d9;
  319. border-radius: 2px;
  320. }
  321. //暗黑主题
  322. body[data-theme="dark"] {
  323. .display-value {
  324. color: #dedede;
  325. background-color: #1f1f1f;
  326. border: 1px solid #434343;
  327. }
  328. }
  329. </style>