279 lines
5.2 KiB
Vue
279 lines
5.2 KiB
Vue
<template>
|
|
<view v-if="visible" class="modal-overlay" @click="handleBackdropClick">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-header">
|
|
<text class="modal-title">{{ title }}</text>
|
|
<text class="modal-close" @click="handleCancel">✕</text>
|
|
</view>
|
|
|
|
<view class="modal-body">
|
|
<input
|
|
v-model="inputValue"
|
|
:type="inputType"
|
|
:placeholder="placeholder"
|
|
class="modal-input"
|
|
@input="handleInput"
|
|
/>
|
|
<text v-if="errorMessage" class="error-message">{{ errorMessage }}</text>
|
|
</view>
|
|
|
|
<view class="modal-footer">
|
|
<button class="btn-cancel" @click="handleCancel">取消</button>
|
|
<button class="btn-confirm" @click="handleConfirm" :disabled="loading">
|
|
{{ loading ? '加载中...' : '确认' }}
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: '编辑'
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: '请输入数值'
|
|
},
|
|
inputType: {
|
|
type: String,
|
|
default: 'number'
|
|
},
|
|
currentValue: {
|
|
type: [String, Number],
|
|
default: ''
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['confirm', 'cancel'])
|
|
|
|
const inputValue = ref('')
|
|
const errorMessage = ref('')
|
|
const loading = ref(false)
|
|
|
|
// 监听 currentValue 变化,更新输入框
|
|
watch(() => props.currentValue, (newVal) => {
|
|
inputValue.value = String(newVal || '')
|
|
errorMessage.value = ''
|
|
}, { immediate: true })
|
|
|
|
// 监听 visible 变化,重置状态
|
|
watch(() => props.visible, (newVal) => {
|
|
if (newVal) {
|
|
inputValue.value = String(props.currentValue || '')
|
|
errorMessage.value = ''
|
|
}
|
|
})
|
|
|
|
const validateInput = (value) => {
|
|
// 检查是否为空
|
|
if (!value || value.trim() === '') {
|
|
errorMessage.value = '请输入有效的数值'
|
|
return false
|
|
}
|
|
|
|
// 转换为数字
|
|
const numValue = parseFloat(value)
|
|
|
|
// 检查是否为有效数字
|
|
if (isNaN(numValue)) {
|
|
errorMessage.value = '请输入数字'
|
|
return false
|
|
}
|
|
|
|
// 检查是否为负数
|
|
if (numValue < 0) {
|
|
errorMessage.value = '数值不能为负数'
|
|
return false
|
|
}
|
|
|
|
errorMessage.value = ''
|
|
return true
|
|
}
|
|
|
|
const formatDecimal = (value) => {
|
|
const numValue = parseFloat(value)
|
|
if (isNaN(numValue)) return value
|
|
return numValue.toFixed(2)
|
|
}
|
|
|
|
const handleInput = (event) => {
|
|
const value = event.detail.value
|
|
inputValue.value = value
|
|
|
|
// 实时验证
|
|
if (value) {
|
|
validateInput(value)
|
|
} else {
|
|
errorMessage.value = ''
|
|
}
|
|
}
|
|
|
|
const handleConfirm = async () => {
|
|
if (!validateInput(inputValue.value)) {
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const formattedValue = formatDecimal(inputValue.value)
|
|
emit('confirm', formattedValue)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const handleCancel = () => {
|
|
emit('cancel')
|
|
}
|
|
|
|
const handleBackdropClick = () => {
|
|
emit('cancel')
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
background: rgba(255, 255, 255, 0.98);
|
|
border-radius: 25rpx;
|
|
width: 80%;
|
|
max-width: 600rpx;
|
|
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
|
|
border: 3rpx solid #C8956E;
|
|
animation: slideUp 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(50rpx);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 30rpx;
|
|
border-bottom: 2rpx solid rgba(200, 149, 110, 0.2);
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #5D4037;
|
|
}
|
|
|
|
.modal-close {
|
|
font-size: 36rpx;
|
|
color: #C8956E;
|
|
cursor: pointer;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.modal-close:active {
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 30rpx;
|
|
}
|
|
|
|
.modal-input {
|
|
width: 100%;
|
|
height: 80rpx;
|
|
padding: 0 20rpx;
|
|
font-size: 28rpx;
|
|
border: 2rpx solid #C8956E;
|
|
border-radius: 15rpx;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
color: #5D4037;
|
|
box-sizing: border-box;
|
|
transition: border-color 0.3s;
|
|
}
|
|
|
|
.modal-input:focus {
|
|
border-color: #FFD700;
|
|
outline: none;
|
|
}
|
|
|
|
.error-message {
|
|
display: block;
|
|
margin-top: 15rpx;
|
|
font-size: 24rpx;
|
|
color: #FA3534;
|
|
text-align: center;
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
padding: 30rpx;
|
|
border-top: 2rpx solid rgba(200, 149, 110, 0.2);
|
|
}
|
|
|
|
.btn-cancel,
|
|
.btn-confirm {
|
|
flex: 1;
|
|
height: 80rpx;
|
|
border-radius: 15rpx;
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.btn-cancel {
|
|
background: rgba(200, 149, 110, 0.2);
|
|
color: #5D4037;
|
|
border: 2rpx solid #C8956E;
|
|
}
|
|
|
|
.btn-cancel:active {
|
|
background: rgba(200, 149, 110, 0.3);
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.btn-confirm {
|
|
background: linear-gradient(135deg, #C8956E, #FFD700);
|
|
color: #fff;
|
|
border: 2rpx solid #C8956E;
|
|
}
|
|
|
|
.btn-confirm:active:not(:disabled) {
|
|
transform: scale(0.98);
|
|
box-shadow: 0 4rpx 12rpx rgba(200, 149, 110, 0.3);
|
|
}
|
|
|
|
.btn-confirm:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|