MultiSelectPopup.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <view v-if="visible" class="popup-overlay" @click.self="closePopup">
  3. <view class="popup-content">
  4. <view class="popup-header">
  5. <text class="popup-action popup-cancel" @click="closePopup">取消</text>
  6. <text class="popup-action popup-confirm" @click="confirmSelection">确定</text>
  7. </view>
  8. <view class="picker-container">
  9. <scroll-view scroll-y class="column parent-column">
  10. <view v-for="(parent, index) in options" :key="index"
  11. class="column-item parent-item"
  12. :class="{ 'active': activeParentIndex === index }"
  13. @click="setActiveParent(index)">
  14. {{ parent.label }}
  15. </view>
  16. </scroll-view>
  17. <scroll-view scroll-y class="column child-column">
  18. <view v-if="activeParentIndex !== null && currentChildOptions.length > 0">
  19. <view v-for="(child, index) in currentChildOptions" :key="index"
  20. class="column-item child-item"
  21. :class="{ 'selected': selectedChildValues.includes(child.value) }"
  22. @click="selectChild(child)">
  23. {{ child.label }}
  24. <view v-if="selectedChildValues.includes(child.value)" class="checkmark">✓</view>
  25. </view>
  26. </view>
  27. <view v-else-if="activeParentIndex !== null && currentChildOptions.length === 0" class="no-children-text">
  28. 暂无选项
  29. </view>
  30. </scroll-view>
  31. </view>
  32. </view>
  33. </view>
  34. </template>
  35. <script>
  36. export default {
  37. name: 'MultiSelectPopup',
  38. props: {
  39. initialOptions: {
  40. type: Array,
  41. default: () => []
  42. },
  43. initialSelectedValues: { // Optional: to pre-select items
  44. type: Array,
  45. default: () => []
  46. }
  47. },
  48. data() {
  49. return {
  50. visible: false,
  51. options: [],
  52. activeParentIndex: null,
  53. selectedChildValues: [] // Changed to array for multi-select
  54. };
  55. },
  56. computed: {
  57. currentChildOptions() {
  58. if (this.activeParentIndex !== null && this.options[this.activeParentIndex] && this.options[this.activeParentIndex].children) {
  59. return this.options[this.activeParentIndex].children;
  60. }
  61. return [];
  62. }
  63. },
  64. watch: {
  65. initialOptions: {
  66. immediate: true,
  67. handler(newVal) {
  68. this.options = JSON.parse(JSON.stringify(newVal));
  69. this.selectedChildValues = [...this.initialSelectedValues];
  70. if (this.options.length > 0) {
  71. let preselectedParentIndex = 0;
  72. for (let i = 0; i < this.options.length; i++) {
  73. const parent = this.options[i];
  74. if (parent.children && parent.children.some(c => this.initialSelectedValues.includes(c.value))) {
  75. preselectedParentIndex = i;
  76. break;
  77. }
  78. }
  79. this.activeParentIndex = preselectedParentIndex;
  80. } else {
  81. this.activeParentIndex = null;
  82. }
  83. }
  84. }
  85. },
  86. methods: {
  87. openPopup() {
  88. this.options = JSON.parse(JSON.stringify(this.initialOptions));
  89. this.selectedChildValues = [...this.initialSelectedValues];
  90. if (this.options.length > 0) {
  91. let preselectedParentIndex = 0;
  92. for (let i = 0; i < this.options.length; i++) {
  93. const parent = this.options[i];
  94. if (parent.children && parent.children.some(c => this.selectedChildValues.includes(c.value))) {
  95. preselectedParentIndex = i;
  96. break;
  97. }
  98. }
  99. this.activeParentIndex = (this.options.length > 0) ? preselectedParentIndex : null;
  100. } else {
  101. this.activeParentIndex = null;
  102. }
  103. this.visible = true;
  104. },
  105. closePopup() {
  106. this.visible = false;
  107. this.$emit('popup-closed');
  108. },
  109. setActiveParent(parentIndex) {
  110. this.activeParentIndex = parentIndex;
  111. },
  112. selectChild(child) {
  113. const index = this.selectedChildValues.indexOf(child.value);
  114. if (index > -1) {
  115. this.selectedChildValues.splice(index, 1);
  116. } else {
  117. this.selectedChildValues.push(child.value);
  118. }
  119. },
  120. confirmSelection() {
  121. if (this.selectedChildValues.length > 0) {
  122. this.$emit('selection-confirmed', [...this.selectedChildValues]);
  123. this.closePopup();
  124. } else {
  125. uni.showToast({
  126. title: '请至少选择一个选项',
  127. icon: 'none'
  128. });
  129. }
  130. }
  131. }
  132. };
  133. </script>
  134. <style lang="scss" scoped>
  135. .popup-overlay {
  136. position: fixed;
  137. top: 0;
  138. left: 0;
  139. width: 100%;
  140. height: 100%;
  141. background-color: rgba(0, 0, 0, 0.5);
  142. display: flex;
  143. align-items: flex-end;
  144. justify-content: center;
  145. z-index: 1000;
  146. }
  147. .popup-content {
  148. background-color: white;
  149. width: 100%;
  150. max-height: 50vh; /* Adjusted max height */
  151. border-top-left-radius: 20rpx;
  152. border-top-right-radius: 20rpx;
  153. display: flex;
  154. flex-direction: column;
  155. animation: slideUp 0.3s ease-out;
  156. }
  157. @keyframes slideUp {
  158. from {
  159. transform: translateY(100%);
  160. }
  161. to {
  162. transform: translateY(0);
  163. }
  164. }
  165. .popup-header {
  166. display: flex;
  167. justify-content: space-between;
  168. align-items: center;
  169. padding: 25rpx 30rpx;
  170. border-bottom: 1rpx solid #eee;
  171. }
  172. .popup-action {
  173. font-size: 30rpx;
  174. cursor: pointer;
  175. }
  176. .popup-cancel {
  177. color: #666;
  178. }
  179. .popup-confirm {
  180. color: #007aff; // Blue color for confirm
  181. }
  182. .picker-container {
  183. display: flex;
  184. flex: 1;
  185. height: calc(50vh - 81rpx); /* Max height minus header height */
  186. overflow: hidden; /* Prevent overall container from scrolling */
  187. }
  188. .column {
  189. height: 100%;
  190. overflow-y: auto;
  191. padding: 10rpx 0;
  192. }
  193. .parent-column {
  194. flex: 1;
  195. background-color: #f8f8f8;
  196. border-right: 1rpx solid #eee;
  197. }
  198. .child-column {
  199. flex: 1.5; // Child column can be wider
  200. background-color: #fff;
  201. }
  202. .column-item {
  203. padding: 25rpx 30rpx;
  204. font-size: 28rpx;
  205. cursor: pointer;
  206. text-align: center;
  207. border-bottom: 1rpx solid #f0f0f0; /* Light border for items */
  208. }
  209. .parent-item.active {
  210. background-color: #fff;
  211. color: #007aff;
  212. font-weight: bold;
  213. border-right: 4rpx solid #007aff; /* Indicate active parent */
  214. margin-right: -1rpx; /* Overlap border */
  215. }
  216. .child-item.selected {
  217. color: #007aff;
  218. font-weight: bold;
  219. background-color: #e6f2ff; /* Light blue background for selected child */
  220. }
  221. .no-children-text {
  222. padding: 40rpx;
  223. text-align: center;
  224. color: #999;
  225. font-size: 26rpx;
  226. }
  227. </style>