332 lines
7.9 KiB
Vue
332 lines
7.9 KiB
Vue
<script setup lang="ts">
|
|
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
|
import { appFeedbackAddPost } from "@/service";
|
|
import { z } from 'zod';
|
|
const { t } = useI18n()
|
|
|
|
const form = ref({
|
|
content: '',
|
|
contactPhone: '',
|
|
images: '',
|
|
})
|
|
|
|
// 创建zod校验schema
|
|
const createValidationSchema = () => {
|
|
return z.object({
|
|
content: z.string()
|
|
.min(1, t('pages-user.complaints.validation.content-required'))
|
|
.min(10, t('pages-user.complaints.validation.content-min-length'))
|
|
.max(500, t('pages-user.complaints.validation.content-max-length')),
|
|
contactPhone: z.string()
|
|
.min(1, t('pages-user.complaints.validation.contact-phone-required'))
|
|
.regex(/^[\d\s\-\+\(\)]+$/, t('pages-user.complaints.validation.contact-phone-invalid'))
|
|
})
|
|
}
|
|
|
|
const chooseImageRef = ref()
|
|
const showNoticePopup = ref(true)
|
|
const confirmCountdown = ref(5)
|
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
function startNoticeCountdown() {
|
|
if (countdownTimer) {
|
|
clearInterval(countdownTimer)
|
|
}
|
|
confirmCountdown.value = 5
|
|
countdownTimer = setInterval(() => {
|
|
if (confirmCountdown.value <= 1) {
|
|
confirmCountdown.value = 0
|
|
if (countdownTimer) {
|
|
clearInterval(countdownTimer)
|
|
countdownTimer = null
|
|
}
|
|
return
|
|
}
|
|
confirmCountdown.value -= 1
|
|
}, 1000)
|
|
}
|
|
|
|
function handleCloseNoticePopup() {
|
|
if (confirmCountdown.value > 0) {
|
|
return
|
|
}
|
|
showNoticePopup.value = false
|
|
}
|
|
|
|
// 校验单个字段(只校验 schema 中存在的字段)
|
|
const validateField = (field: 'content' | 'contactPhone') => {
|
|
try {
|
|
const schema = createValidationSchema()
|
|
const fieldSchema = schema.shape[field]
|
|
fieldSchema.parse(form.value[field])
|
|
return true
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
uni.showToast({
|
|
title: error.errors[0].message,
|
|
icon: 'none'
|
|
})
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 校验整个表单
|
|
const validateForm = () => {
|
|
try {
|
|
const schema = createValidationSchema()
|
|
schema.parse(form.value)
|
|
return true
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
uni.showToast({
|
|
title: error.errors[0].message,
|
|
icon: 'none'
|
|
})
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
const handleSubmit = () => {
|
|
if (!validateForm()) {
|
|
return
|
|
}
|
|
|
|
appFeedbackAddPost({
|
|
body: {
|
|
...form.value
|
|
}
|
|
}).then(res => {
|
|
uni.showToast({
|
|
title: t('toast.submitSuccess'),
|
|
icon: 'none'
|
|
})
|
|
form.value = {
|
|
content: '',
|
|
contactPhone: '',
|
|
images: '',
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleChooseImage = () => {
|
|
chooseImageRef.value.init()
|
|
}
|
|
function onImageChange(files: string[]) {
|
|
form.value.images = files[0]
|
|
}
|
|
|
|
onLoad(() => {
|
|
startNoticeCountdown()
|
|
})
|
|
|
|
onUnload(() => {
|
|
if (countdownTimer) {
|
|
clearInterval(countdownTimer)
|
|
countdownTimer = null
|
|
}
|
|
})
|
|
</script>
|
|
<template>
|
|
<view class="complaints-root">
|
|
<navbar :title="t('pages-user.complaints.title')" />
|
|
<view class="complaints-page">
|
|
|
|
|
|
<view class="form-card">
|
|
<view class="field">
|
|
<view class="field__label">{{ t('pages-user.complaints.feedback-content') }}</view>
|
|
<view class="field__box field__box--textarea">
|
|
<wd-textarea v-model="form.content" :maxlength="500" custom-class="!bg-transparent"
|
|
custom-textarea-container-class="!bg-transparent" no-border auto-height
|
|
:placeholder="t('pages-user.complaints.feedback-content-placeholder')" @blur="validateField('content')" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="field field--mt">
|
|
<view class="field__label">{{ t('pages-user.complaints.image') }}</view>
|
|
<view class="upload" @click="handleChooseImage">
|
|
<image v-if="form.images" src="@img/chef/113.png" class="upload__remove"></image>
|
|
<image v-if="!form.images" src="@img/chef/112.png" class="upload__placeholder"></image>
|
|
<image v-else :src="form.images" class="upload__image" mode="aspectFill"></image>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="field field--mt">
|
|
<view class="field__label">{{ t('pages-user.complaints.contact-information') }}</view>
|
|
<view class="field__box field__box--input">
|
|
<wd-input v-model.trim="form.contactPhone" no-border custom-class="!p-0 !bg-transparent"
|
|
custom-input-class="complaints-input" :placeholder="t('pages-user.complaints.contact-information-placeholder')" :maxlength="50"
|
|
@blur="validateField('contactPhone')" />
|
|
</view>
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<view class="complaints-page__desc">
|
|
{{ t('pages-user.complaints.description') }}
|
|
</view>
|
|
|
|
<!-- 底部固定提交按钮 -->
|
|
<view class="bottom-actions">
|
|
<wd-button custom-class="submit-btn" block @click="handleSubmit">
|
|
{{ t('common.submit') }}
|
|
</wd-button>
|
|
</view>
|
|
|
|
<ChooseImage ref="chooseImageRef" @change="onImageChange" />
|
|
|
|
<wd-popup v-model="showNoticePopup" :close-on-click-modal="false" custom-class="!bg-transparent">
|
|
<view class="notice-dialog">
|
|
<view class="notice-dialog__title">{{ t('pages-user.complaints.title') }}</view>
|
|
<view class="notice-dialog__content">
|
|
{{ t('pages-user.complaints.contact-information-tip') }}
|
|
</view>
|
|
<wd-button
|
|
custom-class="notice-dialog__btn"
|
|
block
|
|
:disabled="confirmCountdown > 0"
|
|
@click="handleCloseNoticePopup"
|
|
>
|
|
{{ confirmCountdown > 0 ? `${t('common.gotIt')} (${confirmCountdown}s)` : t('common.gotIt') }}
|
|
</wd-button>
|
|
</view>
|
|
</wd-popup>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<style lang="scss" scoped>
|
|
.complaints-page {
|
|
padding: 32rpx 30rpx calc(160rpx + env(safe-area-inset-bottom));
|
|
background: #fff;
|
|
min-height: 100vh;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.complaints-page__desc {
|
|
font-size: 22rpx;
|
|
line-height: 36rpx;
|
|
color: #333;
|
|
margin-bottom: 18rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.form-card {
|
|
background: #fff;
|
|
border-radius: 24rpx;
|
|
padding: 26rpx 24rpx 18rpx;
|
|
}
|
|
|
|
.field__label {
|
|
font-size: 26rpx;
|
|
line-height: 36rpx;
|
|
color: #333;
|
|
font-weight: 700;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.field--mt {
|
|
margin-top: 26rpx;
|
|
}
|
|
|
|
.field__box {
|
|
border: 1rpx solid #ececec;
|
|
border-radius: 18rpx;
|
|
background: #fff;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.field__box--textarea {
|
|
padding: 14rpx 16rpx;
|
|
min-height: 240rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.field__box--input {
|
|
padding: 18rpx 16rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:deep(.complaints-input) {
|
|
text-align: left;
|
|
font-size: 26rpx;
|
|
line-height: 36rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.upload {
|
|
width: 176rpx;
|
|
height: 176rpx;
|
|
border: 1rpx solid #ececec;
|
|
border-radius: 18rpx;
|
|
overflow: hidden;
|
|
position: relative;
|
|
background: #fff;
|
|
}
|
|
|
|
.upload__placeholder,
|
|
.upload__image {
|
|
width: 176rpx;
|
|
height: 176rpx;
|
|
}
|
|
|
|
.upload__remove {
|
|
position: absolute;
|
|
top: -10rpx;
|
|
right: -10rpx;
|
|
z-index: 2;
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
}
|
|
|
|
.bottom-actions {
|
|
position: fixed;
|
|
left: 30rpx;
|
|
right: 30rpx;
|
|
bottom: calc(36rpx + env(safe-area-inset-bottom));
|
|
z-index: 20;
|
|
}
|
|
|
|
:deep(.submit-btn) {
|
|
height: 108rpx !important;
|
|
border-radius: 999rpx !important;
|
|
background: #111 !important;
|
|
color: #fff !important;
|
|
font-size: 32rpx !important;
|
|
font-weight: 800 !important;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.notice-dialog {
|
|
width: 620rpx;
|
|
background: #fff;
|
|
border-radius: 24rpx;
|
|
padding: 36rpx 28rpx 28rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.notice-dialog__title {
|
|
text-align: center;
|
|
font-size: 30rpx;
|
|
line-height: 40rpx;
|
|
color: #222;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.notice-dialog__content {
|
|
margin-top: 18rpx;
|
|
margin-bottom: 28rpx;
|
|
font-size: 26rpx;
|
|
line-height: 38rpx;
|
|
color: #666;
|
|
// text-align: center;
|
|
}
|
|
|
|
:deep(.notice-dialog__btn) {
|
|
height: 84rpx !important;
|
|
border-radius: 999rpx !important;
|
|
font-size: 28rpx !important;
|
|
font-weight: 700 !important;
|
|
}
|
|
</style> |