volunteer-4/src/pages-sub/components/collapse/CollapseItem.vue

448 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="uni-collapse-item">
<!-- onClick(!isOpen) -->
<view
@click="onClick(!isOpen)"
class="uni-collapse-item__title"
:class="{
'is-open': isOpen && titleBorder === 'auto',
'uni-collapse-item-border': titleBorder !== 'none',
}"
>
<view class="uni-collapse-item__title-wrap">
<slot name="title" :expanded="isOpen">
<view class="uni-collapse-item__title-box" :class="{ 'is-disabled': disabled }">
<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
<text class="uni-collapse-item__title-text">{{ title }}</text>
</view>
</slot>
</view>
<view
v-if="showArrow && !$slots.title"
:class="{
'uni-collapse-item__title-arrow-active': isOpen,
'uni-collapse-item--animation': showAnimation === true,
}"
class="uni-collapse-item__title-arrow"
>
<view :color="disabled ? '#ddd' : '#bbb'" class="i-carbon-chevron-down" />
</view>
</view>
<view
class="uni-collapse-item__wrap"
:class="{ 'is--transition': showAnimation }"
:style="{ height: (isOpen ? height : 0) + 'px' }"
>
<view
:id="elId"
ref="collapseHook"
class="uni-collapse-item__wrap-content"
:class="{ open: isheight, 'uni-collapse-item--border': border && isOpen }"
>
<slot></slot>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
inject,
watch,
onMounted,
onUnmounted,
onUpdated,
getCurrentInstance,
nextTick,
} from 'vue'
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
/**
* CollapseItem 折叠面板子组件
* @description 折叠面板子组件
* @property {String} title 标题文字
* @property {String} thumb 标题左侧缩略图
* @property {String} name 唯一标志符
* @property {Boolean} open = [true|false] 是否展开组件
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
* @property {Boolean} disabled = [true|false] 是否展开面板
* @property {Boolean} showAnimation = [true|false] 开启动画
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
*/
const props = defineProps({
// 列表标题
title: {
type: String,
default: '',
},
name: {
type: [Number, String],
default: '',
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
// #ifdef APP-PLUS
// 是否显示动画,app 端默认不开启动画,卡顿严重
showAnimation: {
type: Boolean,
default: false,
},
// #endif
// #ifndef APP-PLUS
// 是否显示动画
showAnimation: {
type: Boolean,
default: true,
},
// #endif
// 是否展开
open: {
type: Boolean,
default: false,
},
// 缩略图
thumb: {
type: String,
default: '',
},
// 标题分隔线显示类型
titleBorder: {
type: String,
default: 'auto',
},
border: {
type: Boolean,
default: true,
},
showArrow: {
type: Boolean,
default: true,
},
})
// 生成随机元素ID用于解决百度小程序获取同一个元素位置信息的bug
const elId = ref(`Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`)
const isOpen = ref(false)
const isheight = ref(null)
const height = ref(0)
const nameSync = ref('0')
const collapseHook = ref(null)
const proxy = getCurrentInstance()
// 获取折叠面板父组件上下文
const collapseContext = inject('uniCollapseContext', null)
// 监听open属性变化
watch(
() => props.open,
(val) => {
isOpen.value = val
onClick(val, 'init')
},
)
// 初始化方法
const init = (type) => {
// #ifndef APP-NVUE
getCollapseHeight(type)
// #endif
// #ifdef APP-NVUE
getNvueHwight(type)
// #endif
}
// 卸载组件时从父组件中移除
const uninstall = () => {
if (collapseContext) {
collapseContext.unregister({
nameSync: nameSync.value,
})
}
}
// 点击事件处理 - 修复版
const onClick = (isOpenVal, type) => {
if (props.disabled) return
// 保存旧状态用于比较
const oldIsOpen = isOpen.value
// 设置新状态
isOpen.value = isOpenVal
// 如果是打开操作且在手风琴模式下,关闭其他面板
if (isOpen.value && !oldIsOpen && collapseContext) {
// 传递一个包含必要信息的对象,确保能被识别
collapseContext.setAccordion({
nameSync: nameSync.value,
uid: proxy.uid,
instance: proxy,
})
}
// 非初始化操作时触发onChange回调
if (type !== 'init' && collapseContext) {
collapseContext.onChange(isOpen.value, {
nameSync: nameSync.value,
isOpen: isOpen.value,
})
}
}
// 获取折叠面板高度非APP-NVUE
const getCollapseHeight = (type, index = 0) => {
const views = uni.createSelectorQuery().in(proxy)
views
.select(`#${elId.value}`)
.fields(
{
size: true,
},
(data) => {
// TODO 百度中可能获取不到节点信息,需要循环获取
if (index >= 10) return
if (!data) {
index++
getCollapseHeight(false, index)
return
}
// #ifdef APP-NVUE
height.value = data.height + 1
// #endif
// #ifndef APP-NVUE
height.value = data.height
// #endif
isheight.value = true
if (type) return
onClick(isOpen.value, 'init')
},
)
.exec()
}
// 获取NVUE环境下的高度
const getNvueHwight = (type) => {
// #ifdef APP-NVUE
dom.getComponentRect(collapseHook.value, (option) => {
if (option && option.result && option.size) {
height.value = option.size.height + 1
isheight.value = true
if (type) return
onClick(props.open, 'init')
}
})
// #endif
}
// 组件挂载后初始化 - 注册部分的修改
onMounted(() => {
if (!collapseContext) {
// console.error('找不到折叠面板父组件上下文!')
return
}
// 设置nameSync
nameSync.value = props.name !== '' ? props.name.toString() : `${collapseContext.childrens.length}`
// console.log('折叠项挂载:', nameSync.value)
// 注册到父组件 - 确保传递所有必要的参数
collapseContext.register({
nameSync: nameSync.value,
isOpen: isOpen.value,
instance: proxy,
uid: proxy.uid,
// 添加方法引用,使父组件可以直接操作
setOpen: (val) => {
isOpen.value = val
nextTick(() => init())
},
})
// 初始化动画高度
init()
// 检查是否应该默认展开
nextTick(() => {
if (collapseContext.getActiveNames) {
const activeNames = collapseContext.getActiveNames()
// console.log('当前活动项:', activeNames, '本项:', nameSync.value)
if (activeNames.includes(nameSync.value.toString())) {
// console.log('默认展开:', nameSync.value)
isOpen.value = true
// 更新高度
init()
}
}
})
})
// 组件更新后重新初始化
onUpdated(() => {
nextTick(() => {
init(true)
})
})
// 组件卸载时从父组件移除
onUnmounted(() => {
uninstall()
})
</script>
<style lang="scss">
.uni-collapse-item {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
&__title {
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
transition: border-bottom-color 0.3s;
// transition-property: border-bottom-color;
// transition-duration: 5s;
&-wrap {
width: 100%;
flex: 1;
}
&-box {
padding: 0 15px;
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
box-sizing: border-box;
/* #endif */
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 48px;
line-height: 48px;
background-color: #fff;
color: #303133;
font-size: 13px;
font-weight: 500;
/* #ifdef H5 */
cursor: pointer;
outline: none;
/* #endif */
&.is-disabled {
.uni-collapse-item__title-text {
color: #999;
}
}
}
&.uni-collapse-item-border {
border-bottom: 1px solid #ebeef5;
}
&.is-open {
border-bottom-color: transparent;
}
&-img {
height: 22px;
width: 22px;
margin-right: 10px;
}
&-text {
flex: 1;
font-size: 14px;
/* #ifndef APP-NVUE */
white-space: nowrap;
color: inherit;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
overflow: hidden;
text-overflow: ellipsis;
}
&-arrow {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 10px;
transform: rotate(0deg);
&-active {
transform: rotate(-180deg);
}
}
}
&__wrap {
/* #ifndef APP-NVUE */
will-change: height;
box-sizing: border-box;
/* #endif */
background-color: #fff;
overflow: hidden;
position: relative;
height: 0;
&.is--transition {
// transition: all 0.3s;
transition-property: height, border-bottom-width;
transition-duration: 0.3s;
/* #ifndef APP-NVUE */
will-change: height;
/* #endif */
}
&-content {
position: absolute;
font-size: 13px;
color: #303133;
// transition: height 0.3s;
border-bottom-color: transparent;
border-bottom-style: solid;
border-bottom-width: 0;
&.uni-collapse-item--border {
border-bottom-width: 1px;
border-bottom-color: red;
border-bottom-color: #ebeef5;
}
&.open {
position: relative;
}
}
}
&--animation {
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease;
}
}
</style>