Files
smt/components/smoke-record-dialog/smoke-record-dialog.vue
T

561 lines
11 KiB
Vue
Raw 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>
<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>