Files
2026-01-12 17:52:15 +08:00

558 lines
14 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 美颜面板组件 -->
<!-- 使用方式: <BeautyPanel v-model="showBeautyPanel" @adjust-beauty="handleAdjustBeauty" @reset="handleReset" /> -->
<view class="bottom-drawer-container" v-if="modelValue">
<view class="drawer-overlay" @tap="close"></view>
<view class="bottom-drawer" :class="{ 'drawer-open': modelValue }" @click.stop>
<!-- 程度控制区域 -->
<view class="slider-section" v-if="currentOptionName !== '关闭'">
<view class="control-container">
<view class="custom-slider">
<!-- 减号按钮 -->
<view style="width: 40rpx; height: 40rpx;" @tap="decreaseValue">
<text class="btn-text">-</text>
</view>
<!-- 进度条区域 -->
<view class="progress-section">
<!-- 进度条背景 -->
<view class="progress-bar">
<view
class="progress-fill"
:style="{ width: (currentRealValue / 100 * 400) + 'rpx' }"
></view>
</view>
<!-- 当前数值显示 - 定位在进度条右侧 -->
<text class="current-value">{{ currentRealValue }}</text>
</view>
<!-- 加号按钮 -->
<view style="width: 40rpx; height: 40rpx;" @tap="increaseValue">
<text class="btn-text">+</text>
</view>
</view>
</view>
</view>
<!-- 功能标签页 -->
<!-- <view class="feature-tabs">
<view
v-for="(tab, index) in featureTabs"
:key="index"
class="tab-item"
:class="{ 'active': activeTabIndex === index }"
@tap="switchTab(index)"
>
<text class="tab-text" :class="{ 'active-text': activeTabIndex === index }">{{ tab.name }}</text>
<view v-if="tab.icon" class="tab-icon">
<image :src="tab.icon" mode="aspectFit" class="icon-image" />
</view>
</view>
</view> -->
<!-- 详细选项区域 -->
<view class="options-section">
<view class="options-grid">
<view
v-for="(option, index) in currentOptions"
:key="index"
class="option-item"
:class="{ 'selected': selectedOptionIndex === index }"
@tap="selectOption(index)"
>
<view class="option-icon-container">
<image :src="option.icon" mode="aspectFit" class="option-icon" />
</view>
<text class="option-name">{{ option.name }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useBaseBeautyState } from '@/uni_modules/tuikit-atomic-x/state/BaseBeautyState'
const {
setSmoothLevel,
setWhitenessLevel,
setRuddyLevel,
whitenessLevel,
ruddyLevel,
smoothLevel,
realUiValues,
setRealUiValue,
getRealUiValue,
resetRealUiValues
} = useBaseBeautyState(uni.$liveID)
/**
* 美颜面板组件
*
* Props:
* - modelValue: Boolean - 控制面板显示/隐藏
*
* Events:
* - update:modelValue - 更新面板显示状态
* - adjust-beauty - 调整美颜参数事件
* - reset - 重置事件
*/
// Props 定义
const props = defineProps({
modelValue: {
type: Boolean,
default: false
}
})
// Emits 定义
const emit = defineEmits(['update:modelValue', 'adjust-beauty', 'reset'])
// 响应式数据
const activeTabIndex = ref(0)
const selectedOptionIndex = ref(0) // 默认选中"关闭"
const featureTabs = ref([
{ name: '美颜', icon: null },
// { name: '美体', icon: null },
// { name: '滤镜', icon: null },
// { name: '贴纸', icon: null },
// { name: '风格整妆', icon: null },
// { name: '重置', icon: '/static/images/edit.png' }
])
const beautyOptions = ref([
{ name: '关闭', icon: '/static/images/beauty-close.png' },
{ name: '美白', icon: '/static/images/whiteness.png' },
{ name: '磨皮', icon: '/static/images/smooth.png' },
{ name: '红润', icon: '/static/images/live-ruddy.png' },
// { name: '对比度', icon: '/static/images/setting.png' },
// { name: '饱和', icon: '/static/images/setting.png' }
])
const bodyOptions = ref([
{ name: '瘦脸', icon: '/static/images/beauty.png' },
{ name: '大眼', icon: '/static/images/beauty.png' },
{ name: '瘦身', icon: '/static/images/beauty.png' },
{ name: '长腿', icon: '/static/images/beauty.png' }
])
const filterOptions = ref([
{ name: '原图', icon: '/static/images/beauty.png' },
{ name: '暖色', icon: '/static/images/beauty.png' },
{ name: '冷色', icon: '/static/images/beauty.png' },
{ name: '黑白', icon: '/static/images/beauty.png' }
])
const stickerOptions = ref([
{ name: '无', icon: '/static/images/close.png' },
{ name: '帽子', icon: '/static/images/gift.png' },
{ name: '眼镜', icon: '/static/images/gift.png' },
{ name: '胡子', icon: '/static/images/gift.png' }
])
const styleOptions = ref([
{ name: '自然', icon: '/static/images/beauty.png' },
{ name: '清新', icon: '/static/images/beauty.png' },
{ name: '复古', icon: '/static/images/beauty.png' },
{ name: '时尚', icon: '/static/images/beauty.png' }
])
// 计算属性
const currentOptions = computed(() => {
const optionsMap = {
0: beautyOptions.value,
1: bodyOptions.value,
2: filterOptions.value,
3: stickerOptions.value,
4: styleOptions.value
}
return optionsMap[activeTabIndex.value] || beautyOptions.value
})
// 获取当前选项名称
const currentOptionName = computed(() => {
return currentOptions.value[selectedOptionIndex.value]?.name || '关闭'
})
// 获取当前激活的美颜效果名称(排除"关闭"选项)
const activeBeautyOption = computed(() => {
const options = currentOptions.value
for (let i = 0; i < options.length; i++) {
const option = options[i]
if (option.name !== '关闭') {
// 检查这个美颜效果是否有值
let hasValue = false
switch (option.name) {
case '美白':
hasValue = getRealUiValue('whiteness') > 0
break
case '红润':
hasValue = getRealUiValue('ruddy') > 0
break
case '磨皮':
hasValue = getRealUiValue('smooth') > 0
break
}
if (hasValue) {
return option.name
}
}
}
return null
})
// 获取当前选项对应的真实UI显示值显示用户拖动的真实数据
const currentRealValue = computed(() => {
switch (currentOptionName.value) {
case '美白':
return getRealUiValue('whiteness')
case '红润':
return getRealUiValue('ruddy')
case '磨皮':
return getRealUiValue('smooth')
case '关闭':
default:
return 0
}
})
// 监听外部状态变化只在初始化时设置真实的UI值
watch([smoothLevel, whitenessLevel, ruddyLevel], () => {
// 只在组件初始化时如果真实UI值未设置过才从API状态初始化
// 这样可以保持用户拖动的真实数据不被覆盖
if (getRealUiValue('whiteness') === 0 && whitenessLevel.value > 0) {
setRealUiValue('whiteness', Math.round(whitenessLevel.value * 10))
}
if (getRealUiValue('ruddy') === 0 && ruddyLevel.value > 0) {
setRealUiValue('ruddy', Math.round(ruddyLevel.value * 10))
}
if (getRealUiValue('smooth') === 0 && smoothLevel.value > 0) {
setRealUiValue('smooth', Math.round(smoothLevel.value * 10))
}
}, { immediate: true })
// 监听面板显示状态当面板打开时初始化UI值
watch(() => props.modelValue, (newValue) => {
if (newValue) {
// 面板打开时只有在真实UI值未设置过的情况下才从API状态初始化
// 这样可以保持用户上次拖动的真实数据
if (getRealUiValue('whiteness') === 0 && whitenessLevel.value > 0) {
setRealUiValue('whiteness', Math.round((whitenessLevel.value || 0) * 10))
}
if (getRealUiValue('ruddy') === 0 && ruddyLevel.value > 0) {
setRealUiValue('ruddy', Math.round((ruddyLevel.value || 0) * 10))
}
if (getRealUiValue('smooth') === 0 && smoothLevel.value > 0) {
setRealUiValue('smooth', Math.round((smoothLevel.value || 0) * 10))
}
}
})
// 方法
/**
* 关闭面板
*/
const close = () => {
emit('update:modelValue', false)
}
/**
* 减少数值
*/
const decreaseValue = () => {
const newValue = Math.max(0, currentRealValue.value - 10)
updateBeautyValue(newValue)
}
/**
* 增加数值
*/
const increaseValue = () => {
const newValue = Math.min(100, currentRealValue.value + 10)
updateBeautyValue(newValue)
}
/**
* 更新美颜数值
*/
const updateBeautyValue = (uiValue) => {
const currentOption = currentOptions.value[selectedOptionIndex.value]
// 保存真实的UI拖动值到全局状态
switch (currentOption.name) {
case '美白':
setRealUiValue('whiteness', uiValue)
break
case '红润':
setRealUiValue('ruddy', uiValue)
break
case '磨皮':
setRealUiValue('smooth', uiValue)
break
case '关闭':
return
}
// 如果UI值超过90API调用时限制为90
const limitedUiValue = uiValue > 90 ? 90 : uiValue
const apiValue = limitedUiValue / 10 // 转换为接口值0-9
// 调用对应的设置方法,传入转换后的参数值
switch (currentOption.name) {
case '美白':
setWhitenessLevel({ whitenessLevel: apiValue })
break
case '红润':
setRuddyLevel({ ruddyLevel: apiValue })
break
case '磨皮':
setSmoothLevel({ smoothLevel: apiValue })
break
}
emit('adjust-beauty', {
type: featureTabs.value[activeTabIndex.value].name,
option: currentOption.name,
value: uiValue
})
}
/**
* 切换标签页
*/
const switchTab = (index) => {
activeTabIndex.value = index
selectedOptionIndex.value = 0
// 如果是重置标签,触发重置事件
if (index === 5) {
emit('reset')
// 重置所有美颜参数
setSmoothLevel({ smoothLevel: 0 })
setWhitenessLevel({ whitenessLevel: 0 })
setRuddyLevel({ ruddyLevel: 0 })
selectedOptionIndex.value = 0
activeTabIndex.value = 0
}
}
/**
* 选择选项
*/
const selectOption = (index) => {
const currentOption = currentOptions.value[index]
// 如果选择"关闭",重置当前选中的美颜效果
if (currentOption.name === '关闭') {
// 获取当前选中的美颜效果在更新selectedOptionIndex之前
const currentSelectedOption = currentOptions.value[selectedOptionIndex.value]
// 根据当前选中的美颜效果来重置对应的参数
switch (currentSelectedOption?.name) {
case '美白':
setWhitenessLevel({ whitenessLevel: 0 })
setRealUiValue('whiteness', 0)
break
case '红润':
setRuddyLevel({ ruddyLevel: 0 })
setRealUiValue('ruddy', 0)
break
case '磨皮':
setSmoothLevel({ smoothLevel: 0 })
setRealUiValue('smooth', 0)
break
default:
// 如果当前选中的不是具体的美颜选项,则重置所有
setSmoothLevel({ smoothLevel: 0 })
setWhitenessLevel({ whitenessLevel: 0 })
setRuddyLevel({ ruddyLevel: 0 })
resetRealUiValues()
break
}
}
selectedOptionIndex.value = index
emit('adjust-beauty', {
type: featureTabs.value[activeTabIndex.value].name,
option: currentOption.name,
value: currentRealValue.value // 使用UI显示值0-100
})
}
</script>
<style>
.bottom-drawer-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: 1000;
}
.drawer-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
}
.bottom-drawer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(34, 38, 46, 1);
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
transform: translateY(100%);
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease;
flex-direction: column;
}
.drawer-open {
transform: translateY(0);
}
/* 滑块区域 */
.slider-section {
padding: 40rpx 48rpx 20rpx;
background-color: rgba(34, 38, 46, 1);
}
/* 自定义控制区域样式 */
.control-container {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.custom-slider {
flex: 1;
flex-direction: row;
align-items: center;
margin: 0 20rpx;
}
.control-btn {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
display: flex;
justify-content: center;
align-items: center;
border: 2rpx solid #2b6ad6;
}
.minus-btn {
display: flex;
justify-self: center;
align-items: center;
background-color: rgba(43, 106, 214, 0.1);
}
.plus-btn {
display: flex;
align-items: center;
justify-self: center;
background-color: #2b6ad6;
}
.btn-text {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
}
.plus-btn .btn-text {
color: #ffffff;
}
.progress-section {
flex: 1;
margin: 0 20rpx;
align-items: center;
flex-direction: row;
justify-content: center;
}
.progress-bar {
width: 400rpx;
height: 8rpx;
background-color: rgba(255, 255, 255, 0.3);
border-radius: 4rpx;
position: relative;
overflow: hidden;
margin-right: 16rpx;
}
.progress-fill {
height: 8rpx;
background-color: #2b6ad6;
border-radius: 4rpx;
}
.current-value {
font-size: 24rpx;
color: #ffffff;
font-weight: 600;
text-align: center;
z-index: 10;
}
/* 详细选项区域 */
.options-section {
flex: 1;
background-color: rgba(34, 38, 46, 1);
padding: 20rpx 48rpx;
}
.options-grid {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.option-item {
width: 120rpx;
margin-bottom: 30rpx;
align-items: center;
padding: 16rpx 12rpx;
border-radius: 12rpx;
border: 2rpx solid transparent;
}
.option-item.selected {
border-color: #2b6ad6;
background-color: #2b6ad6;
}
.option-icon-container {
width: 60rpx;
height: 60rpx;
margin-bottom: 12rpx;
justify-content: center;
align-items: center;
}
.option-icon {
width: 60rpx;
height: 60rpx;
}
.option-name {
font-size: 22rpx;
color: #ffffff;
text-align: center;
}
</style>