小说小程序前端代码仓库(小程序)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
8.0 KiB

3 months ago
  1. <template>
  2. <view
  3. class="uv-subsection"
  4. ref="uv-subsection"
  5. :class="[`uv-subsection--${mode}`]"
  6. :style="[$uv.addStyle(customStyle), wrapperStyle]">
  7. <view
  8. class="uv-subsection__bar"
  9. ref="uv-subsection__bar"
  10. :style="[barStyle]"
  11. :class="[
  12. mode === 'button' && 'uv-subsection--button__bar',
  13. current === 0 && mode === 'subsection' && 'uv-subsection__bar--first',
  14. current > 0 && current < list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--center',
  15. current === list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--last'
  16. ]"
  17. ></view>
  18. <view class="uv-subsection__item"
  19. :class="[
  20. `uv-subsection__item--${index}`,
  21. index < list.length - 1 && 'uv-subsection__item--no-border-right',
  22. index === 0 && 'uv-subsection__item--first',
  23. index === list.length - 1 && 'uv-subsection__item--last'
  24. ]"
  25. :ref="`uv-subsection__item--${index}`"
  26. :style="[itemStyle(index)]"
  27. @tap="clickHandler(index)"
  28. v-for="(item, index) in list"
  29. :key="index"
  30. >
  31. <text
  32. class="uv-subsection__item__text"
  33. :style="[textStyle(index)]">{{ getText(item) }}
  34. </text>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
  40. import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
  41. // #ifdef APP-NVUE
  42. const dom = uni.requireNativePlugin("dom");
  43. const animation = uni.requireNativePlugin("animation");
  44. // #endif
  45. import props from "./props.js";
  46. /**
  47. * Subsection 分段器
  48. * @description 该分段器一般用于用户从几个选项中选择某一个的场景
  49. * @tutorial https://www.uvui.cn/components/subsection.html
  50. * @property {Array} list tab的数据
  51. * @property {String Number} current 当前活动的tab的index默认 0
  52. * @property {String} activeColor 激活时的颜色默认 '#3c9cff'
  53. * @property {String} inactiveColor 未激活时的颜色默认 '#303133'
  54. * @property {String} mode 模式选择mode=button为按钮形式mode=subsection时为分段模式默认 'button'
  55. * @property {String Number} fontSize 字体大小单位px默认 12
  56. * @property {Boolean} bold 激活选项的字体是否加粗默认 true
  57. * @property {String} bgColor 组件背景颜色mode为button时有效默认 '#eeeeef'
  58. * @property {Object} customStyle 定义需要用到的外部样式
  59. * @property {String} keyName `list`元素对象中读取的键名默认 'name'
  60. *
  61. * @event {Function} change 分段器选项发生改变时触发 回调 index选项的index索引值从0开始
  62. * @example <uv-subsection :list="list" :current="curNow" @change="sectionChange"></uv-subsection>
  63. */
  64. export default {
  65. name: "uv-subsection",
  66. mixins: [mpMixin, mixin, props],
  67. data() {
  68. return {
  69. // 组件尺寸
  70. itemRect: {
  71. width: 0,
  72. height: 0,
  73. }
  74. }
  75. },
  76. watch: {
  77. list: {
  78. deep: true,
  79. handler(){
  80. this.init();
  81. }
  82. },
  83. current: {
  84. immediate: true,
  85. handler(n) {
  86. // #ifdef APP-NVUE
  87. // 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角
  88. // 故用animation模块进行位移
  89. const ref = this.$refs?.["uv-subsection__bar"]?.ref;
  90. // 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改
  91. this.$uv.sleep(ref ? 0 : 150).then(() => {
  92. animation.transition(this.$refs["uv-subsection__bar"].ref, {
  93. styles: {
  94. transform: `translateX(${ n * this.itemRect.width }px)`,
  95. transformOrigin: "center center"
  96. },
  97. duration: 300,
  98. });
  99. });
  100. // #endif
  101. }
  102. }
  103. },
  104. computed: {
  105. wrapperStyle() {
  106. const style = {};
  107. // button模式时,设置背景色
  108. if (this.mode === "button") {
  109. style.backgroundColor = this.bgColor;
  110. }
  111. return style;
  112. },
  113. // 滑块的样式
  114. barStyle() {
  115. const style = {};
  116. style.width = `${this.itemRect.width}px`;
  117. style.height = `${this.itemRect.height}px`;
  118. // 通过translateX移动滑块,其移动的距离为索引*item的宽度
  119. // #ifndef APP-NVUE
  120. style.transform = `translateX(${ this.current * this.itemRect.width }px)`;
  121. // #endif
  122. if (this.mode === "subsection") {
  123. // 在subsection模式下,需要动态设置滑块的圆角,因为移动滑块使用的是translateX,无法通过父元素设置overflow: hidden隐藏滑块的直角
  124. style.backgroundColor = this.activeColor;
  125. }
  126. return this.$uv.deepMerge(style, this.$uv.addStyle(this.customItemStyle));
  127. },
  128. // 分段器item的样式
  129. itemStyle(index) {
  130. return (index) => {
  131. const style = {};
  132. if (this.mode === "subsection") {
  133. // 设置border的样式
  134. style.borderColor = this.activeColor;
  135. style.borderWidth = "1px";
  136. style.borderStyle = "solid";
  137. }
  138. return style;
  139. };
  140. },
  141. // 分段器文字颜色
  142. textStyle(index) {
  143. return (index) => {
  144. const style = {};
  145. style.fontWeight = this.bold && this.current === index ? "bold" : "normal";
  146. style.fontSize = this.$uv.addUnit(this.fontSize);
  147. // subsection模式下,激活时默认为白色的文字
  148. if (this.mode === "subsection") {
  149. style.color = this.current === index ? "#fff" : this.inactiveColor;
  150. } else {
  151. // button模式下,激活时文字颜色默认为activeColor
  152. style.color = this.current === index ? this.activeColor : this.inactiveColor;
  153. }
  154. return style;
  155. };
  156. },
  157. },
  158. mounted() {
  159. this.init();
  160. },
  161. methods: {
  162. init() {
  163. this.$uv.sleep().then(() => this.getRect());
  164. },
  165. // 判断展示文本
  166. getText(item) {
  167. return typeof item === 'object' ? item[this.keyName] : item
  168. },
  169. // 获取组件的尺寸
  170. getRect() {
  171. // #ifndef APP-NVUE
  172. this.$uvGetRect(".uv-subsection__item--0").then((size) => {
  173. this.itemRect = size;
  174. });
  175. // #endif
  176. // #ifdef APP-NVUE
  177. const ref = this.$refs["uv-subsection__item--0"][0];
  178. ref && dom.getComponentRect(ref, (res) => {
  179. this.itemRect = res.size;
  180. });
  181. // #endif
  182. },
  183. clickHandler(index) {
  184. this.$emit("change", index);
  185. }
  186. }
  187. }
  188. </script>
  189. <style lang="scss" scoped>
  190. @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
  191. .uv-subsection {
  192. @include flex;
  193. position: relative;
  194. overflow: hidden;
  195. /* #ifndef APP-NVUE */
  196. width: 100%;
  197. box-sizing: border-box;
  198. /* #endif */
  199. &--button {
  200. height: 32px;
  201. background-color: rgb(238, 238, 239);
  202. padding: 3px;
  203. border-radius: 3px;
  204. align-items: stretch;
  205. &__bar {
  206. background-color: #ffffff;
  207. }
  208. }
  209. &--subsection {
  210. height: 30px;
  211. }
  212. &__bar {
  213. position: absolute;
  214. /* #ifndef APP-NVUE */
  215. transition-property: transform, color;
  216. transition-duration: 0.3s;
  217. transition-timing-function: ease-in-out;
  218. /* #endif */
  219. &--first {
  220. border-top-left-radius: 3px;
  221. border-bottom-left-radius: 3px;
  222. border-top-right-radius: 0px;
  223. border-bottom-right-radius: 0px;
  224. }
  225. &--center {
  226. border-top-left-radius: 0px;
  227. border-bottom-left-radius: 0px;
  228. border-top-right-radius: 0px;
  229. border-bottom-right-radius: 0px;
  230. }
  231. &--last {
  232. border-top-left-radius: 0px;
  233. border-bottom-left-radius: 0px;
  234. border-top-right-radius: 3px;
  235. border-bottom-right-radius: 3px;
  236. }
  237. }
  238. &__item {
  239. @include flex;
  240. flex: 1;
  241. justify-content: center;
  242. align-items: center;
  243. // vue环境下,需要设置相对定位,因为滑块为绝对定位,item需要在滑块的上面
  244. position: relative;
  245. &--no-border-right {
  246. border-right-width: 0 !important;
  247. }
  248. &--first {
  249. border-top-left-radius: 3px;
  250. border-bottom-left-radius: 3px;
  251. }
  252. &--last {
  253. border-top-right-radius: 3px;
  254. border-bottom-right-radius: 3px;
  255. }
  256. &__text {
  257. font-size: 12px;
  258. line-height: 12px;
  259. @include flex;
  260. align-items: center;
  261. transition-property: color;
  262. transition-duration: 0.3s;
  263. }
  264. }
  265. }
  266. </style>