feat:树形分类的新增和修改

This commit is contained in:
weiwei committed 2025-09-12 17:08:00 +08:00
1 parent 4b02337db3
commit 1b1642e51d
10 files changed
+276 -177

No files matched your search

@@ -1,9 +1,17 @@
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, onUpdated, nextTick } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import { onPageScroll } from '@dcloudio/uni-app';
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, onUpdated, nextTick } from "vue"
import {
arrayNumberValid,
arrayNumberValidByStyleMP,
covetUniNumber,
arrayNumberValidByStyleBorderColor,
linearValid,
getUnit,
getUid,
} from "../../libs/tool"
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors"
import { useTmConfig } from "../../libs/config"
import { onPageScroll } from "@dcloudio/uni-app"
/**
* @displayName 抽屉
@@ -15,7 +23,7 @@ import { onPageScroll } from '@dcloudio/uni-app';
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmDrawer' });
defineOptions({ name: "TmDrawer" })
const { config } = useTmConfig()
const props = defineProps({
@@ -24,84 +32,84 @@ const props = defineProps({
*/
customStyle: {
type: String,
default: ""
default: "",
},
/**
* 标题
*/
title: {
type: String,
default: "标题"
default: "标题",
},
/**
* 显示底部操作栏
*/
showFooter: {
type: Boolean,
default: false
default: false,
},
/**
* 是否显示标题
*/
showTitle: {
type: Boolean,
default: true
default: true,
},
/**
* 是否显示底部关闭按钮
*/
showClose: {
type: Boolean,
default: false
default: false,
},
/**
* 遮罩是否允许点击被关闭
*/
overlayClick: {
type: Boolean,
default: true
default: true,
},
/**
* 显示可v-model:show双向绑定
*/
show: {
type: Boolean,
default: false
default: false,
},
/**
* 显示取消按钮
*/
showCancel: {
type: Boolean,
default: true
default: true,
},
/**
* 取消按钮的文本
*/
cancelText: {
type: String,
default: "取消"
default: "取消",
},
/**
* 确认按钮的文本
*/
confirmText: {
type: String,
default: "确认"
default: "确认",
},
/**
* 动画时间
*/
duration: {
type: Number,
default: 300
default: 300,
},
/**
* 打开方向。
*/
position: {
type: String as PropType<"top" | "bottom" | "left" | "right">,
default: "bottom"
default: "bottom",
},
/**
* 打开方向为上和下时的圆角
@@ -109,7 +117,7 @@ const props = defineProps({
*/
round: {
type: [String, Number],
default: ""
default: "",
},
/**
* 左右时为内容宽,
@@ -118,7 +126,7 @@ const props = defineProps({
*/
size: {
type: [String, Number],
default: "50%"
default: "50%",
},
/**
* 弹层最大的高度值,默认为屏幕的可视高
@@ -126,21 +134,21 @@ const props = defineProps({
*/
maxHeight: {
type: String,
default: ""
default: "",
},
/**
* 背景颜色
*/
bgColor: {
type: String,
default: 'white'
default: "white",
},
/**
* 暗黑背景颜色,如果不提供默认读取全局的sheet配置
*/
darkBgColor: {
type: String,
default: ''
default: "",
},
/**
@@ -150,14 +158,14 @@ const props = defineProps({
*/
disabledScroll: {
type: Boolean,
default: false
default: false,
},
/**
* 内容区域左右和下的边距。
*/
contentMargin: {
type: String,
default: '24'
default: "24",
},
/**
* 宽屏时是否让内容剧中显示
@@ -166,21 +174,21 @@ const props = defineProps({
*/
widthCoverCenter: {
type: Boolean,
default: false
default: false,
},
/**
* 距离顶部的偏移量
*/
offsetTop: {
type: [String, Number],
default: '0'
default: "0",
},
/**
* 弹层的层级
*/
zIndex: {
type: [String, Number],
default: 1100
default: 1100,
},
/**
* 懒加载
@@ -189,50 +197,50 @@ const props = defineProps({
*/
lazy: {
type: Boolean,
default: false
default: false,
},
/**
* 关闭前执行,可以是异步函数,只有当用户点击confirm时才会触发。如果返回false将阻止关闭。true允许关闭。
*/
beforeClose: {
type: [Function, Boolean] as PropType<() => Promise<boolean>>,
default: true
}
default: true,
},
})
const emit = defineEmits([
/**
* 点击遮罩事件
*/
'click',
"click",
/**
* 关闭是触发
*/
'close',
"close",
/**
* 打开时触发
*/
'open',
"open",
/**
* 打开前执行
*/
'beforeOpen',
"beforeOpen",
/**
* 关闭前执行
*/
'beforeClose',
"beforeClose",
/**
* 等同v-model:show
*/
'update:show',
"update:show",
/**
* 取消时触发
*/
'cancel',
"cancel",
/**
* 确认时触发
*/
'confirm'
"confirm",
])
const isLoading = ref(false)
@@ -251,13 +259,16 @@ const windtopReal = ref(0)
const tantiaoTrue = ref(false)
const safeFooterHeight = ref(0)
const lezyShowModal = ref(props.lazy ? false : true)
watch(() => props.show, (newVal) => {
if (newVal) {
showAlert()
} else {
closeAlert()
}
})
watch(
() => props.show,
(newVal) => {
if (newVal) {
showAlert()
} else {
closeAlert()
}
},
)
const _lazy = computed(() => props.lazy)
const _customStyle = computed(() => props.customStyle)
@@ -276,10 +287,10 @@ const _round = computed(() => {
let radius = covetUniNumber(round, config.unit)
let _r = "none"
if (props.position == 'top') {
if (props.position == "top") {
_r = `0px 0px ${radius} ${radius}`
}
if (props.position == 'bottom') {
if (props.position == "bottom") {
_r = `${radius} ${radius} 0px 0px`
}
return _r
@@ -288,7 +299,7 @@ const _offset = computed(() => {
let offset = covetUniNumber(props.offsetTop, config.unit)
let x = parseFloat(offset)
let unit = getUnit(offset)
if (unit == 'rpx') {
if (unit == "rpx") {
x = uni.upx2px(x)
}
return x
@@ -298,7 +309,7 @@ const _contentMargin = computed(() => covetUniNumber(props.contentMargin, config
const _showFooter = computed(() => props.showFooter)
const _maxHeight = computed(() => {
if (props.maxHeight == "") return ""
if (props.position == 'left' || props.position == 'right') return ""
if (props.position == "left" || props.position == "right") return ""
return covetUniNumber(props.maxHeight, config.unit)
})
@@ -308,8 +319,8 @@ const _confirmText = computed(() => props.confirmText)
const _animationFun = computed(() => config.animation)
const _bgColor = computed(() => {
let bgcolor = props.bgColor
if (config.mode == 'dark') {
if (props.darkBgColor != '') {
if (config.mode == "dark") {
if (props.darkBgColor != "") {
bgcolor = props.darkBgColor
} else {
bgcolor = config.sheetDarkColor
@@ -318,19 +329,19 @@ const _bgColor = computed(() => {
return getDefaultColor(bgcolor)
})
const __height = computed(() => {
let h = '100%'
let h = "100%"
// #ifdef WEB
h = `calc(100% - ${windtopReal.value}px)`
// #endif
// #ifdef APP
if (_offset.value > 0) {
h = _height.value + 'px'
h = _height.value + "px"
}
// #endif
return h
})
const _titleFontSize = computed(() => (config.fontSizeScale * 16).toString() + 'px')
const _isDark = computed(() => config.mode == 'dark')
const _titleFontSize = computed(() => (config.fontSizeScale * 16).toString() + "px")
const _isDark = computed(() => config.mode == "dark")
// Lifecycle hooks
onMounted(() => {
@@ -340,8 +351,8 @@ onMounted(() => {
_width.value = sys.windowWidth
_height.value = sys.windowHeight
windtop.value = sys.windowTop + (_position.value == 'bottom' ? 0 : _offset.value)
windtopReal.value = _position.value == 'bottom' ? (sys.windowTop + _offset.value) : sys.windowTop
windtop.value = sys.windowTop + (_position.value == "bottom" ? 0 : _offset.value)
windtopReal.value = _position.value == "bottom" ? sys.windowTop + _offset.value : sys.windowTop
// #endif
// #ifdef APP
_width.value = sys.windowWidth
@@ -360,26 +371,25 @@ onBeforeUnmount(() => {
})
const cancelEvt = () => {
emit('cancel')
emit("cancel")
closeAlert()
}
const confirmEvt = async () => {
emit('confirm')
let isPass = true;
if (typeof props.beforeClose == 'function') {
isLoading.value = true;
emit("confirm")
let isPass = true
if (typeof props.beforeClose == "function") {
isLoading.value = true
// @ts-ignore
isPass = await props.beforeClose();
isPass = await props.beforeClose()
}
isLoading.value = false;
isLoading.value = false
if (!isPass) {
return;
return
}
closeAlert()
}
const onClickOverflowy = (evt: Event) => {
evt.stopPropagation()
/**
@@ -392,22 +402,21 @@ const onClickOverflowy = (evt: Event) => {
tantiaoTrue.value = true
tid2.value = setTimeout(() => {
tantiaoTrue.value = false
}, 200);
return;
}, 200)
return
}
closeAlert()
}
const closeAlert = () => {
if (actinon.value || isLoading.value) return
if (status.value == 'close') return
if (status.value == "close") return
actinon.value = true
status.value = 'close'
status.value = "close"
/**
* 关闭前执行
*/
emit('beforeClose')
emit("beforeClose")
clearTimeout(tid.value)
tid.value = setTimeout(() => {
@@ -417,17 +426,17 @@ const closeAlert = () => {
const showAlert = () => {
if (actinon.value) return
if (status.value == 'open') return
if (status.value == "open") return
showOverflay.value = true
actinon.value = true
/**
* 打开前执行
*/
emit('beforeOpen')
emit("beforeOpen")
clearTimeout(tid.value)
tid.value = setTimeout(() => {
status.value = 'open'
status.value = "open"
tid.value = setTimeout(() => {
onEnd()
}, _duration.value)
@@ -435,35 +444,30 @@ const showAlert = () => {
}
function openDrawer() {
showAlert();
showAlert()
}
function onEnd() {
actinon.value = false;
actinon.value = false
if (status.value === 'close') {
showOverflay.value = false;
emit('close');
emit('update:show', false);
if (status.value === "close") {
showOverflay.value = false
emit("close")
emit("update:show", false)
if (_lazy.value) {
lezyShowModal.value = false;
lezyShowModal.value = false
}
} else {
emit('open');
emit("open")
if (_lazy.value) {
lezyShowModal.value = true;
lezyShowModal.value = true
}
}
}
function maskerMove(evt: TouchEvent) {
evt.preventDefault();
evt.preventDefault()
}
</script>
<script lang="ts">
export default {
@@ -473,7 +477,7 @@ export default {
addGlobalClass: true,
multipleSlots: true,
},
};
}
</script>
<template>
<view>
@@ -490,33 +494,49 @@ export default {
<!-- #ifdef MP-WEIXIN -->
<root-portal>
<!-- #endif -->
<view @click="onClickOverflowy" @touchmove="maskerMove" v-if="showOverflay" :id="id" ref="tmDrawerWrap"
class="tmDrawerWrap" :class="[
status == 'open' ? 'tmModalWrap_on' : 'tmModalWrap_off',
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidthWrapDir' : '',
'tmDrawerWrap_' + _position
]" :style="[{
width: '100%',
top: (windtop + 'px'),
height: __height,
zIndex: zIndex,
'transition-timing-function': _animationFun
}, _customStyle]">
<view @click.stop="" ref="tmDrawerWrapContent" class="tmDrawerWrapContent" :class="[
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidth' : '',
'tmDrawerWrapContent_' + _position + (status == 'open' ? '_on' : ''),
tantiaoTrue ? 'tmModalWrapContent_Tantiao' : ''
]" :id="wrapId" :style="{
width: _position == 'left' || _position == 'right' ? _size : '100%',
height: _position == 'left' || _position == 'right' ? '100%' : _size,
borderRadius: _round,
maxHeight: _maxHeight != '' ? _maxHeight : '100%',
minHeight: _size == 'auto' ? '350rpx' : '0px',
'transition-timing-function': _animationFun,
backgroundColor: _bgColor
}">
<view
@click="onClickOverflowy"
@touchmove="maskerMove"
v-if="showOverflay"
:id="id"
ref="tmDrawerWrap"
class="tmDrawerWrap"
:class="[
status == 'open' ? 'tmModalWrap_on' : 'tmModalWrap_off',
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidthWrapDir' : '',
'tmDrawerWrap_' + _position,
]"
:style="[
{
width: '100%',
top: windtop + 'px',
height: __height,
zIndex: zIndex,
'transition-timing-function': _animationFun,
},
_customStyle,
]"
>
<view
@click.stop=""
ref="tmDrawerWrapContent"
class="tmDrawerWrapContent"
:class="[
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidth' : '',
'tmDrawerWrapContent_' + _position + (status == 'open' ? '_on' : ''),
tantiaoTrue ? 'tmModalWrapContent_Tantiao' : '',
]"
:id="wrapId"
:style="{
width: _position == 'left' || _position == 'right' ? _size : '100%',
height: _position == 'left' || _position == 'right' ? '100%' : _size,
borderRadius: _round,
maxHeight: _maxHeight != '' ? _maxHeight : '100%',
minHeight: _size == 'auto' ? '350rpx' : '0px',
'transition-timing-function': _animationFun,
backgroundColor: _bgColor,
}"
>
<view v-if="_showClose" class="tmDrawerXclose">
<tm-icon @click="closeAlert" color="#dcdcdc" size="52" name="close-circle-fill"></tm-icon>
</view>
@@ -529,14 +549,15 @@ export default {
<slot name="title" :show="show">
<text
:style="{ fontSize: _titleFontSize, color: _isDark ? 'white' : 'black', opacity: '0.64' }"
class="tmDrawertitleBox">{{ _title }}</text>
class="tmDrawertitleBox"
>{{ _title }}</text
>
</slot>
</view>
</view>
<view v-if="!disabledScroll && _size != 'auto'" style="flex: 1;" class="tmModalWrapBoxContent">
<scroll-view style="height:100%;position:absolute;width:100%;" :scroll-y="true"
:rebound="false">
<view v-if="!disabledScroll && _size != 'auto'" style="flex: 1" class="tmModalWrapBoxContent">
<scroll-view style="height: 100%; position: absolute; width: 100%" :scroll-y="true" :rebound="false">
<view :style="{ padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
<!--
@slot 默认插槽
@@ -545,36 +566,36 @@ export default {
</view>
</scroll-view>
</view>
<view v-else
:style="{ height: '100%', padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
<view v-else :style="{ height: '100%', padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
<!--
@slot 默认插槽
-->
<slot></slot>
</view>
<view v-if="showFooter && lezyShowModal" class="tmDrawerFooter"
:style="{ backgroundColor: _bgColor }">
<view v-if="showFooter && lezyShowModal" class="tmDrawerFooter" :style="{ backgroundColor: _bgColor }">
<view :style="{ height: _contentMargin }"></view>
<!--
@slot 底部操作栏
-->
<slot name="footer">
<view
style="flex-direction: row;align-items: center;justify-content: center;display:flex;">
<tm-button :loading="isLoading" width="0px" @click="cancelEvt" v-if="_showCancel"
skin="thin" style="margin-right: 16px;flex:1">{{ _cancelText }}</tm-button>
<tm-button :loading="isLoading" width="0px" @click="confirmEvt" style="flex:1">{{
_confirmText
}}</tm-button>
<view style="flex-direction: row; align-items: center; justify-content: center; display: flex; margin: 0 20rpx">
<tm-button
:loading="isLoading"
width="0px"
@click="cancelEvt"
v-if="_showCancel"
skin="thin"
style="margin-right: 16px; flex: 1"
>{{ _cancelText }}</tm-button
>
<tm-button :loading="isLoading" width="0px" @click="confirmEvt" style="flex: 1">{{ _confirmText }}</tm-button>
</view>
</slot>
<view :style="{ height: safeFooterHeight + 'px' }"></view>
</view>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
</root-portal>
@@ -613,7 +634,6 @@ export default {
line-clamp: 1;
display: -webkit-box;
text-overflow: ellipsis;
}
.tmDrawerWrap_bottom {
@@ -661,13 +681,12 @@ export default {
}
@keyframes bottomAnimate3d {
0% {
transform: translate(0%, 100%) translate3d(0%, 20px, 0px) rotateX(45deg)
transform: translate(0%, 100%) translate3d(0%, 20px, 0px) rotateX(45deg);
}
100% {
transform: translate(0%, 0%) translate3d(0, 0, 0px) rotateX(0deg)
transform: translate(0%, 0%) translate3d(0, 0, 0px) rotateX(0deg);
}
}
@@ -736,7 +755,6 @@ export default {
75% {
transform: scale(0.97);
opacity: 0.9;
}
85% {
@@ -746,11 +764,10 @@ export default {
100% {
transform: scale(1);
opacity: 1;
}
}
.tmModalWrapContent_Tantiao {
animation: tantiao 0.15s linear;
}
</style>
</style>