You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

310 lines
6.7 KiB

  1. <template>
  2. <view class="u-select">
  3. <view :class="['u-select__content', disabled && 'disabled']">
  4. <view :class="['u-select__label', border && 'u-select__label--border']" :style="selectLabelStyle" @click="openSelect">
  5. <slot name="text" :currentLabel="currentLabel">
  6. <text class="u-select__text" :style="{ color: resolvedTextColor }" v-if="showOptionsLabel">
  7. {{ currentLabel }}
  8. </text>
  9. <text class="u-select__text" :style="{ color: resolvedTextColor }" v-else>
  10. {{ label }}
  11. </text>
  12. </slot>
  13. <slot name="icon">
  14. <up-icon name="arrow-down" :size="iconSize" :color="resolvedIconColor"></up-icon>
  15. </slot>
  16. </view>
  17. <u-overlay :show="isOpen" @click="overlayClick" v-if="overlay" :zIndex="zIndex" :duration="duration + 50"
  18. :customStyle="overlayStyle" :opacity="overlayOpacity" @touchmove.stop.prevent="noop"></u-overlay>
  19. <view class="u-select__options__wrap" :style="optionsWrapStyle">
  20. <view class="u-select__options" :style="optionsStyle" v-if="isOpen">
  21. <slot name="options">
  22. <view class="u-select__options_item" :class="current == item[keyName] ? 'active': ''"
  23. :style="{ color: resolvedItemColor }"
  24. :key="index" v-for="(item, index) in options" @click="selectItem(item)">
  25. <slot name="optionItem" :item="item">
  26. <text class="u-select__item_text" :style="{color: resolvedItemColor}">
  27. {{item[labelName]}}
  28. </text>
  29. </slot>
  30. </view>
  31. </slot>
  32. </view>
  33. </view>
  34. </view>
  35. </view>
  36. </template>
  37. <script>
  38. import {
  39. mpMixin
  40. } from '../../libs/mixin/mpMixin';
  41. import {
  42. mixin
  43. } from '../../libs/mixin/mixin';
  44. import {
  45. getWindowInfo
  46. } from '../../libs/function/index';
  47. export default {
  48. name: "up-select",
  49. mixins: [mpMixin, mixin],
  50. emits: ['update:current', 'select'],
  51. props: {
  52. maxHeight: {
  53. type: String,
  54. default: '90vh'
  55. },
  56. overlay: {
  57. type: Boolean,
  58. default: true
  59. },
  60. overlayOpacity: {
  61. type: Number,
  62. default: 0.01
  63. },
  64. overlayStyle: {
  65. type: Object,
  66. default: () => {
  67. return {}
  68. }
  69. },
  70. duration: {
  71. type: Number,
  72. default: 300
  73. },
  74. label: {
  75. type: String,
  76. default: '选项'
  77. },
  78. options: {
  79. type: Array,
  80. default: () => {
  81. return []
  82. }
  83. },
  84. keyName: {
  85. type: String,
  86. default: 'id'
  87. },
  88. labelName: {
  89. type: String,
  90. default: 'name'
  91. },
  92. showOptionsLabel: {
  93. type: Boolean,
  94. default: false
  95. },
  96. current: {
  97. type: [String, Number],
  98. default: ''
  99. },
  100. zIndex: {
  101. type: Number,
  102. default: 11000
  103. },
  104. itemColor: {
  105. type: String,
  106. default: ''
  107. },
  108. iconColor: {
  109. type: String,
  110. default: ''
  111. },
  112. iconSize: {
  113. type: [String],
  114. default: '13px'
  115. },
  116. // 是否禁用
  117. disabled: {
  118. type: Boolean,
  119. default: false
  120. },
  121. // 是否显示触发区边框
  122. border: {
  123. type: Boolean,
  124. default: false
  125. },
  126. // 下拉面板宽度,支持 px/rpx/% 等,如 240px 或 300rpx
  127. optionsWidth: {
  128. type: [String, Number],
  129. default: ''
  130. }
  131. },
  132. data() {
  133. return {
  134. isOpen: false,
  135. optionsWrapLeft: 'auto',
  136. optionsWrapRight: 'auto'
  137. }
  138. },
  139. computed: {
  140. resolvedItemColor() {
  141. return this.itemColor || this.upThemeVar('--up-main-color', '#303133');
  142. },
  143. resolvedTextColor() {
  144. return this.upThemeVar('--up-main-color', '#303133');
  145. },
  146. resolvedIconColor() {
  147. return this.iconColor || this.upThemeVar('--up-content-color', '#606266');
  148. },
  149. normalizedOptionsWidth() {
  150. if (this.optionsWidth === '' || this.optionsWidth === null || typeof this.optionsWidth === 'undefined') {
  151. return '';
  152. }
  153. if (typeof this.optionsWidth === 'number') {
  154. return `${this.optionsWidth}px`;
  155. }
  156. return this.optionsWidth;
  157. },
  158. selectLabelStyle() {
  159. if (!this.border) return {};
  160. return {
  161. borderColor: this.upThemeVar('--up-border-color', '#dadbde'),
  162. backgroundColor: this.upThemeVar('--up-card-bg-color', '#ffffff')
  163. };
  164. },
  165. optionsWrapStyle() {
  166. const style = {
  167. overflowY: 'auto',
  168. zIndex: this.zIndex + 1,
  169. left: this.optionsWrapLeft,
  170. right: this.optionsWrapRight,
  171. maxHeight: this.maxHeight
  172. };
  173. if (this.normalizedOptionsWidth) {
  174. style.width = this.normalizedOptionsWidth;
  175. }
  176. return style;
  177. },
  178. optionsStyle() {
  179. const style = {};
  180. if (this.normalizedOptionsWidth) {
  181. style.width = this.normalizedOptionsWidth;
  182. style.minWidth = this.normalizedOptionsWidth;
  183. }
  184. return style;
  185. },
  186. currentLabel() {
  187. let name = '';
  188. this.options.forEach((ele) => {
  189. if (ele[this.keyName] === this.current) {
  190. name = ele[this.labelName];
  191. }
  192. });
  193. return name;
  194. }
  195. },
  196. methods: {
  197. openSelect() {
  198. if (this.disabled) return;
  199. this.isOpen = true;
  200. this.$nextTick(() => {
  201. if (this.isOpen) {
  202. this.adjustOptionsWrapPosition();
  203. }
  204. });
  205. },
  206. closeSelect() {
  207. this.isOpen = false;
  208. },
  209. overlayClick() {
  210. this.isOpen = false;
  211. },
  212. selectItem(item) {
  213. this.isOpen = false;
  214. this.$emit('update:current', item[this.keyName]);
  215. this.$emit('select', item);
  216. },
  217. adjustOptionsWrapPosition() {
  218. this.optionsWrapLeft = '0px';
  219. this.optionsWrapRight = 'auto';
  220. let wi = getWindowInfo();
  221. let windowWidth = wi.windowWidth;
  222. this.$uGetRect('.u-select__options__wrap').then(rect => {
  223. if (rect.left + rect.width > windowWidth) {
  224. // 如果右侧被遮挡,则调整到左侧
  225. this.optionsWrapLeft = 'auto';
  226. this.optionsWrapRight = `0px`;
  227. }
  228. });
  229. }
  230. }
  231. }
  232. </script>
  233. <style lang="scss" scoped>
  234. .u-select__content {
  235. position: relative;
  236. .u-select__label {
  237. display: flex;
  238. justify-content: space-between;
  239. align-items: center;
  240. &--border {
  241. padding: 8px 10px;
  242. border-width: 1px;
  243. border-style: solid;
  244. border-radius: 4px;
  245. min-height: 36px;
  246. box-sizing: border-box;
  247. }
  248. /* #ifdef H5 */
  249. &:hover {
  250. cursor: pointer;
  251. }
  252. /* #endif */
  253. }
  254. .u-select__text {
  255. margin-right: 2px;
  256. }
  257. &.disabled {
  258. opacity: 0.6;
  259. pointer-events: none;
  260. }
  261. .u-select__options__wrap {
  262. margin-bottom: 46px;
  263. position: absolute;
  264. top: calc(100% + 4px);
  265. left: 0;
  266. }
  267. .u-select__options {
  268. min-width: 100px;
  269. box-sizing: border-box;
  270. border-radius: 4px;
  271. border: 1px solid var(--up-border-color, #f1f1f1);
  272. background-color: var(--up-card-bg-color, #fff);
  273. .u-select__options_item {
  274. padding: 10px 12px;
  275. box-sizing: border-box;
  276. width: 100%;
  277. height: 100%;
  278. &:hover {
  279. background-color: var(--up-bg-color, #f7f7f7);
  280. }
  281. /* #ifdef H5 */
  282. &:hover {
  283. cursor: pointer;
  284. }
  285. .u-select__item_text {
  286. &:hover {
  287. cursor: pointer;
  288. }
  289. }
  290. /* #endif */
  291. }
  292. }
  293. }
  294. </style>