首次提交:初始化项目代码

This commit is contained in:
sunct
2026-07-31 14:42:05 +08:00
commit ecca407485
84 changed files with 24158 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
<template>
<view v-if="visible" class="bottom-sheet-wrapper" @touchmove.stop.prevent>
<view class="mask" :class="{ 'mask-active': animating }" @click="handleMaskClick" @touchmove.stop.prevent></view>
<view class="sheet" :class="{ 'sheet-up': animating }" :style="sheetStyle">
<view class="handle-bar"></view>
<view class="header" :style="headerStyle">
<text class="title" :style="titleStyle">{{ title }}</text>
<view
v-if="showClose"
class="close-btn btn btn--icon"
@click="handleClose"
hover-class="close-btn-hover"
>
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="body">
<slot></slot>
</scroll-view>
<view class="footer" :style="footerStyle">
<slot name="footer"></slot>
</view>
</view>
</view>
</template>
<script>
import themeMixin from '@/mixins/themeMixin.js'
export default {
name: 'BottomSheet',
mixins: [themeMixin],
props: {
visible: { type: Boolean, default: false },
title: { type: String, default: '' },
height: { type: String, default: '85vh' },
showClose: { type: Boolean, default: true },
maskClosable: { type: Boolean, default: true }
},
data() {
return {
animating: false,
closeTimer: null
}
},
computed: {
sheetStyle() {
return {
maxHeight: this.height,
background: this.themeConfig.cardBgColor
}
},
headerStyle() {
return {
borderBottom: `2rpx solid ${this.themeConfig.borderLight}`
}
},
closeBtnStyle() {
return {
background: this.themeConfig.borderLight
}
},
titleStyle() {
return {
color: this.themeConfig.textPrimary
}
},
footerStyle() {
return {
borderTop: `2rpx solid ${this.themeConfig.borderLight}`
}
}
},
watch: {
visible(val) {
if (val) {
this.animating = false
this.$nextTick(() => {
setTimeout(() => {
this.animating = true
}, 30)
})
}
}
},
created() {
this.syncThemeConfig()
this.watchThemeChange()
},
beforeUnmount() {
if (this.themeChangeHandler) {
uni.$off('themeChanged', this.themeChangeHandler)
}
if (this.closeTimer) {
clearTimeout(this.closeTimer)
}
},
methods: {
handleMaskClick() {
if (!this.maskClosable) return
this.handleClose()
},
handleClose() {
this.animating = false
this.closeTimer = setTimeout(() => {
this.$emit('update:visible', false)
this.$emit('close')
}, 300)
},
watchThemeChange() {
this.themeChangeHandler = ({ key, config }) => {
if (key && config) {
this.themeConfig = config
}
}
uni.$on('themeChanged', this.themeChangeHandler)
}
}
}
</script>
<style scoped>
.bottom-sheet-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999;
}
.mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
transition: background 0.3s ease;
}
.mask-active {
background: rgba(0, 0, 0, 0.5);
}
.sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-radius: 24rpx 24rpx 0 0;
transform: translateY(100%);
transition: transform 0.3s ease;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.sheet-up {
transform: translateY(0);
}
.handle-bar {
width: 80rpx;
height: 8rpx;
background: #cccccc;
border-radius: 4rpx;
margin: 16rpx auto 0;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
}
.title {
font-size: 32rpx;
font-weight: 700;
flex: 1;
}
.close-btn {
transition: transform 0.2s ease;
}
.close-btn-hover {
transform: scale(0.9);
}
.close-icon {
font-size: 32rpx;
color: var(--textSecondary);
line-height: 1;
}
.body {
padding: 24rpx 32rpx;
max-height: 60vh;
box-sizing: border-box;
}
.footer {
padding: 24rpx 32rpx;
}
</style>