feat: collect customer identity for assisted bookings
This commit is contained in:
parent
f192d89534
commit
ec4015aaba
@ -135,6 +135,10 @@ npm run dev:h5
|
|||||||
|
|
||||||
当前实现:**提交时登录 + guest 草稿恢复**。未登录用户可填写预约表单,提交时跳转登录页(带 `redirect` 回预约页并保留 `storeId`),登录成功后回到预约页并恢复 guest 草稿(草稿 key 不依赖 `userInfo.id`,避免 `userId: undefined`)。登录态用户继续按 `userId` 维度保存草稿。草稿有效期 7 天。
|
当前实现:**提交时登录 + guest 草稿恢复**。未登录用户可填写预约表单,提交时跳转登录页(带 `redirect` 回预约页并保留 `storeId`),登录成功后回到预约页并恢复 guest 草稿(草稿 key 不依赖 `userInfo.id`,避免 `userId: undefined`)。登录态用户继续按 `userId` 维度保存草稿。草稿有效期 7 天。
|
||||||
|
|
||||||
|
### 门店代客预约身份
|
||||||
|
|
||||||
|
老板/员工在首页新建预约时必须填写宠主手机号,可选填宠主称呼。后端按手机号关联或创建 customer,并分别记录 `customerUserId` 与 `createdByUserId`;前端不得再把当前员工 `userId` 当作宠主提交。预约详情优先读取 `assignedStaffId`,兼容旧字段 `assignedUserId`。
|
||||||
|
|
||||||
## 前端 RC 发布门禁
|
## 前端 RC 发布门禁
|
||||||
|
|
||||||
发布前必须依次执行并全部通过:
|
发布前必须依次执行并全部通过:
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export const getAppointmentList = (/* userId, storeId, */ options = {}) =>
|
|||||||
// 预约详情
|
// 预约详情
|
||||||
export const getAppointmentDetail = (id) => get('/appointment/detail', { id })
|
export const getAppointmentDetail = (id) => get('/appointment/detail', { id })
|
||||||
|
|
||||||
// 创建预约(userId/storeId 由后端从上下文派生;前端仍传 storeId 供 customer 选店场景,后端会按 role 决定是否采纳)
|
// 创建预约:登录人身份始终由后端派生;customer 传选中门店,门店代客预约传 customerUserId 或 customerPhone
|
||||||
export const createAppointment = (data) => post('/appointment/create', data)
|
export const createAppointment = (data) => post('/appointment/create', data)
|
||||||
|
|
||||||
/** 门店某日可预约半小时档(可约 / 已满 / 已过);公开接口 */
|
/** 门店某日可预约半小时档(可约 / 已满 / 已过);公开接口 */
|
||||||
|
|||||||
@ -42,9 +42,9 @@
|
|||||||
<text class="label">备注</text>
|
<text class="label">备注</text>
|
||||||
<text class="value remark">{{ detail.remark }}</text>
|
<text class="value remark">{{ detail.remark }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="detail.assignedUserId" class="detail-row">
|
<view v-if="detail.assignedStaffId || detail.assignedUserId" class="detail-row">
|
||||||
<text class="label">服务技师</text>
|
<text class="label">服务技师</text>
|
||||||
<text class="value">#{{ detail.assignedUserId }}</text>
|
<text class="value">#{{ detail.assignedStaffId || detail.assignedUserId }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-meta">
|
<view class="detail-meta">
|
||||||
<text v-if="detail.id">预约编号 {{ detail.id }}</text>
|
<text v-if="detail.id">预约编号 {{ detail.id }}</text>
|
||||||
|
|||||||
@ -593,7 +593,7 @@ const confirmNewAppt = async () => {
|
|||||||
uni.showToast({ title: '宠物档案保存失败,请重试', icon: 'none' })
|
uni.showToast({ title: '宠物档案保存失败,请重试', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const payload = { ...a, storeId, userId: userInfo.id }
|
const payload = { ...a, storeId }
|
||||||
payload.petId = ensuredPet.id
|
payload.petId = ensuredPet.id
|
||||||
const res = await createAppointment(payload)
|
const res = await createAppointment(payload)
|
||||||
creatingAppt.value = false
|
creatingAppt.value = false
|
||||||
|
|||||||
@ -199,6 +199,20 @@
|
|||||||
<text class="popup-close" @click="showNewAppt = false">✕</text>
|
<text class="popup-close" @click="showNewAppt = false">✕</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="popup-body">
|
<view class="popup-body">
|
||||||
|
<view class="c-field">
|
||||||
|
<text class="app-form-label">宠主手机号</text>
|
||||||
|
<input
|
||||||
|
v-model="newAppt.customerPhone"
|
||||||
|
class="app-form-input"
|
||||||
|
type="number"
|
||||||
|
maxlength="11"
|
||||||
|
placeholder="用于关联宠主与服务报告"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="c-field">
|
||||||
|
<text class="app-form-label">宠主称呼(可选)</text>
|
||||||
|
<input v-model="newAppt.customerName" class="app-form-input" maxlength="64" placeholder="例如:小白妈妈" />
|
||||||
|
</view>
|
||||||
<view class="c-field">
|
<view class="c-field">
|
||||||
<text class="app-form-label">宠物名字</text>
|
<text class="app-form-label">宠物名字</text>
|
||||||
<input v-model="newAppt.petName" class="app-form-input" placeholder="请输入" />
|
<input v-model="newAppt.petName" class="app-form-input" placeholder="请输入" />
|
||||||
@ -387,7 +401,16 @@ const petTypes = [
|
|||||||
{ label: '其他', value: '其他' }
|
{ label: '其他', value: '其他' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const newAppt = ref({ petName: '', petType: '', serviceType: '', appointmentTime: '', remark: '' })
|
const emptyStaffAppointment = () => ({
|
||||||
|
customerPhone: '',
|
||||||
|
customerName: '',
|
||||||
|
petName: '',
|
||||||
|
petType: '',
|
||||||
|
serviceType: '',
|
||||||
|
appointmentTime: '',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
const newAppt = ref(emptyStaffAppointment())
|
||||||
const appointmentTime = computed(() => {
|
const appointmentTime = computed(() => {
|
||||||
const raw = newAppt.value.appointmentTime || ''
|
const raw = newAppt.value.appointmentTime || ''
|
||||||
if (!raw.includes('T')) return ''
|
if (!raw.includes('T')) return ''
|
||||||
@ -671,6 +694,10 @@ const goReport = (item) => {
|
|||||||
|
|
||||||
const confirmNewAppt = async () => {
|
const confirmNewAppt = async () => {
|
||||||
const a = newAppt.value
|
const a = newAppt.value
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(a.customerPhone || '')) {
|
||||||
|
uni.showToast({ title: '请输入正确的宠主手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!a.petName) { uni.showToast({ title: '请输入宠物名字', icon: 'none' }); return }
|
if (!a.petName) { uni.showToast({ title: '请输入宠物名字', icon: 'none' }); return }
|
||||||
if (!a.petType) { uni.showToast({ title: '请选择宠物类型', icon: 'none' }); return }
|
if (!a.petType) { uni.showToast({ title: '请选择宠物类型', icon: 'none' }); return }
|
||||||
if (!a.serviceType) { uni.showToast({ title: '请选择服务类型', icon: 'none' }); return }
|
if (!a.serviceType) { uni.showToast({ title: '请选择服务类型', icon: 'none' }); return }
|
||||||
@ -681,11 +708,11 @@ const confirmNewAppt = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
creatingAppt.value = true
|
creatingAppt.value = true
|
||||||
const res = await createAppointment({ ...a, storeId: storeInfo.id, userId: userInfo.id })
|
const res = await createAppointment({ ...a, storeId: storeInfo.id })
|
||||||
creatingAppt.value = false
|
creatingAppt.value = false
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||||
newAppt.value = { petName: '', petType: '', serviceType: '', appointmentTime: '', remark: '' }
|
newAppt.value = emptyStaffAppointment()
|
||||||
showNewAppt.value = false
|
showNewAppt.value = false
|
||||||
fetchAppointments()
|
fetchAppointments()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -700,7 +700,6 @@ const submitReport = async () => {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
appointmentId: currentAppointmentId.value || null,
|
appointmentId: currentAppointmentId.value || null,
|
||||||
userId: userInfo.id,
|
|
||||||
petName: report.value.petName,
|
petName: report.value.petName,
|
||||||
serviceType: report.value.serviceType,
|
serviceType: report.value.serviceType,
|
||||||
appointmentTime: report.value.appointmentTime || null,
|
appointmentTime: report.value.appointmentTime || null,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user