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