Commit 18a16fc93c479cd252cbf2b572f8c7ac82bfed03

Authored by 刘汉宸
1 parent 91f01c1c
Exists in master

[新增] 步骤条

examples/router/routes.js
... ... @@ -66,6 +66,12 @@ const _components = [
66 66 meta: { title: 'TabBar 标签栏' },
67 67 component: () => import('@/views/docs/component/tab-bar.md'),
68 68 },
  69 + {
  70 + path: 'steps',
  71 + name: 'steps',
  72 + meta: { title: 'Steps 步骤条' },
  73 + component: () => import('@/views/docs/component/steps.md'),
  74 + },
69 75 ]
70 76 },
71 77 {
... ...
examples/views/docs/component/steps.md 0 → 100644
... ... @@ -0,0 +1,64 @@
  1 +# Steps 步骤条
  2 +
  3 +说明
  4 +
  5 +## 基础用法
  6 +
  7 +说明
  8 +
  9 +::: snippet 示例
  10 +
  11 +```html
  12 +<template>
  13 + <div>
  14 + <zui-steps :steps="steps"></zui-steps>
  15 + </div>
  16 +</template>
  17 +
  18 +<script>
  19 +export default {
  20 + data() {
  21 + return {
  22 + steps: [
  23 + { name: '登录/注册' },
  24 + { name: '申请征信报告' },
  25 + { name: '提取征信报告' },
  26 + ],
  27 + }
  28 + }
  29 +}
  30 +</script>
  31 +```
  32 +
  33 +:::
  34 +
  35 +## 纵向展示
  36 +
  37 +说明
  38 +
  39 +::: snippet 示例
  40 +
  41 +```html
  42 +<template>
  43 + <div style="width: 400px;">
  44 + <zui-steps :steps="steps" direction="vertical"></zui-steps>
  45 + </div>
  46 +</template>
  47 +
  48 +<script>
  49 +export default {
  50 + data() {
  51 + return {
  52 + steps: [
  53 + { name: '登录', text: '这是登录的描述' },
  54 + { name: '开通', text: '这是开通的描述' },
  55 + { name: '验证', text: '这是验证的描述' },
  56 + { name: '成功', text: '这是成功的描述' },
  57 + ],
  58 + }
  59 + }
  60 +}
  61 +</script>
  62 +```
  63 +
  64 +:::
0 65 \ No newline at end of file
... ...
packages/steps/index.css 0 → 100644
... ... @@ -0,0 +1,167 @@
  1 +.zui-steps {
  2 + display: flex;
  3 + justify-content: space-around;
  4 + font-size: 14px;
  5 +}
  6 +.zui-steps.zui-steps-horizontal {
  7 + align-items: center;
  8 + padding: 10px 25px 20px;
  9 +}
  10 +.zui-steps.zui-steps-horizontal .step-wrapper {
  11 + justify-content: center;
  12 + align-items: center;
  13 + flex-direction: column;
  14 +}
  15 +.zui-steps.zui-steps-horizontal .step-wrapper.reached .text-wrapper .name {
  16 + color: #41485d;
  17 +}
  18 +.zui-steps.zui-steps-horizontal .step-wrapper.current .text-wrapper .name {
  19 + color: #fcd404;
  20 +}
  21 +.zui-steps.zui-steps-horizontal .text-wrapper {
  22 + top: 100%;
  23 + padding-top: 10px;
  24 + text-align: center;
  25 +}
  26 +.zui-steps.zui-steps-horizontal .text-wrapper .name {
  27 + color: #858b9c;
  28 +}
  29 +.zui-steps.zui-steps-horizontal .text-wrapper .desc {
  30 + margin-top: 10px;
  31 + color: #858b9c;
  32 +}
  33 +.zui-steps.zui-steps-horizontal.no-current .reached:last-of-type {
  34 + display: none !important;
  35 +}
  36 +.zui-steps.zui-steps-vertical {
  37 + align-items: flex-start;
  38 + flex-direction: column;
  39 +}
  40 +.zui-steps.zui-steps-vertical.vertical-adaptive {
  41 + justify-content: normal;
  42 + padding: 20px 20px 4px;
  43 +}
  44 +.zui-steps.zui-steps-vertical.vertical-adaptive .bar.vertical-bar {
  45 + flex: 1;
  46 +}
  47 +.zui-steps.zui-steps-vertical .step-wrapper {
  48 + width: 100%;
  49 + margin: 4px 0;
  50 + align-items: stretch;
  51 +}
  52 +.zui-steps.zui-steps-vertical .step-wrapper .icon-wrapper {
  53 + position: relative;
  54 +}
  55 +.zui-steps.zui-steps-vertical .step-wrapper .icon-wrapper .step-node-default {
  56 + min-width: 20px;
  57 + min-height: 20px;
  58 +}
  59 +.zui-steps.zui-steps-vertical .step-wrapper .text-wrapper {
  60 + left: 16px;
  61 + padding-left: 10px;
  62 +}
  63 +.zui-steps.zui-steps-vertical .step-wrapper .text-wrapper .name,
  64 +.zui-steps.zui-steps-vertical .step-wrapper .text-wrapper .desc {
  65 + white-space: normal;
  66 +}
  67 +.zui-steps.zui-steps-vertical .step-wrapper .text-wrapper .name {
  68 + color: #41485d;
  69 +}
  70 +.zui-steps.zui-steps-vertical .step-wrapper .text-wrapper .desc {
  71 + margin-top: 10px;
  72 + color: #858b9c;
  73 +}
  74 +.zui-steps .icon-wrapper {
  75 + display: flex;
  76 + justify-content: center;
  77 + align-items: center;
  78 + color: #e2e4ea;
  79 +}
  80 +.zui-steps .icon-wrapper >div {
  81 + display: flex;
  82 + justify-content: center;
  83 + align-items: center;
  84 +}
  85 +.zui-steps .icon-wrapper:nth-child(2) {
  86 + display: none;
  87 +}
  88 +.zui-steps .icon-wrapper .step-node-default-icon {
  89 + background: #e2e4ea;
  90 +}
  91 +.zui-steps .step-wrapper {
  92 + display: flex;
  93 + position: relative;
  94 + min-width: 20px;
  95 + min-height: 20px;
  96 +}
  97 +.zui-steps .step-wrapper .icon-wrapper {
  98 + min-width: 20px;
  99 + min-height: 20px;
  100 +}
  101 +.zui-steps .step-wrapper .icon-wrapper .zui-icon {
  102 + width: 20px;
  103 + height: 20px;
  104 + font-size: 20px !important;
  105 + line-height: 20px;
  106 +}
  107 +.zui-steps .step-wrapper .text-wrapper {
  108 + position: absolute;
  109 +}
  110 +.zui-steps .step-wrapper .text-wrapper .name,
  111 +.zui-steps .step-wrapper .text-wrapper .desc {
  112 + white-space: nowrap;
  113 +}
  114 +.zui-steps .step-wrapper .text-wrapper .name {
  115 + font-size: 14px;
  116 +}
  117 +.zui-steps .step-wrapper .text-wrapper .desc {
  118 + line-height: 14px;
  119 + font-size: 12px;
  120 +}
  121 +.zui-steps .step-wrapper.reached .icon-wrapper,
  122 +.zui-steps .step-wrapper.current .icon-wrapper {
  123 + color: #fcd404;
  124 +}
  125 +.zui-steps .step-wrapper.reached .icon-wrapper .step-node-default-icon,
  126 +.zui-steps .step-wrapper.current .icon-wrapper .step-node-default-icon {
  127 + background: #fcd404;
  128 +}
  129 +.zui-steps .bar {
  130 + position: relative;
  131 + background-color: #e2e4ea;
  132 + overflow: hidden;
  133 +}
  134 +.zui-steps .bar .bar-inner {
  135 + z-index: 10;
  136 + position: absolute;
  137 + top: 0;
  138 + left: 0;
  139 + display: block;
  140 + content: '';
  141 + transition: all linear 1s;
  142 +}
  143 +.zui-steps .bar.horizontal-bar {
  144 + flex: 1;
  145 + height: 1px;
  146 +}
  147 +.zui-steps .bar.horizontal-bar .bar-inner {
  148 + width: 100%;
  149 + height: 1px;
  150 + background-color: #fcd404;
  151 +}
  152 +.zui-steps .bar.vertical-bar {
  153 + left: 10px;
  154 + width: 1px;
  155 + transform: translateX(-50%);
  156 +}
  157 +.zui-steps .bar.vertical-bar .bar-inner {
  158 + width: 1px;
  159 + height: 100%;
  160 + background-color: #fcd404;
  161 +}
  162 +.zui-steps .bar:last-of-type.horizontal-bar {
  163 + display: none;
  164 +}
  165 +.zui-steps .bar:last-of-type.vertical-bar {
  166 + visibility: hidden;
  167 +}
... ...
packages/steps/index.vue 0 → 100644
... ... @@ -0,0 +1,268 @@
  1 +<template>
  2 + <div class="zui-steps" :class="stepsClassRender">
  3 + <template v-for="(step, index) of steps">
  4 + <div class="step-wrapper" :class="[$_getStepStatusClass(index)]" :key="stepKey(index)">
  5 + <div v-if="$scopedSlots.icon" class="icon-wrapper">
  6 + <slot name="icon" :index="index" :current-index="currentLength" :step="step"></slot>
  7 + </div>
  8 + <div v-else class="icon-wrapper">
  9 + <template v-if="index < currentLength">
  10 + <slot v-if="$scopedSlots.reached || $slots.reached" name="reached" :index="index"></slot>
  11 + <div v-else class="step-node-default">
  12 + <div class="step-node-default-icon" style="width: 8px;height: 8px;border-radius: 50%;"></div>
  13 + </div>
  14 + </template>
  15 + <template v-else-if="index == currentLength">
  16 + <slot v-if="$scopedSlots.current || $slots.current" name="current" :index="index"></slot>
  17 + <zui-icon v-else name="checkcircle"></zui-icon>
  18 + </template>
  19 + <template v-else>
  20 + <slot v-if="$scopedSlots.unreached || $slots.unreached" name="unreached" :index="index"></slot>
  21 + <div v-else class="step-node-default">
  22 + <div class="step-node-default-icon" style="width: 8px;height: 8px;border-radius: 50%;"></div>
  23 + </div>
  24 + </template>
  25 + </div>
  26 + <div class="text-wrapper">
  27 + <slot v-if="$scopedSlots.content" name="content" :index="index" :step="step"></slot>
  28 + <template v-else>
  29 + <div class="name">{{step.name}}</div>
  30 + <div class="desc" v-if="step.text">{{step.text}}</div>
  31 + </template>
  32 + </div>
  33 + </div>
  34 + <div class="bar" :class="barClassRender" :style="$_getStepSizeForStyle(index)" :key="barKey(index)">
  35 + <i class="bar-inner" v-if="progress[index]" :style="$_barInnerStyle(index)"></i>
  36 + </div>
  37 + </template>
  38 + </div>
  39 +</template>
  40 +
  41 +<script>
  42 +export default {
  43 + name: "Steps",
  44 + props: {
  45 + steps: {
  46 + type: Array,
  47 + default: function() {
  48 + /* istanbul ignore next */
  49 + return [];
  50 + }
  51 + },
  52 + current: {
  53 + type: Number,
  54 + default: 0,
  55 + validator(val) {
  56 + return val >= 0;
  57 + }
  58 + },
  59 + direction: {
  60 + type: String,
  61 + default: "horizontal"
  62 + },
  63 + transition: {
  64 + type: Boolean,
  65 + default: false
  66 + },
  67 + verticalAdaptive: {
  68 + type: Boolean,
  69 + default: false
  70 + }
  71 + },
  72 + data: function() {
  73 + return {
  74 + initialed: false,
  75 + progress: [],
  76 + stepsSize: [],
  77 + currentLength: 0,
  78 + duration: 0.3,
  79 + timer: null
  80 + };
  81 + },
  82 + computed: {
  83 + stepsClassRender: function() {
  84 + return {
  85 + "zui-steps-vertical": this.direction == "vertical",
  86 + "zui-steps-horizontal": this.direction == "horizontal",
  87 + "vertical-adaptive":
  88 + this.direction == "vertical" && this.verticalAdaptive,
  89 + "no-current": this.currentLength % 1 !== 0
  90 + };
  91 + },
  92 + barClassRender: function() {
  93 + return [this.direction == 'horizontal' ? 'horizontal-bar' : 'vertical-bar']
  94 + },
  95 + $_barInnerStyle: function() {
  96 + var progress = this.progress;
  97 + var self = this;
  98 + return function(index) {
  99 + var transform =
  100 + self.direction == "horizontal"
  101 + ? '(' + (progress[index]["len"] - 1) * 100 + '%, 0, 0)'
  102 + : '(0, ' + (progress[index]["len"] - 1) * 100 + '%, 0)';
  103 + return {
  104 + transform: 'translate3d' + transform,
  105 + transition: 'all ' + progress[index]["time"] + 's linear'
  106 + };
  107 + };
  108 + }
  109 + },
  110 + watch: {
  111 + current: function(val, oldVal) {
  112 + var currentStep = this.$_formatValue(val);
  113 + var newProgress = this.$_sliceProgress(currentStep);
  114 + if (this.transition) {
  115 + var isAdd = currentStep >= oldVal;
  116 + var self = this;
  117 + this.timer && clearTimeout(this.timer);
  118 + this.timer = setTimeout(function() {
  119 + self.$_doTransition(function(newProgress, isAdd, len) {
  120 + if (
  121 + (isAdd && len > self.currentLength) ||
  122 + (!isAdd && len < self.currentLength)
  123 + ) {
  124 + self.currentLength = len;
  125 + }
  126 + });
  127 + }, 100);
  128 + } else {
  129 + this.progress = newProgress;
  130 + this.currentLength = currentStep;
  131 + }
  132 + }
  133 + },
  134 + created: function() {
  135 + var currentStep = this.$_formatValue(this.current);
  136 + this.currentLength = currentStep;
  137 + this.progress = this.$_sliceProgress(currentStep);
  138 + },
  139 + mounted: function() {
  140 + this.$_initStepSize();
  141 + },
  142 + updated: function() {
  143 + this.$nextTick(function() {
  144 + this.$_initStepSize();
  145 + });
  146 + },
  147 + methods: {
  148 + stepKey: function(index) {
  149 + return "steps-" + index;
  150 + },
  151 + barKey: function(index) {
  152 + return "bar-" + index;
  153 + },
  154 + toArray: function(list, start) {
  155 + start = start || 0;
  156 + var i = list.length - start;
  157 + var ret = [];
  158 + while (i--) {
  159 + ret.unshift(list[i + start]);
  160 + }
  161 + return ret;
  162 + },
  163 + // MARK: private methods
  164 + $_initStepSize: function() {
  165 + if (this.direction != "vertical" || this.verticalAdaptive) {
  166 + return;
  167 + }
  168 + var iconWrappers = this.$el.querySelectorAll(".icon-wrapper");
  169 + var textWrappers = this.$el.querySelectorAll(".text-wrapper");
  170 + var self = this;
  171 + var stepsSize = this.toArray(textWrappers).map(function(wrapper, index) {
  172 + var stepHeight = wrapper.clientHeight;
  173 + var iconHeight = iconWrappers[index].clientHeight;
  174 + if (index == textWrappers.length - 1) {
  175 + // The last step needs to subtract floated height
  176 + stepHeight -= iconHeight;
  177 + } else {
  178 + // Add spacing between steps to prevent distance too close
  179 + // stepHeight += 40
  180 + // 这里减小空白间隙,使其看起来更紧凑
  181 + stepHeight += self.stepHeightFix == 0 ? self.stepHeightFix : self.stepHeightFix || -15;
  182 + }
  183 + return stepHeight > 0 ? stepHeight : 0;
  184 + });
  185 + if (stepsSize.toString() != this.stepsSize.toString()) {
  186 + this.stepsSize = stepsSize;
  187 + }
  188 + },
  189 + $_getStepSizeForStyle: function(index) {
  190 + var size =
  191 + this.direction == "vertical" && !this.verticalAdaptive
  192 + ? this.stepsSize[index]
  193 + : 0;
  194 + return size
  195 + ? {
  196 + height: size + 'px'
  197 + }
  198 + : null;
  199 + },
  200 + $_getStepStatusClass: function(index) {
  201 + var currentLength = this.currentLength;
  202 + var status = [];
  203 + if (index < currentLength) {
  204 + status.push("reached");
  205 + }
  206 + if (index == Math.floor(currentLength)) {
  207 + status.push("current");
  208 + }
  209 + return status.join(" ");
  210 + },
  211 + $_formatValue: function(val) {
  212 + if (val < 0) {
  213 + return 0;
  214 + } else if (val > this.steps.length - 1) {
  215 + return this.steps.length - 1;
  216 + } else {
  217 + return val;
  218 + }
  219 + },
  220 + $_sliceProgress: function(current) {
  221 + var self = this;
  222 + return this.steps.slice(0, this.steps.length - 1).map(function(step, index) {
  223 + var offset = current - index;
  224 + var progress = self.progress[index];
  225 + var isNewProgress = progress == undefined;
  226 + var len, time;
  227 + if (offset <= 0) {
  228 + len = 0;
  229 + } else if (offset >= 1) {
  230 + len = 1;
  231 + } else {
  232 + len = offset;
  233 + }
  234 + time =
  235 + (isNewProgress ? len : Math.abs(progress.len - len)) * self.duration;
  236 + return {
  237 + len,
  238 + time
  239 + };
  240 + });
  241 + },
  242 + $_doTransition: function(progress, isAdd, step) {
  243 + var currentLength = isAdd ? 0 : this.currentLength;
  244 + var self = this;
  245 + var walk = function(index) {
  246 + if ((index < progress.length) & (index > -1) && progress[index]) {
  247 + if (isAdd) {
  248 + currentLength += progress[index].len;
  249 + } else {
  250 + currentLength -= self.progress[index].len - progress[index].len;
  251 + }
  252 + setTimeout(function() {
  253 + index += isAdd ? 1 : -1;
  254 + step(currentLength);
  255 + walk(index);
  256 + }, progress[index].time * 1000);
  257 + }
  258 + self.$set(self.progress, index, progress[index]);
  259 + };
  260 + walk(isAdd ? 0 : progress.length - 1);
  261 + }
  262 + }
  263 +};
  264 +</script>
  265 +
  266 +<style>
  267 +@import "./index.css";
  268 +</style>
0 269 \ No newline at end of file
... ...