uni-data-select.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <template>
  2. <view class="uni-stat__select">
  3. <span v-if="label" class="uni-label-text hide-on-phone">{{label + ':'}}</span>
  4. <view class="uni-stat-box" :class="{'uni-stat__actived': current}">
  5. <view class="uni-select" :class="{'uni-select--disabled':disabled}">
  6. <view class="uni-select__input-box" @click="toggleSelector">
  7. <view v-if="current" :class="['uni-select__input-text',{'studentStatus':selectName=='studentStatus'}]">
  8. <!-- 学生名单状态 -->
  9. <template v-if="selectName=='studentStatus'">
  10. <view class="status_icon normal_icon" v-if="current == '正常'"></view>
  11. <view class="status_icon not_included_icon" v-if="current == '不计入'"></view>
  12. <view class="status_icon no_release_icon" v-if="current == '缺考'"></view>
  13. <view class="status_icon ready_close_icon" v-if="current == '违纪'"></view>
  14. <text class="input_text">{{textShow}}</text>
  15. </template>
  16. <template v-else>{{textShow}}</template>
  17. </view>
  18. <view v-else class="uni-select__input-text uni-select__input-placeholder">{{typePlaceholder}}</view>
  19. <view key="clear-button" v-if="current && clear && !disabled" @click.stop="clearVal">
  20. <uni-icons type="clear" color="#c0c4cc" size="24" />
  21. </view>
  22. <view key="arrow-button" v-else>
  23. <uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" />
  24. </view>
  25. </view>
  26. <view class="uni-select--mask" v-if="showSelector" @click="toggleSelector" />
  27. <view class="uni-select__selector" :style="getOffsetByPlacement" v-if="showSelector">
  28. <view :class="placement=='bottom'?'uni-popper__arrow_bottom':'uni-popper__arrow_top'"></view>
  29. <scroll-view scroll-y="true" class="uni-select__selector-scroll">
  30. <view class="uni-select__selector-empty" v-if="mixinDatacomResData.length === 0">
  31. <text>{{emptyTips}}</text>
  32. </view>
  33. <view v-else :class="['uni-select__selector-item',{'uni-select__selector-item_active':current===item.text}]" v-for="(item,index) in mixinDatacomResData" :key="index"
  34. @click="change(item)">
  35. <view class="uni-select__selector-item_text">
  36. <!-- 学生名单状态 -->
  37. <template v-if="selectName=='studentStatus'">
  38. <view class="status_icon normal_icon" v-if="item.value == '1'"></view>
  39. <view class="status_icon not_included_icon" v-if="item.value == '3'"></view>
  40. <view class="status_icon no_release_icon" v-if="item.value == '0'"></view>
  41. <view class="status_icon ready_close_icon" v-if="item.value == '2'"></view>
  42. </template>
  43. <text :class="['uni-select__selector_text',{'uni-select__selector__disabled': item.disable}]">{{formatItemName(item)}}</text>
  44. </view>
  45. <uni-icons v-if="current===item.text" type="checkmarkempty" color="#2E64FA" style="font-size: 28rpx;" />
  46. </view>
  47. </scroll-view>
  48. </view>
  49. </view>
  50. </view>
  51. </view>
  52. </template>
  53. <script>
  54. import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
  55. /**
  56. * DataChecklist 数据选择器
  57. * @description 通过数据渲染的下拉框组件
  58. * @tutorial https://uniapp.dcloud.io/component/uniui/uni-data-select
  59. * @property {String} value 默认值
  60. * @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
  61. * @property {Boolean} clear 是否可以清空已选项
  62. * @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
  63. * @property {String} label 左侧标题
  64. * @property {String} placeholder 输入框的提示文字
  65. * @property {Boolean} disabled 是否禁用
  66. * @property {String} placement 弹出位置
  67. * @property {String} selectName 下拉框名称 2025.8.1 新增 处理下拉框样式
  68. * @value top 顶部弹出
  69. * @value bottom 底部弹出(default)
  70. * @event {Function} change 选中发生变化触发
  71. */
  72. export default {
  73. name: "uni-data-select",
  74. mixins: [uniCloud.mixinDatacom || {}],
  75. components:{
  76. uniIcons
  77. },
  78. props: {
  79. localdata: {
  80. type: Array,
  81. default () {
  82. return []
  83. }
  84. },
  85. value: {
  86. type: [String, Number],
  87. default: ''
  88. },
  89. modelValue: {
  90. type: [String, Number],
  91. default: ''
  92. },
  93. label: {
  94. type: String,
  95. default: ''
  96. },
  97. placeholder: {
  98. type: String,
  99. default: '请选择'
  100. },
  101. emptyTips: {
  102. type: String,
  103. default: '无选项'
  104. },
  105. clear: {
  106. type: Boolean,
  107. default: true
  108. },
  109. defItem: {
  110. type: Number,
  111. default: 0
  112. },
  113. disabled: {
  114. type: Boolean,
  115. default: false
  116. },
  117. // 格式化输出 用法 field="_id as value, version as text, uni_platform as label" format="{label} - {text}"
  118. format: {
  119. type: String,
  120. default: ''
  121. },
  122. placement: {
  123. type: String,
  124. default: 'bottom'
  125. },
  126. selectName: {
  127. type: String,
  128. default: ''
  129. },
  130. },
  131. data() {
  132. return {
  133. showSelector: false,
  134. current: '',
  135. mixinDatacomResData: [],
  136. apps: [],
  137. channels: [],
  138. cacheKey: "uni-data-select-lastSelectedValue",
  139. };
  140. },
  141. created() {
  142. this.debounceGet = this.debounce(() => {
  143. this.query();
  144. }, 300);
  145. if (this.collection && !this.localdata.length) {
  146. this.debounceGet();
  147. }
  148. },
  149. computed: {
  150. typePlaceholder() {
  151. const text = {
  152. 'opendb-stat-app-versions': '版本',
  153. 'opendb-app-channels': '渠道',
  154. 'opendb-app-list': '应用'
  155. }
  156. const common = this.placeholder
  157. const placeholder = text[this.collection]
  158. return placeholder ?
  159. common + placeholder :
  160. common
  161. },
  162. valueCom() {
  163. // #ifdef VUE3
  164. return this.modelValue;
  165. // #endif
  166. // #ifndef VUE3
  167. return this.value;
  168. // #endif
  169. },
  170. textShow() {
  171. // 长文本显示
  172. let text = this.current;
  173. return text;
  174. },
  175. getOffsetByPlacement() {
  176. switch (this.placement) {
  177. case 'top':
  178. return "bottom:calc(100% + 12px);";
  179. case 'bottom':
  180. return "top:calc(100% + 12px);";
  181. }
  182. }
  183. },
  184. watch: {
  185. localdata: {
  186. immediate: true,
  187. handler(val, old) {
  188. if (Array.isArray(val) && old !== val) {
  189. this.mixinDatacomResData = val
  190. }
  191. }
  192. },
  193. valueCom(val, old) {
  194. this.initDefVal()
  195. },
  196. mixinDatacomResData: {
  197. immediate: true,
  198. handler(val) {
  199. if (val.length) {
  200. this.initDefVal()
  201. }
  202. }
  203. },
  204. },
  205. methods: {
  206. debounce(fn, time = 100) {
  207. let timer = null
  208. return function(...args) {
  209. if (timer) clearTimeout(timer)
  210. timer = setTimeout(() => {
  211. fn.apply(this, args)
  212. }, time)
  213. }
  214. },
  215. // 执行数据库查询
  216. query() {
  217. this.mixinDatacomEasyGet();
  218. },
  219. // 监听查询条件变更事件
  220. onMixinDatacomPropsChange() {
  221. if (this.collection) {
  222. this.debounceGet();
  223. }
  224. },
  225. initDefVal() {
  226. let defValue = ''
  227. if ((this.valueCom || this.valueCom === 0) && !this.isDisabled(this.valueCom)) {
  228. defValue = this.valueCom
  229. } else {
  230. let strogeValue
  231. if (this.collection) {
  232. strogeValue = this.getCache()
  233. }
  234. if (strogeValue || strogeValue === 0) {
  235. defValue = strogeValue
  236. } else {
  237. let defItem = ''
  238. if (this.defItem > 0 && this.defItem <= this.mixinDatacomResData.length) {
  239. defItem = this.mixinDatacomResData[this.defItem - 1].value
  240. }
  241. defValue = defItem
  242. }
  243. if (defValue || defValue === 0) {
  244. this.emit(defValue)
  245. }
  246. }
  247. const def = this.mixinDatacomResData.find(item => item.value === defValue)
  248. this.current = def ? this.formatItemName(def) : ''
  249. },
  250. /**
  251. * @param {[String, Number]} value
  252. * 判断用户给的 value 是否同时为禁用状态
  253. */
  254. isDisabled(value) {
  255. let isDisabled = false;
  256. this.mixinDatacomResData.forEach(item => {
  257. if (item.value === value) {
  258. isDisabled = item.disable
  259. }
  260. })
  261. return isDisabled;
  262. },
  263. clearVal() {
  264. this.emit('')
  265. this.current = ''
  266. if (this.collection) {
  267. this.removeCache()
  268. }
  269. },
  270. change(item) {
  271. if (!item.disable) {
  272. this.showSelector = false
  273. this.current = this.formatItemName(item)
  274. this.emit(item.value)
  275. }
  276. },
  277. emit(val) {
  278. this.$emit('input', val)
  279. this.$emit('update:modelValue', val)
  280. this.$emit('change', val)
  281. if (this.collection) {
  282. this.setCache(val);
  283. }
  284. },
  285. toggleSelector() {
  286. if (this.disabled) {
  287. return
  288. }
  289. this.showSelector = !this.showSelector
  290. },
  291. formatItemName(item) {
  292. let {
  293. text,
  294. value,
  295. channel_code
  296. } = item
  297. channel_code = channel_code ? `(${channel_code})` : ''
  298. if (this.format) {
  299. // 格式化输出
  300. let str = "";
  301. str = this.format;
  302. for (let key in item) {
  303. str = str.replace(new RegExp(`{${key}}`, "g"), item[key]);
  304. }
  305. return str;
  306. } else {
  307. return this.collection.indexOf('app-list') > 0 ?
  308. `${text}(${value})` :
  309. (
  310. text ?
  311. text :
  312. `未命名${channel_code}`
  313. )
  314. }
  315. },
  316. // 获取当前加载的数据
  317. getLoadData() {
  318. return this.mixinDatacomResData;
  319. },
  320. // 获取当前缓存key
  321. getCurrentCacheKey() {
  322. return this.collection;
  323. },
  324. // 获取缓存
  325. getCache(name = this.getCurrentCacheKey()) {
  326. let cacheData = uni.getStorageSync(this.cacheKey) || {};
  327. return cacheData[name];
  328. },
  329. // 设置缓存
  330. setCache(value, name = this.getCurrentCacheKey()) {
  331. let cacheData = uni.getStorageSync(this.cacheKey) || {};
  332. cacheData[name] = value;
  333. uni.setStorageSync(this.cacheKey, cacheData);
  334. },
  335. // 删除缓存
  336. removeCache(name = this.getCurrentCacheKey()) {
  337. let cacheData = uni.getStorageSync(this.cacheKey) || {};
  338. delete cacheData[name];
  339. uni.setStorageSync(this.cacheKey, cacheData);
  340. },
  341. }
  342. }
  343. </script>
  344. <style lang="scss">
  345. $uni-base-color: #6a6a6a !default;
  346. $uni-main-color: #333 !default;
  347. $uni-secondary-color: #909399 !default;
  348. $uni-border-3: #e5e5e5;
  349. /* #ifndef APP-NVUE */
  350. @media screen and (max-width: 500px) {
  351. .hide-on-phone {
  352. display: none;
  353. }
  354. }
  355. /* #endif */
  356. .uni-stat__select {
  357. display: flex;
  358. align-items: center;
  359. // padding: 15px;
  360. /* #ifdef H5 */
  361. cursor: pointer;
  362. /* #endif */
  363. width: 100%;
  364. flex: 1;
  365. box-sizing: border-box;
  366. }
  367. .uni-stat-box {
  368. background-color: #fff;
  369. width: 100%;
  370. flex: 1;
  371. }
  372. .uni-stat__actived {
  373. width: 100%;
  374. flex: 1;
  375. // outline: 1px solid #2979ff;
  376. }
  377. .uni-label-text {
  378. font-size: 14px;
  379. font-weight: bold;
  380. color: $uni-base-color;
  381. margin: auto 0;
  382. margin-right: 5px;
  383. }
  384. .uni-select {
  385. font-size: 14px;
  386. border: 1px solid $uni-border-3;
  387. box-sizing: border-box;
  388. border-radius: 4px;
  389. padding: 0 5px;
  390. padding-left: 10px;
  391. position: relative;
  392. /* #ifndef APP-NVUE */
  393. display: flex;
  394. user-select: none;
  395. /* #endif */
  396. flex-direction: row;
  397. align-items: center;
  398. border-bottom: solid 1px $uni-border-3;
  399. width: 100%;
  400. flex: 1;
  401. height: 35px;
  402. &--disabled {
  403. background-color: #f5f7fa;
  404. cursor: not-allowed;
  405. }
  406. }
  407. .uni-select__label {
  408. font-size: 16px;
  409. // line-height: 22px;
  410. height: 35px;
  411. padding-right: 10px;
  412. color: $uni-secondary-color;
  413. }
  414. .uni-select__input-box {
  415. height: 35px;
  416. width: 0px;
  417. position: relative;
  418. /* #ifndef APP-NVUE */
  419. display: flex;
  420. /* #endif */
  421. flex: 1;
  422. flex-direction: row;
  423. align-items: center;
  424. }
  425. .uni-select__input {
  426. flex: 1;
  427. font-size: 14px;
  428. height: 22px;
  429. line-height: 22px;
  430. }
  431. .uni-select__input-plac {
  432. font-size: 14px;
  433. color: $uni-secondary-color;
  434. }
  435. .uni-select__selector {
  436. /* #ifndef APP-NVUE */
  437. box-sizing: border-box;
  438. /* #endif */
  439. position: absolute;
  440. left: 0;
  441. width: 100%;
  442. background-color: #FFFFFF;
  443. border: 1px solid #EBEEF5;
  444. border-radius: 6px;
  445. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  446. z-index: 3;
  447. padding: 4px 0;
  448. }
  449. .uni-select__selector-scroll {
  450. /* #ifndef APP-NVUE */
  451. max-height: 200px;
  452. box-sizing: border-box;
  453. /* #endif */
  454. }
  455. /* #ifdef H5 */
  456. @media (min-width: 768px) {
  457. .uni-select__selector-scroll {
  458. max-height: 600px;
  459. }
  460. }
  461. /* #endif */
  462. .uni-select__selector-empty,
  463. .uni-select__selector-item {
  464. /* #ifndef APP-NVUE */
  465. display: flex;
  466. cursor: pointer;
  467. /* #endif */
  468. line-height: 35px;
  469. font-size: 14px;
  470. text-align: center;
  471. /* border-bottom: solid 1px $uni-border-3; */
  472. padding: 0px 10px;
  473. }
  474. .uni-select__selector-item{
  475. justify-content: space-between;
  476. align-items: center;
  477. }
  478. .uni-select__selector-item_text{
  479. flex:1;
  480. overflow: hidden;
  481. text-align: left;
  482. display: flex;
  483. align-items: center;
  484. .status_icon{
  485. width:16rpx;
  486. height:16rpx;
  487. border-radius: 50%;
  488. margin-right:20rpx;
  489. flex-shrink: 0;
  490. &.normal_icon {
  491. background: #2bc644;
  492. }
  493. &.not_included_icon {
  494. background: #C0C4CC;
  495. }
  496. &.no_release_icon{
  497. background: #fb9f34;
  498. }
  499. &.ready_close_icon{
  500. background: #f56c6c;
  501. }
  502. }
  503. }
  504. .uni-select__selector_text{
  505. flex:1;
  506. white-space: nowrap;
  507. overflow: hidden;
  508. text-overflow: ellipsis;
  509. text-align: left;
  510. }
  511. .uni-select__selector-item_active{
  512. color: #2E64FA !important;
  513. font-weight: 400;
  514. }
  515. .uni-select__selector-item:hover {
  516. background-color: #f9f9f9;
  517. }
  518. .uni-select__selector-empty:last-child,
  519. .uni-select__selector-item:last-child {
  520. /* #ifndef APP-NVUE */
  521. border-bottom: none;
  522. /* #endif */
  523. }
  524. .uni-select__selector__disabled {
  525. opacity: 0.4;
  526. cursor: default;
  527. }
  528. /* picker 弹出层通用的指示小三角 */
  529. .uni-popper__arrow_bottom,
  530. .uni-popper__arrow_bottom::after,
  531. .uni-popper__arrow_top,
  532. .uni-popper__arrow_top::after,
  533. {
  534. position: absolute;
  535. display: block;
  536. width: 0;
  537. height: 0;
  538. border-color: transparent;
  539. border-style: solid;
  540. border-width: 6px;
  541. }
  542. .uni-popper__arrow_bottom {
  543. filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
  544. top: -6px;
  545. left: 10%;
  546. margin-right: 3px;
  547. border-top-width: 0;
  548. border-bottom-color: #EBEEF5;
  549. }
  550. .uni-popper__arrow_bottom::after {
  551. content: " ";
  552. top: 1px;
  553. margin-left: -6px;
  554. border-top-width: 0;
  555. border-bottom-color: #fff;
  556. }
  557. .uni-popper__arrow_top {
  558. filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
  559. bottom: -6px;
  560. left: 10%;
  561. margin-right: 3px;
  562. border-bottom-width: 0;
  563. border-top-color: #EBEEF5;
  564. }
  565. .uni-popper__arrow_top::after {
  566. content: " ";
  567. bottom: 1px;
  568. margin-left: -6px;
  569. border-bottom-width: 0;
  570. border-top-color: #fff;
  571. }
  572. .uni-select__input-text {
  573. // width: 280px;
  574. width: 100%;
  575. color: $uni-main-color;
  576. white-space: nowrap;
  577. text-overflow: ellipsis;
  578. -o-text-overflow: ellipsis;
  579. overflow: hidden;
  580. &.studentStatus{
  581. display: flex;
  582. align-items: center;
  583. }
  584. .status_icon{
  585. width:16rpx;
  586. height:16rpx;
  587. border-radius: 50%;
  588. margin-right:10rpx;
  589. flex-shrink: 0;
  590. &.normal_icon {
  591. background: #2bc644;
  592. }
  593. &.not_included_icon {
  594. background: #C0C4CC;
  595. }
  596. &.no_release_icon{
  597. background: #fb9f34;
  598. }
  599. &.ready_close_icon{
  600. background: #f56c6c;
  601. }
  602. }
  603. }
  604. .uni-select__input-placeholder {
  605. color: $uni-base-color;
  606. font-size: 12px;
  607. }
  608. .uni-select--mask {
  609. position: fixed;
  610. top: 0;
  611. bottom: 0;
  612. right: 0;
  613. left: 0;
  614. z-index: 2;
  615. }
  616. </style>