refactor: clean up components, fix date label font size

- Delete unused local image asset and unused GoodInfo SCSS stylesheet
- Replace legacy border utility classes with modern Tailwind border-t syntax
- Fix incorrect 100px font size on date selector input labels, update to 10px
- Refactor LocationCard to use Lucide Phone/Navigation icons instead of VanIcons, improve button styling
- Overhaul ImageSwiper component: add proper refs, improve scroll logic, replace VanIcons with Lucide icons, clean up redundant code
- Update goods detail and quick booking pages to properly use Pinia selected date store, add date data normalization
- Fix scrollbar styles and linear gradient syntax in goods detail page
- Update development environment auth token for local development
This commit is contained in:
duanshuwen
2026-05-31 21:05:22 +08:00
parent 83279ca9bd
commit 6c353163df
10 changed files with 137 additions and 88 deletions

View File

@@ -14,6 +14,6 @@ VITE_SOCKET_BASE_URL = "/ingress/agent/ws/chat"
VITE_CLIENT_ID = "6"
# Token
VITE_TOKEN = "eyJraWQiOiJiMTVhZTk0Mi03MjI5LTMyOWUtODA1Yi0wNjFlNmRjYTE1MDQiLCJhbGciOiJSUzI1NiJ9.eyJ0ZW5hbnRfaWQiOjEsInN1YiI6Im94T3NGN2lqTkxvbEFIdkhDZDYtek1acE5kNWsiLCJjbGllbnRJZCI6ImN1c3RvbSIsImlzcyI6Imh0dHBzOi8vcGlnNGNsb3VkLmNvbSIsImNsaWVudF9pZCI6ImN1c3RvbSIsImF1dGhvcml0aWVzIjpbXSwiYXVkIjoiY3VzdG9tIiwibGljZW5zZSI6Imh0dHBzOi8vcGlnNGNsb3VkLmNvbSIsIndlY2hhdF9vcGVuaWQiOiJveE9zRjdpak5Mb2xBSHZIQ2Q2LXpNWnBOZDVrIiwibmJmIjoxNzgwMjE5NjU0LCJ1c2VyX2lkIjoiMjAwNTEwMjg0NDQ2OTM4NzI2NSIsInNjb3BlIjpbInNlcnZlciJdLCJleHAiOjE3ODAyMjk2NTQsImlhdCI6MTc4MDIxOTY1NCwianRpIjoiZTlmZGJiNTAtYjFjZC00YThhLWJkOTctZTU0NTAxMDNhNTkyIiwidXNlcm5hbWUiOiJveE9zRjdpak5Mb2xBSHZIQ2Q2LXpNWnBOZDVrIn0.o_nvQVBPt1fYg3DIs7I1mapX124VqAgvHr4jEPoVZuFJjgPfziDvEBJRLw0yw4C1Ii3BxRlKw1CGRjmZ2FHPxk0gYBXGRD31isqezNLiBgVlv7wyI6pkfdk-_QEysu2Lh5hH5RWqndeNNKtQsjeGKOXV1amF7nekN4p9HnSHj6Y2h1iD6Zt2wVoKDlaJZ8yu7LB8uE8XMhKjY_ua2ujDXCRadrD4ihXqPOTaiqb5NERjHyCRKuSkEWUqFtLG7WbdOEH7SmXhlasRQ_ERVb3yKLsQdxSzP88BxKaqB7Xc8hBE1iROrF3iNGQCTK12QN8VuYMDq9CBNW10gGCsdR9vvQ"
VITE_TOKEN = "eyJraWQiOiJiMTVhZTk0Mi03MjI5LTMyOWUtODA1Yi0wNjFlNmRjYTE1MDQiLCJhbGciOiJSUzI1NiJ9.eyJ0ZW5hbnRfaWQiOjEsInN1YiI6Im94T3NGN2lqTkxvbEFIdkhDZDYtek1acE5kNWsiLCJjbGllbnRJZCI6ImN1c3RvbSIsImlzcyI6Imh0dHBzOi8vcGlnNGNsb3VkLmNvbSIsImNsaWVudF9pZCI6ImN1c3RvbSIsImF1dGhvcml0aWVzIjpbXSwiYXVkIjoiY3VzdG9tIiwibGljZW5zZSI6Imh0dHBzOi8vcGlnNGNsb3VkLmNvbSIsIndlY2hhdF9vcGVuaWQiOiJveE9zRjdpak5Mb2xBSHZIQ2Q2LXpNWnBOZDVrIiwibmJmIjoxNzgwMjI5ODMzLCJ1c2VyX2lkIjoiMjAwNTEwMjg0NDQ2OTM4NzI2NSIsInNjb3BlIjpbInNlcnZlciJdLCJleHAiOjE3ODAyMzk4MzMsImlhdCI6MTc4MDIyOTgzMywianRpIjoiM2ZkYzhiZTgtNTJlMy00YWIxLWEwOGQtZDU5NzBhZGM4YmE5IiwidXNlcm5hbWUiOiJveE9zRjdpak5Mb2xBSHZIQ2Q2LXpNWnBOZDVrIn0.cLOXAoMWxQvUdATgWYamdm12PEnPtLpAyddU398NxSWo-vYNKmH2ooTVXL2tDqWw7Gp2QoqUxHg1jyIAYxfAAXe1LQ946yZkOSHN8F16dTg27siif-8siCbo6NZ6FQmW-Ep1I0txrGB7_gx1AwRONse-zS5hq5ez42-3sF-c-nzcmAA84DMWm427eiPYnLDbd9Ook4VENChjsuWDSDPxhKb9nutOnUp8AR4kXKjzoUBHj33wrlW9Bf8W7w9S6weSxX_tApZ7YY26em6AnUpiOE4MZo5fR6nUncT1aZepBqfcvhqRHn7EGJ27q_cPJyZBB4grf9hd2ylpsxag0oMR7Q"

