tm-dropDownMenu.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <template>
  2. <view class="relative">
  3. <view class="tm-dropDownMenu absolute fulled" :style="{zIndex:101}">
  4. <view
  5. class="tm-dropDownMenu-bar"
  6. :class="[
  7. !black_tmeme && bgColor != 'white' ? bgColor : black_tmeme && bgColor == 'white' ? 'grey-darken-4' : bgColor,
  8. black_tmeme ? '' : 'shadow-' + bgColor + '-' + shadow,
  9. black_tmeme ? 'bk' : ''
  10. ]"
  11. >
  12. <tm-row align="center" justify="center">
  13. <tm-col color="none" justify="center" align="middle" @click="changeIndex(index)" v-for="(item, index) in formartData" :key="index" :width="itemLength + '%'">
  14. <view class="flex-center" :style="{height: height+'rpx',lineHeight:height+'rpx'}">
  15. <text class=" pr-10" :style="{fontSize:fontSize+'rpx'}" :class="[activeIndex == index ? 'text-' + activeColor : 'text-' + unColor]">{{ item.title }}</text>
  16. <tm-icons
  17. style="line-height: 0;"
  18. dense
  19. :color="activeIndex == index ? activeColor : unColor"
  20. size="24"
  21. :name="activeIndex == index ? 'icon-sort-up' : 'icon-sort-down'"
  22. ></tm-icons>
  23. </view>
  24. </tm-col>
  25. </tm-row>
  26. </view>
  27. <view v-if="formartData[activeIndex]" class="tm-dropDownMenu-body py-24 " :class="[black_tmeme ? 'grey-darken-5 bk' : 'white', 'shadow-' + shadow]">
  28. <view v-if="formartData[activeIndex]['children']" :style="{maxHeight:maxHeight+'rpx',overflowY: 'auto'}">
  29. <block v-for="(item, index) in formartData[activeIndex].children" :key="index">
  30. <block v-if="item['children']&&rendIdx>=index" >
  31. <view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">{{ item.title }}</view>
  32. <view class="optAniopt">
  33. <block v-if="item.model == 'checkbox'">
  34. <tm-groupcheckbox>
  35. <block v-for="(item2, index2) in item.children" :key="index2">
  36. <tm-checkbox :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
  37. <view class="px-10" :class="[item2['disabled'] || item['disabled'] ? 'opacity-6' : '']">
  38. <tm-button
  39. :fllowTheme="false"
  40. :black="black_tmeme"
  41. :theme="item2.checked ? color: (black_tmeme?'grey-darken-3':'grey-lighten-2')"
  42. :font-color="item2.checked ? color : 'grey'"
  43. dense
  44. style="width: auto"
  45. font-size="24"
  46. height="70"
  47. item-class="mx-14 my-10"
  48. plan
  49. block
  50. icon="icon-check-circle"
  51. :shadow="2"
  52. :height="64"
  53. :round="2"
  54. >
  55. {{ item2.title }}
  56. </tm-button>
  57. </view>
  58. </tm-checkbox>
  59. </block>
  60. </tm-groupcheckbox>
  61. </block>
  62. <block v-if="item.model == 'radio'">
  63. <tm-groupradio>
  64. <block v-for="(item2, index2) in item.children" :key="index2">
  65. <tm-radio :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
  66. <view class="px-10" :class="[item2['disabled'] || item['disabled'] ? 'opacity-6' : '']">
  67. <tm-button
  68. :fllowTheme="false"
  69. :black="black_tmeme"
  70. :theme="item2.checked ? color: (black_tmeme?'grey-darken-3':'grey-lighten-2')"
  71. :font-color="item2.checked ? color : 'grey'"
  72. dense
  73. style="width: auto"
  74. font-size="24"
  75. height="70"
  76. item-class="mx-14 my-10"
  77. plan
  78. block
  79. icon="icon-check-circle"
  80. :shadow="2"
  81. :height="64"
  82. :round="2"
  83. >
  84. {{ item2.title }}
  85. </tm-button>
  86. </view>
  87. </tm-radio>
  88. </block>
  89. </tm-groupradio>
  90. </block>
  91. <block v-if="item.model == 'list'">
  92. <tm-groupradio key="test">
  93. <block v-for="(item2, index2) in item.children" :key="index2">
  94. <tm-radio :inline="false" :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
  95. <view class="fulled">
  96. <tm-listitem
  97. :disabled="item2['disabled'] || item['disabled'] ? true : false"
  98. :title-color="item2.checked ? color : 'grey-darken-3'"
  99. :rightIconColor="item2.checked ? color : 'grey-lighten-3'"
  100. :margin="[24, 12]"
  101. :title="item2.title"
  102. fontSize="28"
  103. :shadow="0"
  104. :borderBottom="true"
  105. :rightIconSize='30'
  106. :rightIcon="item2.checked ? 'icon-check-circle' : ''"
  107. ></tm-listitem>
  108. </view>
  109. </tm-radio>
  110. </block>
  111. </tm-groupradio>
  112. </block>
  113. <block v-if="item.model == 'listCheckbox'">
  114. <tm-groupcheckbox >
  115. <block v-for="(item2, index2) in item.children" :key="index2">
  116. <tm-checkbox :inline="false" :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
  117. <view class="fulled">
  118. <tm-listitem
  119. :disabled="item2['disabled'] || item['disabled'] ? true : false"
  120. :title-color="item2.checked ? color : 'grey-darken-3'"
  121. :rightIconColor="item2.checked ? color : 'grey-lighten-3'"
  122. :margin="[24, 12]"
  123. :title="item2.title"
  124. fontSize="28"
  125. :shadow="0"
  126. :borderBottom="true"
  127. :rightIconSize='30'
  128. :rightIcon="item2.checked ? 'icon-check-circle' : ''"
  129. ></tm-listitem>
  130. </view>
  131. </tm-checkbox>
  132. </block>
  133. </tm-groupcheckbox>
  134. </block>
  135. </view>
  136. </block>
  137. <block v-else>
  138. <block v-if="item.model == 'input'&&rendIdx>=index" >
  139. <view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">{{ item.title }}</view>
  140. <tm-input
  141. :fllowTheme="fllowTheme"
  142. border-color="grey-lighten-1"
  143. :disabled="chiludis(item)"
  144. :black="black_tmeme"
  145. :color="color"
  146. :border-bottom="false"
  147. :input-type="item.type || 'text'"
  148. :value.sync="item.value"
  149. ></tm-input>
  150. </block>
  151. <block v-if="item.model == 'slider'&&rendIdx>=index" >
  152. <view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">
  153. {{ item.title }}
  154. <text class="px-24 " :class="[`text-${color}`]">
  155. {{ item.value ? item.value : '未设置' }}{{ item.value ? (item['suffix'] ? item.suffix : '') : '' }}
  156. </text>
  157. </view>
  158. <view class="px-42 py-24 optAniopt">
  159. <tm-slider
  160. :fllowTheme="fllowTheme"
  161. :disabled="chiludis(item)"
  162. :black="black_tmeme"
  163. :color="color"
  164. :max="item.max ? item.max : 100"
  165. v-model="item.value"
  166. >
  167. <template v-slot:tips>
  168. {{ item.value }}
  169. </template>
  170. </tm-slider>
  171. </view>
  172. </block>
  173. <block v-if="item.model == 'pickers'&&rendIdx>=index" >
  174. <view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">
  175. {{ item.title }}
  176. </view>
  177. <view class="optAniopt">
  178. <tm-pickers
  179. :default-value.sync="item.value"
  180. rang-key="title"
  181. :btn-color="color"
  182. :list="item.data"
  183. >
  184. <tm-input :value="pickTostring(item.value)" prefixp-icon="icon-calendaralt-fill" disabled :placeholder="item['placeholder']?item['placeholder']:'请选择'" suffix-icon="icon-sort-down"></tm-input>
  185. </tm-pickers>
  186. </view>
  187. </block>
  188. </block>
  189. </block>
  190. </view>
  191. <view class="flex-between px-32 pt-32">
  192. <tm-button :fllowTheme="fllowTheme" @click="getData" :theme="color" block style="width: 48%;" height="80">确认</tm-button>
  193. <tm-button
  194. :fllowTheme="fllowTheme"
  195. @click="resetinit"
  196. :black="black_tmeme"
  197. block
  198. :theme="color"
  199. :font-color="color"
  200. text
  201. shadow="0"
  202. style="width: 48%;"
  203. height="80"
  204. >
  205. 重置
  206. </tm-button>
  207. </view>
  208. </view>
  209. </view>
  210. <view @click="activeIndex=-1" v-if="activeIndex>-1" class="fixed fulled" :style="{height:height_bg+'px',top:vtop+'px',width:barwidth,background:'rgba(0,0,0,0.33)',zIndex:100}">
  211. </view>
  212. </view>
  213. </template>
  214. <script>
  215. /**
  216. * 下拉选项
  217. * @property {String} color = [] 默认:primary ,主题色下方选项子组件的主题色。
  218. * @property {String} un-color = [] 默认:black ,默认未激活时。bar条上的文字颜色
  219. * @property {String} active-color = [] 默认:primary ,默认激活时。bar条上的文字颜色
  220. * @property {String} bg-color = [] 默认:white ,导航条背景主题色。
  221. * @property {Number} shadow = [] 默认:10 ,导航条的投影。
  222. * @property {Array} list = [] 默认:[] ,数据格式见文档
  223. * @property {Number} maxHeight = [] 默认:650 ,弹出的标签页,最大内容高度,超过自动滚动。
  224. * @property {Number} height = [] 默认:88 ,标签导航的高度
  225. * @property {Number} font-size = [] 默认:28 ,标签导航的文字大小
  226. * @property {Array} default-selected = [] 默认:[] ,默认赋值选中的选项,注意可以是id数组或者对象数组,对象数组情况下必须含id标签符,且是唯一的。
  227. * @property {Boolean} black = [] 默认:false ,暗黑模式。
  228. * @property {Function} change 切换选项页面时触发。
  229. * @property {Function} confirm 点击确认按钮时触发,返回所有选中的项。
  230. * @example <tm-dropDownMenu color="orange" :list="list"></tm-dropDownMenu>
  231. */
  232. import tmRow from '@/tm-vuetify/components/tm-row/tm-row.vue';
  233. import tmCol from '@/tm-vuetify/components/tm-col/tm-col.vue';
  234. import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
  235. import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
  236. import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
  237. import tmGroupcheckbox from '@/tm-vuetify/components/tm-groupcheckbox/tm-groupcheckbox.vue';
  238. import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
  239. import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
  240. import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
  241. import tmSlider from '@/tm-vuetify/components/tm-slider/tm-slider.vue';
  242. import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
  243. import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
  244. export default {
  245. components: {tmPickers, tmRow, tmCol, tmButton, tmIcons, tmInput, tmGroupcheckbox, tmCheckbox, tmGroupradio, tmRadio, tmSlider, tmListitem },
  246. name: 'tm-dropDownMenu',
  247. props: {
  248. // 主题色下方选项子组件的主题色
  249. color: {
  250. type: String,
  251. default: 'primary'
  252. },
  253. // 默认未激活时。bar条上的文字颜色
  254. unColor: {
  255. type: String,
  256. default: 'black'
  257. },
  258. // 默认激活时。bar条上的文字颜色
  259. activeColor: {
  260. type: String,
  261. default: 'primary'
  262. },
  263. // 背景颜色。
  264. bgColor: {
  265. type: String,
  266. default: 'white'
  267. },
  268. list: {
  269. type: Array,
  270. default: () => {
  271. return [];
  272. }
  273. },
  274. maxHeight:{
  275. type:Number|String,
  276. default:650
  277. },
  278. height:{
  279. type:Number|String,
  280. default:88
  281. },
  282. fontSize:{
  283. type:Number|String,
  284. default:28
  285. },
  286. //菜单的投影。
  287. shadow: {
  288. type: Number | String,
  289. default: 10
  290. },
  291. // 可以是id索引也可以是对象数组,可以混着来。
  292. defaultSelected: {
  293. type: Array,
  294. default: () => {
  295. return [];
  296. }
  297. },
  298. black: {
  299. type: Boolean | String,
  300. default: null
  301. },
  302. // 跟随主题色的改变而改变。
  303. fllowTheme: {
  304. type: Boolean | String,
  305. default: true
  306. }
  307. },
  308. computed: {
  309. itemLength: function() {
  310. if (this.list.length == 0) return 100;
  311. return 100 / this.list.length;
  312. },
  313. black_tmeme: function() {
  314. if (this.black !== null) return this.black;
  315. return this.$tm.vx.state().tmVuetify.black;
  316. }
  317. },
  318. watch:{
  319. list:{
  320. deep:true,
  321. handler(){
  322. this.$nextTick(function() {
  323. this.formartData = this.chulidata();
  324. });
  325. }
  326. }
  327. },
  328. data() {
  329. return {
  330. activeIndex: -1,
  331. formartData: [],
  332. oldList: [],
  333. test: [],
  334. height_bg:0,
  335. vtop:0,
  336. maxLeng:40,//最大渲染级别
  337. rendIdx:0,
  338. barwidth:'100%'
  339. };
  340. },
  341. created() {
  342. this.height_bg = uni.getSystemInfoSync().screenHeight;
  343. },
  344. mounted() {
  345. this.$nextTick(function() {
  346. this.formartData = this.chulidata();
  347. this.oldList = [...this.list]
  348. let t = this;
  349. uni.$tm.sleep(40).then(e=>{
  350. uni.createSelectorQuery().in(this).select('.tm-dropDownMenu').boundingClientRect().exec(function(v){
  351. // #ifdef H5
  352. t.vtop = v[0].top+v[0].height+uni.getSystemInfoSync().windowTop;
  353. // #endif
  354. // #ifndef H5
  355. t.vtop = v[0].top+v[0].height;
  356. console.log(v[0]);
  357. // #endif
  358. t.barwidth = v[0].width+'px'
  359. })
  360. })
  361. });
  362. },
  363. methods: {
  364. pickTostring(item){
  365. let p = [];
  366. item.forEach(el=>{
  367. if(typeof(el)=="string"){
  368. p.push(el)
  369. }else if(typeof el == 'object'){
  370. p.push(el.title);
  371. }
  372. })
  373. return p.join("-")
  374. },
  375. chiludis(item) {
  376. return item?.disabled || false;
  377. },
  378. chulidata(list) {
  379. // 处理相关数据格式以保持 一致。
  380. let t = this;
  381. let p = this.$tm.deepClone(list||this.list);
  382. for (let j = 0; j < p.length; j++) {
  383. p[j]['dot'] = 0;
  384. if (p[j]['children']) {
  385. let ic = p[j].children;
  386. if (ic.length > 0) {
  387. for (let k = 0; k < ic.length; k++) {
  388. let children = ic[k]['children'];
  389. if (children) {
  390. if (ic[k]['model'] == 'checkbox'|| ic[k]['model'] == 'listCheckbox' || ic[k]['model'] == 'list' || (ic[k]['model'] == 'radio' && children.length > 0)) {
  391. for (let z = 0; z < children.length; z++) {
  392. let im = children[z];
  393. if (!im.hasOwnProperty('checked')) {
  394. im['checked'] = false;
  395. }
  396. for (let i = 0; i < t.defaultSelected.length; i++) {
  397. let lsitem = t.defaultSelected[i];
  398. if (typeof lsitem === 'object') {
  399. if (lsitem['id'] == im['id']) {
  400. im['checked'] = true;
  401. }
  402. } else {
  403. if (lsitem == im['id']) {
  404. im['checked'] = true;
  405. }
  406. }
  407. }
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }
  414. }
  415. return p;
  416. },
  417. // 重置只重置当前打开的页面数量,并不重置其它页面数据。
  418. resetinit(index) {
  419. let pd = this.formartData[this.activeIndex];
  420. if (pd['children']) {
  421. let ic = pd.children;
  422. if (ic.length > 0) {
  423. for (let k = 0; k < ic.length; k++) {
  424. let children = ic[k]['children'];
  425. if (children) {
  426. if (ic[k]['model'] == 'checkbox'||ic[k]['model'] == 'listCheckbox'||ic[k]['model'] == 'list' || (ic[k]['model'] == 'radio' && children.length > 0)) {
  427. for (let z = 0; z < children.length; z++) {
  428. let im = children[z];
  429. im['checked'] = false;
  430. }
  431. }
  432. } else {
  433. if (ic[k]['model'] == 'slider') {
  434. ic[k].value = 0;
  435. } else if (ic[k]['model'] == 'input') {
  436. ic[k].value = '';
  437. } else if (ic[k]['model'] == 'pickers') {
  438. ic[k].value = [];
  439. } else if (ic[k]['model'] == 'pickersDate') {
  440. ic[k].value = "";
  441. }
  442. }
  443. }
  444. }
  445. }
  446. const p = this.chulidata(this.oldList);
  447. this.formartData.splice(this.activeIndex, 1, p[this.activeIndex]);
  448. },
  449. changeIndex(index) {
  450. let t = this;
  451. let itmod = 659;
  452. clearInterval(itmod)
  453. if (this.activeIndex === index) {
  454. this.activeIndex = -1;
  455. } else {
  456. this.activeIndex = index;
  457. }
  458. this.$emit('change', this.activeIndex);
  459. this.rendIdx = 0;
  460. clearInterval(itmod)
  461. itmod = setInterval(function(){
  462. t.rendIdx+=1;
  463. if(t.rendIdx>t.maxLeng||t.activeIndex==-1){
  464. clearInterval(itmod)
  465. }
  466. },10)
  467. },
  468. // 获取选中以及填写的数据。
  469. getData() {
  470. let p = [...this.formartData];
  471. let xz = [];
  472. for (let i = 0; i < p.length; i++) {
  473. if (p[i]['children']) {
  474. for (let j = 0; j < p[i].children.length; j++) {
  475. let ic = p[i].children[j];
  476. let ps = [];
  477. if (ic.model == 'checkbox' || ic.model == 'radio' || ic.model == 'listCheckbox' || ic.model == 'list') {
  478. if (ic['children']) {
  479. for (let k = 0; k < ic.children.length; k++) {
  480. if (ic.children[k].checked === true) {
  481. ps.push(ic.children[k]);
  482. }
  483. }
  484. }
  485. } else if (ic.model == 'input' || ic.model == 'slider') {
  486. ps.push(ic);
  487. } else if(ic.model == 'pickers'){
  488. ps.push(ic);
  489. }
  490. let pyz = { ...ic };
  491. delete pyz.children;
  492. xz.push({
  493. ...pyz,
  494. children: ps
  495. });
  496. }
  497. }
  498. }
  499. this.$emit('confirm', xz);
  500. this.activeIndex = -1;
  501. }
  502. }
  503. };
  504. </script>
  505. <style lang="scss" scoped>
  506. .tm-dropDownMenu {
  507. position: relative;
  508. .tm-dropDownMenu-bar {
  509. position: relative;
  510. z-index: 303;
  511. }
  512. .tm-dropDownMenu-body {
  513. background-color: rgba(0, 0, 0, 0.35);
  514. min-height: 150upx;
  515. position: absolute;
  516. z-index: 304;
  517. width: 100%;
  518. }
  519. }
  520. .optAniopt{
  521. animation: opt 0.2s linear;
  522. }
  523. @keyframes opt{
  524. 0%{opacity: 0;}
  525. 100%{opacity: 1;}
  526. }
  527. </style>