Merge branch 'master' of https://gitea.zianxin.com/zax/biz-mobile-cloud-zax
This commit is contained in:
commit
f04663c2c1
8 files changed
+500
-21
No files matched your search
@@ -12,8 +12,14 @@
|
||||
mode="aspectFill" />
|
||||
<view class="ml-18rpx min-w-0">
|
||||
<view class="sub-text-32 font-700 truncate">{{ state.row.merchantName || "—" }}</view>
|
||||
<view v-if="state.row.createTime" class="second-text-22 color-#999 mt-6rpx">
|
||||
{{ state.row.createTime }}
|
||||
<view class="second-text-22 color-#666 mt-8rpx">
|
||||
下单时间:{{ formatOrderTimeLabel(state.row.createTime) }}
|
||||
</view>
|
||||
<view class="second-text-24 color-#333 mt-8rpx font-600">
|
||||
订单类型:{{ expectDeliveryTypeLabel(state.row.expectDeliveryType) }}
|
||||
</view>
|
||||
<view class="second-text-22 color-#1879ff mt-6rpx leading-relaxed">
|
||||
{{ deliveryExpectDetailLine(state.row) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -124,6 +130,11 @@ import { onLoad } from "@dcloudio/uni-app"
|
||||
import { hooks } from "@@/index"
|
||||
import { canteenDeliveryApi } from "@@/api"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import {
|
||||
deliveryExpectDetailLine,
|
||||
expectDeliveryTypeLabel,
|
||||
formatOrderTimeLabel,
|
||||
} from "@@/utils/canteen-delivery-display"
|
||||
import { getFileUploadActionUrl, getUploadAuthHeader, parseSysFileUploadResponse } from "@@/utils/upload"
|
||||
|
||||
const UPLOAD_OK = 2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<crud-layout title="骑手配送" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<crud-layout title="骑手配送" mainIsScroll bg="/static/page/blue-bg.png" @scrolltolower="onReachBottom">
|
||||
<view class="mx-30rpx pt-20rpx">
|
||||
<scroll-view scroll-x class="tabs-scroll white-space-nowrap" :show-scrollbar="false">
|
||||
<view class="flex gap-16rpx pb-20rpx">
|
||||
@@ -14,8 +14,10 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="state.loading" class="py-80rpx text-center second-text-26 color-#999">加载中…</view>
|
||||
<view v-else-if="state.list.length === 0" class="py-80rpx text-center second-text-26 color-#999">
|
||||
<view v-if="state.loading && state.list.length === 0" class="py-80rpx text-center second-text-26 color-#999">
|
||||
加载中…
|
||||
</view>
|
||||
<view v-else-if="!state.loading && state.list.length === 0" class="py-80rpx text-center second-text-26 color-#999">
|
||||
暂无配送单
|
||||
</view>
|
||||
<view v-else>
|
||||
@@ -32,7 +34,15 @@
|
||||
mode="aspectFill" />
|
||||
<view class="ml-16rpx min-w-0">
|
||||
<view class="sub-text-30 font-700 truncate">{{ item.merchantName || "—" }}</view>
|
||||
<view v-if="item.createTime" class="second-text-22 color-#999 mt-6rpx">{{ item.createTime }}</view>
|
||||
<view class="second-text-22 color-#666 mt-8rpx">
|
||||
下单时间:{{ formatOrderTimeLabel(item.createTime) }}
|
||||
</view>
|
||||
<view class="second-text-24 color-#333 mt-6rpx font-600">
|
||||
订单类型:{{ expectDeliveryTypeLabel(item.expectDeliveryType) }}
|
||||
</view>
|
||||
<view class="second-text-22 color-#1879ff mt-4rpx leading-relaxed">
|
||||
{{ deliveryExpectDetailLine(item) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-pill sub-text-24 px-14rpx py-8rpx rounded-full shrink-0" :style="statusStyle(item.status)">
|
||||
@@ -83,6 +93,11 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="state.list.length > 0" class="py-24rpx text-center second-text-24 color-#999">
|
||||
<text v-if="state.loadingMore">加载更多…</text>
|
||||
<text v-else-if="!state.hasMore">没有更多了</text>
|
||||
<text v-else>上拉加载更多</text>
|
||||
</view>
|
||||
<view class="h-40rpx" />
|
||||
</view>
|
||||
</crud-layout>
|
||||
@@ -96,6 +111,11 @@ import { hooks, router } from "@@/index"
|
||||
import { canteenDeliveryApi } from "@@/api"
|
||||
import type { CanteenDeliveryTab } from "@@/api/canteen-delivery"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import {
|
||||
deliveryExpectDetailLine,
|
||||
expectDeliveryTypeLabel,
|
||||
formatOrderTimeLabel,
|
||||
} from "@@/utils/canteen-delivery-display"
|
||||
|
||||
const tabs: { key: CanteenDeliveryTab; label: string }[] = [
|
||||
{ key: "pending", label: "待配送" },
|
||||
@@ -108,15 +128,58 @@ const state = reactive({
|
||||
tab: "pending" as CanteenDeliveryTab,
|
||||
list: [] as any[],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
/** 每页条数(与服务端约定,可被响应里的 size 覆盖) */
|
||||
size: 5,
|
||||
current: 0,
|
||||
pages: 0,
|
||||
total: 0,
|
||||
hasMore: true,
|
||||
})
|
||||
|
||||
const isApiOk = (res: any) => res?.code === 0 || res?.ok === true
|
||||
|
||||
const extractList = (res: any): any[] => {
|
||||
/** 分页 data:records、total、current、size、pages;兼容旧版 data 为数组 */
|
||||
const extractPage = (res: any): { records: any[]; total: number; current: number; size: number; pages: number } => {
|
||||
const d = res?.data
|
||||
if (Array.isArray(d)) return d
|
||||
if (Array.isArray(d?.records)) return d.records
|
||||
return []
|
||||
if (Array.isArray(d)) {
|
||||
return { records: d, total: d.length, current: 1, size: d.length, pages: 1 }
|
||||
}
|
||||
if (!d || typeof d !== "object") {
|
||||
return { records: [], total: 0, current: 1, size: state.size, pages: 0 }
|
||||
}
|
||||
const records = Array.isArray(d.records) ? d.records : []
|
||||
return {
|
||||
records,
|
||||
total: Number(d.total ?? 0),
|
||||
current: Number(d.current ?? 1),
|
||||
size: Number(d.size ?? state.size),
|
||||
pages: Number(d.pages ?? 0),
|
||||
}
|
||||
}
|
||||
|
||||
const applyPage = (page: ReturnType<typeof extractPage>, append: boolean) => {
|
||||
if (append) {
|
||||
state.list = [...state.list, ...page.records]
|
||||
} else {
|
||||
state.list = page.records
|
||||
}
|
||||
state.total = page.total
|
||||
if (page.size > 0) state.size = page.size
|
||||
state.current = page.current
|
||||
state.pages = page.pages
|
||||
|
||||
if (append && page.records.length === 0) {
|
||||
state.hasMore = false
|
||||
return
|
||||
}
|
||||
if (state.pages > 0) {
|
||||
state.hasMore = state.current < state.pages
|
||||
} else if (state.total > 0) {
|
||||
state.hasMore = state.list.length < state.total
|
||||
} else {
|
||||
state.hasMore = page.records.length >= state.size && page.records.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
const statusText = (s: number) => {
|
||||
@@ -142,28 +205,75 @@ const statusStyle = (s: number) => {
|
||||
return { color: c, backgroundColor: `${c}18` }
|
||||
}
|
||||
|
||||
const loadList = async () => {
|
||||
const loadFirstPage = async () => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
try {
|
||||
const res: any = await canteenDeliveryApi.list({ tab: state.tab })
|
||||
const res: any = await canteenDeliveryApi.list({
|
||||
tab: state.tab,
|
||||
current: 1,
|
||||
size: state.size,
|
||||
})
|
||||
if (!isApiOk(res)) {
|
||||
state.list = []
|
||||
state.hasMore = false
|
||||
uni.showToast({ title: res?.msg || "加载失败", icon: "none" })
|
||||
return
|
||||
}
|
||||
state.list = extractList(res)
|
||||
applyPage(extractPage(res), false)
|
||||
} catch (e: any) {
|
||||
state.list = []
|
||||
state.hasMore = false
|
||||
uni.showToast({ title: e?.message || "加载失败", icon: "none" })
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadNextPage = async () => {
|
||||
if (state.loading || state.loadingMore || !state.hasMore) return
|
||||
const next = state.current + 1
|
||||
if (state.pages > 0 && next > state.pages) {
|
||||
state.hasMore = false
|
||||
return
|
||||
}
|
||||
state.loadingMore = true
|
||||
try {
|
||||
const res: any = await canteenDeliveryApi.list({
|
||||
tab: state.tab,
|
||||
current: next,
|
||||
size: state.size,
|
||||
})
|
||||
if (!isApiOk(res)) {
|
||||
uni.showToast({ title: res?.msg || "加载失败", icon: "none" })
|
||||
return
|
||||
}
|
||||
const p = extractPage(res)
|
||||
if (p.records.length === 0) {
|
||||
state.hasMore = false
|
||||
return
|
||||
}
|
||||
applyPage(p, true)
|
||||
} catch (e: any) {
|
||||
uni.showToast({ title: e?.message || "加载失败", icon: "none" })
|
||||
} finally {
|
||||
state.loadingMore = false
|
||||
}
|
||||
}
|
||||
|
||||
const onReachBottom = () => {
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
const switchTab = async (key: CanteenDeliveryTab) => {
|
||||
if (state.tab === key) return
|
||||
state.tab = key
|
||||
await loadList()
|
||||
state.list = []
|
||||
state.hasMore = true
|
||||
state.current = 0
|
||||
state.pages = 0
|
||||
state.total = 0
|
||||
await loadFirstPage()
|
||||
}
|
||||
|
||||
const goDetail = (item: any) => {
|
||||
@@ -185,12 +295,15 @@ const calcTotalPrice = (item: any): number => {
|
||||
}
|
||||
|
||||
onShow(async () => {
|
||||
await loadList()
|
||||
await loadFirstPage()
|
||||
})
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
await loadList()
|
||||
uni.stopPullDownRefresh()
|
||||
try {
|
||||
await loadFirstPage()
|
||||
} finally {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
+252
-1
@@ -49,6 +49,69 @@
|
||||
|
||||
<view class="h-20rpx" />
|
||||
|
||||
<!-- 送达时间:立即送 / 预约时段(对接 HardAppTakeOutApptDto.appt) -->
|
||||
<view class="mx-30rpx rounded-20rpx shadow-main p-30rpx bg-white">
|
||||
<view class="sub-text-30 mb-16rpx">送达时间</view>
|
||||
<view class="flex rounded-16rpx bg-#f5f6f8 p-6rpx mb-24rpx">
|
||||
<view
|
||||
class="flex-1 py-16rpx text-center second-text-26 rounded-12rpx"
|
||||
:class="state.deliveryExpectType === 'IMMEDIATE' ? 'bg-white shadow-main sub-text-28' : 'color-#666'"
|
||||
@tap="selectDeliveryMode('IMMEDIATE')">
|
||||
立即送出
|
||||
</view>
|
||||
<view
|
||||
class="flex-1 py-16rpx text-center second-text-26 rounded-12rpx"
|
||||
:class="state.deliveryExpectType === 'SCHEDULED' ? 'bg-white shadow-main sub-text-28' : 'color-#666'"
|
||||
@tap="selectDeliveryMode('SCHEDULED')">
|
||||
预约时段
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="state.deliveryExpectType === 'SCHEDULED'">
|
||||
<view class="second-text-26 color-#666 mb-12rpx">选择日期</view>
|
||||
<scroll-view scroll-x class="whitespace-nowrap mb-20rpx" :show-scrollbar="false">
|
||||
<view class="inline-flex gap-12rpx pb-8rpx">
|
||||
<view
|
||||
v-for="d in dateOptions"
|
||||
:key="d.value"
|
||||
class="inline-block px-22rpx py-14rpx rounded-24rpx second-text-26 border border-solid sched-date-chip"
|
||||
:class="
|
||||
d.value === state.schedDate
|
||||
? 'border-blue bg-blue sched-date-chip--active'
|
||||
: 'border-gray-200 color-#333'
|
||||
"
|
||||
@tap="onPickSchedDate(d.value)">
|
||||
{{ d.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="second-text-26 color-#666 mb-12rpx">选择时段</view>
|
||||
<view v-if="slotsLoading" class="second-text-26 color-#999 py-20rpx">可预约时段加载中…</view>
|
||||
<template v-else>
|
||||
<view v-if="slotList.length === 0" class="second-text-26 color-#999 py-20rpx">
|
||||
{{ scheduledSlotsEmptyText }}
|
||||
</view>
|
||||
<view v-else class="flex flex-wrap gap-12rpx">
|
||||
<view
|
||||
v-for="s in slotList"
|
||||
:key="s.start + s.end"
|
||||
class="px-18rpx py-14rpx rounded-16rpx second-text-24 border border-solid min-w-160rpx text-center sched-slot-chip"
|
||||
:class="
|
||||
s.start === state.schedStart && s.end === state.schedEnd
|
||||
? 'border-blue bg-blue sched-slot-chip--active'
|
||||
: 'border-gray-200 color-#333'
|
||||
"
|
||||
@tap="onPickSlot(s)">
|
||||
{{ s.label }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="h-20rpx" />
|
||||
|
||||
<!-- 订单详情(购物车明细) -->
|
||||
<view class="mx-30rpx rounded-20rpx shadow-main p-30rpx bg-white">
|
||||
<view class="sub-text-30 mb-20rpx">订单详情</view>
|
||||
@@ -92,13 +155,70 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue"
|
||||
import { computed, reactive, ref } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
|
||||
import { hooks, router } from "@@/index"
|
||||
import { orderApi, payApi, userAddressApi } from "@@/api"
|
||||
import { filePath } from "@@/utils/file"
|
||||
|
||||
/** 与文档 §6.3 一致:GET /hard-order/app/take-out/delivery-time-slots,与下单校验同源 */
|
||||
/** 连续可选配送日:从今天起共 3 天(与接口 days 一致) */
|
||||
const TAKE_OUT_SLOT_DAYS = 3
|
||||
|
||||
const pad2 = (n: number) => (n < 10 ? `0${n}` : `${String(n)}`)
|
||||
|
||||
const formatYmd = (d: Date) =>
|
||||
`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
|
||||
|
||||
const startOfDay = (d: Date) => {
|
||||
const x = new Date(d)
|
||||
x.setHours(0, 0, 0, 0)
|
||||
return x
|
||||
}
|
||||
|
||||
const dateChipLabel = (ymd: string) => {
|
||||
const [y, mo, day] = ymd.split("-").map(Number)
|
||||
const d = new Date(y, mo - 1, day)
|
||||
const today = startOfDay(new Date())
|
||||
const diff = Math.round((startOfDay(d).getTime() - today.getTime()) / 86400000)
|
||||
if (diff === 0) return "今天"
|
||||
if (diff === 1) return "明天"
|
||||
const weeks = ["日", "一", "二", "三", "四", "五", "六"]
|
||||
return `${mo}月${day}日 周${weeks[d.getDay()]}`
|
||||
}
|
||||
|
||||
/** 下单 appt 使用 HH:mm:ss */
|
||||
const normalizeTime = (t: string | undefined) => {
|
||||
if (t == null || t === "") return ""
|
||||
const s = String(t).trim()
|
||||
if (/^\d{2}:\d{2}:\d{2}$/.test(s)) return s
|
||||
if (/^\d{2}:\d{2}$/.test(s)) return `${s}:00`
|
||||
return s
|
||||
}
|
||||
|
||||
const slotLabel = (start: string, end: string) => {
|
||||
const hm = (t: string) => (t.length >= 5 ? t.slice(0, 5) : t)
|
||||
return `${hm(start)}–${hm(end)}`
|
||||
}
|
||||
|
||||
/** 解析 HardTakeOutDeliveryTimeOptionVO:优先 daySlots;days=1 时可能仅有顶层 slots */
|
||||
const parseDeliveryTimePayload = (data: any): { expectDate: string; slots: any[] }[] => {
|
||||
if (!data) return []
|
||||
if (Array.isArray(data.daySlots) && data.daySlots.length) {
|
||||
return data.daySlots.map((d: any) => ({
|
||||
expectDate: String(d.expectDate ?? "").slice(0, 10),
|
||||
slots: Array.isArray(d.slots) ? d.slots : [],
|
||||
}))
|
||||
}
|
||||
if (Array.isArray(data.slots) && data.slots.length) {
|
||||
const ed =
|
||||
String(data.expectDate ?? "").slice(0, 10) || formatYmd(new Date())
|
||||
return [{ expectDate: ed, slots: data.slots }]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const state = reactive<any>({
|
||||
merchantId: "",
|
||||
carParentId: "",
|
||||
@@ -110,8 +230,122 @@ const state = reactive<any>({
|
||||
cartList: [],
|
||||
totPrice: 0,
|
||||
totCount: 0,
|
||||
|
||||
/** IMMEDIATE | SCHEDULED → 提交 appt.expectDeliveryType */
|
||||
deliveryExpectType: "IMMEDIATE" as "IMMEDIATE" | "SCHEDULED",
|
||||
schedDate: "",
|
||||
schedStart: "",
|
||||
schedEnd: "",
|
||||
|
||||
/** GET delivery-time-slots → parseDeliveryTimePayload */
|
||||
deliveryDaySlots: [] as { expectDate: string; slots: any[] }[],
|
||||
})
|
||||
|
||||
const slotsLoading = ref(false)
|
||||
|
||||
const scheduledSlotsEmptyText = computed(() => {
|
||||
if (!state.merchantId) return "缺少商户信息,请从食堂返回重新进入"
|
||||
if (!state.deliveryDaySlots?.length) return "暂无可预约时段(请确认门店已配置营业时间)"
|
||||
if (!state.schedDate) return "请选择日期"
|
||||
return "当天暂无可选时段,请换一天试试"
|
||||
})
|
||||
|
||||
const dateOptions = computed(() =>
|
||||
(state.deliveryDaySlots || []).map((d: { expectDate: string }) => ({
|
||||
value: d.expectDate,
|
||||
label: dateChipLabel(d.expectDate),
|
||||
})),
|
||||
)
|
||||
|
||||
const slotList = computed(() => {
|
||||
const day = (state.deliveryDaySlots || []).find((d: any) => d.expectDate === state.schedDate)
|
||||
if (!day?.slots?.length) return []
|
||||
return day.slots.map((s: any) => {
|
||||
const start = normalizeTime(s.expectDeliveryStart ?? s.start)
|
||||
const end = normalizeTime(s.expectDeliveryEnd ?? s.end)
|
||||
return { start, end, label: slotLabel(start, end) }
|
||||
})
|
||||
})
|
||||
|
||||
const ensureScheduledSelection = () => {
|
||||
const days = state.deliveryDaySlots || []
|
||||
const withSlots = days.find((d: any) => Array.isArray(d.slots) && d.slots.length > 0)
|
||||
if (withSlots && withSlots.slots[0]) {
|
||||
state.schedDate = withSlots.expectDate
|
||||
const s0 = withSlots.slots[0]
|
||||
state.schedStart = normalizeTime(s0.expectDeliveryStart ?? s0.start)
|
||||
state.schedEnd = normalizeTime(s0.expectDeliveryEnd ?? s0.end)
|
||||
return
|
||||
}
|
||||
state.schedDate = ""
|
||||
state.schedStart = ""
|
||||
state.schedEnd = ""
|
||||
}
|
||||
|
||||
const loadDeliveryTimeSlots = async () => {
|
||||
if (!state.merchantId) {
|
||||
state.deliveryDaySlots = []
|
||||
return
|
||||
}
|
||||
slotsLoading.value = true
|
||||
try {
|
||||
const res: any = await orderApi.takeOutDeliveryTimeSlots({
|
||||
merchantId: state.merchantId,
|
||||
days: TAKE_OUT_SLOT_DAYS,
|
||||
})
|
||||
if (res?.code === 0 && res?.data != null) {
|
||||
state.deliveryDaySlots = parseDeliveryTimePayload(res.data)
|
||||
} else {
|
||||
state.deliveryDaySlots = []
|
||||
const msg = res?.msg || "加载可预约时段失败"
|
||||
uni.showToast({ title: msg, icon: "none" })
|
||||
}
|
||||
} catch (e: any) {
|
||||
state.deliveryDaySlots = []
|
||||
uni.showToast({ title: e?.message || "加载可预约时段失败", icon: "none" })
|
||||
} finally {
|
||||
slotsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectDeliveryMode = async (mode: "IMMEDIATE" | "SCHEDULED") => {
|
||||
state.deliveryExpectType = mode
|
||||
if (mode === "SCHEDULED") {
|
||||
await loadDeliveryTimeSlots()
|
||||
ensureScheduledSelection()
|
||||
}
|
||||
}
|
||||
|
||||
const onPickSchedDate = (ymd: string) => {
|
||||
state.schedDate = ymd
|
||||
const day = (state.deliveryDaySlots || []).find((d: any) => d.expectDate === ymd)
|
||||
if (day?.slots?.length) {
|
||||
const s0 = day.slots[0]
|
||||
state.schedStart = normalizeTime(s0.expectDeliveryStart ?? s0.start)
|
||||
state.schedEnd = normalizeTime(s0.expectDeliveryEnd ?? s0.end)
|
||||
} else {
|
||||
state.schedStart = ""
|
||||
state.schedEnd = ""
|
||||
}
|
||||
}
|
||||
|
||||
const onPickSlot = (s: { start: string; end: string }) => {
|
||||
state.schedStart = s.start
|
||||
state.schedEnd = s.end
|
||||
}
|
||||
|
||||
const buildTakeOutAppt = () => {
|
||||
if (state.deliveryExpectType === "IMMEDIATE") {
|
||||
return { expectDeliveryType: "IMMEDIATE" }
|
||||
}
|
||||
return {
|
||||
expectDeliveryType: "SCHEDULED",
|
||||
expectDeliveryDate: state.schedDate,
|
||||
expectDeliveryStart: state.schedStart,
|
||||
expectDeliveryEnd: state.schedEnd,
|
||||
}
|
||||
}
|
||||
|
||||
const loadAddressList = async () => {
|
||||
const res: any = await userAddressApi.list()
|
||||
if (res?.code === 0) {
|
||||
@@ -160,6 +394,11 @@ const doSubmitOrder = async () => {
|
||||
if (!state.carParentId) {
|
||||
return uni.showToast({ title: "订单信息缺失(carParentId)", icon: "none" })
|
||||
}
|
||||
if (state.deliveryExpectType === "SCHEDULED") {
|
||||
if (!state.schedDate || !state.schedStart || !state.schedEnd) {
|
||||
return uni.showToast({ title: "请选择预约送达时段", icon: "none" })
|
||||
}
|
||||
}
|
||||
submitting.value = true
|
||||
uni.showLoading({ title: "提交中...", mask: true })
|
||||
try {
|
||||
@@ -177,6 +416,9 @@ const doSubmitOrder = async () => {
|
||||
const _pa: any = {
|
||||
// Long id 可能超过 JS 安全整数范围,禁止 Number(),否则末尾会变(精度丢失)
|
||||
carParentId: String(state.carParentId),
|
||||
deliveryPrice: 0,
|
||||
/** 硬件侧 @NotNull:HardAppTakeOutApptDto */
|
||||
appt: buildTakeOutAppt(),
|
||||
address: {
|
||||
id: String(selected.id),
|
||||
addressId: String(selected.id),
|
||||
@@ -189,6 +431,9 @@ const doSubmitOrder = async () => {
|
||||
fullAddress: fullAddr,
|
||||
},
|
||||
}
|
||||
if (String(state.apptId || "").trim()) {
|
||||
_pa.apptId = String(state.apptId).trim()
|
||||
}
|
||||
const res: any = await payApi.createTakeOutOrder(_pa)
|
||||
if (res?.code === 0 || res === 0) {
|
||||
uni.showToast({ title: "下单成功", icon: "none" })
|
||||
@@ -215,5 +460,11 @@ const doSubmitOrder = async () => {
|
||||
.addr-set-btn {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 选中日期/时段:压过 second-text-* 的字色,保证蓝底白字清晰 */
|
||||
.sched-date-chip--active,
|
||||
.sched-slot-chip--active {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -104,6 +104,7 @@ const doRefund = (v: any) => {
|
||||
<tm-tag v-if="v.orderStatus == 10" color="error" size="s">售后</tm-tag>
|
||||
<tm-tag v-if="v.orderStatus == 20" size="s">已核销</tm-tag>
|
||||
<tm-tag v-if="v.orderStatus == 30" color="info" size="s">已取消(智能柜)</tm-tag>
|
||||
<tm-tag v-if="v.orderStatus == 110" color="success" size="s">配送中</tm-tag>
|
||||
</view>
|
||||
<view class="flex-row items-center justify-between mt-5rpx second-text-24">
|
||||
<view>
|
||||
|
||||
@@ -9,7 +9,11 @@ export type CanteenDeliveryTab = "pending" | "all" | "completed" | "cancelled"
|
||||
* @see canteen-delivery-rider 对接文档
|
||||
*/
|
||||
export default {
|
||||
list: (params: { tab?: CanteenDeliveryTab }) =>
|
||||
/**
|
||||
* 分页与 MyBatis-Plus 一致:current、size;未传由服务端默认。
|
||||
* 响应 data 为分页对象:records、total、current、size、pages
|
||||
*/
|
||||
list: (params: { tab?: CanteenDeliveryTab; current?: number; size?: number }) =>
|
||||
request({
|
||||
url: `${base}/list`,
|
||||
method: "GET",
|
||||
|
||||
@@ -53,6 +53,24 @@ export default {
|
||||
url: "/hard/hard-appt/app/list",
|
||||
method: "GET",
|
||||
}),
|
||||
/**
|
||||
* 外卖预约配送可选时段(与下单校验同源,见 take-out-scheduled-delivery 业务说明 §6.3)
|
||||
* GET /app/hard-order/app/take-out/delivery-time-slots
|
||||
*/
|
||||
takeOutDeliveryTimeSlots: (params: {
|
||||
merchantId: string | number
|
||||
/** yyyy-MM-dd,起始配送日;不传默认今天 */
|
||||
expectDate?: string
|
||||
/** 连续天数,默认 1,最大 14 */
|
||||
days?: number
|
||||
slotMinutes?: number
|
||||
leadMinutes?: number
|
||||
}) =>
|
||||
request({
|
||||
url: "/hard/hard-order/app/take-out/delivery-time-slots",
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
/**
|
||||
* 用户确认设备二维码扫一扫订单
|
||||
* @param orderId 订单id
|
||||
|
||||
@@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
mainIsScroll: false,
|
||||
bg: "",
|
||||
})
|
||||
const emit = defineEmits(["scroll", "navRightClick"])
|
||||
const emit = defineEmits(["scroll", "scrolltolower", "navRightClick"])
|
||||
|
||||
const riderPushStore = useRiderPushBannerStore()
|
||||
const { visible: riderPushVisible, title: riderPushTitle, body: riderPushBody, merchantName: riderPushMerchant, fullAddress: riderPushAddr, sourceOrderId: riderPushOrderId, remainSec: riderPushRemain, topPx: riderPushTopPx } =
|
||||
@@ -133,7 +133,11 @@ function initBackStyle() {
|
||||
<view class="layout_main_top">
|
||||
<slot name="main-top"></slot>
|
||||
</view>
|
||||
<scroll-view :scroll-y="mainIsScroll" @scroll="scroll">
|
||||
<scroll-view
|
||||
:scroll-y="mainIsScroll"
|
||||
@scroll="scroll"
|
||||
@scrolltolower="emit('scrolltolower')"
|
||||
:lower-threshold="100">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
<view class="layout_main_bottom">
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 骑手端外送单:配送方式 / 预计时间展示(与 CanteenDeliveryOrderAppVO 字段一致)
|
||||
*/
|
||||
|
||||
export type CanteenDeliveryExpectFields = {
|
||||
expectDeliveryType?: string | null
|
||||
expectDeliveryDate?: string | null
|
||||
expectDeliveryStart?: string | null
|
||||
expectDeliveryEnd?: string | null
|
||||
expectArriveTime?: string | null
|
||||
createTime?: string | null
|
||||
}
|
||||
|
||||
const pad2 = (n: number) => (n < 10 ? `0${n}` : `${n}`)
|
||||
|
||||
/** 展示用 yyyy-MM-dd HH:mm */
|
||||
export const formatDateTime = (d: Date): string =>
|
||||
`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`
|
||||
|
||||
/** 解析后端常见 createTime:2026-04-09 17:26:31 */
|
||||
export const parseDateTimeLoose = (s?: string | null): Date | null => {
|
||||
if (s == null || String(s).trim() === "") return null
|
||||
const t = String(s).trim()
|
||||
const d = new Date(t.includes("T") ? t : t.replace(/-/g, "/"))
|
||||
return Number.isNaN(d.getTime()) ? null : d
|
||||
}
|
||||
|
||||
const hm = (t?: string | null) => {
|
||||
if (t == null || t === "") return ""
|
||||
const u = String(t).trim()
|
||||
return u.length >= 5 ? u.slice(0, 5) : u
|
||||
}
|
||||
|
||||
/** IMMEDIATE / SCHEDULED — 订单类型:立即送 / 预约送 */
|
||||
export const expectDeliveryTypeLabel = (t?: string | null) =>
|
||||
String(t || "").toUpperCase() === "SCHEDULED" ? "预约送" : "立即送"
|
||||
|
||||
/**
|
||||
* 预约单:预计送达(日期 + 时段)
|
||||
*/
|
||||
export const formatScheduledExpectLine = (row: CanteenDeliveryExpectFields): string => {
|
||||
const d = row.expectDeliveryDate ? String(row.expectDeliveryDate).slice(0, 10) : ""
|
||||
const s = hm(row.expectDeliveryStart)
|
||||
const e = hm(row.expectDeliveryEnd)
|
||||
if (d && s && e) return `预计送达 ${d} ${s}–${e}`
|
||||
if (d && s) return `预计送达 ${d} ${s} 起`
|
||||
if (d) return `预计送达 ${d}`
|
||||
return "预约送(时间未返回)"
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即送:优先展示后端 expectArriveTime;否则 下单时间 + 1 小时
|
||||
*/
|
||||
export const formatImmediateExpectLine = (row: CanteenDeliveryExpectFields): string => {
|
||||
const arrive = row.expectArriveTime
|
||||
if (arrive != null && String(arrive).trim() !== "") {
|
||||
const d = parseDateTimeLoose(String(arrive).replace("T", " "))
|
||||
if (d) return `预计送达约 ${formatDateTime(d)}`
|
||||
return `预计送达约 ${String(arrive).trim()}`
|
||||
}
|
||||
const base = parseDateTimeLoose(row.createTime)
|
||||
if (!base) return "立即送(无下单时间,无法预估)"
|
||||
const plus1h = new Date(base.getTime() + 60 * 60 * 1000)
|
||||
return `预计送达约 ${formatDateTime(plus1h)}(下单后约 1 小时)`
|
||||
}
|
||||
|
||||
/** 列表 / 详情:配送时间说明(预约=时段;立即=预计约或下单+1h) */
|
||||
export const deliveryExpectDetailLine = (row: CanteenDeliveryExpectFields): string => {
|
||||
if (String(row.expectDeliveryType || "").toUpperCase() === "SCHEDULED") {
|
||||
return formatScheduledExpectLine(row)
|
||||
}
|
||||
return formatImmediateExpectLine(row)
|
||||
}
|
||||
|
||||
/** 下单时间展示(原样或略格式化) */
|
||||
export const formatOrderTimeLabel = (createTime?: string | null): string =>
|
||||
createTime != null && String(createTime).trim() !== "" ? String(createTime).trim() : "—"
|
||||
Reference in new issue
Block a user