Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

459 Zeilen
16 KiB

  1. <template>
  2. <view
  3. class="u-subsection"
  4. ref="u-subsection"
  5. :class="[`u-subsection--${mode}`]"
  6. :style="[addStyle(customStyle), wrapperStyle]"
  7. >
  8. <view
  9. class="u-subsection__bar cursor-pointer"
  10. ref="u-subsection__bar"
  11. :style="[barStyle]"
  12. :class="[
  13. mode === 'button' && 'u-subsection--button__bar',
  14. innerCurrent === 0 &&
  15. mode === 'subsection' &&
  16. 'u-subsection__bar--first',
  17. innerCurrent > 0 &&
  18. innerCurrent < list.length - 1 &&
  19. mode === 'subsection' &&
  20. 'u-subsection__bar--center',
  21. innerCurrent === list.length - 1 &&
  22. mode === 'subsection' &&
  23. 'u-subsection__bar--last',
  24. ]"
  25. ></view>
  26. <view
  27. class="u-subsection__item cursor-pointer"
  28. :class="[
  29. `u-subsection__item--${index}`,
  30. index < list.length - 1 &&
  31. 'u-subsection__item--no-border-right',
  32. index === 0 && 'u-subsection__item--first',
  33. index === list.length - 1 && 'u-subsection__item--last',
  34. getTextViewDisableClass(index),
  35. ]"
  36. :ref="`u-subsection__item--${index}`"
  37. :style="[itemStyle(index)]"
  38. @tap="clickHandler(index)"
  39. v-for="(item, index) in list"
  40. :key="index"
  41. >
  42. <text
  43. class="u-subsection__item__text"
  44. :class="[disabled ? 'u-subsection--disabled' : '']"
  45. :style="[textStyle(index,item)]"
  46. >{{ getText(item) }}</text
  47. >
  48. </view>
  49. </view>
  50. </template>
  51. <script>
  52. // #ifdef APP-NVUE
  53. const dom = uni.requireNativePlugin("dom");
  54. const animation = uni.requireNativePlugin("animation");
  55. // #endif
  56. import { props } from "./props.js";
  57. import { mpMixin } from '../../libs/mixin/mpMixin';
  58. import { mixin } from '../../libs/mixin/mixin';
  59. import { addStyle, addUnit, sleep } from '../../libs/function/index';
  60. /**
  61. * Subsection 分段器
  62. * @description 该分段器一般用于用户从几个选项中选择某一个的场景
  63. * @tutorial https://uview-plus.jiangruyi.com/components/subsection.html
  64. * @property {Array} list tab的数据
  65. * @property {String | Number} current 当前活动的tab的index(默认 0 )
  66. * @property {String} activeColor 激活时的颜色(默认 '#3c9cff' )
  67. * @property {String} inactiveColor 未激活时的颜色(默认 '#303133' )
  68. * @property {String} mode 模式选择,mode=button为按钮形式,mode=subsection时为分段模式(默认 'button' )
  69. * @property {String | Number} fontSize 字体大小,单位px(默认 12 )
  70. * @property {Boolean} bold 激活选项的字体是否加粗(默认 true )
  71. * @property {String} bgColor 组件背景颜色,mode为button时有效(默认 '#eeeeef' )
  72. * @property {Object} customStyle 定义需要用到的外部样式
  73. * @property {String} keyName 从`list`元素对象中读取的键名(默认 'name' )
  74. * @property {String} activeColorKeyName 从`list`元素对象中读取激活时的颜色(默认 'activeColorKey' ) 如果存在字段 优先级大于 activeColor
  75. * @property {String} inactiveColorKeyName 从`list`元素对象中读取未激活时的颜色 (默认 'inactiveColorKey' )如果存在字段 优先级大于 inactiveColor
  76. * @property {Boolean} disabled 是否禁用分段器 (默认 false )
  77. *
  78. * @event {Function} change 分段器选项发生改变时触发 回调 index:选项的index索引值,从0开始
  79. * @example <u-subsection :list="list" :current="curNow" @change="sectionChange"></u-subsection>
  80. */
  81. export default {
  82. name: "u-subsection",
  83. mixins: [mpMixin, mixin, props],
  84. data() {
  85. return {
  86. // 组件尺寸
  87. itemRect: {
  88. width: 0,
  89. height: 0,
  90. },
  91. innerCurrent: '',
  92. windowResizeCallback: {}
  93. };
  94. },
  95. watch: {
  96. list(newValue, oldValue) {
  97. this.init();
  98. },
  99. current: {
  100. immediate: true,
  101. handler(n) {
  102. if (n !== this.innerCurrent) {
  103. this.innerCurrent = Number(n)
  104. }
  105. // #ifdef APP-NVUE
  106. // 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角
  107. // 故用animation模块进行位移
  108. const ref = this.$refs?.["u-subsection__bar"]?.ref;
  109. // 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改
  110. sleep(ref ? 0 : 100).then(() => {
  111. animation.transition(this.$refs["u-subsection__bar"].ref, {
  112. styles: {
  113. transform: `translateX(${
  114. n * this.itemRect.width
  115. }px)`,
  116. transformOrigin: "center center",
  117. },
  118. duration: 300,
  119. });
  120. });
  121. // #endif
  122. },
  123. },
  124. },
  125. computed: {
  126. isDarkMode() {
  127. return this.$u?.theme?.mode === 'dark';
  128. },
  129. resolvedInactiveColor() {
  130. if (this.upHasProp('inactiveColor')) return this.inactiveColor;
  131. return this.upThemeVar(
  132. '--up-subsection-inactive-color',
  133. this.isDarkMode
  134. ? (this.$u?.color?.contentColor || '#d1d5db')
  135. : '#303133'
  136. );
  137. },
  138. resolvedButtonBgColor() {
  139. if (this.upHasProp('bgColor')) return this.bgColor;
  140. return this.upThemeVar(
  141. '--up-subsection-bg-color',
  142. this.isDarkMode ? '#2b2c30' : '#eeeeef'
  143. );
  144. },
  145. resolvedButtonBarColor() {
  146. if (this.disabled) {
  147. return this.upThemeVar(
  148. '--up-subsection-disabled-bar-color',
  149. this.isDarkMode ? '#3a3a3c' : '#f5f5f5'
  150. );
  151. }
  152. return this.upThemeVar(
  153. '--up-subsection-bar-color',
  154. this.isDarkMode ? '#3a3b40' : '#ffffff'
  155. );
  156. },
  157. resolvedDisabledTextColor() {
  158. return this.upThemeVar(
  159. '--up-subsection-disabled-text-color',
  160. this.isDarkMode
  161. ? (this.$u?.color?.lightColor || '#6b7280')
  162. : '#c8c9cc'
  163. );
  164. },
  165. resolvedDisabledBorderColor() {
  166. return this.upThemeVar(
  167. '--up-subsection-disabled-border-color',
  168. this.isDarkMode
  169. ? (this.$u?.color?.borderColor || '#3a3a3c')
  170. : '#d4d4d4'
  171. );
  172. },
  173. wrapperStyle() {
  174. const style = {};
  175. // button模式时,设置背景色
  176. if (this.mode === "button") {
  177. style.backgroundColor = this.resolvedButtonBgColor;
  178. }
  179. return style;
  180. },
  181. // 滑块的样式
  182. barStyle() {
  183. const style = {};
  184. style.width = `${this.itemRect.width}px`;
  185. style.height = `${this.itemRect.height}px`;
  186. // 通过translateX移动滑块,其移动的距离为索引*item的宽度
  187. // #ifndef APP-NVUE
  188. style.transform = `translateX(${
  189. this.innerCurrent * this.itemRect.width
  190. }px)`;
  191. // #endif
  192. if (this.mode === "subsection") {
  193. // 在subsection模式下,需要动态设置滑块的圆角,因为移动滑块使用的是translateX,无法通过父元素设置overflow: hidden隐藏滑块的直角
  194. style.backgroundColor = this.disabled
  195. ? this.resolvedDisabledBorderColor
  196. : this.activeColor;
  197. } else {
  198. style.backgroundColor = this.resolvedButtonBarColor;
  199. }
  200. return style;
  201. },
  202. // 分段器item的样式
  203. itemStyle(index) {
  204. return (index) => {
  205. const style = {};
  206. if (this.mode === "subsection") {
  207. // 设置border的样式
  208. style.borderColor = this.disabled
  209. ? this.resolvedDisabledBorderColor
  210. : this.activeColor;
  211. style.borderWidth = "1px";
  212. style.borderStyle = "solid";
  213. }
  214. return style;
  215. };
  216. },
  217. // 分段器文字颜色
  218. textStyle(index,item) {
  219. return (index,item) => {
  220. const style = {};
  221. if (this.disabled) {
  222. style.fontWeight = 'normal';
  223. style.fontSize = addUnit(this.fontSize);
  224. style.color = this.resolvedDisabledTextColor;
  225. return style;
  226. }
  227. style.fontWeight =
  228. this.bold && this.innerCurrent === index ? "bold" : "normal";
  229. style.fontSize = addUnit(this.fontSize);
  230. let activeColorTemp = null;
  231. let inactiveColorTemp = null;
  232. // 如果是对象并且设置了对应的背景色字段 则优先使用设置的字段
  233. if(typeof item === 'object' && item[this.activeColorKeyName]){
  234. activeColorTemp = item[this.activeColorKeyName];
  235. }
  236. if(typeof item === 'object' && item[this.inactiveColorKeyName]){
  237. inactiveColorTemp = item[this.inactiveColorKeyName];
  238. }
  239. // subsection模式下,激活时默认为白色的文字
  240. if (this.mode === "subsection") {
  241. // 判断当前是否激活
  242. if(this.innerCurrent === index){
  243. // 判断当前是否有自定义的颜色
  244. style.color = activeColorTemp ? activeColorTemp : '#FFF'
  245. // style.color = activeColorTemp ? activeColorTemp : this.activeColor
  246. }
  247. else{
  248. // 判断当前是否有自定义的颜色
  249. style.color = inactiveColorTemp ? inactiveColorTemp : this.resolvedInactiveColor;
  250. }
  251. }
  252. else {
  253. // button模式下,激活时文字颜色默认为activeColor
  254. if(this.innerCurrent === index){
  255. // 判断当前是否有自定义的颜色
  256. style.color = activeColorTemp ? activeColorTemp : this.activeColor
  257. }
  258. else{
  259. // 判断当前是否有自定义的颜色
  260. style.color = inactiveColorTemp ? inactiveColorTemp : this.resolvedInactiveColor;
  261. }
  262. }
  263. return style;
  264. };
  265. },
  266. },
  267. mounted() {
  268. this.init();
  269. // #ifndef APP || MP-WEIXIN || MP-LARK|| MP-QQ || H5
  270. this.windowResizeCallback = (res) => {
  271. this.init();
  272. }
  273. uni.onWindowResize(this.windowResizeCallback)
  274. // #endif
  275. },
  276. beforeUnmount() {
  277. // #ifndef APP || MP-WEIXIN || MP-LARK|| MP-QQ || H5
  278. uni.offWindowResize(this.windowResizeCallback)
  279. // #endif
  280. },
  281. emits: ["change", "update:current"],
  282. methods: {
  283. addStyle,
  284. init() {
  285. this.innerCurrent = this.current
  286. sleep().then(() => this.getRect());
  287. },
  288. // 判断展示文本
  289. getText(item) {
  290. return typeof item === 'object' ? item[this.keyName] : item
  291. },
  292. // 获取组件的尺寸
  293. getRect() {
  294. // #ifndef APP-NVUE
  295. this.$uGetRect(".u-subsection__item--0").then((size) => {
  296. this.itemRect = size;
  297. });
  298. // #endif
  299. // #ifdef APP-NVUE
  300. const ref = this.$refs["u-subsection__item--0"][0];
  301. ref &&
  302. dom.getComponentRect(ref, (res) => {
  303. this.itemRect = res.size;
  304. });
  305. // #endif
  306. },
  307. clickHandler(index) {
  308. // 防止某些平台 css 无法阻止点击事件 在此处拦截
  309. if(this.disabled){
  310. return
  311. }
  312. this.innerCurrent = index;
  313. this.$emit('update:current', index);
  314. this.$emit("change", index);
  315. },
  316. /**
  317. * 获取当前文字区域的 class禁用样式
  318. * @param index
  319. */
  320. getTextViewDisableClass(index){
  321. // 禁用状态下
  322. if(this.disabled){
  323. // 判断模式
  324. if(this.mode === 'button'){
  325. return 'item-button--disabled'
  326. }
  327. else{
  328. return 'item-subsection--disabled'
  329. }
  330. }
  331. return '';
  332. }
  333. },
  334. };
  335. </script>
  336. <style lang="scss">
  337. @import "./theme-vars.scss";
  338. </style>
  339. <style lang="scss" scoped>
  340. .u-subsection {
  341. @include flex;
  342. position: relative;
  343. overflow: hidden;
  344. /* #ifndef APP-NVUE */
  345. width: 100%;
  346. box-sizing: border-box;
  347. /* #endif */
  348. &--button {
  349. height: 34px;
  350. background-color: rgb(238, 238, 239);
  351. padding: 3px;
  352. border-radius: 4px;
  353. align-items: stretch;
  354. &__bar {
  355. background-color: #ffffff;
  356. border-radius: 4px !important;
  357. }
  358. }
  359. &--subsection {
  360. height: 32px;
  361. }
  362. &__bar {
  363. position: absolute;
  364. /* #ifndef APP-NVUE */
  365. transition-property: transform, color;
  366. transition-duration: 0.3s;
  367. transition-timing-function: ease-in-out;
  368. /* #endif */
  369. &--first {
  370. border-top-left-radius: 4px;
  371. border-bottom-left-radius: 4px;
  372. border-top-right-radius: 0px;
  373. border-bottom-right-radius: 0px;
  374. }
  375. &--center {
  376. border-top-left-radius: 0px;
  377. border-bottom-left-radius: 0px;
  378. border-top-right-radius: 0px;
  379. border-bottom-right-radius: 0px;
  380. }
  381. &--last {
  382. border-top-left-radius: 0px;
  383. border-bottom-left-radius: 0px;
  384. border-top-right-radius: 4px;
  385. border-bottom-right-radius: 4px;
  386. }
  387. }
  388. &__item {
  389. @include flex;
  390. flex: 1;
  391. justify-content: center;
  392. align-items: center;
  393. // vue环境下,需要设置相对定位,因为滑块为绝对定位,item需要在滑块的上面
  394. position: relative;
  395. &--no-border-right {
  396. border-right-width: 0 !important;
  397. }
  398. &--first {
  399. border-top-left-radius: 4px;
  400. border-bottom-left-radius: 4px;
  401. }
  402. &--last {
  403. border-top-right-radius: 4px;
  404. border-bottom-right-radius: 4px;
  405. }
  406. &__text {
  407. font-size: 12px;
  408. line-height: 14px;
  409. @include flex;
  410. align-items: center;
  411. transition-property: color;
  412. transition-duration: 0.3s;
  413. }
  414. }
  415. // 禁用标志
  416. //
  417. //&--subsectio--disabled{
  418. // cursor: no-drop;
  419. // background: #FFFFFF !important;
  420. // color: #BDBDBD !important;
  421. // border-color: #BDBDBD !important;
  422. //}
  423. //
  424. //&--button--disabled{
  425. // cursor: no-drop;
  426. // color: #BDBDBD !important;
  427. // border-color: #BDBDBD !important;
  428. //}
  429. }
  430. .item-button--disabled{
  431. cursor: no-drop;
  432. }
  433. .item-subsection--disabled{
  434. cursor: no-drop;
  435. background: transparent !important;
  436. }
  437. </style>