index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. <template>
  2. <view class="da-dropdown" :class="{'is-fixed': fixedTop, 'has-search': hasSearch}" :style="dropdownStyle">
  3. <!-- 搜索 -->
  4. <view class="da-dropdown-search" v-if="hasSearch" @touchmove.stop.prevent="handleMove">
  5. <input
  6. class="da-dropdown-search-input"
  7. :value="searchItem.value"
  8. @input="handleSearchChange"
  9. :placeholder="searchItem.placeholder || '请输入'"
  10. @confirm="handleSearch"
  11. confirm-type="search" />
  12. <button class="da-dropdown-search-btn" @click="handleSearch">搜索</button>
  13. </view>
  14. <!-- 菜单 -->
  15. <view class="da-dropdown-menu" @touchmove.stop.prevent="handleMove">
  16. <view
  17. class="da-dropdown-menu-item"
  18. :class="{'is-hidden':item.isHidden === 'true'}"
  19. v-for="(item, index) in menuList"
  20. :key="index"
  21. @click="handleMenuClick(index,item)">
  22. <text class="da-dropdown-menu-item--text" :class="item.isActived ? 'is-actived' : ''">{{ item.title }}</text>
  23. <view class="da-dropdown-menu-item--icon" v-if="item.showArrow">
  24. <text v-if="item.isLoading" class="is--loading"></text>
  25. <text v-else-if="item.isClick" class="is--arrup"></text>
  26. <text v-else class="is--arrdown"></text>
  27. </view>
  28. <view class="da-dropdown-menu-item--sort" v-else-if="item.showSort" :class="'is--' + item.value"></view>
  29. </view>
  30. </view>
  31. <!-- 弹出 -->
  32. <view class="da-dropdown-content" :class="{'is-show': isShow,'is-visible': isVisible}">
  33. <view class="da-dropdown-content-popup" :class="isShow ? 'is-show' : ''">
  34. <view class="da-dropdown-popup-box" v-for="(item, index) in menuList" :key="index">
  35. <!-- 下拉列表 -->
  36. <DropdownCell
  37. v-if="item.type === 'cell' && index === currentIndex"
  38. :dropdownItem="item"
  39. :dropdownIndex="index"
  40. @success="handleCellSelect"></DropdownCell>
  41. <!-- 多条件筛选 -->
  42. <DropdownFilter
  43. v-if="item.type === 'filter' && index === currentIndex"
  44. :dropdownItem="item"
  45. :dropdownIndex="index"
  46. @success="handleFilterConfirm"></DropdownFilter>
  47. <!-- 级联选择 -->
  48. <DropdownPicker
  49. v-if="item.type === 'picker' && index === currentIndex"
  50. :dropdownItem="item"
  51. :dropdownIndex="index"
  52. @success="handlePickerConfirm" />
  53. <!-- 日期范围 -->
  54. <DropdownDaterange
  55. v-if="item.type === 'daterange' && index === currentIndex"
  56. :dropdownItem="item"
  57. :dropdownIndex="index"
  58. @success="handleDaterangeConfirm" />
  59. <!-- 弹窗插槽拓展X5 -->
  60. <template v-if="item.type === 'slot1' && index === currentIndex">
  61. <slot name="slot1" :item="item" :index="index"></slot>
  62. </template>
  63. <template v-if="item.type === 'slot2' && index === currentIndex">
  64. <slot name="slot2" :item="item" :index="index"></slot>
  65. </template>
  66. <template v-if="item.type === 'slot3' && index === currentIndex">
  67. <slot name="slot3" :item="item" :index="index"></slot>
  68. </template>
  69. <template v-if="item.type === 'slot4' && index === currentIndex">
  70. <slot name="slot4" :item="item" :index="index"></slot>
  71. </template>
  72. <template v-if="item.type === 'slot5' && index === currentIndex">
  73. <slot name="slot5" :item="item" :index="index"></slot>
  74. </template>
  75. </view>
  76. </view>
  77. <view
  78. class="da-dropdown-content-mask"
  79. v-if="fixedTop"
  80. @tap="handlePopupMask"
  81. @touchmove.stop.prevent="handleMove" />
  82. </view>
  83. <view class="da-dropdown--blank" v-if="fixedTop"></view>
  84. </view>
  85. </template>
  86. <script>
  87. import { deepClone, menuInitOpts, getValueByKey, checkDataField } from './utils'
  88. import DropdownPicker from './components/picker.vue'
  89. import DropdownCell from './components/cell.vue'
  90. import DropdownFilter from './components/filter.vue'
  91. import DropdownDaterange from './components/daterange.vue'
  92. export default {
  93. components: { DropdownPicker, DropdownCell, DropdownFilter, DropdownDaterange },
  94. props: {
  95. /**
  96. * 导航菜单数据
  97. */
  98. dropdownMenu: {
  99. type: Array,
  100. default: () => [],
  101. },
  102. /**
  103. * 激活颜色
  104. */
  105. themeColor: {
  106. type: String,
  107. default: '#007aff',
  108. },
  109. /**
  110. * 常规颜色
  111. */
  112. textColor: {
  113. type: String,
  114. default: '#333333',
  115. },
  116. /**
  117. * 背景颜色,当固定在顶部时,此为必填
  118. */
  119. bgColor: {
  120. type: String,
  121. default: '#ffffff',
  122. },
  123. /**
  124. * 是否固定在顶部
  125. */
  126. fixedTop: {
  127. type: Boolean,
  128. default: false,
  129. },
  130. /**
  131. * 固定在头部时的位置,单位px
  132. * 如果页面定义了 "navigationStyle": "custom" ,因此固定头部时需要额外获取状态栏高度,以免被异形屏头部覆盖
  133. */
  134. fixedTopValue: {
  135. type: Number,
  136. default: 0,
  137. },
  138. /**
  139. * 弹窗过渡时间
  140. */
  141. duration: {
  142. type: [Number, String],
  143. default: 300,
  144. },
  145. },
  146. data() {
  147. return {
  148. currentIndex: -1,
  149. isVisible: false,
  150. isShow: false,
  151. menuList: [],
  152. hasSearch: false,
  153. searchItem: null,
  154. }
  155. },
  156. computed: {
  157. /**
  158. * 主题样式
  159. */
  160. dropdownStyle() {
  161. return `
  162. --dropdown-theme-color: ${this.themeColor};
  163. --dropdown-text-color: ${this.textColor};
  164. --dropdown-background-color: ${this.bgColor};
  165. --dropdown-popup-duration: ${this.duration / 1000}}s;
  166. --dropdown-fixed-top: ${this.fixedTopValue || 0}}px;
  167. `
  168. },
  169. },
  170. mounted() {
  171. this.initDomInfo()
  172. this.initData()
  173. },
  174. methods: {
  175. /**
  176. * 初始化数据
  177. */
  178. async initData() {
  179. const newMenu = deepClone(this.dropdownMenu || [])
  180. const allItem = { label: '不限', value: '-9999' }
  181. if (!newMenu || newMenu.length === 0) {
  182. this.menuList = []
  183. return
  184. }
  185. for (let i = 0; i < newMenu.length; i++) {
  186. let item = newMenu[i]
  187. if (item?.type) {
  188. item = { ...(menuInitOpts[newMenu[i]['type']] || {}), ...item }
  189. }
  190. // 处理异步初始项
  191. if (typeof item.syncDataFn === 'function') {
  192. item.isLoading = true
  193. item.syncDataFn(item, i).then((res) => {
  194. this.menuList[i].options = checkDataField(item.syncDataKey ? getValueByKey(res, item.syncDataKey) : res, item.field)
  195. // 处理 不限 项
  196. if (this.menuList[i].showAll) {
  197. if (this.menuList[i].options.findIndex((k) => k.value === allItem.value) === -1) {
  198. this.menuList[i].options.unshift(allItem)
  199. }
  200. }
  201. this.menuList[i].isLoading = false
  202. }).catch(() => {
  203. this.menuList[i].isLoading = false
  204. })
  205. }
  206. if (item.options?.length) {
  207. // 同步差异字段
  208. item.options = checkDataField(item.options, item.field)
  209. // 处理 不限 项
  210. if (item.showAll) {
  211. if (item.options.findIndex((k) => k.value === allItem.value) === -1) {
  212. item.options.unshift(allItem)
  213. }
  214. }
  215. }
  216. // 处理已选项
  217. if (typeof item.value !== 'undefined') {
  218. switch (item.type) {
  219. case 'cell':
  220. for (let x = 0; x < item.options.length; x++) {
  221. const k = item.options[x]
  222. if (k.value === item.value) {
  223. item.isActived = true
  224. break
  225. }
  226. }
  227. break
  228. case 'click':
  229. item.isActived = item.value === true
  230. break
  231. case 'sort':
  232. item.isActived = item.value === 'asc' || item.value === 'desc'
  233. break
  234. case 'filter':
  235. item.isActived = JSON.stringify(item.value || {}) !== '{}'
  236. break
  237. case 'picker':
  238. item.isActived = item.value?.length
  239. break
  240. case 'daterange':
  241. item.isActived = item.value?.start && item.value?.end
  242. break
  243. case 'slot':
  244. item.isActived = !!item.value
  245. break
  246. default:
  247. break
  248. }
  249. } else {
  250. item.isActived = false
  251. }
  252. // 搜索项特殊处理
  253. if (!this.hasSearch && item.type === 'search') {
  254. item.isHidden = 'true'
  255. this.searchItem = item
  256. this.hasSearch = true
  257. }
  258. newMenu[i] = item
  259. }
  260. this.menuList = newMenu
  261. },
  262. /**
  263. * 更新数据
  264. * @param prop
  265. * @param value
  266. * @param key
  267. */
  268. updateMenu(prop, value, key) {
  269. if (!key) {
  270. console.error('updateMenu 错误,key不存在')
  271. return
  272. }
  273. const idx = this.getMenuIndex(prop)
  274. this.menuList[idx][key] = key === 'options' ? checkDataField(value, this.menuList[idx].field || null) : value
  275. // 去除点击效果
  276. if (key === 'value' && (!value && value !== 0)) {
  277. this.menuList[idx][idx].isActived = false
  278. }
  279. },
  280. /**
  281. * 更新数据
  282. * @param prop
  283. * @param state
  284. */
  285. setMenuLoading(prop, state) {
  286. const idx = this.getMenuIndex(prop)
  287. this.menuList[idx].isLoading = state
  288. },
  289. /**
  290. * 获取菜单项位置
  291. * @param prop
  292. */
  293. getMenuIndex(prop) {
  294. return this.menuList.findIndex(k => k.prop === prop)
  295. },
  296. /**
  297. * 获取菜单数据
  298. */
  299. getMenuList() {
  300. return this.menuList
  301. },
  302. /**
  303. * 初始化获取系统信息
  304. */
  305. initDomInfo() {},
  306. /**
  307. * 打开弹窗
  308. * @param index 当前激活索引
  309. */
  310. openMenuItemPopup(index) {
  311. this.isShow = true
  312. this.isVisible = true
  313. this.currentIndex = index
  314. this.menuList[index].isClick = true
  315. this.$emit('open', this.currentIndex)
  316. },
  317. /**
  318. * 关闭弹窗
  319. */
  320. closeMenuPopup() {
  321. this.clearClickState()
  322. this.isShow = false
  323. // 延迟移除下拉弹窗
  324. setTimeout(() => {
  325. this.isVisible = false
  326. this.clearIndex()
  327. }, this.duration)
  328. this.$forceUpdate()
  329. this.$emit('close', this.currentIndex, this.menuList)
  330. },
  331. /**
  332. * 点击蒙层
  333. */
  334. handlePopupMask() {
  335. this.closeMenuPopup()
  336. },
  337. /**
  338. * 清除点击状态
  339. */
  340. clearClickState() {
  341. if (this.menuList?.length) {
  342. this.menuList.forEach(k => {
  343. k.isClick = false
  344. })
  345. }
  346. },
  347. /**
  348. * 清理滚动
  349. */
  350. handleMove() {
  351. return false
  352. },
  353. /**
  354. * 关闭弹窗
  355. */
  356. clearIndex() {
  357. this.currentIndex = -1
  358. },
  359. /**
  360. * 点击菜单项
  361. */
  362. handleMenuClick(index, item) {
  363. if (item.isLoading) return
  364. const dropdownMenu = this.menuList
  365. const menuItem = dropdownMenu[index]
  366. dropdownMenu.forEach(k => {
  367. k.isClick = false
  368. })
  369. if (menuItem.type === 'click') {
  370. return this.handleItemClick(menuItem, index)
  371. }
  372. if (menuItem.type === 'sort') {
  373. return this.handleItemSort(menuItem, index)
  374. }
  375. if (index === this.currentIndex) {
  376. item.isClick = false
  377. this.closeMenuPopup()
  378. return
  379. }
  380. item.isClick = true
  381. this.openMenuItemPopup(index)
  382. },
  383. /**
  384. * 获取菜单值
  385. */
  386. getMenuValue() {
  387. const obj = {}
  388. this.menuList.forEach(k => {
  389. obj[k.prop] = k.value
  390. })
  391. return obj
  392. },
  393. /**
  394. * 搜索输入
  395. */
  396. handleSearchChange(e) {
  397. this.searchItem.value = e?.detail?.value
  398. },
  399. /**
  400. * 确定搜索
  401. */
  402. handleSearch() {
  403. if (this.searchItem?.prop) {
  404. const res = { [this.searchItem.prop]: this.searchItem.value }
  405. this.$emit('confirm', res, this.getMenuValue())
  406. } else {
  407. console.error(`菜单项${this.searchItem.title}未定义prop,返回内容失败`)
  408. }
  409. },
  410. /**
  411. * 菜单项-下拉列表回调
  412. * @param callbackData 操作返回的数据
  413. * @param cellItem 下拉列表项数据
  414. * @param index 菜单索引
  415. */
  416. handleCellSelect(callbackData, cellItem, index) {
  417. const dropdownMenu = this.menuList
  418. const item = dropdownMenu[index]
  419. item.isClick = false
  420. if (cellItem.value === '-9999') {
  421. item.isActived = false
  422. item.activeTitle = undefined
  423. item.value = null
  424. } else {
  425. item.isActived = true
  426. item.activeTitle = cellItem.label
  427. item.value = cellItem.value
  428. }
  429. this.closeMenuPopup()
  430. this.$emit('confirm', callbackData, this.getMenuValue())
  431. },
  432. /**
  433. * 菜单项-点击
  434. * @param item 菜单项
  435. * @param index 菜单项索引
  436. */
  437. handleItemClick(item, index) {
  438. this.closeMenuPopup()
  439. if (this.currentIndex === -1) {
  440. this.currentIndex = index
  441. item.value = true
  442. item.isActived = true
  443. } else {
  444. item.value = false
  445. item.isActived = false
  446. this.clearIndex()
  447. }
  448. if (item?.prop) {
  449. const res = { [item.prop]: item.value }
  450. this.$emit('confirm', res, this.getMenuValue())
  451. } else {
  452. console.error(`菜单项${item.title}未定义prop,返回内容失败`)
  453. }
  454. },
  455. /**
  456. * 菜单项-排序
  457. * @param item 菜单项
  458. * @param index 菜单项索引
  459. */
  460. handleItemSort(item, index) {
  461. this.closeMenuPopup()
  462. if (item.value === 'asc') {
  463. item.value = 'desc'
  464. this.currentIndex = index
  465. item.isActived = true
  466. } else if (item.value === 'desc') {
  467. item.value = undefined
  468. item.isActived = false
  469. this.clearIndex()
  470. } else {
  471. item.value = 'asc'
  472. this.currentIndex = index
  473. item.isActived = true
  474. }
  475. if (item?.prop) {
  476. const res = { [item.prop]: item.value }
  477. this.$emit('confirm', res, this.getMenuValue())
  478. } else {
  479. console.error(`菜单项${item.title}未定义prop,返回内容失败`)
  480. }
  481. },
  482. /**
  483. * 菜单项-筛选回调
  484. * @param callbackData 操作返回的数据
  485. * @param filterData 筛选数据
  486. * @param index 菜单索引
  487. */
  488. handleFilterConfirm(callbackData, filterData, index) {
  489. const dropdownMenu = this.menuList
  490. const item = dropdownMenu[index]
  491. item.isClick = false
  492. item.isActived = JSON.stringify(filterData || {}) !== '{}'
  493. item.activeTitle = undefined
  494. item.value = filterData
  495. this.closeMenuPopup()
  496. this.$emit('confirm', callbackData, this.getMenuValue())
  497. },
  498. /**
  499. * 菜单项-级联回调
  500. * @param callbackData 操作返回的数据
  501. * @param pickerItem 级联已选数据
  502. * @param index 菜单索引
  503. */
  504. handlePickerConfirm(callbackData, pickerItem, index) {
  505. const dropdownMenu = this.menuList
  506. const item = dropdownMenu[index]
  507. item.isClick = false
  508. if (!pickerItem || pickerItem[0] === '-9999') {
  509. item.isActived = false
  510. item.activeTitle = undefined
  511. item.value = null
  512. } else {
  513. item.isActived = true
  514. item.value = pickerItem
  515. }
  516. this.closeMenuPopup()
  517. this.$emit('confirm', callbackData, this.getMenuValue())
  518. },
  519. /**
  520. * 菜单项-日期范围回调
  521. * @param callbackData 操作返回的数据
  522. * @param daterangeItem 日期范围数据
  523. * @param index 菜单索引
  524. */
  525. handleDaterangeConfirm(callbackData, daterangeItem, index) {
  526. const dropdownMenu = this.menuList
  527. const item = dropdownMenu[index]
  528. item.isClick = false
  529. if (daterangeItem?.start && daterangeItem?.end) {
  530. item.isActived = true
  531. item.value = daterangeItem
  532. } else {
  533. item.isActived = false
  534. item.activeTitle = undefined
  535. item.value = null
  536. }
  537. this.closeMenuPopup()
  538. this.$emit('confirm', callbackData, this.getMenuValue())
  539. },
  540. },
  541. }
  542. </script>
  543. <style lang="scss" scoped>
  544. @font-face {
  545. font-family: 'da-dropdown-iconfont'; /* Project id */
  546. src: url('data:application/octet-stream;base64,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') format('truetype');
  547. }
  548. .da-dropdown {
  549. --dropdown-menu-height: 80rpx;
  550. --dropdown-popup-duration: 0.3s;
  551. position: relative;
  552. z-index: 888;
  553. width: 100%;
  554. line-height: 1;
  555. &--blank {
  556. width: 100%;
  557. height: var(--dropdown-menu-height);
  558. }
  559. &-search {
  560. box-sizing: border-box;
  561. display: flex;
  562. align-items: center;
  563. justify-content: center;
  564. width: 100%;
  565. height: var(--dropdown-menu-height);
  566. padding: 10rpx 20rpx 6rpx;
  567. background: var(--dropdown-background-color, #fff);
  568. &-input {
  569. flex-grow: 1;
  570. height: 60rpx;
  571. padding: 0 20rpx;
  572. overflow: hidden;
  573. font-size: 28rpx;
  574. color: var(--dropdown-text-color);
  575. background: #f6f6f6;
  576. border-radius: 8rpx 0 0 8rpx;
  577. }
  578. &-btn {
  579. display: flex;
  580. flex-shrink: 0;
  581. align-items: center;
  582. justify-content: center;
  583. height: 60rpx;
  584. padding: 0 20rpx;
  585. overflow: hidden;
  586. font-size: 28rpx;
  587. color: var(--dropdown-text-color);
  588. background: #f6f6f6;
  589. border: none;
  590. border-radius: 0 8rpx 8rpx 0;
  591. &::after {
  592. display: none;
  593. }
  594. }
  595. }
  596. &-menu {
  597. position: relative;
  598. z-index: 1;
  599. display: flex;
  600. align-items: center;
  601. height: var(--dropdown-menu-height);
  602. background: var(--dropdown-background-color, #fff);
  603. box-shadow: 0 1rpx 0 0 #bbb;
  604. &-item {
  605. display: flex;
  606. flex-grow: 1;
  607. align-items: center;
  608. justify-content: center;
  609. height: 100%;
  610. &:hover {
  611. background: #fafafa;
  612. }
  613. &.is-hidden {
  614. display: none;
  615. }
  616. &--text {
  617. font-size: 24rpx;
  618. color: var(--dropdown-text-color);
  619. &.is-actived {
  620. color: var(--dropdown-theme-color);
  621. }
  622. }
  623. &--icon {
  624. flex-shrink: 0;
  625. margin-left: 2px;
  626. color: #bbb;
  627. .is--loading,
  628. .is--arrup,
  629. .is--arrdown {
  630. display: flex;
  631. align-items: center;
  632. justify-content: center;
  633. width: 24rpx;
  634. height: 24rpx;
  635. &::after {
  636. /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
  637. font-family: 'da-dropdown-iconfont' !important;
  638. font-size: 24rpx;
  639. font-style: normal;
  640. content: '\e604';
  641. -webkit-font-smoothing: antialiased;
  642. -moz-osx-font-smoothing: grayscale;
  643. }
  644. }
  645. .is--loading {
  646. animation: RunLoading 1s linear 0s infinite;
  647. &::after {
  648. content: '\e627';
  649. }
  650. }
  651. .is--arrup {
  652. color: var(--dropdown-theme-color);
  653. transform: rotate(180deg);
  654. }
  655. }
  656. &--sort {
  657. position: relative;
  658. margin-left: 6rpx;
  659. transition: transform 0.3s;
  660. &::before,
  661. &::after {
  662. position: absolute;
  663. top: calc(50% - 16rpx);
  664. left: 0;
  665. content: '';
  666. border-color: transparent;
  667. border-style: solid;
  668. border-width: 8rpx;
  669. border-bottom-color: #bbb;
  670. }
  671. &::after {
  672. top: calc(50% + 6rpx);
  673. border-top-color: #bbb;
  674. border-bottom-color: transparent;
  675. }
  676. &.is--asc::before {
  677. border-bottom-color: var(--dropdown-theme-color);
  678. }
  679. &.is--desc::after {
  680. border-top-color: var(--dropdown-theme-color);
  681. }
  682. }
  683. }
  684. }
  685. &-content {
  686. position: absolute;
  687. top: var(--dropdown-menu-height);
  688. left: 0;
  689. z-index: -1;
  690. box-sizing: border-box;
  691. width: 100%;
  692. overflow: hidden;
  693. visibility: hidden;
  694. box-shadow: 0 -1rpx 0 0 #bbb;
  695. opacity: 0;
  696. transition: all var(--dropdown-popup-duration, 0.3s) linear;
  697. &.is-show {
  698. z-index: 901;
  699. opacity: 1;
  700. }
  701. &.is-visible {
  702. visibility: visible;
  703. animation: CustomBS var(--dropdown-popup-duration) linear var(--dropdown-popup-duration) forwards;
  704. }
  705. &-mask {
  706. position: absolute;
  707. top: 0;
  708. bottom: 0;
  709. left: 0;
  710. z-index: 9;
  711. width: 100%;
  712. background: rgba(0, 0, 0, 0.3);
  713. }
  714. &-popup {
  715. position: relative;
  716. z-index: 10;
  717. max-height: 100%;
  718. overflow: auto;
  719. transition: transform var(--dropdown-popup-duration) linear;
  720. transform: translateY(-100%);
  721. &.is-show {
  722. transform: translateY(0);
  723. }
  724. }
  725. }
  726. &-popup-box {
  727. width: 100%;
  728. height: 100%;
  729. overflow: hidden;
  730. font-size: 28rpx;
  731. line-height: 1;
  732. background: var(--dropdown-background-color, #fff);
  733. transition: border-radius var(--dropdown-popup-duration) linear;
  734. }
  735. &.has-search {
  736. .da-dropdown {
  737. &-content {
  738. top: calc(var(--dropdown-menu-height) + var(--dropdown-menu-height));
  739. }
  740. }
  741. }
  742. /* 固定至顶 */
  743. &.is-fixed {
  744. z-index: 980;
  745. .da-dropdown {
  746. &-search {
  747. position: fixed;
  748. top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px));
  749. right: 0;
  750. left: 0;
  751. max-width: 1190px;
  752. margin: auto;
  753. }
  754. &-menu {
  755. position: fixed;
  756. top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px));
  757. right: 0;
  758. left: 0;
  759. max-width: 1190px;
  760. margin: auto;
  761. }
  762. &-content {
  763. position: fixed;
  764. top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px));
  765. right: 0;
  766. bottom: 0;
  767. left: 0;
  768. height: 100%;
  769. box-shadow: none;
  770. }
  771. }
  772. &.has-search {
  773. .da-dropdown {
  774. &-menu {
  775. top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px));
  776. }
  777. &-content {
  778. top: calc(var(--window-top, 0px) + var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px) + var(--dropdown-menu-height, 0px));
  779. }
  780. &--blank {
  781. height: calc(var(--dropdown-fixed-top, 0px) + var(--dropdown-menu-height, 0px) + var(--dropdown-menu-height, 0px));
  782. }
  783. }
  784. }
  785. }
  786. }
  787. @keyframes RunLoading {
  788. 0% {
  789. transform: rotate(0deg);
  790. }
  791. 100% {
  792. transform: rotate(360deg);
  793. }
  794. }
  795. @keyframes CustomBS {
  796. 0% {
  797. box-shadow: 0 -1rpx 0 0 #bbb;
  798. }
  799. 100% {
  800. box-shadow: 0 -1rpx 0 0 #bbb, 0 20rpx 20rpx -10rpx rgba(0, 0, 0, 0.1);
  801. }
  802. }
  803. </style>