|
@@ -271,30 +271,32 @@
|
|
|
>
|
|
>
|
|
|
<view class="time-picker">
|
|
<view class="time-picker">
|
|
|
<view class="fs-22 color-999 tp-tip">仅可选择从现在起24小时内的时间</view>
|
|
<view class="fs-22 color-999 tp-tip">仅可选择从现在起24小时内的时间</view>
|
|
|
- <!-- 时段网格(30分钟粒度) -->
|
|
|
|
|
- <scroll-view scroll-y class="tp-grid_scroll">
|
|
|
|
|
- <block v-for="(group, gi) in timeGroups" :key="gi">
|
|
|
|
|
- <view class="tp-group_title fs-26 color-999">{{ group.title }}</view>
|
|
|
|
|
- <view class="tp-grid">
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="opt in group.options"
|
|
|
|
|
- :key="opt.ts"
|
|
|
|
|
- class="tp-cell pressable"
|
|
|
|
|
- hover-class="pressed"
|
|
|
|
|
- hover-stay-time="80"
|
|
|
|
|
- :class="{
|
|
|
|
|
- 'tp-cell_active': selectedTs === opt.ts,
|
|
|
|
|
- 'tp-cell_disabled': opt.disabled,
|
|
|
|
|
- }"
|
|
|
|
|
- @click="pickOption(opt)"
|
|
|
|
|
- >
|
|
|
|
|
- <view class="fs-28">{{ opt.timeLabel }}</view>
|
|
|
|
|
- <view class="fs-20 tp-cell_price" v-if="opt.price">{{ opt.price }}元/度</view>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <!-- 滚动选择:日期 + 时间 -->
|
|
|
|
|
+ <picker-view
|
|
|
|
|
+ class="tp-picker"
|
|
|
|
|
+ indicator-style="height: 88rpx;"
|
|
|
|
|
+ mask-style="background-size: 100% 176rpx; background-position: top, bottom; background-image: linear-gradient(180deg, rgba(244,247,246,0.9), rgba(244,247,246,0.4)), linear-gradient(0deg, rgba(244,247,246,0.9), rgba(244,247,246,0.4));"
|
|
|
|
|
+ :value="pickerIndex"
|
|
|
|
|
+ @change="onPickerChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <picker-view-column>
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(g, i) in timeGroups"
|
|
|
|
|
+ :key="i"
|
|
|
|
|
+ class="tp-picker_item fs-28"
|
|
|
|
|
+ >{{ g.title }}</view>
|
|
|
|
|
+ </picker-view-column>
|
|
|
|
|
+ <picker-view-column>
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(t, i) in currentTimeOptions"
|
|
|
|
|
+ :key="i"
|
|
|
|
|
+ class="tp-picker_item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="fs-32">{{ t.timeLabel }}</text>
|
|
|
|
|
+ <text class="fs-20 color-999 ml-8" v-if="t.price">{{ t.price }}元/度</text>
|
|
|
</view>
|
|
</view>
|
|
|
- </block>
|
|
|
|
|
- <view style="height: 24rpx"></view>
|
|
|
|
|
- </scroll-view>
|
|
|
|
|
|
|
+ </picker-view-column>
|
|
|
|
|
+ </picker-view>
|
|
|
<!-- 底部确认 -->
|
|
<!-- 底部确认 -->
|
|
|
<view class="tp-footer">
|
|
<view class="tp-footer">
|
|
|
<view class="flex-column flex-grow" v-if="selected">
|
|
<view class="flex-column flex-grow" v-if="selected">
|
|
@@ -441,9 +443,27 @@ const WINDOW = 24 * 60 * 60 * 1000; // 只能选择从此时开始24小时内
|
|
|
|
|
|
|
|
const timesInfo = ref<any>({
|
|
const timesInfo = ref<any>({
|
|
|
groups: [] as any[],
|
|
groups: [] as any[],
|
|
|
- flat: {} as Record<number, any>,
|
|
|
|
|
});
|
|
});
|
|
|
-const selectedTs = ref(0);
|
|
|
|
|
|
|
+
|
|
|
|
|
+const timeGroups = computed(() => timesInfo.value.groups);
|
|
|
|
|
+/** 滚轮选中位置:[日期列索引, 时间列索引] */
|
|
|
|
|
+const pickerIndex = ref([0, 0]);
|
|
|
|
|
+const currentTimeOptions = computed(
|
|
|
|
|
+ () => timesInfo.value.groups[pickerIndex.value[0]]?.options || []
|
|
|
|
|
+);
|
|
|
|
|
+const selected = computed(
|
|
|
|
|
+ () => currentTimeOptions.value[pickerIndex.value[1]]
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const onPickerChange = (e: any) => {
|
|
|
|
|
+ let [d, t] = e.detail.value;
|
|
|
|
|
+ // 切换日期时,钳制时间索引到该日期的可用范围
|
|
|
|
|
+ if (d !== pickerIndex.value[0]) {
|
|
|
|
|
+ const len = timesInfo.value.groups[d]?.options.length || 1;
|
|
|
|
|
+ t = Math.min(t, len - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ pickerIndex.value = [d, t];
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
const pad2 = (n: number) => (n < 10 ? `0${n}` : `${n}`);
|
|
const pad2 = (n: number) => (n < 10 ? `0${n}` : `${n}`);
|
|
|
|
|
|
|
@@ -454,7 +474,6 @@ const buildOption = (ts: number, now: number): any => {
|
|
|
ts,
|
|
ts,
|
|
|
timeLabel: hourStr,
|
|
timeLabel: hourStr,
|
|
|
price: getHourPrice(`${pad2(d.getHours())}`),
|
|
price: getHourPrice(`${pad2(d.getHours())}`),
|
|
|
- disabled: ts <= now,
|
|
|
|
|
get fullLabel() {
|
|
get fullLabel() {
|
|
|
const dayDiff = Math.floor(
|
|
const dayDiff = Math.floor(
|
|
|
(new Date(ts).setHours(0, 0, 0, 0) - new Date(now).setHours(0, 0, 0, 0)) / DAY
|
|
(new Date(ts).setHours(0, 0, 0, 0) - new Date(now).setHours(0, 0, 0, 0)) / DAY
|
|
@@ -468,11 +487,10 @@ const buildOption = (ts: number, now: number): any => {
|
|
|
/** 生成24小时内的可选项:今天/明天两组,30分钟粒度 */
|
|
/** 生成24小时内的可选项:今天/明天两组,30分钟粒度 */
|
|
|
const buildTimeOptions = () => {
|
|
const buildTimeOptions = () => {
|
|
|
const now = Date.now();
|
|
const now = Date.now();
|
|
|
- // 网格首个时间:10分钟后取整到下一个半点
|
|
|
|
|
- let first = Math.ceil((now + 10 * 60 * 1000) / STEP) * STEP;
|
|
|
|
|
|
|
+ // 首个可选时间:10分钟后取整到下一个半点
|
|
|
|
|
+ const first = Math.ceil((now + 10 * 60 * 1000) / STEP) * STEP;
|
|
|
const last = now + WINDOW; // 窗口上限
|
|
const last = now + WINDOW; // 窗口上限
|
|
|
const groups: any[] = [];
|
|
const groups: any[] = [];
|
|
|
- const flat: Record<number, any> = {};
|
|
|
|
|
|
|
|
|
|
// 按天分组
|
|
// 按天分组
|
|
|
const firstDay = new Date(first).setHours(0, 0, 0, 0);
|
|
const firstDay = new Date(first).setHours(0, 0, 0, 0);
|
|
@@ -487,9 +505,7 @@ const buildTimeOptions = () => {
|
|
|
if (d.getMinutes() !== 0 && d.getMinutes() !== 30) {
|
|
if (d.getMinutes() !== 0 && d.getMinutes() !== 30) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- const opt = buildOption(ts, now);
|
|
|
|
|
- options.push(opt);
|
|
|
|
|
- flat[opt.ts] = opt;
|
|
|
|
|
|
|
+ options.push(buildOption(ts, now));
|
|
|
}
|
|
}
|
|
|
if (!options.length) {
|
|
if (!options.length) {
|
|
|
continue;
|
|
continue;
|
|
@@ -500,21 +516,7 @@ const buildTimeOptions = () => {
|
|
|
options,
|
|
options,
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
- timesInfo.value = { groups, flat };
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const selected = computed(() => timesInfo.value.flat[selectedTs.value]);
|
|
|
|
|
-const timeGroups = computed(() => timesInfo.value.groups);
|
|
|
|
|
-
|
|
|
|
|
-const pickOption = (opt: any) => {
|
|
|
|
|
- if (opt.disabled) {
|
|
|
|
|
- uni.showToast({
|
|
|
|
|
- title: "无法选择过去的时间",
|
|
|
|
|
- icon: "none",
|
|
|
|
|
- });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- selectedTs.value = opt.ts;
|
|
|
|
|
|
|
+ timesInfo.value = { groups };
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const selectTime = () => {
|
|
const selectTime = () => {
|
|
@@ -533,16 +535,23 @@ const selectTime = () => {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
buildTimeOptions();
|
|
buildTimeOptions();
|
|
|
- // 已预约修改时间时回显当前预约时间;新预约由用户自主选择,不预选
|
|
|
|
|
|
|
+ // 已预约修改时间时滚轮定位到当前预约时间;新预约定位到最早可选时间
|
|
|
|
|
+ let dayIdx = 0;
|
|
|
|
|
+ let timeIdx = 0;
|
|
|
if (appointmentData.value) {
|
|
if (appointmentData.value) {
|
|
|
const start = new Date(
|
|
const start = new Date(
|
|
|
appointmentData.value.startTime.replace(/-/g, "/")
|
|
appointmentData.value.startTime.replace(/-/g, "/")
|
|
|
).getTime();
|
|
).getTime();
|
|
|
const aligned = Math.ceil(start / STEP) * STEP;
|
|
const aligned = Math.ceil(start / STEP) * STEP;
|
|
|
- selectedTs.value = timesInfo.value.flat[aligned] ? aligned : 0;
|
|
|
|
|
- } else {
|
|
|
|
|
- selectedTs.value = 0;
|
|
|
|
|
|
|
+ timesInfo.value.groups.forEach((g: any, gi: number) => {
|
|
|
|
|
+ const idx = g.options.findIndex((o: any) => o.ts === aligned);
|
|
|
|
|
+ if (idx >= 0) {
|
|
|
|
|
+ dayIdx = gi;
|
|
|
|
|
+ timeIdx = idx;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
+ pickerIndex.value = [dayIdx, timeIdx];
|
|
|
chargeTimeDialog.value = true;
|
|
chargeTimeDialog.value = true;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -995,58 +1004,16 @@ onShow(() => {
|
|
|
padding: 20rpx 30rpx 0;
|
|
padding: 20rpx 30rpx 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tp-grid_scroll {
|
|
|
|
|
- height: 560rpx;
|
|
|
|
|
- padding: 8rpx 30rpx 0;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
|
|
+.tp-picker {
|
|
|
|
|
+ height: 440rpx;
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tp-group_title {
|
|
|
|
|
- padding: 24rpx 0 16rpx;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tp-grid {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
- gap: 12rpx;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tp-cell {
|
|
|
|
|
|
|
+.tp-picker_item {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- height: 96rpx;
|
|
|
|
|
- background: var(--color-sec);
|
|
|
|
|
- border-radius: 16rpx;
|
|
|
|
|
- color: #1c2b29;
|
|
|
|
|
- transition: background-color 0.15s cubic-bezier(0.22, 1, 0.36, 1),
|
|
|
|
|
- color 0.15s cubic-bezier(0.22, 1, 0.36, 1);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tp-cell_price {
|
|
|
|
|
- margin-top: 2rpx;
|
|
|
|
|
- color: #8c9997;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tp-cell_active {
|
|
|
|
|
- background: var(--color-primary-tint-strong);
|
|
|
|
|
- color: var(--color-primary);
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
-
|
|
|
|
|
- .tp-cell_price {
|
|
|
|
|
- color: var(--color-primary);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tp-cell_disabled {
|
|
|
|
|
- color: #c4cfcd;
|
|
|
|
|
- background: rgba(28, 43, 41, 0.03);
|
|
|
|
|
-
|
|
|
|
|
- .tp-cell_price {
|
|
|
|
|
- color: #d5dedc;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ height: 88rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.tp-footer {
|
|
.tp-footer {
|