View File

@@ -1,5 +1,5 @@
<template>
<div class=" border-top-8">
<div class=" border-t-[8px] border-t-[#f5f5f5]">
<div v-if="goodsData.commodityPurchaseInstruction" class=" pl-[12px] pr-[12px]">
<ModuleTitle v-if="showTitle" :title="goodsData.commodityPurchaseInstruction.templateTitle" />
<div v-for="(moduleItem, index) in goodsData.commodityPurchaseInstruction

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -1,78 +1,72 @@
<template>
<div class="relative w-full">
<van-swipe class="overflow-hidden" :style="swiperStyle" @change="handleSwiperChange">
<van-swipe ref="swipeRef" class="overflow-hidden" :style="swiperStyle" @change="handleSwiperChange">
<van-swipe-item v-for="(item, index) in thumbnails" :key="index">
<img class="w-full h-full" :src="item.photoUrl">
</van-swipe-item>
</van-swipe>
<!-- 缩略图部分 -->
<div v-if="showThumbnails && thumbnails.length"
class="absolute left-[12px] right-[12px] flex items-end flex-row flex-nowrap" :style="thumbnailBoxStyle">
<div class="flex-auto h-full whitespace-nowrap" scroll-x="true" :scroll-left="scrollLeft"
<div ref="thumbnailScrollRef" class="flex-auto h-full whitespace-nowrap" scroll-x="true" :scroll-left="scrollLeft"
:scroll-with-animation="true" show-scrollbar="false">
<div class="flex items-center gap-[10px]" v-if="thumbnails.length > 1">
<div v-for="(thumb, index) in thumbnails" :key="index"
:class="['shrink-0 text-center', index === active && 'border-2 border-white border-solid']"
:class="['shrink-0 text-center rounded-card border-solid overflow-hidden', index === active ? 'border-2 border-white ' : 'border border-[#171717]']"
:id="`thumbnail-${index}`" @click="handleThumbnailClick(index)">
<img class="w-[36px] h-[36px] rounded-card border border-[#171717] border-solid" :src="thumb.photoUrl">
<img class="w-[36px] h-[36px]" :src="thumb.photoUrl">
</div>
</div>
</div>
<div class="ml-[12px] bg-[rgba(0,0,0,0.5)] rounded-[50px] p-[0_6px_4px_8px] flex-auto shrink-0 whitespace-nowrap"
@click="handlePreviewClick">
<van-icon name="arrow-left" size="10" color="#fff"></van-icon>
<div class="ml-[12px] bg-black/50 rounded-full p-[4px_8px] flex items-center" @click="handlePreviewClick">
<Camera size="14" color="#fff" />
<span class="mx-[4px] text-[10px] text-white align-center">
{{ thumbnails.length }}
</span>
<van-icon name="arrow-right" size="10" color="#fff"></van-icon>
<ChevronRight size="14" color="#fff" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick } from "vue";
import { ref, computed, nextTick, watch } from "vue";
import { useRouter } from 'vue-router'
import { Camera, ChevronRight } from '@lucide/vue'
import { usePictureStore } from "@/store";
const pictureStore = usePictureStore();
const router = useRouter();
// Props定义
const props = defineProps({
// 轮播图圆角大小,支持数字(px)或字符串
borderRadius: {
type: [Number, String],
default: 8,
},
// 轮播图数据
images: {
type: Array,
default: () => [],
},
// 轮播图高度,支持数字(px)或字符串
height: {
type: [Number, String],
default: 200,
},
// 是否显示缩略图
showThumbnails: {
type: Boolean,
default: true,
},
// 缩略图距离底部的距离,支持数字(px)或字符串
thumbnailBottom: {
type: [Number, String],
default: 0,
},
});
const swipeRef = ref(null);
const thumbnailScrollRef = ref(null);
const active = ref(0);
const scrollLeft = ref(0);
// 计算圆角样式
const borderRadiusStyle = computed(() => {
const radius =
typeof props.borderRadius === "number"
@@ -83,7 +77,6 @@ const borderRadiusStyle = computed(() => {
};
});
// 计算轮播图样式(包含高度和圆角)
const swiperStyle = computed(() => {
const radius =
typeof props.borderRadius === "number"
@@ -97,10 +90,8 @@ const swiperStyle = computed(() => {
};
});
// 使用传入的图片数据或默认数据
const thumbnails = computed(() => props.images);
const thumbnails = computed(() => Array.isArray(props.images) ? props.images : []);
// 计算缩略图底部距离样式
const thumbnailBoxStyle = computed(() => {
const bottom =
typeof props.thumbnailBottom === "number"
@@ -111,33 +102,80 @@ const thumbnailBoxStyle = computed(() => {
};
});
const handleThumbnailClick = (index) => {
active.value = index;
scrollToActiveItem(index);
const clampIndex = (index) => {
const maxIndex = Math.max(thumbnails.value.length - 1, 0);
const nextIndex = Number.isFinite(Number(index)) ? Number(index) : 0;
return Math.min(Math.max(nextIndex, 0), maxIndex);
};
const setActiveIndex = (index, options = {}) => {
const nextIndex = clampIndex(index);
active.value = nextIndex;
scrollToActiveItem(nextIndex);
if (options.swipe !== false) {
swipeRef.value?.swipeTo?.(nextIndex, {
immediate: Boolean(options.immediate),
});
}
};
const handleThumbnailClick = (index) => {
setActiveIndex(index);
};
// 滚动到选中项
const scrollToActiveItem = async (index) => {
await nextTick();
// 计算每个缩略图项的宽度(包括间距)
const itemWidth = 58; // 48px宽度 + 10px间距
const containerWidth = 300; // 大概的容器宽度
const itemWidth = 58;
const containerWidth = 300;
const targetScrollLeft = Math.max(
0,
index * itemWidth - containerWidth / 2 + itemWidth / 2,
);
scrollLeft.value = targetScrollLeft;
const scrollEl = thumbnailScrollRef.value;
const activeEl = scrollEl?.querySelector?.(`#thumbnail-${index}`);
if (!scrollEl || !activeEl) return;
const scrollRect = scrollEl.getBoundingClientRect();
const activeRect = activeEl.getBoundingClientRect();
const h5TargetScrollLeft =
scrollEl.scrollLeft +
activeRect.left - scrollRect.left -
(scrollRect.width - activeRect.width) / 2;
scrollEl.scrollTo?.({
left: Math.max(0, h5TargetScrollLeft),
behavior: "smooth",
});
};
// 监听主轮播图变化,同步缩略图滚动
const handleSwiperChange = (e) => {
const currentIndex = e.detail.current;
active.value = currentIndex;
scrollToActiveItem(currentIndex);
const handleSwiperChange = (index) => {
setActiveIndex(index, { swipe: false });
};
watch(
() => thumbnails.value.length,
(length) => {
if (!length) {
active.value = 0;
scrollLeft.value = 0;
return;
}
const nextIndex = clampIndex(active.value);
nextTick(() => {
swipeRef.value?.resize?.();
setActiveIndex(nextIndex, {
immediate: true,
});
});
},
);
const handlePreviewClick = () => {
pictureStore.setPreviewImageData(thumbnails.value);

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex items-end justify-between my-[12px] px-[12px]" @click="showCalendar">
<div class="flex-1 relative">
<div class="absolute top-[-6px] left-[12px] text-[100px] text-ink-600 bg-white px-[4px]">入住日期</div>
<div class="absolute top-[-6px] left-[12px] text-[10px] text-ink-600 bg-white px-[4px]">入住日期</div>
<div class="flex items-center justify-start border border-[#f0f0f0] rounded-[8px] px-[12px] py-[10px] bg-white">
<div class="flex items-baseline gap-[4px]">
<span class="text-[16px] font-medium text-[#333]">{{ checkInDate }}</span>
@@ -15,7 +15,7 @@
</div>
<div class="flex-1 relative">
<div class="absolute top-[-6px] left-[12px] text-[100px] text-ink-600 bg-white px-[4px]">退房日期</div>
<div class="absolute top-[-6px] left-[12px] text-[10px] text-ink-600 bg-white px-[4px]">退房日期</div>
<div class="flex items-center justify-start border border-[#f0f0f0] rounded-[8px] px-[12px] py-[10px] bg-white">
<div class="flex items-baseline gap-[4px]">
<span class="text-[16px] font-medium text-[#333]">{{ checkOutDate }}</span>

View File

@@ -1,5 +1,5 @@
<template>
<div class="border-top-8">
<div class="border-t-[8px] border-t-[#f5f5f5]">
<div class="pt-[12px] pl-[12px] pr-[12px]" v-for="(moduleItem, index) in goodsData.commodityEquipment" :key="index">
<div class="flex flex-col items-start" :class="{
'border-b border-ink-200': index < goodsData.commodityEquipment.length - 1,

View File

@@ -1,32 +0,0 @@
.good-info {
// 标题区域
// 设施信息区域
.facilities-section {
margin-top: 12px;
.facilities-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
.facility-item {
display: flex;
align-items: center;
gap: 4px;
padding: 8px;
background: #fafafa;
border-radius: 6px;
.facility-text {
font-size: 12px;
color: #333;
line-height: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="flex items-center mt-[12px] p-[12px] bg-white border border-t border-[#e0e0e0]">
<div class="flex items-center mt-[12px] p-[12px] bg-white border-t border-[#e0e0e0]">
<div class="flex flex-col flex-1">
<span class="text-[14px] font-medium text-ink-800">位于 {{ orderData.oneLevelAddress }}</span>
<span class="mt-[4px] text-[12px] text-ink-500">{{ orderData.commodityAddress }}</span>
@@ -7,15 +7,16 @@
<div class="flex items-center gap-[10px] ml-[16px]">
<div>
<div class="actions-btn">
<van-icon type="paperplane-filled" size="16" color="#171717" />
<div class="w-[28px] h-[28px] rounded-[10px] bg-[#F5F5F5] flex items-center justify-center">
<Navigation size="16" color="#171717" />
</div>
<span class="text-[12px] text-ink-600">导航</span>
</div>
<div>
<div class="actions-btn" @click.stop="callPhone">
<van-icon type="phone-filled" size="16" color="#171717" />
<div class="w-[28px] h-[28px] rounded-[10px] bg-[#F5F5F5] flex items-center justify-center"
@click.stop="callPhone">
<Phone size="16" color="#171717" />
</div>
<span class="text-[12px] text-ink-600">电话</span>
</div>
@@ -25,6 +26,7 @@
<script setup>
import { defineProps } from "vue";
import { Phone, Navigation } from '@lucide/vue'
const props = defineProps({
orderData: {

View File

@@ -1,7 +1,8 @@
<template>
<div class="flex flex-col h-screen bg-white">
<!-- 滚动区域 -->
<div class="flex-1 overflow-y-auto" @scroll="handleScroll">
<div class="flex-1 overflow-y-auto scrollbar-none [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
@scroll="handleScroll">
<ImageSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
<div class="bg-white py-[20px] relative mt-[-30px] z-10 rounded-t-[28px]">
@@ -44,7 +45,7 @@
</div>
<div
class="flex items-center ml-auto pl-[8px] rounded-[10px] w-[120px] h-[48px] bg-linear-[90deg, #ff3d60_57%, #ff990c_100%]"
class="flex items-center ml-auto pl-[8px] rounded-[10px] w-[120px] h-[48px] [background:linear-gradient(90deg,#ff3d60_57%,#ff990c_100%)]"
@click="navigateToPay(goodsData)">
<img class="w-[34px] h-[48px]" src="https://oss.nianxx.cn/mp/static/version_101/common/btn.png" />
<span class="text-[16px] font-medium text-white">立即预定</span>
@@ -58,8 +59,8 @@
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from 'vue-router'
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from 'vue-router'
import { goodsDetail, commodityDailyPriceList } from "@/api/goods";
import { DateUtils } from "@/utils/dateUtils";
import ImageSwiper from "@/components/ImageSwiper/index.vue";
@@ -72,8 +73,8 @@ import GoodFacility from "./components/GoodFacility/index.vue";
import GoodPackage from "./components/GoodPackage/index.vue";
import { useSelectedDateStore } from "@/store";
const router = useRouter()
const route = useRoute()
// 导航栏透明度 - 默认透明,随滚动变为不透明
const calendarVisible = ref(false);
@@ -143,13 +144,34 @@ const getGoodsDailyPrice = async (params) => {
};
// TODO
// const selectedDateStore = useSelectedDateStore();
// onLoad(({ commodityId = "1950766939442774018" }) => {
// // 从store中获取选中的日期
// selectedDate.value = selectedDateStore.selectedDate;
const selectedDateStore = useSelectedDateStore();
const getDefaultSelectedDate = () => ({
startDate: DateUtils.formatDate(),
endDate: DateUtils.formatDate(new Date(Date.now() + 24 * 60 * 60 * 1000)),
totalDays: 1,
});
// goodsInfo({ commodityId });
// });
const normalizeSelectedDate = (data) => {
if (data?.startDate && data?.endDate && Number(data.totalDays) > 0) {
return {
startDate: data.startDate,
endDate: data.endDate,
totalDays: Number(data.totalDays),
};
}
return getDefaultSelectedDate();
};
onMounted(() => {
// 从路由参数中获取商品ID
const commodityId = route.query.commodityId || "1950766939442774018";
// 从store中获取选中的日期
selectedDate.value = normalizeSelectedDate(selectedDateStore.selectedDate);
selectedDateStore.setData({ ...selectedDate.value });
goodsInfo({ commodityId });
});
// 显示日历弹窗
const showCalendar = () => (calendarVisible.value = true);

View File

@@ -50,6 +50,7 @@ import Tabs from "./components/Tabs/index.vue";
import Card from "./components/Card/index.vue";
import { quickBookingList } from "@/api/goods";
import { DateUtils } from "@/utils/dateUtils";
import { useSelectedDateStore } from "@/store";
type SelectedDate = {
startDate: string;
@@ -66,11 +67,28 @@ type QuickListItem = Record<string, any>;
const PAGE_SIZE = 10;
const calenderRef = ref();
const selectedDate = ref<SelectedDate>({
const selectedDateStore = useSelectedDateStore();
const getDefaultSelectedDate = (): SelectedDate => ({
startDate: DateUtils.formatDate(),
endDate: DateUtils.formatDate(new Date(Date.now() + 24 * 60 * 60 * 1000)),
totalDays: 1,
});
const normalizeSelectedDate = (data?: Partial<SelectedDate> | null): SelectedDate => {
if (data?.startDate && data?.endDate && Number(data.totalDays) > 0) {
return {
startDate: data.startDate,
endDate: data.endDate,
totalDays: Number(data.totalDays),
};
}
return getDefaultSelectedDate();
};
const selectedDate = ref<SelectedDate>(normalizeSelectedDate(selectedDateStore.selectedDate));
selectedDateStore.setData({ ...selectedDate.value });
const dataList = ref<QuickListItem[]>([]);
const currentPage = ref(1);
const loading = ref(false);
@@ -161,7 +179,8 @@ const handleCalendarClose = () => {
};
const handleDateSelect = (data: SelectedDate) => {
selectedDate.value = data;
selectedDate.value = normalizeSelectedDate(data);
selectedDateStore.setData({ ...selectedDate.value });
calenderRef.value?.close?.();
dataList.value = [];
reloadList();