334 lines
10 KiB
Vue
334 lines
10 KiB
Vue
<template>
|
|
<view class="report-page">
|
|
<!-- 提交结果 -->
|
|
<view v-if="reportResult" class="result-wrap">
|
|
<view class="notice-bar">✓ 提交成功,可复制链接或扫描二维码分享给宠主</view>
|
|
<view class="result-content">
|
|
<view class="result-link-row">
|
|
<text class="result-link-label">报告链接</text>
|
|
<text class="result-link-val" selectable>{{ reportResult.reportUrl }}</text>
|
|
</view>
|
|
<view class="qr-wrap">
|
|
<image :src="qrUrl" mode="aspectFit" class="qr-img" />
|
|
</view>
|
|
<button class="btn-primary-full" @click="downloadQr">📥 保存二维码到相册</button>
|
|
<button class="btn-default-full" @click="goHome" style="margin-top:12px;">返回首页</button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 填写表单 -->
|
|
<view v-else class="report-form">
|
|
<!-- 基本信息 -->
|
|
<view class="form-section">
|
|
<view class="form-title">基本信息</view>
|
|
<view class="form-card">
|
|
<view class="form-row">
|
|
<text class="row-label">宠物名字</text>
|
|
<input class="row-input" v-model="report.petName" placeholder="请输入宠物名字" />
|
|
</view>
|
|
<view class="form-row">
|
|
<text class="row-label">服务类型</text>
|
|
<picker :value="serviceTypeIdx" :range="serviceTypeOptions" :range-key="'name'" @change="onServiceTypeChange">
|
|
<view class="picker-val">{{ report.serviceType || '请选择' }} ▾</view>
|
|
</picker>
|
|
</view>
|
|
<view class="form-row">
|
|
<text class="row-label">服务时间</text>
|
|
<picker mode="datetime" :value="report.appointmentTime" @change="onDateChange">
|
|
<view class="picker-val">{{ report.appointmentTime || '请选择' }} ▾</view>
|
|
</picker>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 服务照片 -->
|
|
<view class="form-section">
|
|
<view class="form-title">服务照片</view>
|
|
<view class="form-card">
|
|
<view class="photo-row">
|
|
<text class="row-label">服务前</text>
|
|
<view class="photo-upload" @click="chooseImage('before')">
|
|
<image v-if="report.before" :src="report.before" class="photo-preview" mode="aspectFill" />
|
|
<view v-else class="photo-placeholder">
|
|
<text class="plus-icon">+</text>
|
|
<text class="upload-tip">上传照片</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="photo-row">
|
|
<text class="row-label">服务后</text>
|
|
<view class="photo-upload" @click="chooseImage('after')">
|
|
<image v-if="report.after" :src="report.after" class="photo-preview" mode="aspectFill" />
|
|
<view v-else class="photo-placeholder">
|
|
<text class="plus-icon">+</text>
|
|
<text class="upload-tip">上传照片</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 备注 -->
|
|
<view class="form-section">
|
|
<view class="form-title">备注</view>
|
|
<view class="form-card">
|
|
<textarea class="remark-input" v-model="report.remark" placeholder="请输入备注信息..." rows="3" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="submit-wrap">
|
|
<button class="btn-submit" :class="{ loading: submitting }" @click="submitReport">提交报告</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { createReport, getServiceTypeList, uploadImage } from '../../utils/api.js'
|
|
import { imgUrl } from '../../utils/api.js'
|
|
|
|
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
|
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
|
|
|
const report = ref({ petName: '', serviceType: '', appointmentTime: '', before: '', after: '', remark: '' })
|
|
const reportResult = ref(null)
|
|
const serviceTypes = ref([])
|
|
const serviceTypeIdx = ref(0)
|
|
const submitting = ref(false)
|
|
const currentAppointmentId = ref(null)
|
|
|
|
const serviceTypeOptions = computed(() =>
|
|
serviceTypes.value.map(s => ({
|
|
name: `${serviceEmoji(s.name)} ${s.name}`,
|
|
value: s.name
|
|
}))
|
|
)
|
|
|
|
const serviceEmoji = (name) => {
|
|
if (!name) return ''
|
|
if (name.includes('洗澡') && name.includes('美容')) return '🛁✂️'
|
|
if (name.includes('洗澡')) return '🛁'
|
|
if (name.includes('美容')) return '✂️'
|
|
if (name.includes('剪指甲')) return '💅'
|
|
if (name.includes('驱虫')) return '🐛'
|
|
return '✨'
|
|
}
|
|
|
|
const qrUrl = computed(() => reportResult.value
|
|
? `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(reportResult.value.reportUrl)}`
|
|
: '')
|
|
|
|
const loadServiceTypes = async () => {
|
|
if (!storeInfo.id) return
|
|
const res = await getServiceTypeList(storeInfo.id)
|
|
if (res.code === 200) serviceTypes.value = res.data
|
|
}
|
|
|
|
const onServiceTypeChange = (e) => {
|
|
serviceTypeIdx.value = e.detail.value
|
|
report.value.serviceType = serviceTypes.value[e.detail.value]?.name || ''
|
|
}
|
|
|
|
const onDateChange = (e) => {
|
|
report.value.appointmentTime = e.detail.value
|
|
}
|
|
|
|
const chooseImage = async (field) => {
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sizeType: ['compressed'],
|
|
sourceType: ['album', 'camera'],
|
|
success: async (res) => {
|
|
const filePath = res.tempFilePaths[0]
|
|
uni.showLoading({ title: '上传中...' })
|
|
const r = await uploadImage(filePath)
|
|
uni.hideLoading()
|
|
if (r.code === 200) {
|
|
report.value[field] = imgUrl(r.data.url)
|
|
uni.showToast({ title: '上传成功', icon: 'success' })
|
|
} else {
|
|
uni.showToast({ title: r.message || '上传失败', icon: 'none' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const submitReport = async () => {
|
|
if (!report.value.petName) { uni.showToast({ title: '请输入宠物名字', icon: 'none' }); return }
|
|
if (!report.value.serviceType) { uni.showToast({ title: '请选择服务类型', icon: 'none' }); return }
|
|
submitting.value = true
|
|
const payload = {
|
|
appointmentId: currentAppointmentId.value || null,
|
|
userId: userInfo.id,
|
|
petName: report.value.petName,
|
|
serviceType: report.value.serviceType,
|
|
appointmentTime: report.value.appointmentTime || null,
|
|
beforePhoto: report.value.before,
|
|
afterPhoto: report.value.after,
|
|
remark: report.value.remark
|
|
}
|
|
const res = await createReport(payload)
|
|
submitting.value = false
|
|
if (res.code === 200) {
|
|
const token = res.data.reportToken
|
|
// #ifdef MP-WEIXIN
|
|
const reportUrl = `/pages/report-view/reportView?token=${token}`
|
|
// #endif
|
|
// #ifdef H5
|
|
const reportUrl = `${window.location.origin}/#/pages/report-view/reportView?token=${token}`
|
|
// #endif
|
|
reportResult.value = { token, reportUrl }
|
|
} else {
|
|
uni.showToast({ title: res.message || '提交失败', icon: 'none' })
|
|
}
|
|
}
|
|
|
|
const copyLink = () => {
|
|
uni.setClipboardData({ data: reportResult.value.reportUrl, success: () => uni.showToast({ title: '链接已复制', icon: 'success' }) })
|
|
}
|
|
|
|
const downloadQr = () => {
|
|
uni.showToast({ title: '请长按二维码图片保存', icon: 'none' })
|
|
}
|
|
|
|
const goHome = () => {
|
|
reportResult.value = null
|
|
report.value = { petName: '', serviceType: '', appointmentTime: '', before: '', after: '', remark: '' }
|
|
uni.switchTab({ url: '/pages/home/home' })
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await loadServiceTypes()
|
|
const prefill = JSON.parse(uni.getStorageSync('petstore_report_prefill') || 'null')
|
|
if (prefill) {
|
|
currentAppointmentId.value = prefill.appointmentId
|
|
report.value.petName = prefill.petName || ''
|
|
report.value.serviceType = prefill.serviceType || ''
|
|
report.value.appointmentTime = prefill.appointmentTime ? prefill.appointmentTime.slice(0, 16) : ''
|
|
uni.removeStorageSync('petstore_report_prefill')
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.report-page { padding-bottom: 20px; background: #f5f5f5; min-height: 100vh; }
|
|
|
|
.notice-bar {
|
|
background: #f6ffed;
|
|
color: #52c41a;
|
|
font-size: 13px;
|
|
padding: 10px 16px;
|
|
border-bottom: 1px solid #b7eb8f;
|
|
}
|
|
|
|
.result-content { padding: 16px; }
|
|
.result-link-row { background: #f9f9f9; border-radius: 8px; padding: 12px; margin-bottom: 16px; }
|
|
.result-link-label { font-size: 12px; color: #999; display: block; margin-bottom: 4px; }
|
|
.result-link-val { font-size: 12px; color: #07c160; word-break: break-all; }
|
|
|
|
.qr-wrap { text-align: center; padding: 20px; background: #f9f9f9; border-radius: 12px; margin: 16px 0; }
|
|
.qr-img { width: 180px; height: 180px; }
|
|
|
|
.form-section { padding: 12px 16px 0; }
|
|
.form-title { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 8px; padding-left: 4px; }
|
|
.form-card { background: #fff; border-radius: 12px; padding: 4px 16px; }
|
|
.form-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
.form-row:last-child { border-bottom: none; }
|
|
.row-label { font-size: 14px; color: #666; width: 80px; flex-shrink: 0; }
|
|
.row-input {
|
|
flex: 1;
|
|
font-size: 14px;
|
|
color: #333;
|
|
text-align: right;
|
|
}
|
|
.picker-val {
|
|
flex: 1;
|
|
font-size: 14px;
|
|
color: #333;
|
|
text-align: right;
|
|
}
|
|
.photo-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
.photo-row:last-child { border-bottom: none; }
|
|
.photo-upload {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
.photo-preview {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 8px;
|
|
}
|
|
.photo-placeholder {
|
|
width: 80px;
|
|
height: 80px;
|
|
border: 1px dashed #ddd;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #999;
|
|
}
|
|
.plus-icon { font-size: 24px; line-height: 1; }
|
|
.upload-tip { font-size: 11px; margin-top: 2px; }
|
|
.remark-input {
|
|
width: 100%;
|
|
padding: 8px 0;
|
|
font-size: 14px;
|
|
color: #333;
|
|
border: none;
|
|
resize: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.submit-wrap { padding: 16px; }
|
|
.btn-submit {
|
|
width: 100%;
|
|
height: 46px;
|
|
background: #07c160;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 23px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
.btn-submit.loading { opacity: 0.6; }
|
|
|
|
.btn-primary-full {
|
|
width: 100%;
|
|
height: 44px;
|
|
background: #07c160;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.btn-default-full {
|
|
width: 100%;
|
|
height: 44px;
|
|
background: #fff;
|
|
color: #333;
|
|
border: 1px solid #eee;
|
|
border-radius: 8px;
|
|
font-size: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
</style>
|