petstore-frontend/src/components/report/TestimonialModal.vue
2026-04-19 14:54:38 +08:00

151 lines
3.2 KiB
Vue

<template>
<view v-if="modelValue" class="tm-mask" @click="onMask">
<view class="tm-card" @click.stop>
<text class="tm-title">{{ titleText }}</text>
<textarea
class="tm-input"
v-model="localText"
maxlength="200"
:placeholder="placeholderText"
:disabled="submitting"
:show-confirm-bar="false"
/>
<text class="tm-hint">选填,最多 200 字;跳过则直接生成不含寄语的版本</text>
<view class="tm-actions">
<button class="tm-btn tm-btn-ghost" :disabled="submitting" @click="emitSkip">跳过</button>
<button class="tm-btn tm-btn-primary" :disabled="submitting" @click="emitConfirm">
{{ submitting ? '请稍候' : '生成海报' }}
</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
petName: { type: String, default: '' },
submitting: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue', 'skip', 'confirm'])
const localText = ref('')
const titleText = computed(() => {
const n = (props.petName || '').trim()
return n ? `想对「${n}」说点什么?` : '想对宝贝说点什么?'
})
const placeholderText = computed(() => {
const n = (props.petName || '').trim()
return n ? `例如:${n}洗完又香又软,今天超可爱` : '例如:洗完香香的,精神又可爱'
})
watch(
() => props.modelValue,
(v) => {
if (v) localText.value = ''
}
)
const close = () => {
emit('update:modelValue', false)
}
const onMask = () => {
if (props.submitting) return
emitSkip()
}
const emitSkip = () => {
close()
emit('skip')
}
const emitConfirm = () => {
emit('confirm', localText.value.trim())
}
defineExpose({ close })
</script>
<style scoped>
.tm-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.45);
z-index: 9999;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 16px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.tm-card {
width: 100%;
max-width: 420px;
background: #fff;
border-radius: 16px 16px 12px 12px;
padding: 18px 16px 16px;
box-shadow: 0 -8px 32px rgba(15, 23, 42, 0.12);
}
.tm-title {
display: block;
font-size: 16px;
font-weight: 700;
color: #0f172a;
margin-bottom: 12px;
}
.tm-input {
width: 100%;
min-height: 88px;
padding: 12px;
font-size: 15px;
line-height: 1.5;
color: #334155;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-sizing: border-box;
}
.tm-hint {
display: block;
font-size: 12px;
color: #94a3b8;
margin-top: 8px;
line-height: 1.4;
}
.tm-actions {
display: flex;
gap: 10px;
margin-top: 16px;
}
.tm-btn {
flex: 1;
height: 44px;
line-height: 44px;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
border: none;
}
.tm-btn-ghost {
background: #f1f5f9;
color: #475569;
}
.tm-btn-primary {
background: linear-gradient(135deg, var(--c-brand-mid), var(--c-brand-accent));
color: #fff;
}
.tm-btn[disabled] {
opacity: 0.55;
}
</style>