index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import styleToCssString from '../helpers/libs/styleToCssString'
  4. import arrayTreeFilter from '../helpers/libs/arrayTreeFilter'
  5. import fieldNamesBehavior from '../helpers/mixins/fieldNamesBehavior'
  6. const WUX_CASCADER_VIEW = 'wux-cascader-view'
  7. baseComponent({
  8. behaviors: [fieldNamesBehavior],
  9. externalClasses: ['wux-scroll-view-class'],
  10. properties: {
  11. prefixCls: {
  12. type: String,
  13. value: 'wux-cascader-view',
  14. },
  15. defaultValue: {
  16. type: Array,
  17. value: [],
  18. },
  19. value: {
  20. type: Array,
  21. value: [],
  22. },
  23. controlled: {
  24. type: Boolean,
  25. value: false,
  26. },
  27. options: {
  28. type: Array,
  29. value: [],
  30. },
  31. full: {
  32. type: Boolean,
  33. value: false,
  34. },
  35. placeholder: {
  36. type: String,
  37. value: '请选择',
  38. },
  39. height: {
  40. type: [String, Number],
  41. value: 'auto',
  42. },
  43. skipAnimation: {
  44. type: Boolean,
  45. value: false,
  46. },
  47. },
  48. data: {
  49. activeOptions: [],
  50. activeIndex: 0,
  51. bodyStyle: '',
  52. activeValue: [],
  53. showOptions: [],
  54. scrollViewStyle: '',
  55. },
  56. computed: {
  57. classes: ['prefixCls, full', function(prefixCls, full) {
  58. const wrap = classNames(prefixCls)
  59. const hd = `${prefixCls}__hd`
  60. const bd = `${prefixCls}__bd`
  61. const innerScroll = classNames(`${prefixCls}__inner-scroll`, {
  62. [`${prefixCls}__inner-scroll--full`]: full,
  63. })
  64. const scrollView = `${prefixCls}__scroll-view`
  65. const ft = `${prefixCls}__ft`
  66. return {
  67. wrap,
  68. hd,
  69. bd,
  70. innerScroll,
  71. scrollView,
  72. ft,
  73. }
  74. }],
  75. },
  76. observers: {
  77. value(newVal) {
  78. if (this.data.controlled) {
  79. this.setData({ activeValue: newVal })
  80. this.getCurrentOptions(newVal)
  81. }
  82. },
  83. options() {
  84. this.getCurrentOptions(this.data.activeValue)
  85. },
  86. height(newVal) {
  87. this.updateStyle(newVal)
  88. },
  89. },
  90. methods: {
  91. getActiveOptions(activeValue) {
  92. const { options } = this.data
  93. const value = this.getFieldName('value')
  94. const childrenKeyName = this.getFieldName('children')
  95. return arrayTreeFilter(options, (option, level) => option[value] === activeValue[level], { childrenKeyName })
  96. },
  97. getShowOptions(activeValue) {
  98. const { options } = this.data
  99. const children = this.getFieldName('children')
  100. const result = this.getActiveOptions(activeValue).map((activeOption) => activeOption[children]).filter((activeOption) => !!activeOption)
  101. return [options, ...result]
  102. },
  103. getMenus(activeValue = [], hasChildren) {
  104. const { placeholder } = this.data
  105. const activeOptions = this.getActiveOptions(activeValue)
  106. if (hasChildren) {
  107. const value = this.getFieldName('value')
  108. const label = this.getFieldName('label')
  109. activeOptions.push({
  110. [value]: WUX_CASCADER_VIEW,
  111. [label]: placeholder,
  112. })
  113. }
  114. return activeOptions
  115. },
  116. getNextActiveValue(value, optionIndex) {
  117. let { activeValue } = this.data
  118. activeValue = activeValue.slice(0, optionIndex + 1)
  119. activeValue[optionIndex] = value
  120. return activeValue
  121. },
  122. updated(currentOptions, optionIndex, condition, callback) {
  123. const value = this.getFieldName('value')
  124. const children = this.getFieldName('children')
  125. const hasChildren = currentOptions && currentOptions[children] && currentOptions[children].length > 0
  126. const activeValue = this.getNextActiveValue(currentOptions[value], optionIndex)
  127. const activeOptions = this.getMenus(activeValue, hasChildren)
  128. const activeIndex = activeOptions.length - 1
  129. const showOptions = this.getShowOptions(activeValue)
  130. const props = {
  131. activeValue,
  132. activeOptions,
  133. activeIndex,
  134. showOptions,
  135. }
  136. // 判断 hasChildren 计算需要更新的数据
  137. if (hasChildren || (activeValue.length === showOptions.length && (optionIndex = Math.max(0, optionIndex - 1)))) {
  138. props.bodyStyle = this.getTransform(optionIndex + 1)
  139. props.showOptions = showOptions
  140. }
  141. // 判断是否需要 setData 更新数据
  142. if (condition) {
  143. this.setCascaderView(props)
  144. }
  145. // 回调函数
  146. if (typeof callback === 'function') {
  147. callback.call(this, currentOptions, activeValue)
  148. }
  149. },
  150. /**
  151. * 更新级联数据
  152. * @param {Array} activeValue 当前选中值
  153. */
  154. getCurrentOptions(activeValue = this.data.activeValue) {
  155. const optionIndex = Math.max(0, activeValue.length - 1)
  156. const activeOptions = this.getActiveOptions(activeValue)
  157. const currentOptions = activeOptions[optionIndex]
  158. if (currentOptions) {
  159. this.updated(currentOptions, optionIndex, true)
  160. } else {
  161. const value = this.getFieldName('value')
  162. const label = this.getFieldName('label')
  163. activeOptions.push({
  164. [value]: WUX_CASCADER_VIEW,
  165. [label]: this.data.placeholder,
  166. })
  167. const showOptions = this.getShowOptions(activeValue)
  168. const activeIndex = activeOptions.length - 1
  169. const props = {
  170. showOptions,
  171. activeOptions,
  172. activeIndex,
  173. bodyStyle: '',
  174. }
  175. this.setCascaderView(props)
  176. }
  177. },
  178. setCascaderView(props) {
  179. const { activeOptions, ...restProps } = props
  180. this.setData({ activeOptions }, () => {
  181. if (this.data.activeIndex !== restProps.activeIndex) {
  182. this.triggerEvent('tabsChange', { index: restProps.activeIndex })
  183. }
  184. this.setData(restProps)
  185. })
  186. },
  187. getTransform(index, animating = !this.data.skipAnimation) {
  188. const pt = this.data.full ? 2 : 1
  189. const i = this.data.full ? index : index - 1
  190. const bodyStyle = styleToCssString({
  191. transition: animating ? 'transform .3s' : 'none',
  192. transform: `translate(${-50 * pt * Math.max(0, i)}%)`,
  193. })
  194. return bodyStyle
  195. },
  196. /**
  197. * 点击菜单时的回调函数
  198. */
  199. onTabsChange(e) {
  200. const activeIndex = parseInt(e.detail.key)
  201. const bodyStyle = this.getTransform(activeIndex)
  202. if (
  203. this.data.bodyStyle !== bodyStyle ||
  204. this.data.activeIndex !== activeIndex
  205. ) {
  206. this.setData({
  207. bodyStyle,
  208. activeIndex,
  209. })
  210. this.triggerEvent('tabsChange', { index: activeIndex })
  211. }
  212. },
  213. /**
  214. * 点击选项时的回调函数
  215. */
  216. onItemSelect(e) {
  217. const { optionIndex } = e.currentTarget.dataset
  218. const { index } = e.detail
  219. const { showOptions } = this.data
  220. const item = showOptions[optionIndex][index]
  221. // updated
  222. this.updated(item, optionIndex, !this.data.controlled, this.onChange)
  223. },
  224. /**
  225. * 选择完成时的回调函数
  226. */
  227. onChange(currentOptions = {}, activeValue = []) {
  228. const childrenKeyName = this.getFieldName('children')
  229. const values = this.getValue(activeValue)
  230. // 判断是否异步加载
  231. if (currentOptions && currentOptions.isLeaf === false && !currentOptions[childrenKeyName]) {
  232. this.triggerEvent('change', { ...values })
  233. this.triggerEvent('load', { value: values.value, options: values.options })
  234. return
  235. }
  236. // 正常加载
  237. this.triggerEvent('change', { ...values })
  238. },
  239. getValue(activeValue = this.data.activeValue) {
  240. const optionIndex = Math.max(0, activeValue.length - 1)
  241. const activeOptions = this.getActiveOptions(activeValue)
  242. const currentOptions = activeOptions[optionIndex]
  243. const valueName = this.getFieldName('value')
  244. const childrenKeyName = this.getFieldName('children')
  245. const hasChildren = currentOptions && currentOptions[childrenKeyName] && currentOptions[childrenKeyName].length > 0
  246. const options = activeOptions.filter((n) => n[valueName] !== WUX_CASCADER_VIEW)
  247. const value = options.map((n) => n[valueName])
  248. if (currentOptions && currentOptions.isLeaf === false && !currentOptions[childrenKeyName]) {
  249. return {
  250. value,
  251. options,
  252. done: false,
  253. }
  254. }
  255. return {
  256. value,
  257. options,
  258. done: !hasChildren,
  259. }
  260. },
  261. updateStyle(height) {
  262. const scrollViewStyle = styleToCssString({
  263. height,
  264. minHeight: height,
  265. })
  266. if (this.data.scrollViewStyle !== scrollViewStyle) {
  267. this.setData({
  268. scrollViewStyle,
  269. })
  270. }
  271. },
  272. },
  273. attached() {
  274. const { defaultValue, value, controlled, height } = this.data
  275. const activeValue = controlled ? value : defaultValue
  276. this.setData({ activeValue })
  277. this.getCurrentOptions(activeValue)
  278. this.updateStyle(height)
  279. },
  280. })