index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <template>
  2. <view>
  3. <!-- <view @tap="handleOpen">
  4. <slot></slot>
  5. </view> -->
  6. <uni-popup ref="popup">
  7. <view class="popup-content" :style="props.popupStyle">
  8. <view class="popup-content-label">
  9. <view class="popup-content-label-item active">{{ label }}</view>
  10. </view>
  11. <view class="popup-content-body">
  12. <view v-for="(arr, i) in showList" :key="i" class="popup-content-body-list">
  13. <view class="popup-content-body-list-items">
  14. <view class="content">
  15. <view
  16. v-for="_arr in arr.data"
  17. :key="_arr[props.itemValue]"
  18. class="py-1 dFlex"
  19. :class="arr.choose === _arr[props.itemValue] || (Array.isArray(arr.choose) && arr.choose.includes(_arr[props.itemValue]))? 'active' : ''"
  20. @tap="handleNext(_arr, i)"
  21. >
  22. {{ _arr[props.itemLabel] }}
  23. <uni-icons
  24. v-if="Array.isArray(arr.choose) && arr.choose.includes(_arr[props.itemValue])"
  25. type="checkmarkempty"
  26. color="#00B760"
  27. size="16"
  28. />
  29. </view>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. <view class="popup-content-footer">
  35. <!-- <button class="btn cancel" @tap="reset">重置</button> -->
  36. <button class="btn submit" @tap="submit">确定</button>
  37. </view>
  38. </view>
  39. </uni-popup>
  40. </view>
  41. </template>
  42. <script setup>
  43. import { ref, watch } from 'vue'
  44. const emit = defineEmits(['change', 'init'])
  45. const props = defineProps({
  46. popupStyle: {
  47. type: [String, Object],
  48. default: ''
  49. },
  50. value: {
  51. type: [String, Array, Number, Object],
  52. default: null
  53. },
  54. items: { // 树结构
  55. type: Array,
  56. default: () => []
  57. },
  58. label: {
  59. type: String,
  60. default: ''
  61. },
  62. multiple: {
  63. type: Boolean,
  64. default: false
  65. },
  66. itemLabel: {
  67. type: String,
  68. default: 'label'
  69. },
  70. itemValue: {
  71. type: String,
  72. default: 'value'
  73. },
  74. children: {
  75. type: String,
  76. default: 'children'
  77. }
  78. })
  79. const popup = ref()
  80. const showList = ref([])
  81. watch(
  82. () => props.items,
  83. (val) => {
  84. // 初始化赋值
  85. showList.value = [{
  86. choose: -1,
  87. data: val
  88. }]
  89. },
  90. {
  91. immediate: true,
  92. deep: true
  93. }
  94. )
  95. const handleData = (val) => {
  96. if (!val) {
  97. showList.value = [{
  98. choose: -1,
  99. data: props.items
  100. }]
  101. return
  102. }
  103. // 单选 设置回显
  104. if (!props.multiple) {
  105. const item = findItem(val, props.items)
  106. if (!item) {
  107. showList.value = [{
  108. choose: -1,
  109. data: props.items
  110. }]
  111. return
  112. }
  113. showList.value = item.map(e => {
  114. return {
  115. choose: e[props.itemValue],
  116. data: e.data
  117. }
  118. })
  119. emit('init', item[item.length - 1][props.itemLabel])
  120. return
  121. }
  122. if (!Array.isArray(val)) {
  123. showList.value = [{
  124. choose: -1,
  125. data: props.items
  126. }]
  127. return
  128. }
  129. // 多选 设置回显
  130. const arr = []
  131. const label = []
  132. val.forEach(e => {
  133. const item = findItem(e, props.items)
  134. if (!item) {
  135. return
  136. }
  137. label.push(item[item.length - 1][props.itemLabel])
  138. if (!arr.length) {
  139. arr.push(...item.map((e, i) => {
  140. return {
  141. choose: i === item.length - 1 ? [e[props.itemValue]] : e[props.itemValue],
  142. data: e.data
  143. }
  144. }))
  145. return
  146. }
  147. arr[arr.length - 1].choose.push(item[item.length - 1][props.itemValue])
  148. })
  149. if (!arr.length) {
  150. showList.value = [{
  151. choose: -1,
  152. data: props.items
  153. }]
  154. return
  155. }
  156. showList.value = arr
  157. emit('init', label)
  158. }
  159. const findItem = (value, lists) => {
  160. let level = -1
  161. return check(value, lists)
  162. function check (val, items, arr = []) {
  163. let i = 0
  164. level++
  165. while (i < items.length) {
  166. arr[level] = {
  167. ...items[i],
  168. data: items
  169. }
  170. if (items[i][props.itemValue] === val) {
  171. return arr
  172. }
  173. if (items[i][props.children] && items[i][props.children].length > 0) {
  174. const data = check(val, items[i][props.children], arr)
  175. if (data) {
  176. return data
  177. }
  178. }
  179. i++
  180. }
  181. level--
  182. return false
  183. }
  184. }
  185. const handleOpen = () => {
  186. handleData(props.value)
  187. popup.value.open('bottom')
  188. }
  189. const handleNext = (item, index) => {
  190. const _i = index + 1
  191. // active.value = _i
  192. showList.value.splice(_i, showList.value.length - _i)
  193. showList.value[index].label = item[props.itemLabel]
  194. if (item[props.children] && item[props.children].length) {
  195. showList.value[index].choose = item[props.itemValue]
  196. showList.value.push({
  197. choose: -1,
  198. data: item[props.children]
  199. })
  200. return
  201. }
  202. if (!props.multiple) {
  203. showList.value[index].choose = item[props.itemValue]
  204. return
  205. }
  206. if (!Array.isArray(showList.value[index].choose)) {
  207. showList.value[index].choose = [item[props.itemValue]]
  208. return
  209. }
  210. if (!showList.value[index].choose.includes(item[props.itemValue])) {
  211. showList.value[index].choose.push(item[props.itemValue])
  212. return
  213. }
  214. const _index = showList.value[index].choose.indexOf(item[props.itemValue])
  215. showList.value[index].choose.splice(_index, 1)
  216. }
  217. const reset = () => {
  218. showList.value = [{
  219. choose: -1,
  220. data: props.items
  221. }]
  222. emit('change', props.multiple ? [] : null)
  223. popup.value.close()
  224. }
  225. const submit = () => {
  226. const item = showList.value[showList.value.length - 1]
  227. if (item.choose === -1) {
  228. const _item = showList.value[showList.value.length - 2]
  229. if (!item) {
  230. emit('change', props.multiple ? [] : null)
  231. popup.value.close()
  232. return
  233. }
  234. emit('change', props.multiple ? [_item.choose] : _item.choose, props.multiple ? showList.value[0].label : _item.label, item)
  235. popup.value.close()
  236. return
  237. }
  238. emit('change', item.choose, props.multiple ? showList.value[0].label : item.label, item)
  239. popup.value.close()
  240. }
  241. defineExpose({
  242. handleOpen
  243. })
  244. </script>
  245. <style lang="scss" scoped>
  246. .popup-content {
  247. height: 580px;
  248. background: #FFF;
  249. border-radius: 20rpx 20rpx 0 0;
  250. padding: 10px;
  251. box-sizing: border-box;
  252. display: flex;
  253. flex-direction: column;
  254. &-label {
  255. height: 50px;
  256. display: flex;
  257. font-size: 32rpx;
  258. font-weight: bold;
  259. margin-bottom: 10px;
  260. border-bottom: 2rpx solid #EEE;
  261. &-item {
  262. display: flex;
  263. align-items: center;
  264. padding: 0 15px;
  265. box-sizing: border-box;
  266. &.active {
  267. border-bottom: 2px solid #00B760;
  268. }
  269. }
  270. }
  271. &-body {
  272. flex: 1;
  273. height: 0;
  274. display: flex;
  275. &-list {
  276. height: 100%;
  277. min-width: 100px;
  278. margin-right: 20px;
  279. display: flex;
  280. flex-direction: column;
  281. &-label {
  282. border-bottom: 1px solid #eee;
  283. display: flex;
  284. align-items: center;
  285. justify-content: center;
  286. height: 50px;
  287. box-sizing: border-box;
  288. margin-bottom: 10px;
  289. &.active {
  290. border-bottom: 2px solid #00B760;
  291. }
  292. }
  293. &-items {
  294. flex: 1;
  295. height: 0;
  296. .content {
  297. height: 100%;
  298. overflow-x: hidden;
  299. overflow-y: auto;
  300. .active {
  301. color: #00B760;
  302. }
  303. }
  304. }
  305. }
  306. }
  307. &-footer {
  308. height: 50px;
  309. display: flex;
  310. align-items: center;
  311. .btn {
  312. font-size: 28rpx;
  313. height: 30px;
  314. line-height: 30px;
  315. &.cancel {
  316. width: 40%;
  317. margin-right: 10px;
  318. }
  319. &.submit {
  320. flex: 1;
  321. background: #00B760;
  322. color: #FFF ;
  323. }
  324. }
  325. }
  326. }
  327. .dFlex {
  328. display: flex;
  329. justify-content: space-between;
  330. height: 20px;
  331. }
  332. .py-1 {
  333. padding: 10px 0;
  334. }
  335. </style>