index.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/classNames'
  3. import arrayTreeFilter from '../helpers/arrayTreeFilter'
  4. const WUX_CASCADER = 'wux-cascader'
  5. const defaultFieldNames = {
  6. label: 'label',
  7. value: 'value',
  8. children: 'children',
  9. }
  10. baseComponent({
  11. externalClasses: ['wux-scroll-view-class'],
  12. properties: {
  13. prefixCls: {
  14. type: String,
  15. value: 'wux-cascader',
  16. },
  17. defaultValue: {
  18. type: Array,
  19. value: [],
  20. },
  21. value: {
  22. type: Array,
  23. value: [],
  24. observer(newVal) {
  25. if (this.data.controlled) {
  26. this.setData({ activeValue: newVal }, () => this.getCurrentOptions(newVal))
  27. }
  28. },
  29. },
  30. controlled: {
  31. type: Boolean,
  32. value: false,
  33. },
  34. title: {
  35. type: String,
  36. value: '',
  37. },
  38. options: {
  39. type: Array,
  40. value: [],
  41. observer: 'getCurrentOptions',
  42. },
  43. chooseTitle: {
  44. type: String,
  45. value: '请选择',
  46. },
  47. visible: {
  48. type: Boolean,
  49. value: false,
  50. },
  51. defaultFieldNames: {
  52. type: Object,
  53. value: defaultFieldNames,
  54. },
  55. },
  56. data: {
  57. activeOptions: [],
  58. activeIndex: 0,
  59. bodyStyle: '',
  60. activeValue: [],
  61. showOptions: [],
  62. fieldNames: {},
  63. },
  64. computed: {
  65. classes() {
  66. const { prefixCls } = this.data
  67. const wrap = classNames(prefixCls)
  68. const hd = `${prefixCls}__hd`
  69. const title = `${prefixCls}__title`
  70. const menus = `${prefixCls}__menus`
  71. const menu = `${prefixCls}__menu`
  72. const bd = `${prefixCls}__bd`
  73. const inner = `${prefixCls}__inner`
  74. const scrollView = `${prefixCls}__scroll-view`
  75. const option = `${prefixCls}__option`
  76. const item = `${prefixCls}__item`
  77. const icon = `${prefixCls}__icon`
  78. const ft = `${prefixCls}__ft`
  79. return {
  80. wrap,
  81. hd,
  82. title,
  83. menus,
  84. menu,
  85. bd,
  86. inner,
  87. scrollView,
  88. option,
  89. item,
  90. icon,
  91. ft,
  92. }
  93. },
  94. },
  95. methods: {
  96. getActiveOptions(activeValue) {
  97. const { options } = this.data
  98. const value = this.getFieldName('value')
  99. const childrenKeyName = this.getFieldName('children')
  100. return arrayTreeFilter(options, (option, level) => option[value] === activeValue[level], { childrenKeyName })
  101. },
  102. getShowOptions(activeValue) {
  103. const { options } = this.data
  104. const children = this.getFieldName('children')
  105. const result = this.getActiveOptions(activeValue).map((activeOption) => activeOption[children]).filter((activeOption) => !!activeOption)
  106. return [options, ...result]
  107. },
  108. getMenus(activeValue = [], hasChildren) {
  109. const { options, chooseTitle } = this.data
  110. const activeOptions = this.getActiveOptions(activeValue)
  111. if (hasChildren && activeOptions.length < options.length) {
  112. const value = this.getFieldName('value')
  113. const label = this.getFieldName('label')
  114. activeOptions.push({
  115. [value]: WUX_CASCADER,
  116. [label]: chooseTitle
  117. })
  118. }
  119. return activeOptions
  120. },
  121. getNextActiveValue(value, optionIndex) {
  122. let { activeValue } = this.data
  123. activeValue = activeValue.slice(0, optionIndex + 1)
  124. activeValue[optionIndex] = value
  125. return activeValue
  126. },
  127. updated(currentOptions, optionIndex, condition, callback) {
  128. const value = this.getFieldName('value')
  129. const children = this.getFieldName('children')
  130. const hasChildren = currentOptions[children] && currentOptions[children].length > 0
  131. const activeValue = this.getNextActiveValue(currentOptions[value], optionIndex)
  132. const activeOptions = this.getMenus(activeValue, hasChildren)
  133. const activeIndex = activeOptions.length - 1
  134. const showOptions = this.getShowOptions(activeValue)
  135. const params = {
  136. activeValue,
  137. activeOptions,
  138. activeIndex,
  139. showOptions,
  140. }
  141. // 判断 hasChildren 计算需要更新的数据
  142. if (hasChildren || (activeValue.length === showOptions.length && (optionIndex = Math.max(0, optionIndex - 1)))) {
  143. params.bodyStyle = `transform: translate(${-50 * optionIndex}%)`
  144. params.showOptions = showOptions
  145. }
  146. // 判断是否需要 setData 更新数据
  147. if (condition) {
  148. this.setData(params)
  149. }
  150. // 回调函数
  151. if (typeof callback === 'function') {
  152. callback.call(this, currentOptions, activeOptions, !hasChildren)
  153. }
  154. },
  155. /**
  156. * 更新级联数据
  157. * @param {Array} activeValue 当前选中值
  158. */
  159. getCurrentOptions(activeValue = this.data.activeValue) {
  160. const optionIndex = Math.max(0, activeValue.length - 1)
  161. const activeOptions = this.getActiveOptions(activeValue)
  162. const currentOptions = activeOptions[optionIndex]
  163. if (currentOptions) {
  164. this.updated(currentOptions, optionIndex, true)
  165. } else {
  166. const value = this.getFieldName('value')
  167. const label = this.getFieldName('label')
  168. activeOptions.push({
  169. [value]: WUX_CASCADER,
  170. [label]: this.data.chooseTitle
  171. })
  172. const showOptions = this.getShowOptions(activeValue)
  173. const activeIndex = activeOptions.length - 1
  174. const params = {
  175. showOptions,
  176. activeOptions,
  177. activeIndex,
  178. bodyStyle: '',
  179. }
  180. this.setData(params)
  181. }
  182. },
  183. /**
  184. * 点击菜单时的回调函数
  185. */
  186. onMenuClick(e) {
  187. const { menuIndex } = e.currentTarget.dataset
  188. const index = menuIndex > 1 ? menuIndex - 1 : 0
  189. const bodyStyle = `transform: translate(${-50 * index}%)`
  190. this.setData({
  191. bodyStyle,
  192. activeIndex: menuIndex,
  193. })
  194. },
  195. /**
  196. * 点击选项时的回调函数
  197. */
  198. onItemSelect(e) {
  199. const { item, optionIndex } = e.currentTarget.dataset
  200. // 判断是否禁用
  201. if (!item || item.disabled) return
  202. // updated
  203. this.updated(item, optionIndex, !this.data.controlled, this.onChange)
  204. },
  205. /**
  206. * 组件关闭时的回调函数
  207. */
  208. onPopupClose() {
  209. this.triggerEvent('close')
  210. },
  211. /**
  212. * 选择完成时的回调函数
  213. */
  214. onChange(currentOptions = {}, activeOptions = [], done = false) {
  215. const options = activeOptions.filter((n) => n[this.getFieldName('value')] !== WUX_CASCADER)
  216. const value = options.map((n) => n[this.getFieldName('value')])
  217. // 判断是否异步加载
  218. if (currentOptions.isLeaf === false && !currentOptions.children) {
  219. this.emitEvent({ value, options, done: false })
  220. this.triggerEvent('load', { value, options })
  221. return
  222. }
  223. // 正常加载
  224. this.emitEvent({ value, options, done })
  225. },
  226. emitEvent(params = {}) {
  227. this.triggerEvent('change', params)
  228. // 当选择完成时关闭组件
  229. if (params.done) {
  230. this.onPopupClose()
  231. }
  232. },
  233. getFieldName(name) {
  234. return this.data.fieldNames[name]
  235. },
  236. },
  237. attached() {
  238. const { defaultValue, value, controlled } = this.data
  239. const activeValue = controlled ? value : defaultValue
  240. const fieldNames = Object.assign({}, defaultFieldNames, this.data.defaultFieldNames)
  241. this.setData({ activeValue, fieldNames }, () => this.getCurrentOptions(activeValue))
  242. },
  243. })