智能柜补货

This commit is contained in:
weiwei committed 2025-04-17 17:48:12 +08:00
1 parent 587858fc74
commit eb57fc50b0
10 files changed
+1148 -812

No files matched your search

@@ -1,8 +1,15 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, PropType } from 'vue'
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import { ref, computed, onMounted, watch } from "vue"
import {
arrayNumberValid,
arrayNumberValidByStyleMP,
covetUniNumber,
arrayNumberValidByStyleBorderColor,
linearValid,
getUnit,
} from "../../libs/tool"
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors"
import { useTmConfig } from "../../libs/config"
/**
* @displayName 角标
* @exportName tm-badge
@@ -13,210 +20,204 @@ import { useTmConfig } from "../../libs/config";
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmBadge' });
defineOptions({ name: "TmBadge" })
const { config } = useTmConfig()
const attrs = defineProps({
fontSize: {
type: [String, Number],
default: "20"
},
bgColor: {
type: String,
default: "error"
},
fontColor: {
type: String,
default: "white"
},
maxCount: {
type: [String, Number],
default: 99
},
/**
* 如果是boolean就显示角标小红点
* 如果是string显示文本标签
* 如果是数字显示数字并启用maxCount
*/
label: {
type: [String, Number,Boolean],
default: ""
},
position: {
type: String as PropType<"right" | "left" | "bottomLeft" | "bottomRight" | 'top' | 'bottom'>,
default: "right"
},
offset: {
type: Array as PropType<number[]>,
default: (): number[] => [0, 0] as number[],
validator(val: number[]): boolean {
if (val.length == 0 || val.length == 2) return true
console.error("x:必须是长度为2的数字数组参数或者空数组")
return false
}
}
fontSize: {
type: [String, Number],
default: "20",
},
bgColor: {
type: String,
default: "error",
},
fontColor: {
type: String,
default: "white",
},
maxCount: {
type: [String, Number],
default: 99,
},
/**
* 如果是boolean就显示角标小红点
* 如果是string显示文本标签
* 如果是数字显示数字并启用maxCount
*/
label: {
type: [String, Number, Boolean],
default: "",
},
// "right" | "left" | "bottomLeft" | "bottomRight" | "top" | "bottom"
position: {
type: String,
default: "right",
},
// number[]
offset: {
type: Array,
default: (): number[] => [0, 0] as number[],
validator(val: number[]): boolean {
if (val.length == 0 || val.length == 2) return true
console.error("x:必须是长度为2的数字数组参数或者空数组")
return false
},
},
})
const _offset = computed(() => attrs.offset)
const _isDot = computed(() => {
if(typeof attrs.label === 'boolean') return attrs.label
return false;
if (typeof attrs.label === "boolean") return attrs.label
return false
})
const _cStyles = computed(() => {
let trs = ''
if (attrs.position == 'right') {
trs = 'translate(50%, -50%)'
} else if (attrs.position == 'left') {
trs = 'translate(-50%, -50%)'
} else if (attrs.position == 'bottomLeft') {
trs = 'translate(-50%, 50%)'
} else if (attrs.position == 'bottomRight') {
trs = 'translate(50%, 50%)'
} else if (attrs.position == 'top') {
trs = 'translate(0%, -50%)'
} else if (attrs.position == 'bottom') {
trs = 'translate(0%, 50%)'
}
let trs = ""
if (attrs.position == "right") {
trs = "translate(50%, -50%)"
} else if (attrs.position == "left") {
trs = "translate(-50%, -50%)"
} else if (attrs.position == "bottomLeft") {
trs = "translate(-50%, 50%)"
} else if (attrs.position == "bottomRight") {
trs = "translate(50%, 50%)"
} else if (attrs.position == "top") {
trs = "translate(0%, -50%)"
} else if (attrs.position == "bottom") {
trs = "translate(0%, 50%)"
}
let top = ''
let bottom = ''
let left = ''
let right = ''
let top = ""
let bottom = ""
let left = ""
let right = ""
if (attrs.position == 'top') {
top = '0px'
left = 'auto'
right = 'auto'
} else if (attrs.position == 'bottom') {
bottom = '0px'
left = 'auto'
right = 'auto'
} else if (attrs.position == 'right') {
top = _offset.value[1].toString() + 'px'
right = _offset.value[0].toString() + 'px'
} else if (attrs.position == 'left') {
top = '0px'
left = '0px'
} else if (attrs.position == 'bottomLeft') {
bottom = '0px'
left = '0px'
} else if (attrs.position == 'bottomRight') {
bottom = '0px'
right = '0px'
}
if (attrs.position == "top") {
top = "0px"
left = "auto"
right = "auto"
} else if (attrs.position == "bottom") {
bottom = "0px"
left = "auto"
right = "auto"
} else if (attrs.position == "right") {
top = _offset.value[1].toString() + "px"
right = _offset.value[0].toString() + "px"
} else if (attrs.position == "left") {
top = "0px"
left = "0px"
} else if (attrs.position == "bottomLeft") {
bottom = "0px"
left = "0px"
} else if (attrs.position == "bottomRight") {
bottom = "0px"
right = "0px"
}
let dotMapCs:{[key:string]:any} = {}
dotMapCs['background'] = getDefaultColor(attrs.bgColor)
dotMapCs['left'] = left
dotMapCs['right'] = right
dotMapCs['top'] = top
dotMapCs['bottom'] = bottom
dotMapCs['transform'] = trs
let dotMapCs: { [key: string]: any } = {}
dotMapCs["background"] = getDefaultColor(attrs.bgColor)
dotMapCs["left"] = left
dotMapCs["right"] = right
dotMapCs["top"] = top
dotMapCs["bottom"] = bottom
dotMapCs["transform"] = trs
let labelMapCs:{[key:string]:any} = {}
labelMapCs['background'] = getDefaultColor(attrs.bgColor)
labelMapCs['left'] = left
labelMapCs['right'] = right
labelMapCs['top'] = top
labelMapCs['bottom'] = bottom
labelMapCs['transform'] = trs
labelMapCs['visibility'] = _label.value == "" ? "hidden" : "visible"
return [dotMapCs, labelMapCs]
let labelMapCs: { [key: string]: any } = {}
labelMapCs["background"] = getDefaultColor(attrs.bgColor)
labelMapCs["left"] = left
labelMapCs["right"] = right
labelMapCs["top"] = top
labelMapCs["bottom"] = bottom
labelMapCs["transform"] = trs
labelMapCs["visibility"] = _label.value == "" ? "hidden" : "visible"
return [dotMapCs, labelMapCs]
})
const _fontColor = computed(() => getDefaultColor(attrs.fontColor))
const _fontSize = computed(() => covetUniNumber(attrs.fontSize, config.unit))
const _label = computed((): string => {
if(typeof attrs.label === 'boolean') return ''
if(typeof attrs.label === 'string') return attrs.label
let count = attrs.label
let maxCount = typeof attrs.maxCount == "number" ? attrs.maxCount : parseInt(attrs.maxCount)
if (count > 0 && count <= maxCount) return count.toString();
if (count <= 0) return ""
return maxCount.toString() + "+"
if (typeof attrs.label === "boolean") return ""
if (typeof attrs.label === "string") return attrs.label
let count = attrs.label
let maxCount = typeof attrs.maxCount == "number" ? attrs.maxCount : parseInt(attrs.maxCount)
if (count > 0 && count <= maxCount) return count.toString()
if (count <= 0) return ""
return maxCount.toString() + "+"
})
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
}
</script>
<template>
<view class="tmBadge">
<view class="tmBadgeWrap">
<text :style='_cStyles[0]' class="tmBadge-dot" :class="[_isDot ? 'noneShow' : 'nonex']"></text>
<view id="tmBadge-countAndLabel" class="tmBadge-countAndLabel" :class="[_isDot ? 'nonex' : 'noneShow']"
:style='_cStyles[1]'>
<text class="tmBadge-countAndLabelText" :style='{ color: _fontColor, fontSize: _fontSize }'>{{ _label}}</text>
</view>
<!--
<view class="tmBadge">
<view class="tmBadgeWrap">
<text :style="_cStyles[0]" class="tmBadge-dot" :class="[_isDot ? 'noneShow' : 'nonex']"></text>
<view id="tmBadge-countAndLabel" class="tmBadge-countAndLabel" :class="[_isDot ? 'nonex' : 'noneShow']" :style="_cStyles[1]">
<text class="tmBadge-countAndLabelText" :style="{ color: _fontColor, fontSize: _fontSize }">{{ _label }}</text>
</view>
<!--
@slot 默认内容插槽
-->
<slot></slot>
</view>
</view>
<slot></slot>
</view>
</view>
</template>
<style>
.tmBadge {
overflow: visible;
display: inline-flex;
position: relative;
overflow: visible;
display: inline-flex;
position: relative;
}
.tmBadge-countAndLabel {
position: absolute;
z-index: 3;
border-radius: 100px;
min-width: 0px;
padding: 0rpx 5px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
z-index: 3;
border-radius: 100px;
min-width: 0px;
padding: 0rpx 5px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.tmBadge-countAndLabelText {
line-height: 1.5;
text-align: center;
line-height: 1.5;
text-align: center;
}
.tmBadge-dot {
position: absolute;
width: 6px;
height: 6px;
border-radius: 18px;
z-index: 3;
opacity: 0;
position: absolute;
width: 6px;
height: 6px;
border-radius: 18px;
z-index: 3;
opacity: 0;
}
.noneShow {
opacity: 1;
opacity: 1;
}
.nonex {
opacity: 0;
opacity: 0;
}
.tmBadgeWrap {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: visible;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: visible;
}
</style>
</style>
@@ -1,8 +1,36 @@
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, PropType, getCurrentInstance, ComponentInstance, onUpdated, nextTick, VueElement, inject, watchEffect } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj, isBlackAndWhite, setBgColorLightByDark } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import {
ref,
computed,
onMounted,
onBeforeUnmount,
watch,
getCurrentInstance,
onUpdated,
nextTick,
VueElement,
inject,
watchEffect,
} from "vue"
import {
arrayNumberValid,
arrayNumberValidByStyleMP,
covetUniNumber,
arrayNumberValidByStyleBorderColor,
linearValid,
getUnit,
getUid,
} from "../../libs/tool"
import {
getDefaultColor,
getDefaultColorObj,
getOutlineColorObj,
getTextColorObj,
getThinColorObj,
isBlackAndWhite,
setBgColorLightByDark,
} from "../../libs/colors"
import { useTmConfig } from "../../libs/config"
/**
* @displayName 步进器
@@ -14,150 +42,150 @@ import { useTmConfig } from "../../libs/config";
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmNavbar' });
defineOptions({ name: "TmNavbar" })
const { config } = useTmConfig()
const props = defineProps({
/**
* 当前值,可v-model
*/
modelValue: {
type: Number,
default: 0
},
/**
* 最大值
*/
max: {
type: Number,
default: 100
},
/**
* 组件宽
*/
width: {
type: String,
default: "auto"
},
/**
* 最小值
*/
min: {
type: Number,
default: 0
},
/**
* 是否禁用
*/
disabled: {
type: Boolean,
default: false
},
/**
* 步进值
*/
step: {
type: Number,
default: 1
},
/**
* 如果进步值是小数位需要设置此值
*/
decimalLen: {
type: Number,
default: 0
},
/**
* 按钮的颜色
*/
btnColor: {
type: String,
default: "info"
},
/**
* 按钮的暗黑颜色
* 空值读取全局的Input暗黑背景色
*/
darkBtnColor: {
type: String,
default: ""
},
/**
* 输入框的背景色
*/
bgColor: {
type: String,
default: "info"
},
/**
* 输入框的暗黑背景色
* 空值读取全局的Input暗黑背景色
*/
darkBgColor: {
type: String,
default: ""
},
/**
* 按钮的宽
*/
btnWidth: {
type: String,
default: "64"
},
/**
* 输入框及按钮的高
*/
height: {
type: String,
default: "64"
},
/**
* 按钮的圆角。
*/
round: {
type: String,
default: "8"
},
/**
* 是否按钮与输入框独立开来
* 不和输入框粘一起。
*/
splitBtn: {
type: Boolean,
default: false
},
/**
* 按钮文本颜色,暗黑时取白色
*/
btnFontColor: {
type: String,
default: "#333333"
},
/**
* 文本颜色,暗黑时取白色
*/
fontColor: {
type: String,
default: "#333333"
},
/**
* 文本文字大小
*/
fontSize: {
type: String,
default: "28"
},
/**
* 当前值,可v-model
*/
modelValue: {
type: Number,
default: 0,
},
/**
* 最大值
*/
max: {
type: Number,
default: 100,
},
/**
* 组件宽
*/
width: {
type: String,
default: "auto",
},
/**
* 最小值
*/
min: {
type: Number,
default: 0,
},
/**
* 是否禁用
*/
disabled: {
type: Boolean,
default: false,
},
/**
* 步进值
*/
step: {
type: Number,
default: 1,
},
/**
* 如果进步值是小数位需要设置此值
*/
decimalLen: {
type: Number,
default: 0,
},
/**
* 按钮的颜色
*/
btnColor: {
type: String,
default: "info",
},
/**
* 按钮的暗黑颜色
* 空值读取全局的Input暗黑背景色
*/
darkBtnColor: {
type: String,
default: "",
},
/**
* 输入框的背景色
*/
bgColor: {
type: String,
default: "info",
},
/**
* 输入框的暗黑背景色
* 空值读取全局的Input暗黑背景色
*/
darkBgColor: {
type: String,
default: "",
},
/**
* 按钮的宽
*/
btnWidth: {
type: String,
default: "64",
},
/**
* 输入框及按钮的高
*/
height: {
type: String,
default: "64",
},
/**
* 按钮的圆角。
*/
round: {
type: String,
default: "8",
},
/**
* 是否按钮与输入框独立开来
* 不和输入框粘一起。
*/
splitBtn: {
type: Boolean,
default: false,
},
/**
* 按钮文本颜色,暗黑时取白色
*/
btnFontColor: {
type: String,
default: "#333333",
},
/**
* 文本颜色,暗黑时取白色
*/
fontColor: {
type: String,
default: "#333333",
},
/**
* 文本文字大小
*/
fontSize: {
type: String,
default: "28",
},
})
const emit = defineEmits([
/**
* 输入值或者点击按钮时触发
* @param {number} str - 当前的值。
*/
'change',
/**
* 等同vmodel
*/
'update:modelValue'
/**
* 输入值或者点击按钮时触发
* @param {number} str - 当前的值。
*/
"change",
/**
* 等同vmodel
*/
"update:modelValue",
])
const _value = ref(0)
@@ -166,19 +194,19 @@ const addDomDisabeld = ref(false)
const surDomDisabeld = ref(false)
const _round = computed((): string => {
return covetUniNumber(props.round, config.unit)
return covetUniNumber(props.round, config.unit)
})
const _width = computed((): string => covetUniNumber(props.width, config.unit))
const _fontSize = computed((): string => {
let fontSize = covetUniNumber(props.fontSize, config.unit);
if (config.fontSizeScale == 1) return fontSize;
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 14
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
let fontSize = covetUniNumber(props.fontSize, config.unit)
if (config.fontSizeScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 14
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
})
const _unFontSize = computed((): string => props.fontSize)
@@ -186,8 +214,8 @@ const _unFontSize = computed((): string => props.fontSize)
const _btnFontColor = computed((): string => getDefaultColor(props.btnFontColor))
const _fontColor = computed((): string => {
if (config.mode == 'dark') return '#ffffff'
return getDefaultColor(props.fontColor)
if (config.mode == "dark") return "#ffffff"
return getDefaultColor(props.fontColor)
})
const _height = computed((): string => covetUniNumber(props.height, config.unit))
@@ -197,27 +225,27 @@ const _btnWidth = computed((): string => covetUniNumber(props.btnWidth, config.u
const _splitBtn = computed((): boolean => props.splitBtn)
const _btnColor = computed((): string => {
let color = getDefaultColor(props.btnColor)
if (config.mode == 'dark') {
if (props.darkBtnColor == "") {
color = config.inputDarkColor
} else {
color = getDefaultColor(props.darkBtnColor)
}
}
return color
let color = getDefaultColor(props.btnColor)
if (config.mode == "dark") {
if (props.darkBtnColor == "") {
color = config.inputDarkColor
} else {
color = getDefaultColor(props.darkBtnColor)
}
}
return color
})
const _bgColor = computed((): string => {
let color = getDefaultColor(props.bgColor)
if (config.mode == 'dark') {
if (props.darkBgColor == "") {
color = config.inputDarkColor
} else {
color = getDefaultColor(props.darkBgColor)
}
}
return color
let color = getDefaultColor(props.bgColor)
if (config.mode == "dark") {
if (props.darkBgColor == "") {
color = config.inputDarkColor
} else {
color = getDefaultColor(props.darkBgColor)
}
}
return color
})
const _max = computed((): number => props.max)
@@ -225,137 +253,163 @@ const _min = computed((): number => props.min)
const _step = computed((): number => props.step)
const _disabled = computed((): boolean => props.disabled)
watch(() => props.modelValue, (newval: number) => {
if (newval == _value.value) return;
setValue(newval, false)
})
watch(
() => props.modelValue,
(newval: number) => {
if (newval == _value.value) return
setValue(newval, false)
},
)
onMounted(() => {
setValue(props.modelValue, false)
setValue(props.modelValue, false)
})
const isInRange = (value: number): boolean => {
return value >= _min.value && value <= _max.value;
return value >= _min.value && value <= _max.value
}
const clampValue = (value: number): number => {
return Math.min(Math.max(value, _min.value), _max.value);
return Math.min(Math.max(value, _min.value), _max.value)
}
const getDecimalPlaces = (): number => {
return props.decimalLen;
return props.decimalLen
}
/**
* 加
*/
const handleIncrement = () => {
const newValue = Math.min(_value.value + _step.value, props.max);
let val = clampValue(parseFloat(newValue.toFixed(props.decimalLen)));
setValue(val, true)
const newValue = Math.min(_value.value + _step.value, props.max)
let val = clampValue(parseFloat(newValue.toFixed(props.decimalLen)))
setValue(val, true)
}
/**减 */
const handleDecrement = () => {
const newValue = Math.max(_value.value - _step.value, props.min);
let val = clampValue(parseFloat(newValue.toFixed(props.decimalLen)));
setValue(val, true)
const newValue = Math.max(_value.value - _step.value, props.min)
let val = clampValue(parseFloat(newValue.toFixed(props.decimalLen)))
setValue(val, true)
}
const handleInputChange = (event: any) => {
_input_value.value = event.detail.value
_input_value.value = event.detail.value
}
const inputBlur = () => {
const inputValue = parseFloat(_input_value.value);
if (!isNaN(inputValue)) {
let val = clampValue(inputValue);
setValue(val, true)
} else {
setValue(parseFloat(_min.value.toFixed(props.decimalLen)), true)
}
const inputValue = parseFloat(_input_value.value)
if (!isNaN(inputValue)) {
let val = clampValue(inputValue)
setValue(val, true)
} else {
setValue(parseFloat(_min.value.toFixed(props.decimalLen)), true)
}
}
const setValue = (value: number, isEmit: boolean) => {
const clampedValue = clampValue(value);
_value.value = clampedValue;
_input_value.value = _value.value.toString();
addDomDisabeld.value = _value.value >= props.max
surDomDisabeld.value = _value.value <= props.min
const clampedValue = clampValue(value)
_value.value = clampedValue
_input_value.value = _value.value.toString()
addDomDisabeld.value = _value.value >= props.max
surDomDisabeld.value = _value.value <= props.min
if (isEmit) {
/**
* 等同v-model
*/
emit("update:modelValue", clampedValue)
/**
* 输入值或者点击按钮时触发
* @param {number} 当前的值。
*/
emit("change", clampedValue)
}
if (isEmit) {
/**
* 等同v-model
*/
emit("update:modelValue", clampedValue)
/**
* 输入值或者点击按钮时触发
* @param {number} 当前的值。
*/
emit("change", clampedValue)
}
}
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
}
</script>
<template>
<view class="xStepper" :style="{ width: _width, borderRadius: _round }">
<view :hover-start-time="20" :hover-stay-time="150" :hover-class="addDomDisabeld ? '' : 'xStepperHoverbtn'"
@click="handleDecrement" class="xStepperBtn"
:style="{ backgroundColor: _btnColor, height: _height, width: _btnWidth, opacity: surDomDisabeld ? 0.6 : 1, borderRadius: _splitBtn ? '50px' : '0rpx' }">
<tm-icon _style="pointer-events: none" :color="_btnFontColor" :size="_unFontSize"
name="subtract-line"></tm-icon>
</view>
<view class="xStepper" :style="{ width: _width, borderRadius: _round }">
<view
:hover-start-time="20"
:hover-stay-time="150"
:hover-class="addDomDisabeld ? '' : 'xStepperHoverbtn'"
@click="handleDecrement"
class="xStepperBtn"
:style="{
backgroundColor: _btnColor,
height: _height,
width: _btnWidth,
opacity: surDomDisabeld ? 0.6 : 1,
borderRadius: _splitBtn ? '50px' : '0rpx',
}"
>
<tm-icon _style="pointer-events: none" :color="_btnFontColor" :size="_unFontSize" name="subtract-line"></tm-icon>
</view>
<input @blur="inputBlur" @input="handleInputChange" :value="_input_value" class="xStepperInput"
:style="{ backgroundColor: _splitBtn ? 'transparent' : _btnColor, height: _height, color: _fontColor, fontSize: _fontSize }"
:type="decimalLen > 0 ? 'digit' : 'number'" />
<view :hover-start-time="20" :hover-stay-time="150" :hover-class="addDomDisabeld ? '' : 'xStepperHoverbtn'"
@click="handleIncrement" class="xStepperBtn"
:style="{ backgroundColor: _btnColor, height: _height, width: _btnWidth, borderRadius: _splitBtn ? '50px' : '0rpx' }">
<tm-icon :_style="` opacity: ${addDomDisabeld} ? 0.6 : 1,'pointerEvents': 'none'`" :color="_btnFontColor"
:size="_unFontSize" name="add-line"></tm-icon>
</view>
</view>
<input
@blur="inputBlur"
@input="handleInputChange"
:value="_input_value"
class="xStepperInput"
:style="{ backgroundColor: _splitBtn ? 'transparent' : _btnColor, height: _height, color: _fontColor, fontSize: _fontSize }"
:type="decimalLen > 0 ? 'digit' : 'number'"
/>
<view
:hover-start-time="20"
:hover-stay-time="150"
:hover-class="addDomDisabeld ? '' : 'xStepperHoverbtn'"
@click="handleIncrement"
class="xStepperBtn"
:style="{ backgroundColor: _btnColor, height: _height, width: _btnWidth, borderRadius: _splitBtn ? '50px' : '0rpx' }"
>
<tm-icon
:_style="` opacity: ${addDomDisabeld} ? 0.6 : 1,'pointerEvents': 'none'`"
:color="_btnFontColor"
:size="_unFontSize"
name="add-line"
></tm-icon>
</view>
</view>
</template>
<style scoped>
.xStepperBtnBtn {
pointer-events: none;
flex-shrink: 0;
pointer-events: none;
flex-shrink: 0;
}
.xStepperHoverbtn {
opacity: 0.8;
opacity: 0.8;
}
.xStepper {
display: flex;
flex-direction: row;
overflow: hidden;
display: flex;
flex-direction: row;
overflow: hidden;
}
.xStepperInput {
flex: 1;
margin: 0 1px;
padding: 0 5px;
text-align: center;
flex-shrink: 0;
min-width: 20px;
flex: 1;
margin: 0 1px;
padding: 0 5px;
text-align: center;
flex-shrink: 0;
min-width: 20px;
}
.xStepperBtn {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
</style>
</style>
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, PropType, provide, nextTick } from 'vue';
import { useTmConfig } from "../../libs/config";
import { tmDate, type tmDateTypeTime, createDate } from '../../libs/tmDate';
import { covetUniNumber, getUnit } from '../../libs/tool';
import { getDefaultColor } from '../../libs/colors';
import { ref, computed, onMounted, watch, provide, nextTick } from "vue"
import { useTmConfig } from "../../libs/config"
import { tmDate, type tmDateTypeTime, createDate } from "../../libs/tmDate"
import { covetUniNumber, getUnit } from "../../libs/tool"
import { getDefaultColor } from "../../libs/colors"
/**
* @displayName 标签导航
@@ -15,221 +15,219 @@ import { getDefaultColor } from '../../libs/colors';
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmTabs' });
defineOptions({ name: "TmTabs" })
const { config } = useTmConfig()
const emit = defineEmits([
/** 变化时触发 */
"change",
/** 同步vmodel */
"update:modelValue"
/** 变化时触发 */
"change",
/** 同步vmodel */
"update:modelValue",
])
const props = defineProps(
{
/**
* 圆角
*/
round: {
type: [String, Number],
default: 0
},
/**
* 宽
*/
width: {
type: [String, Number],
default: "auto"
},
/**
* 高是必填,不可为auto。
*/
height: {
type: [String, Number],
default: "88"
},
/**
* 排版模式
* around:居中并且均分[数据多时会造成无法滚动]
* between:均分在整个宽上(如想相项目宽一样,你可以设置属性width比如你有3个可以设置为33%)[数据多时会造成无法滚动]
* start:左对齐[数据多时可滚动]
* end:右对齐[数据多时可滚动]
* center:中间对齐[数据多时会造成无法滚动]
*/
mode: {
type: String as PropType<"around" | "between" | "start" | "end" | "center">,
default: "start"
},
/**
* 背景
*/
color: {
type: String,
default: "white"
},
/**
* 暗黑时的背景,如果不填写读取sheetDark
*/
darkColor: {
type: String,
default: ""
},
/**
* 文本激活时的颜色 ,空值默认取全局主题色
*/
activeTitleColor: {
type: String,
default: ""
},
/**
* 文本默认颜色
*/
titleColor: {
type: String,
default: "#888888"
},
/**
* 文本默认的暗黑颜色,如果不填写取白色。
*/
darkTitleColor: {
type: String,
default: "#cacaca"
},
/**
* 底部线条激活时的颜色,空值默认取全局主题色
*/
lineColor: {
type: String,
default: ""
},
lineHeight: {
type: String,
default: "3px"
},
/**
* 底部的线条是与项目等宽还是固定默认的小宽度。
*/
lineFull: {
type: Boolean,
default: false
},
/**
* 是否显示底部的线条
*/
showLine: {
type: Boolean,
default: true
},
/**
* id值,如果数据没有提供id属性,这里的id就是索引
* 等同v-model
*/
modelValue: {
type: [String, Number],
default: 0
},
/**
* 标题字号
*/
fontSize: {
type: [String, Number],
default: "30"
},
/**
* 激活时的标题字号
*/
activeFontSize: {
type: [String, Number],
default: "32"
},
/**
* 项目宽度,默认是auto,即自动根据标题内容自动撑开宽度。
* 如果你想均分(适合不超过一行),比如你有5个标签,那么你就可以设置为"20%"
*/
itemWidth: {
type: [String, Number],
default: "auto"
},
/**
* 标题的padding是左右的间隙。
*/
titlePadding: {
type: [String, Number],
default: "12px"
},
/**
* 选中当前项是,移动的差值个数。比如你要让选中的居中,你一屏内
* 刚好显示5个,你就设置为2,如果你一屏显示3个,就设置为1,就是你一屏内
* 里面要显示的是单数,才好控制选中的始终是在中间位置 。
*/
moveCount: {
type: Number,
default: 2
},
/**
* 项目之间的间隙.如果你设定了itemwidth一般会有富余宽,两边自然有间隙,此时你应该设置为0以免造成不居中的假象.
*/
gap: {
type: [String, Number],
default: 20
},
/**
* 数据数组,可以混合类型可以是单类型
* 数据格式比较灵活.
* 如果数据是对象且出现了dot:true该项数组右上角出现角标红点
* 如果dot:非boolean比如:dot:32会出现数字角标或者文本角标.如果为false或者空值为隐藏
*/
list: {
type: [Array<Record<string, any> | string>],
default: () => [],
require: true
},
/**
* 当你的list为对象时可用
* 如果数据是string会直接取数据本身
*/
rangKey: {
type: String,
default: "title"
},
/**
* id标识,用来vmodel双向绑定选中的值,
* 如果你的list中不存在或者没有id标识
* 那么取数组循环的索引值
* */
rangId: {
type: String,
default: "id"
},
/**
* 读取角标的标识字段,默认是dot
* */
rangDot: {
type: String,
default: "dot"
},
/**
* 角标的背景,默认是等同激活的标题颜色
* 如果填写使用本值,文字颜色始终是白的.
*/
dotBgColor: {
type: String,
default: ""
},
/**
* 默认的自定项目的style样式,可以用来完全覆盖默认的样式.
*/
customItemStyle: {
type: String,
default: ""
},
/**
* 默认的激活自定项目的style样式,可以用来完全覆盖默认的样式.
*/
activeItemStyle: {
type: String,
default: ""
}
}
)
const props = defineProps({
/**
* 圆角
*/
round: {
type: [String, Number],
default: 0,
},
/**
* 宽
*/
width: {
type: [String, Number],
default: "auto",
},
/**
* 高是必填,不可为auto。
*/
height: {
type: [String, Number],
default: "88",
},
/**
* 排版模式
* around:居中并且均分[数据多时会造成无法滚动]
* between:均分在整个宽上(如想相项目宽一样,你可以设置属性width比如你有3个可以设置为33%)[数据多时会造成无法滚动]
* start:左对齐[数据多时可滚动]
* end:右对齐[数据多时可滚动]
* center:中间对齐[数据多时会造成无法滚动]
*/
mode: {
type: String,
default: "start",
},
/**
* 背景
*/
color: {
type: String,
default: "white",
},
/**
* 暗黑时的背景,如果不填写读取sheetDark
*/
darkColor: {
type: String,
default: "",
},
/**
* 文本激活时的颜色 ,空值默认取全局主题色
*/
activeTitleColor: {
type: String,
default: "",
},
/**
* 文本默认颜色
*/
titleColor: {
type: String,
default: "#888888",
},
/**
* 文本默认的暗黑颜色,如果不填写取白色。
*/
darkTitleColor: {
type: String,
default: "#cacaca",
},
/**
* 底部线条激活时的颜色,空值默认取全局主题色
*/
lineColor: {
type: String,
default: "",
},
lineHeight: {
type: String,
default: "3px",
},
/**
* 底部的线条是与项目等宽还是固定默认的小宽度。
*/
lineFull: {
type: Boolean,
default: false,
},
/**
* 是否显示底部的线条
*/
showLine: {
type: Boolean,
default: true,
},
/**
* id值,如果数据没有提供id属性,这里的id就是索引
* 等同v-model
*/
modelValue: {
type: [String, Number],
default: 0,
},
/**
* 标题字号
*/
fontSize: {
type: [String, Number],
default: "30",
},
/**
* 激活时的标题字号
*/
activeFontSize: {
type: [String, Number],
default: "32",
},
/**
* 项目宽度,默认是auto,即自动根据标题内容自动撑开宽度。
* 如果你想均分(适合不超过一行),比如你有5个标签,那么你就可以设置为"20%"
*/
itemWidth: {
type: [String, Number],
default: "auto",
},
/**
* 标题的padding是左右的间隙。
*/
titlePadding: {
type: [String, Number],
default: "12px",
},
/**
* 选中当前项是,移动的差值个数。比如你要让选中的居中,你一屏内
* 刚好显示5个,你就设置为2,如果你一屏显示3个,就设置为1,就是你一屏内
* 里面要显示的是单数,才好控制选中的始终是在中间位置 。
*/
moveCount: {
type: Number,
default: 2,
},
/**
* 项目之间的间隙.如果你设定了itemwidth一般会有富余宽,两边自然有间隙,此时你应该设置为0以免造成不居中的假象.
*/
gap: {
type: [String, Number],
default: 20,
},
/**
* 数据数组,可以混合类型可以是单类型
* 数据格式比较灵活.
* 如果数据是对象且出现了dot:true该项数组右上角出现角标红点
* 如果dot:非boolean比如:dot:32会出现数字角标或者文本角标.如果为false或者空值为隐藏
*/
list: {
type: [Array<Record<string, any> | string>],
default: () => [],
require: true,
},
/**
* 当你的list为对象时可用
* 如果数据是string会直接取数据本身
*/
rangKey: {
type: String,
default: "title",
},
/**
* id标识,用来vmodel双向绑定选中的值,
* 如果你的list中不存在或者没有id标识
* 那么取数组循环的索引值
* */
rangId: {
type: String,
default: "id",
},
/**
* 读取角标的标识字段,默认是dot
* */
rangDot: {
type: String,
default: "dot",
},
/**
* 角标的背景,默认是等同激活的标题颜色
* 如果填写使用本值,文字颜色始终是白的.
*/
dotBgColor: {
type: String,
default: "",
},
/**
* 默认的自定项目的style样式,可以用来完全覆盖默认的样式.
*/
customItemStyle: {
type: String,
default: "",
},
/**
* 默认的激活自定项目的style样式,可以用来完全覆盖默认的样式.
*/
activeItemStyle: {
type: String,
default: "",
},
})
const _showLine = computed(() => props.showLine)
const _lineFull = computed(() => props.lineFull)
@@ -237,13 +235,13 @@ const _customItemStyle = computed(() => props.customItemStyle)
const _activeItemStyle = computed(() => props.activeItemStyle)
const _fontSize = computed(() => {
let fontSize = covetUniNumber(props.fontSize, config.unit);
if (config.fontSizeScale == 1) return fontSize;
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 30
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
let fontSize = covetUniNumber(props.fontSize, config.unit)
if (config.fontSizeScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 30
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
})
const _itemWidth = computed(() => covetUniNumber(props.itemWidth, config.unit))
@@ -251,14 +249,14 @@ const _itemWidth = computed(() => covetUniNumber(props.itemWidth, config.unit))
const _round = computed(() => covetUniNumber(props.round, config.unit))
const _activeFontSize = computed(() => {
// covetUniNumber(props.activeFontSize, config.unit)
let fontSize = covetUniNumber(props.activeFontSize, config.unit);
if (config.fontSizeScale == 1) return fontSize;
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 32
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
// covetUniNumber(props.activeFontSize, config.unit)
let fontSize = covetUniNumber(props.activeFontSize, config.unit)
if (config.fontSizeScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 32
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(fontSize)
})
const _lineHeight = computed(() => covetUniNumber(props.lineHeight, config.unit))
@@ -268,199 +266,213 @@ const _height = computed(() => covetUniNumber(props.height, config.unit))
const _width = computed(() => covetUniNumber(props.width, config.unit))
const _color = computed(() => {
if (config.mode == 'dark') {
if (props.darkColor != '') {
return getDefaultColor(props.darkColor)
} else {
return getDefaultColor(config.sheetDarkColor)
}
}
return getDefaultColor(props.color)
if (config.mode == "dark") {
if (props.darkColor != "") {
return getDefaultColor(props.darkColor)
} else {
return getDefaultColor(config.sheetDarkColor)
}
}
return getDefaultColor(props.color)
})
const _activeTitleColor = computed(() => {
if (props.activeTitleColor == "") return getDefaultColor(config.color);
return getDefaultColor(props.activeTitleColor)
if (props.activeTitleColor == "") return getDefaultColor(config.color)
return getDefaultColor(props.activeTitleColor)
})
const _dotBgColor = computed(() => {
if (props.dotBgColor == "") return _activeTitleColor.value
return props.dotBgColor
if (props.dotBgColor == "") return _activeTitleColor.value
return props.dotBgColor
})
const _titleColor = computed(() => {
if (config.mode == 'dark') {
if (props.darkTitleColor != '') {
return getDefaultColor(props.darkTitleColor)
} else {
return "#ffffff"
}
}
return getDefaultColor(props.titleColor)
if (config.mode == "dark") {
if (props.darkTitleColor != "") {
return getDefaultColor(props.darkTitleColor)
} else {
return "#ffffff"
}
}
return getDefaultColor(props.titleColor)
})
const _lineColor = computed(() => {
if (props.lineColor == "") return _activeTitleColor.value;
return getDefaultColor(props.lineColor)
if (props.lineColor == "") return _activeTitleColor.value
return getDefaultColor(props.lineColor)
})
const activeName = ref(props.modelValue)
const moveName = ref(props.modelValue)
const _list = computed(() => props.list)
const _listIds = computed(() => {
let ids: Array<string | number> = _list.value.map((item, index) => {
if (typeof item === 'string') return index
let id = typeof item[props.rangId] == 'undefined' ? index : item[props.rangId]
return id;
})
return ids;
let ids: Array<string | number> = _list.value.map((item, index) => {
if (typeof item === "string") return index
let id = typeof item[props.rangId] == "undefined" ? index : item[props.rangId]
return id
})
return ids
})
const _mode = computed(() => {
if (props.mode == 'around') return "space-around"
if (props.mode == 'between') return "space-between"
if (props.mode == 'center') return "center"
if (props.mode == 'end') return "flex-end"
return 'flex-start'
if (props.mode == "around") return "space-around"
if (props.mode == "between") return "space-between"
if (props.mode == "center") return "center"
if (props.mode == "end") return "flex-end"
return "flex-start"
})
provide('tmTabsActiveName', computed((): string | number => activeName.value))
provide(
"tmTabsActiveName",
computed((): string | number => activeName.value),
)
watch(() => props.modelValue, (newValue: string | number) => {
if (newValue == activeName.value) return;
toggleActivate(newValue)
})
watch(
() => props.modelValue,
(newValue: string | number) => {
if (newValue == activeName.value) return
toggleActivate(newValue)
},
)
const getValue = (item: Record<string, any> | string): string => {
if (typeof item === 'string') return item;
let value = item[props.rangKey] || ""
return value;
if (typeof item === "string") return item
let value = item[props.rangKey] || ""
return value
}
const getDot = (item: Record<string, any> | string): boolean | string | number => {
if (typeof item === 'string') return false;
let value = typeof item[props.rangDot] == 'undefined' ? false : item[props.rangDot]
return value;
if (typeof item === "string") return false
let value = typeof item[props.rangDot] == "undefined" ? false : item[props.rangDot]
return value
}
/**
* 切换选中
*/
const toggleActivate = (name: string | number, isEmits = false) => {
activeName.value = name
if (isEmits) {
emit('update:modelValue', name)
emit('change', name)
}
changMovename(name)
activeName.value = name
if (isEmits) {
emit("update:modelValue", name)
emit("change", name)
}
changMovename(name)
}
const changMovename = (name: string | number) => {
let index = _listIds.value.findIndex(el => el == name)
if (index == -1) return;
index = index - props.moveCount
index = Math.max(0, index)
moveName.value = ''
let index = _listIds.value.findIndex((el) => el == name)
if (index == -1) return
index = index - props.moveCount
index = Math.max(0, index)
moveName.value = ""
nextTick(() => {
moveName.value = _listIds.value[index];
})
nextTick(() => {
moveName.value = _listIds.value[index]
})
}
const tabsOnclick = (item: string | Record<string, any>, index: number) => {
let name = _listIds.value[index];
if (name == activeName.value) return;
toggleActivate(name, true)
let name = _listIds.value[index]
if (name == activeName.value) return
toggleActivate(name, true)
}
defineExpose({ toggleActivate })
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true
}
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
}
</script>
<template>
<view class="tmTabs" :style="{
backgroundColor: _color,
'border-radius': _round,
height: _height,
width: _width
}
">
<scroll-view :show-scrollbar="false" enable-flex :scroll-into-view="'tabs_' + moveName" class="tmTabsWrapBox"
:scroll-x="true">
<view class="tmTabsWrap" :style="{ justifyContent: _mode }">
<view @click="tabsOnclick(item, index)" class="tmTabsItem" :id="'tabs_' + _listIds[index]"
v-for="(item, index) in _list" :key="index" :style="[
{
color: activeName == _listIds[index] ? _activeTitleColor : _titleColor,
fontSize: activeName == _listIds[index] ? _activeFontSize : _fontSize,
fontWeight: activeName == _listIds[index] ? 'bold' : 'normal',
width: _itemWidth,
padding: `0px ${_gap}`
},
activeName == _listIds[index] ? _activeItemStyle : _customItemStyle
]">
<tm-badge :bg-color="_dotBgColor" :label="getDot(item)">
{{ getValue(item) }}
</tm-badge>
<view v-if="_showLine" class="tmTabsItemLiner" :style="{
width: activeName == _listIds[index] ? (_lineFull ? '100%' : '36%') : '0%',
height: _lineHeight,
maxWidth: !_lineFull ? '36px' : 'auto',
backgroundColor: activeName == _listIds[index] ? _lineColor : 'transparent'
}"></view>
</view>
</view>
</scroll-view>
</view>
<view
class="tmTabs"
:style="{
backgroundColor: _color,
'border-radius': _round,
height: _height,
width: _width,
}"
>
<scroll-view :show-scrollbar="false" enable-flex :scroll-into-view="'tabs_' + moveName" class="tmTabsWrapBox" :scroll-x="true">
<view class="tmTabsWrap" :style="{ justifyContent: _mode }">
<view
@click="tabsOnclick(item, index)"
class="tmTabsItem"
:id="'tabs_' + _listIds[index]"
v-for="(item, index) in _list"
:key="index"
:style="[
{
color: activeName == _listIds[index] ? _activeTitleColor : _titleColor,
fontSize: activeName == _listIds[index] ? _activeFontSize : _fontSize,
fontWeight: activeName == _listIds[index] ? 'bold' : 'normal',
width: _itemWidth,
padding: `0px ${_gap}`,
},
activeName == _listIds[index] ? _activeItemStyle : _customItemStyle,
]"
>
<tm-badge :bg-color="_dotBgColor" :label="getDot(item)">
{{ getValue(item) }}
</tm-badge>
<view
v-if="_showLine"
class="tmTabsItemLiner"
:style="{
width: activeName == _listIds[index] ? (_lineFull ? '100%' : '36%') : '0%',
height: _lineHeight,
maxWidth: !_lineFull ? '36px' : 'auto',
backgroundColor: activeName == _listIds[index] ? _lineColor : 'transparent',
}"
></view>
</view>
</view>
</scroll-view>
</view>
</template>
<style lang="css">
.tmTabsItem {
/* padding: 0 20rpx; */
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
flex-shrink: 0;
box-sizing: border-box;
/* padding: 0 20rpx; */
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
flex-shrink: 0;
box-sizing: border-box;
}
.tmTabsItemLiner {
/* height: 2px; */
position: absolute;
z-index: 1;
bottom: 0px;
transition: all 0.3s ease-in-out;
/* height: 2px; */
position: absolute;
z-index: 1;
bottom: 0px;
transition: all 0.3s ease-in-out;
}
.tmTabs {
position: relative;
box-sizing: border-box;
position: relative;
box-sizing: border-box;
}
.tmTabsWrapBox {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.tmTabsWrap {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
min-width: 100%;
/* justify-content: flex-start; */
align-items: center;
/* width: auto; */
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
min-width: 100%;
/* justify-content: flex-start; */
align-items: center;
/* width: auto; */
height: 100%;
box-sizing: border-box;
}
</style>
</style>