2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
@@ -0,0 +1,4 @@
export * from './steps-custom'
export * from './step-custom'
export * from './use-steps'
export * from './use-step'
@@ -0,0 +1,191 @@
import { computed, inject } from 'vue'
import { stepsContextKey } from '../../../../tokens'
import { useComponentColor, useNamespace } from '../../../../hooks'
import type { CSSProperties, Ref } from 'vue'
import type { StepProps } from '../steps-item'
import type { StepsMode } from '../steps'
type ModeSelector = 'dot' | 'icon'
type StepModeClass = (selector?: ModeSelector) => string
type StepModeStyle = (selector?: ModeSelector) => CSSProperties
export const useStepCustomStyle = (
props: StepProps,
isActive: Ref<boolean>,
stepMode: Ref<StepsMode>
) => {
const ns = useNamespace('step')
const stepsContext = inject(stepsContextKey)
const normalColor = computed<string | undefined>(
() => props.color || stepsContext?.color
)
const activeColor = computed<string | undefined>(
() => props.activeColor || stepsContext?.activeColor
)
// 解析颜色
const [textColorClass, textColorStyle] = useComponentColor(
normalColor,
'text'
)
const [textActiveColorClass, textActiveColorStyle] = useComponentColor(
activeColor,
'text'
)
const [bgColorClass, bgColorStyle] = useComponentColor(normalColor, 'bg')
const [bgActiveColorClass, bgActiveColorStyle] = useComponentColor(
activeColor,
'bg'
)
// step对应的类
const stepClass = computed<string>(() => {
const cls: string[] = [ns.b()]
if (isActive.value) cls.push(ns.is('active'))
// 设置颜色
if (isActive.value) {
if (textActiveColorClass.value) cls.push(textActiveColorClass.value)
} else {
if (textColorClass.value) cls.push(textColorClass.value)
}
return cls.join(' ')
})
// step对应的样式
const stepStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置颜色
if (isActive.value) {
if (!textActiveColorClass.value)
style.color = textActiveColorStyle.value || 'var(--tn-color-primary)'
} else {
if (!textColorClass.value)
style.color = textColorStyle.value || 'var(--tn-color-gray)'
}
return style
})
// step-mode对应的类
const modeClass = computed<StepModeClass>(() => {
return (selector?: ModeSelector) => {
const cls: string[] = []
if (!selector) {
cls.push(ns.e(`mode-${stepMode.value}`))
}
if (stepMode.value === 'dot') {
if (isActive.value) {
if (bgActiveColorClass.value) cls.push(bgActiveColorClass.value)
} else {
if (bgColorClass.value) cls.push(bgColorClass.value)
}
}
if (stepMode.value === 'number') {
if (isActive.value) {
if (bgActiveColorClass.value) cls.push(bgActiveColorClass.value)
}
}
if (stepMode.value === 'dotIcon') {
if (isActive.value && selector === 'icon') {
if (textActiveColorClass.value) cls.push(textActiveColorClass.value)
}
if (!isActive.value && selector === 'dot') {
if (bgColorClass.value) cls.push(bgColorClass.value)
}
}
return cls.join(' ')
}
})
// step-mode对应的样式
const modeStyle = computed<StepModeStyle>(() => {
return (selector?: ModeSelector) => {
const style: CSSProperties = {}
if (stepMode.value === 'dot') {
if (isActive.value) {
if (!bgActiveColorClass.value)
style.backgroundColor =
bgActiveColorStyle.value || 'var(--tn-color-primary)'
} else {
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-gray)'
}
}
if (stepMode.value === 'number') {
if (isActive.value) {
if (!bgActiveColorClass.value) {
style.backgroundColor =
bgActiveColorStyle.value || 'var(--tn-color-primary)'
style.color = 'var(--tn-color-white)'
}
}
}
if (stepMode.value === 'dotIcon') {
if (isActive.value && selector === 'icon') {
if (!textActiveColorClass.value) {
style.color =
textActiveColorStyle.value || 'var(--tn-color-primary)'
}
}
if (!isActive.value && selector === 'dot') {
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-gray)'
}
}
return style
}
})
// step-line对应的类
const lineClass = computed<string>(() => {
const cls: string[] = [ns.e('line'), ns.is('no-title', !props.title)]
if (isActive.value) {
if (bgActiveColorClass.value) cls.push(bgActiveColorClass.value)
} else {
if (bgColorClass.value) cls.push(bgColorClass.value)
}
return cls.join(' ')
})
// step-line对应的样式
const lineStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (isActive.value) {
if (!bgActiveColorClass.value)
style.backgroundColor =
bgActiveColorStyle.value || 'var(--tn-color-primary)'
} else {
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-gray)'
}
return style
})
return {
ns,
stepClass,
stepStyle,
modeClass,
modeStyle,
lineClass,
lineStyle,
}
}
@@ -0,0 +1,9 @@
import { useNamespace } from '../../../../hooks'
export const useStepsCustomStyle = () => {
const ns = useNamespace('steps')
return {
ns,
}
}
@@ -0,0 +1,57 @@
import {
computed,
getCurrentInstance,
inject,
nextTick,
onMounted,
onUnmounted,
} from 'vue'
import { stepsContextKey } from '../../../../tokens'
import { debugWarn } from '../../../../utils'
import type { StepProps } from '../steps-item'
import type { StepsMode } from '../steps'
export const useStep = (props: StepProps) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnStep', '请在 setup 中使用 useStep')
}
const { emit, uid } = instance!
const stepsContext = inject(stepsContextKey)
// 判断当前是否被激活
const isActive = computed<boolean>(
() => stepsContext?.activeUidList.includes(uid) || false
)
// 步骤条模式
const stepMode = computed<StepsMode>(() => stepsContext?.mode || 'dot')
// item点击事件
const itemClickEvent = () => {
if (props.disabled || stepsContext?.disabled) return
stepsContext?.setActiveItem(uid)
emit('click')
}
onMounted(() => {
nextTick(() => {
stepsContext?.addItem({ uid })
})
})
onUnmounted(() => {
stepsContext?.removeItem(uid)
})
return {
isActive,
stepMode,
itemClickEvent,
}
}
@@ -0,0 +1,96 @@
import { getCurrentInstance, provide, reactive, ref, toRefs, watch } from 'vue'
import { stepsContextKey } from '../../../../tokens'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../../constants'
import { useOrderedChildren } from '../../../../hooks'
import { debugWarn } from '../../../../utils'
import type { StepsProps } from '../steps'
import type { StepContext } from '../../../../tokens'
export const useSteps = (props: StepsProps) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnSteps', '请在 setup 中使用 useSteps')
}
const { emit } = instance!
const {
children: items,
addChild,
removeChild: removeItem,
} = useOrderedChildren<StepContext>()
// 当前已激活的Uid列表
const activeUidList = ref<number[]>([])
let innerUpdate = false
// 更新当前激活的Uid列表
const setActiveUidList = (uid?: number, changeEmit = false) => {
if (uid === undefined) {
activeUidList.value = []
return
}
// 查找出当前激活的uid的index
const index = items.value.findIndex((item) => item.uid === uid)
if (index === -1) return
// 根据index查找出当前激活的uid列表
activeUidList.value = items.value
.slice(0, index + 1)
.map((item) => item.uid)
// 触发更新事件
emit(UPDATE_MODEL_EVENT, index)
if (changeEmit) {
emit(CHANGE_EVENT, index)
}
}
// 设置当前激活的Step
const setActiveItem = (uid: number) => {
if (props.disabled) return
innerUpdate = true
setActiveUidList(uid, true)
}
watch(
() => props.modelValue,
(val) => {
if (innerUpdate) {
innerUpdate = false
return
}
if (val !== undefined) {
const uid = items.value?.[val]?.uid
setActiveUidList(uid)
}
}
)
// 添加item到items
const addItem = (item: StepContext) => {
addChild(item)
if (
!activeUidList.value.length &&
props.modelValue !== undefined &&
props.modelValue >= 0
) {
setActiveUidList(items.value?.[props.modelValue]?.uid)
}
}
provide(
stepsContextKey,
reactive({
...toRefs(props),
items,
activeUidList,
addItem,
removeItem,
setActiveItem,
})
)
}
@@ -0,0 +1,5 @@
import type Stpes from './steps.vue'
import type StepsItem from './steps-item.vue'
export type TnStepsInstance = InstanceType<typeof Stpes>
export type TnStepsItemInstance = InstanceType<typeof StepsItem>
@@ -0,0 +1,30 @@
import { buildProps } from '../../../utils'
import { stepsBaseProps } from '../../base/common-props/steps'
import type { ExtractPropTypes } from 'vue'
export const stepProps = buildProps({
...stepsBaseProps,
/**
* @description 标题
*/
title: String,
/**
* @description 默认的图标
*/
icon: String,
/**
* @description 激活时的图标
*/
activeIcon: String,
})
export const stepEmits = {
/**
* @description 点击事件
*/
click: () => true,
}
export type StepProps = ExtractPropTypes<typeof stepProps>
export type StepEmits = typeof stepEmits
@@ -0,0 +1,91 @@
<script lang="ts" setup>
import TnIcon from '../../icon/src/icon.vue'
import { stepEmits, stepProps } from './steps-item'
import { useStep, useStepCustomStyle } from './composables'
const props = defineProps(stepProps)
defineEmits(stepEmits)
const { isActive, stepMode, itemClickEvent } = useStep(props)
const { ns, stepClass, stepStyle, modeClass, modeStyle, lineClass, lineStyle } =
useStepCustomStyle(props, isActive, stepMode)
</script>
// #ifdef MP-WEIXIN
<script lang="ts">
export default {
options: {
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
virtualHost: true,
},
}
</script>
// #endif
<template>
<view :class="[stepClass]" :style="stepStyle">
<!-- 步骤内容容器 -->
<view :class="[ns.e('container')]" @tap.stop="itemClickEvent">
<!-- 模式 -->
<view :class="[ns.e('mode-item')]">
<slot>
<!-- 点模式 -->
<view
v-if="stepMode === 'dot'"
:class="[modeClass()]"
:style="modeStyle()"
/>
<!-- 数字模式 -->
<view
v-if="stepMode === 'number'"
:class="[modeClass()]"
:style="modeStyle()"
>
<view class="icon">
<TnIcon name="check" />
</view>
</view>
<!-- 点图标模式 -->
<view
v-if="stepMode === 'dotIcon'"
:class="[modeClass()]"
:style="modeStyle()"
>
<view
class="dot"
:class="[modeClass('dot')]"
:style="modeStyle('dot')"
/>
<view
class="icon"
:class="[modeClass('icon')]"
:style="modeStyle('icon')"
>
<TnIcon :name="activeIcon" />
</view>
</view>
<!-- 图标模式 -->
<view
v-if="stepMode === 'icon'"
:class="[modeClass()]"
:style="modeStyle()"
>
<view class="icon">
<TnIcon :name="isActive ? activeIcon : icon" />
</view>
</view>
</slot>
</view>
<!-- 标题 -->
<view v-if="title" :class="[ns.e('mode-title')]">{{ title }}</view>
</view>
<!-- 横线 -->
<view :class="[lineClass]" :style="lineStyle" />
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/step.scss';
</style>
+33
View File
@@ -0,0 +1,33 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../constants'
import { buildProps, isNumber } from '../../../utils'
import { stepsBaseProps } from '../../base/common-props/steps'
import type { ExtractPropTypes } from 'vue'
export const stepModes = ['dot', 'number', 'dotIcon', 'icon'] as const
export const stepsProps = buildProps({
...stepsBaseProps,
/**
* @description 当前激活步骤绑定的index
*/
modelValue: Number,
/**
* @description 步骤条模式
*/
mode: {
type: String,
values: stepModes,
default: 'dot',
},
})
export const stepsEmits = {
[UPDATE_MODEL_EVENT]: (index: number) => isNumber(index),
[CHANGE_EVENT]: (index: number) => isNumber(index),
}
export type StepsProps = ExtractPropTypes<typeof stepsProps>
export type StepsEmits = typeof stepsEmits
export type StepsMode = (typeof stepModes)[number]
+20
View File
@@ -0,0 +1,20 @@
<script lang="ts" setup>
import { stepsEmits, stepsProps } from './steps'
import { useSteps, useStepsCustomStyle } from './composables'
const props = defineProps(stepsProps)
defineEmits(stepsEmits)
useSteps(props)
const { ns } = useStepsCustomStyle()
</script>
<template>
<view :class="[ns.b()]">
<slot />
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/steps.scss';
</style>
+5
View File
@@ -0,0 +1,5 @@
export type StepRectInfo = {
width: number
height: number
left: number
}