561 lines
11 KiB
Vue
561 lines
11 KiB
Vue
<template>
|
||
<view v-if="show" class="dialog-mask" @tap="handleMaskClick">
|
||
<view class="dialog-container" :class="{ 'dialog-show': showAnimation }" @tap.stop>
|
||
<view class="dialog-handle"></view>
|
||
<view class="dialog-header">
|
||
<text class="dialog-title">{{ title }}</text>
|
||
<view class="dialog-close" @tap="close">×</view>
|
||
</view>
|
||
|
||
<view class="dialog-body">
|
||
<view class="form-row">
|
||
<picker class="picker-card" mode="date" :value="formData.smoke_time" @change="onDateChange">
|
||
<view class="input-card">
|
||
<text class="input-label">日期</text>
|
||
<view class="input-value-row">
|
||
<view class="input-icon input-icon-date"></view>
|
||
<text class="input-value">{{ formData.smoke_time || '选择日期' }}</text>
|
||
</view>
|
||
</view>
|
||
</picker>
|
||
<picker class="picker-card" mode="time" :value="formData.smoke_time_only" @change="onTimeChange">
|
||
<view class="input-card">
|
||
<text class="input-label">时间</text>
|
||
<view class="input-value-row">
|
||
<view class="input-icon input-icon-time"></view>
|
||
<text class="input-value">{{ formData.smoke_time_only || '选择时间' }}</text>
|
||
</view>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="section-card" v-if="type === 'smoke'">
|
||
<view class="section-left">
|
||
<view class="section-icon"></view>
|
||
<text class="section-title">抽烟数量</text>
|
||
</view>
|
||
<view class="counter">
|
||
<view class="counter-btn" @tap="decreaseNum">-</view>
|
||
<text class="counter-value">{{ formData.num }}</text>
|
||
<view class="counter-btn" @tap="increaseNum">+</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-card" v-if="type === 'smoke'">
|
||
<view class="level-header">
|
||
<text class="section-title">烟瘾程度</text>
|
||
<view class="level-badge">Level {{ formData.level }}</view>
|
||
</view>
|
||
<slider
|
||
class="level-slider"
|
||
:value="formData.level"
|
||
:min="1"
|
||
:max="5"
|
||
:step="1"
|
||
activeColor="#22C55E"
|
||
backgroundColor="#E5E7EB"
|
||
block-color="#22C55E"
|
||
:block-size="20"
|
||
@change="onLevelChange"
|
||
/>
|
||
<view class="level-scale">
|
||
<text class="level-scale-text">无感</text>
|
||
<text class="level-scale-text">强烈</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="remark-section">
|
||
<text class="section-title">备注(可选)</text>
|
||
<view class="remark-card">
|
||
<textarea
|
||
class="form-textarea"
|
||
v-model="formData.remark"
|
||
:placeholder="type === 'smoke' ? '记录此时的心情或诱因,如压力大、应酬...' : '记录抵抗心得或诱因...'"
|
||
maxlength="200"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="dialog-footer">
|
||
<view class="dialog-btn-primary" @tap="submit">
|
||
<view class="btn-icon"></view>
|
||
<text class="btn-text">保存记录</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'SmokeRecordDialog',
|
||
props: {
|
||
show: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
type: {
|
||
type: String,
|
||
default: 'smoke' // 'smoke' 或 'resisted'
|
||
},
|
||
initialData: {
|
||
type: Object,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
showAnimation: false,
|
||
formData: {
|
||
smoke_time: '',
|
||
smoke_time_only: '',
|
||
smoke_at: '',
|
||
remark: '',
|
||
level: 2,
|
||
num: 1
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
title() {
|
||
return '添加记录'
|
||
}
|
||
},
|
||
watch: {
|
||
show(newVal) {
|
||
if (newVal) {
|
||
this.initFormData()
|
||
setTimeout(() => {
|
||
this.showAnimation = true
|
||
}, 50)
|
||
} else {
|
||
this.showAnimation = false
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
initFormData() {
|
||
// 如果有初始数据(编辑模式),使用初始数据
|
||
if (this.initialData) {
|
||
this.formData = {
|
||
smoke_time: this.initialData.smoke_time || '',
|
||
smoke_time_only: this.initialData.smoke_time_only || '',
|
||
smoke_at: this.initialData.smoke_at || '',
|
||
remark: this.initialData.remark || '',
|
||
level: this.initialData.level ?? 2,
|
||
num: this.initialData.num ?? 1
|
||
}
|
||
} else {
|
||
// 新建模式,使用当前时间
|
||
const now = new Date()
|
||
const dateStr = now.toISOString().split('T')[0]
|
||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||
const datetimeStr = `${dateStr} ${timeStr}:00`
|
||
|
||
this.formData = {
|
||
smoke_time: dateStr,
|
||
smoke_time_only: timeStr,
|
||
smoke_at: datetimeStr,
|
||
remark: '',
|
||
level: 2,
|
||
num: this.type === 'smoke' ? 1 : 0
|
||
}
|
||
}
|
||
},
|
||
handleMaskClick() {
|
||
this.close()
|
||
},
|
||
close() {
|
||
this.showAnimation = false
|
||
setTimeout(() => {
|
||
this.$emit('update:show', false)
|
||
}, 300)
|
||
},
|
||
onDateChange(e) {
|
||
this.formData.smoke_time = e.detail.value
|
||
this.updateSmokeAt()
|
||
},
|
||
onTimeChange(e) {
|
||
this.formData.smoke_time_only = e.detail.value
|
||
this.updateSmokeAt()
|
||
},
|
||
updateSmokeAt() {
|
||
this.formData.smoke_at = `${this.formData.smoke_time} ${this.formData.smoke_time_only}:00`
|
||
},
|
||
decreaseNum() {
|
||
if (this.formData.num > 1) {
|
||
this.formData.num--
|
||
}
|
||
},
|
||
increaseNum() {
|
||
this.formData.num++
|
||
},
|
||
onLevelChange(e) {
|
||
this.formData.level = e.detail.value
|
||
},
|
||
isTimeValid() {
|
||
const dateStr = this.formData.smoke_time
|
||
const timeStr = this.formData.smoke_time_only
|
||
|
||
if (!dateStr || !timeStr) {
|
||
uni.showToast({ title: '请选择日期和时间', icon: 'none' })
|
||
return false
|
||
}
|
||
|
||
const selected = new Date(`${dateStr}T${timeStr}:00`)
|
||
if (isNaN(selected.getTime())) {
|
||
uni.showToast({ title: '时间格式有误', icon: 'none' })
|
||
return false
|
||
}
|
||
|
||
const now = new Date()
|
||
const maxTime = new Date(now.getTime() + 5 * 60 * 1000)
|
||
if (selected > maxTime) {
|
||
uni.showToast({ title: '时间不能超过当前时间5分钟', icon: 'none' })
|
||
return false
|
||
}
|
||
|
||
return true
|
||
},
|
||
submit() {
|
||
if (!this.isTimeValid()) {
|
||
return
|
||
}
|
||
|
||
const submitData = {
|
||
smoke_time: this.formData.smoke_time,
|
||
smoke_at: this.formData.smoke_at,
|
||
remark: this.formData.remark,
|
||
level: this.type === 'smoke' ? this.formData.level : 0,
|
||
num: this.type === 'smoke' ? this.formData.num : 0
|
||
}
|
||
|
||
this.$emit('submit', submitData)
|
||
this.close()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dialog-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.35);
|
||
z-index: 9999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.dialog-container {
|
||
width: 100%;
|
||
max-height: 85vh;
|
||
background-color: #FFFFFF;
|
||
border-radius: 36rpx 36rpx 0 0;
|
||
overflow: hidden;
|
||
transform: translateY(100%);
|
||
transition: transform 0.3s ease-out;
|
||
padding-bottom: 16rpx;
|
||
}
|
||
|
||
.dialog-show {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.dialog-handle {
|
||
width: 80rpx;
|
||
height: 8rpx;
|
||
background-color: #E5E7EB;
|
||
border-radius: 999rpx;
|
||
margin: 16rpx auto 0;
|
||
}
|
||
|
||
.dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 32rpx 16rpx;
|
||
}
|
||
|
||
.dialog-title {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
}
|
||
|
||
.dialog-close {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 44rpx;
|
||
color: #9CA3AF;
|
||
line-height: 1;
|
||
background-color: #F3F4F6;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.dialog-body {
|
||
padding: 16rpx 32rpx 24rpx;
|
||
max-height: 62vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.picker-card {
|
||
flex: 1;
|
||
}
|
||
|
||
.input-card {
|
||
background-color: #F9FAFB;
|
||
border-radius: 24rpx;
|
||
padding: 20rpx 24rpx;
|
||
border: 2rpx solid #F3F4F6;
|
||
min-height: 120rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
|
||
.input-label {
|
||
font-size: 22rpx;
|
||
color: #9CA3AF;
|
||
margin-bottom: 12rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.input-value-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.input-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 8rpx;
|
||
background-color: #DCFCE7;
|
||
position: relative;
|
||
}
|
||
|
||
.input-icon-date::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 6rpx;
|
||
top: 8rpx;
|
||
width: 20rpx;
|
||
height: 16rpx;
|
||
border: 2rpx solid #22C55E;
|
||
border-top-width: 6rpx;
|
||
border-radius: 4rpx;
|
||
}
|
||
|
||
.input-icon-time::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 8rpx;
|
||
top: 8rpx;
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border: 2rpx solid #22C55E;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.input-icon-time::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 16rpx;
|
||
top: 12rpx;
|
||
width: 2rpx;
|
||
height: 8rpx;
|
||
background-color: #22C55E;
|
||
transform-origin: bottom;
|
||
}
|
||
|
||
.input-value {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
}
|
||
|
||
.section-card {
|
||
background-color: #FFFFFF;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
border: 2rpx solid #F3F4F6;
|
||
box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.section-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.section-icon {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
background-color: #DCFCE7;
|
||
border-radius: 16rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.section-icon::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 18rpx;
|
||
top: 18rpx;
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
border-radius: 6rpx;
|
||
border: 3rpx solid #22C55E;
|
||
border-top-color: transparent;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
}
|
||
|
||
.counter {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
background-color: #F8FAFC;
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
border: 2rpx solid #F1F5F9;
|
||
margin-top: 16rpx;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.counter-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 50%;
|
||
background-color: #E8FFF1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 36rpx;
|
||
color: #22C55E;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.counter-value {
|
||
min-width: 40rpx;
|
||
text-align: center;
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
}
|
||
|
||
.level-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.level-badge {
|
||
padding: 8rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background-color: #DCFCE7;
|
||
color: #22C55E;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.level-slider {
|
||
margin: 8rpx 0 4rpx;
|
||
}
|
||
|
||
.level-scale {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.level-scale-text {
|
||
font-size: 22rpx;
|
||
color: #9CA3AF;
|
||
}
|
||
|
||
.remark-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.remark-card {
|
||
margin-top: 16rpx;
|
||
background-color: #F9FAFB;
|
||
border-radius: 20rpx;
|
||
padding: 8rpx;
|
||
border: 2rpx solid #F3F4F6;
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 180rpx;
|
||
background-color: #F9FAFB;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx 20rpx;
|
||
font-size: 28rpx;
|
||
color: #111827;
|
||
border: 2rpx solid transparent;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.dialog-footer {
|
||
padding: 16rpx 32rpx 32rpx;
|
||
}
|
||
|
||
.dialog-btn-primary {
|
||
height: 96rpx;
|
||
border-radius: 32rpx;
|
||
background-color: #22C55E;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16rpx;
|
||
box-shadow: 0 10rpx 24rpx rgba(34, 197, 94, 0.3);
|
||
}
|
||
|
||
.btn-icon {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
border-radius: 50%;
|
||
background-color: #0F172A;
|
||
position: relative;
|
||
}
|
||
|
||
.btn-icon::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 14rpx;
|
||
top: 18rpx;
|
||
width: 12rpx;
|
||
height: 6rpx;
|
||
border-left: 4rpx solid #FFFFFF;
|
||
border-bottom: 4rpx solid #FFFFFF;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #0F172A;
|
||
}
|
||
</style>
|