tn-steps.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <template>
  2. <view
  3. class="tn-steps-class tn-steps"
  4. :style="{
  5. flexDirection: direction
  6. }"
  7. >
  8. <view
  9. v-for="(item, index) in list"
  10. :key="index"
  11. class="tn-steps__item"
  12. :class="[`tn-steps__item--${direction}`]"
  13. @tap="clickStepItem(index)"
  14. >
  15. <!-- 数值模式 -->
  16. <view
  17. v-if="mode === 'number'"
  18. class="tn-steps__item__number"
  19. :style="{
  20. backgroundColor: currentIndex <= index ? 'transparent' : activeColor,
  21. borderColor: currentIndex <= index ? inActiveColor : activeColor
  22. }"
  23. >
  24. <text
  25. class="tn-steps__item__number__text"
  26. :class="[{'tn-steps__item__number__text--visible': currentIndex <= index}]"
  27. :style="{
  28. color: currentIndex <= index ? inActiveColor : activeColor
  29. }"
  30. >
  31. {{ index + 1}}
  32. </text>
  33. <view class="tn-steps__item__number__icon" :class="[`tn-icon-${item.icon || icon}`,{'tn-steps__item__number__icon--visible': currentIndex > index}]"></view>
  34. </view>
  35. <!-- 点模式 -->
  36. <view
  37. v-if="mode === 'dot'"
  38. class="tn-steps__item__dot"
  39. :style="{
  40. backgroundColor: currentIndex <= index ? inActiveColor : activeColor,
  41. fontSize:fontSize
  42. }"
  43. ></view>
  44. <!-- 图标模式 -->
  45. <view
  46. v-if="mode === 'icon'"
  47. class="tn-steps__item__icon"
  48. :class="[iconModeClass(index)]"
  49. :style="{
  50. color: currentIndex <= index ? inActiveColor : activeColor,
  51. fontSize:fontSize
  52. }"
  53. ></view>
  54. <!-- 点图标模式 -->
  55. <view v-if="mode === 'dotIcon'" class="tn-steps__item__dot-icon">
  56. <view v-if="currentIndex <= index" class="tn-steps__item__dot-icon--dot" :style="{backgroundColor: inActiveColor}"></view>
  57. <view v-else class="tn-steps__item__dot-icon--icon" :class="[iconModeClass(index)]" :style="{color: activeColor}"></view>
  58. </view>
  59. <!-- 步骤下的文字 -->
  60. <text
  61. v-if="showTitle"
  62. class="tn-steps__item__text tn-text-ellipsis"
  63. :class="[`tn-steps__item__text--${direction}`]"
  64. :style="{
  65. color: currentIndex <= index ? inActiveColor : activeColor,
  66. fontSize:fontSize
  67. }"
  68. >
  69. {{ item.name }}
  70. </text>
  71. <!-- 连接的横线 -->
  72. <view
  73. v-if="index < list.length - 1"
  74. class="tn-steps__item__line"
  75. :class="[`tn-steps__item__line--${mode}`]"
  76. :style="{
  77. borderColor: currentIndex <= index + 1 ? inActiveColor : activeColor
  78. }"
  79. ></view>
  80. </view>
  81. </view>
  82. </template>
  83. <script>
  84. export default {
  85. name: 'tn-steps',
  86. props: {
  87. // 模式类型
  88. // dot -> 点 number -> 数字 icon -> 图标 dotIcon -> 点图标
  89. mode: {
  90. type: String,
  91. default: 'dot'
  92. },
  93. // 步骤条的方向
  94. // row -> 横向 column -> 竖向
  95. direction: {
  96. type: String,
  97. default: 'row'
  98. },
  99. // 步骤条数据
  100. list: {
  101. type: Array,
  102. default() {
  103. return []
  104. }
  105. },
  106. // 当前激活的步数
  107. current: {
  108. type: Number,
  109. default: 0
  110. },
  111. // 激活步骤的颜色
  112. activeColor: {
  113. type: String,
  114. default: '#01BEFF'
  115. },
  116. // 未激活步骤的颜色
  117. inActiveColor: {
  118. type: String,
  119. default: '#AAAAAA'
  120. },
  121. // 激活后显示的图标,在数字模式下有效
  122. icon: {
  123. type: String,
  124. default: 'success'
  125. },
  126. // 是否显示标题
  127. showTitle: {
  128. type: Boolean,
  129. default: true
  130. },
  131. fontSize:{
  132. type: String,
  133. default: '14px'
  134. }
  135. },
  136. computed: {
  137. // icon模式下图标的值
  138. iconModeClass() {
  139. return (index) => {
  140. const item = this.list[index]
  141. // 状态被选中并且对应数据下存在selectIcon属性
  142. if (this.currentIndex > index && item.hasOwnProperty('selectIcon')) {
  143. return `tn-icon-${item.selectIcon}`
  144. } else {
  145. // 未选中
  146. return `tn-icon-${item.icon || this.icon}`
  147. }
  148. }
  149. }
  150. },
  151. data() {
  152. return {
  153. currentIndex: 0
  154. }
  155. },
  156. watch: {
  157. current: {
  158. handler(val) {
  159. this.currentIndex = val
  160. },
  161. immediate: true
  162. }
  163. },
  164. methods: {
  165. // 点击了某一个选项
  166. clickStepItem(index) {
  167. const chooseIndex = index + 1
  168. this.currentIndex = chooseIndex
  169. this.$emit('click', { index: chooseIndex })
  170. }
  171. }
  172. }
  173. </script>
  174. <style lang="scss" scoped>
  175. $tn-steps-item-number-width: 44rpx;
  176. $tn-steps-item-dot-width: 20rpx;
  177. .tn-steps {
  178. display: flex;
  179. flex-direction: row;
  180. &__item {
  181. flex: 1;
  182. position: relative;
  183. display: flex;
  184. align-items: center;
  185. justify-content: center;
  186. flex-direction: column;
  187. min-width: 100rpx;
  188. font-size: 28rpx;
  189. text-align: center;
  190. &__number {
  191. // display: flex;
  192. // flex-wrap: wrap;
  193. // align-items: center;
  194. // justify-content: center;
  195. position: relative;
  196. width: $tn-steps-item-number-width;
  197. height: $tn-steps-item-number-width;
  198. line-height: calc(#{$tn-steps-item-number-width} - 2rpx);
  199. border: 1px solid #AAAAAA;
  200. border-radius: 50%;
  201. overflow: hidden;
  202. transition: all 0.3s linear;
  203. &__text {
  204. position: absolute;
  205. top: 0;
  206. right: 0;
  207. bottom: 0;
  208. left: 0;
  209. margin: auto;
  210. transition: inherit;
  211. transform: translateY(-#{$tn-steps-item-number-width});
  212. &--visible {
  213. transform: translateY(0);
  214. }
  215. }
  216. &__icon {
  217. position: absolute;
  218. top: 0;
  219. right: 0;
  220. bottom: 0;
  221. left: 0;
  222. margin: auto;
  223. color: #FFFFFF;
  224. transition: all 0.3s linear;
  225. transform: translateY(#{$tn-steps-item-number-width});
  226. &--visible {
  227. transform: translateY(0);
  228. }
  229. }
  230. }
  231. &__dot {
  232. width: $tn-steps-item-dot-width;
  233. height: $tn-steps-item-dot-width;
  234. display: flex;
  235. flex-direction: row;
  236. border-radius: 50%;
  237. transition: all 0.3s linear;
  238. &--icon {
  239. width: $tn-steps-item-number-width;
  240. height: $tn-steps-item-number-width;
  241. }
  242. }
  243. &__icon {
  244. width: $tn-steps-item-number-width;
  245. height: $tn-steps-item-number-width;
  246. font-size: $tn-steps-item-number-width;
  247. transition: all 0.3s linear;
  248. }
  249. &__dot-icon {
  250. width: $tn-steps-item-number-width;
  251. height: $tn-steps-item-number-width;
  252. display: flex;
  253. flex-direction: row;
  254. align-items: center;
  255. justify-content: center;
  256. transition: all 0.3s linear;
  257. &--dot {
  258. width: $tn-steps-item-dot-width;
  259. height: $tn-steps-item-dot-width;
  260. border-radius: 50%;
  261. transition: inherit;
  262. }
  263. &--icon {
  264. width: $tn-steps-item-number-width;
  265. height: $tn-steps-item-number-width;
  266. font-size: $tn-steps-item-number-width;
  267. transition: inherit;
  268. }
  269. }
  270. &__text {
  271. transition: all 0.3s linear;
  272. &--row {
  273. margin-top: 14rpx;
  274. }
  275. &--column {
  276. margin-left: 14rpx;
  277. }
  278. }
  279. &__line {
  280. position: absolute;
  281. z-index: 0;
  282. vertical-align: middle;
  283. transition: all 0.3s linear;
  284. }
  285. &--row {
  286. display: flex;
  287. flex-direction: column;
  288. .tn-steps__item__line {
  289. border-bottom-width: 1px;
  290. border-bottom-style: solid;
  291. width: 50%;
  292. left: 75%;
  293. &--dot {
  294. top: calc(#{$tn-steps-item-dot-width} / 2);
  295. }
  296. &--number, &--icon, &--dotIcon {
  297. top: calc(#{$tn-steps-item-number-width} / 2);
  298. }
  299. }
  300. }
  301. &--column {
  302. display: flex;
  303. flex-direction: row;
  304. justify-content: flex-start;
  305. min-height: 120rpx;
  306. .tn-steps__item__line {
  307. border-left-width: 1px;
  308. border-left-style: solid;
  309. height: 50%;
  310. top: 75%;
  311. &--dot {
  312. left: calc(#{$tn-steps-item-dot-width} / 2);
  313. }
  314. &--number, &--icon, &--dotIcon {
  315. left: calc(#{$tn-steps-item-number-width} / 2);
  316. }
  317. }
  318. }
  319. }
  320. }
  321. </style>