refactor(goods): clean up components, update imports and add album page
- replace scoped SCSS styles with inline utility classes for goods components - move LocationCard to goods subdirectory and update relative imports - fix DebounceUtils import path in FooterSection - update goods index page: replace scroll wrapper, switch to vue-router composable, replace uni modal with vant showDialog - add new album page component - remove unused PNG assets, old README and deprecated style files - update global type declarations for vant showDialog
This commit is contained in:
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@@ -6,5 +6,6 @@
|
|||||||
// biome-ignore lint: disable
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
const showDialog: typeof import('vant/es').showDialog
|
||||||
const showToast: typeof import('vant/es').showToast
|
const showToast: typeof import('vant/es').showToast
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="store-address">
|
|
||||||
<div class="text-container" @click.stop="openMap">
|
|
||||||
<span class="location-label">位于 {{ orderData.oneLevelAddress }}</span>
|
|
||||||
<span class="address-text">{{ orderData.commodityAddress }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<div>
|
|
||||||
<div class="actions-btn" @click.stop="openMap">
|
|
||||||
<uni-icons type="paperplane-filled" size="16" color="#171717" />
|
|
||||||
</div>
|
|
||||||
<span class="actions-text">导航</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="actions-btn" @click.stop="callPhone">
|
|
||||||
<uni-icons type="phone-filled" size="16" color="#171717" />
|
|
||||||
</div>
|
|
||||||
<span class="actions-text">电话</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
orderData: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开地图
|
|
||||||
const openMap = () => {
|
|
||||||
const address = props.orderData.commodityAddress;
|
|
||||||
const latitude = Number(props.orderData.commodityLatitude);
|
|
||||||
const longitude = Number(props.orderData.commodityLongitude);
|
|
||||||
|
|
||||||
uni.getLocation({
|
|
||||||
type: "gcj02",
|
|
||||||
success: () => {
|
|
||||||
uni.openLocation({
|
|
||||||
latitude: latitude,
|
|
||||||
longitude: longitude,
|
|
||||||
address: address,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.openLocation({
|
|
||||||
latitude: latitude,
|
|
||||||
longitude: longitude,
|
|
||||||
address: address,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 拨打电话
|
|
||||||
const getPhoneNumber = () => {
|
|
||||||
const o = props.orderData || {};
|
|
||||||
return o.commodityPhone || o.phone || o.contactPhone || "15608199221";
|
|
||||||
};
|
|
||||||
|
|
||||||
const callPhone = () => {
|
|
||||||
const phone = getPhoneNumber();
|
|
||||||
if (!phone) {
|
|
||||||
uni.showToast({ title: "未提供电话号码", icon: "none" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.makePhoneCall({ phoneNumber: String(phone) });
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "./styles/index.scss";
|
|
||||||
</style>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
.store-address {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
padding: 12px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
|
|
||||||
// 左侧文本容器
|
|
||||||
.text-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-label {
|
|
||||||
color: $text-color-800;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.address-text {
|
|
||||||
margin-top: 4px;
|
|
||||||
color: $text-color-600;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右侧操作按钮组
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-btn {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: $text-color-600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="date-selector" @click="showCalendar">
|
<div class="flex items-end justify-between my-[12px] px-[12px]" @click="showCalendar">
|
||||||
<div class="date-item">
|
<div class="flex-1 relative">
|
||||||
<div class="date-label">入住日期</div>
|
<div class="absolute top-[-6px] left-[12px] text-[100px] text-ink-600 bg-white px-[4px]">入住日期</div>
|
||||||
<div class="date-box">
|
<div class="flex items-center justify-start border border-[#f0f0f0] rounded-[8px] px-[12px] py-[10px] bg-white">
|
||||||
<div class="date-content">
|
<div class="flex items-baseline gap-[4px]">
|
||||||
<span class="date-span">{{ checkInDate }}</span>
|
<span class="text-[16px] font-medium text-[#333]">{{ checkInDate }}</span>
|
||||||
<span class="day-span">{{ checkInDay }}</span>
|
<span class="text-[10px] text-[#666]">{{ checkInDay }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nights-info">
|
<div class="flex items-center justify-center min-w-[40px] mx-[8px] mb-[10px]">
|
||||||
<span class="nights-span">{{ nights }}晚</span>
|
<span class="text-[12px] text-[#666]">{{ nights }}晚</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date-item">
|
<div class="flex-1 relative">
|
||||||
<div class="date-label">退房日期</div>
|
<div class="absolute top-[-6px] left-[12px] text-[100px] text-ink-600 bg-white px-[4px]">退房日期</div>
|
||||||
<div class="date-box">
|
<div class="flex items-center justify-start border border-[#f0f0f0] rounded-[8px] px-[12px] py-[10px] bg-white">
|
||||||
<div class="date-content">
|
<div class="flex items-baseline gap-[4px]">
|
||||||
<span class="date-span">{{ checkOutDate }}</span>
|
<span class="text-[16px] font-medium text-[#333]">{{ checkOutDate }}</span>
|
||||||
<span class="day-span">{{ checkOutDay }}</span>
|
<span class="text-[10px] text-[#666]">{{ checkOutDay }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
.date-selector {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 12px 0;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-item {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-label {
|
|
||||||
position: absolute;
|
|
||||||
top: -12rpx;
|
|
||||||
left: 24rpx;
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #333-grey;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 0 8rpx;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-box {
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
border: 2rpx solid #f0f0f0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-text {
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nights-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 80rpx;
|
|
||||||
margin: 0 16rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nights-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666666;
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="border-box border-top-8">
|
<div class="border-top-8">
|
||||||
<div class="border-box pt-12 pl-12 pr-12" v-for="(moduleItem, index) in goodsData.commodityEquipment" :key="index">
|
<div class="pt-[12px] pl-[12px] pr-[12px]" v-for="(moduleItem, index) in goodsData.commodityEquipment" :key="index">
|
||||||
<div class="flex flex-items-start flex-col" :class="{
|
<div class="flex flex-col items-start" :class="{
|
||||||
'border-bottom': index < goodsData.commodityEquipment.length - 1,
|
'border-bottom': index < goodsData.commodityEquipment.length - 1,
|
||||||
}">
|
}">
|
||||||
<div class="flex flex-items-center flex-row flex-shrink-0">
|
<div class="flex items-center flex-row shrink-0">
|
||||||
<uni-icons fontFamily="znicons" size="20" color="#171717">
|
<van-icon fontFamily="znicons" size="20" color="#171717">
|
||||||
{{ zniconsMap[moduleItem.icon] }}
|
|
||||||
</uni-icons>
|
</van-icon>
|
||||||
<span class="ml-4 font-size-12 color-171717 line-height-20">
|
<span class="ml-[4px] text-[12px] text-[#171717] leading-[20px]">
|
||||||
{{ moduleItem.title }}
|
{{ moduleItem.title }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-box flex flex-items-center flex-row mt-4 pb-12">
|
<div class="flex items-center flex-row mt-[4px] pb-[12px]">
|
||||||
<span class="font-size-12 color-525866 line-height-20 mr-4" v-for="(span, index) in moduleItem.desc"
|
<span class="text-[12px] text-[#525866] leading-[20px] mr-[4px]" v-for="(span, index) in moduleItem.desc"
|
||||||
:key="index">
|
:key="index">
|
||||||
{{ span }}
|
{{ span }}
|
||||||
</span>
|
</span>
|
||||||
@@ -35,10 +35,3 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@font-face {
|
|
||||||
font-family: znicons;
|
|
||||||
src: url("@/assets/fonts/znicons.ttf");
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
# GoodInfo 商品信息组件
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
`GoodInfo` 是一个高性能的商品信息展示组件,专为电商、旅游、服务类应用设计。组件采用现代化的UI设计,支持响应式布局和暗色模式,提供优秀的用户体验。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
### 🎯 核心功能
|
|
||||||
|
|
||||||
- **价格展示**: 突出显示商品价格,支持货币符号和价格标签
|
|
||||||
- **商品标题**: 清晰展示商品名称和相关标签
|
|
||||||
- **地址信息**: 显示商品/服务地址,支持图标和交互
|
|
||||||
- **设施展示**: 网格布局展示商品特色设施或服务项目
|
|
||||||
|
|
||||||
### ⚡ 性能优化
|
|
||||||
|
|
||||||
- **计算属性缓存**: 使用 `computed` 优化设施列表渲染
|
|
||||||
- **按需渲染**: 条件渲染减少不必要的DOM节点
|
|
||||||
- **轻量级设计**: 最小化组件体积和依赖
|
|
||||||
- **懒加载支持**: 支持图标和内容的懒加载
|
|
||||||
|
|
||||||
### 🎨 UI特性
|
|
||||||
|
|
||||||
- **现代化设计**: 圆角卡片、阴影效果、渐变背景
|
|
||||||
- **响应式布局**: 适配不同屏幕尺寸
|
|
||||||
- **暗色模式**: 自动适配系统主题
|
|
||||||
- **交互反馈**: 悬停效果和过渡动画
|
|
||||||
|
|
||||||
## 基础用法
|
|
||||||
|
|
||||||
### 简单使用
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<GoodInfo :goodsInfo="goodsData" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import GoodInfo from "./components/GoodInfo/index.vue";
|
|
||||||
|
|
||||||
const goodsData = {
|
|
||||||
price: 399,
|
|
||||||
title: "【成人票】云从朵花温泉门票",
|
|
||||||
timeTag: "随时可退",
|
|
||||||
address: "距您43.1公里 黔南州布依族苗族自治州龙里县",
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 完整配置
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<GoodInfo :goodsInfo="fullGoodsData" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const fullGoodsData = {
|
|
||||||
price: 399,
|
|
||||||
title: "【成人票】云从朵花温泉门票",
|
|
||||||
tag: "热销",
|
|
||||||
timeTag: "随时可退",
|
|
||||||
address: "距您43.1公里 黔南州布依族苗族自治州龙里县",
|
|
||||||
facilities: [
|
|
||||||
{ icon: "home", name: "48个泡池" },
|
|
||||||
{ icon: "color", name: "11个特色药池" },
|
|
||||||
{ icon: "fire", name: "4个汗蒸房" },
|
|
||||||
{ icon: "person", name: "儿童充气水上乐园" },
|
|
||||||
{ icon: "game", name: "儿童戏水区" },
|
|
||||||
{ icon: "home-filled", name: "石板浴" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## API 文档
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| --------- | ------ | ------ | ------------ |
|
|
||||||
| goodsInfo | Object | `{}` | 商品信息对象 |
|
|
||||||
|
|
||||||
### goodsInfo 对象结构
|
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
||||||
| ---------- | ------------- | ---- | --------------------------------------------- | ------------------------------ |
|
|
||||||
| price | Number/String | 否 | `399` | 商品价格 |
|
|
||||||
| title | String | 否 | `'【成人票】云从朵花温泉门票'` | 商品标题 |
|
|
||||||
| tag | String | 否 | - | 价格标签(如:热销、限时优惠) |
|
|
||||||
| timeTag | String | 否 | `'随时可退'` | 时间相关标签 |
|
|
||||||
| address | String | 否 | `'距您43.1公里 黔南州布依族苗族自治州龙里县'` | 地址信息 |
|
|
||||||
| facilities | Array | 否 | 默认设施列表 | 设施/特色列表 |
|
|
||||||
|
|
||||||
### facilities 数组结构
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
[
|
|
||||||
{
|
|
||||||
icon: "home", // uni-icons 图标名称
|
|
||||||
name: "48个泡池", // 设施名称
|
|
||||||
},
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
## 样式定制
|
|
||||||
|
|
||||||
### CSS 变量
|
|
||||||
|
|
||||||
组件支持通过 CSS 变量进行主题定制:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.good-info {
|
|
||||||
--primary-color: #ff6b35; // 主色调
|
|
||||||
--background-color: #fff; // 背景色
|
|
||||||
--text-color: #333; // 文字颜色
|
|
||||||
--border-radius: 16rpx; // 圆角大小
|
|
||||||
--shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); // 阴影
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 响应式断点
|
|
||||||
|
|
||||||
- **小屏设备**: `max-width: 750rpx`
|
|
||||||
- **暗色模式**: `prefers-color-scheme: dark`
|
|
||||||
|
|
||||||
## 性能优化建议
|
|
||||||
|
|
||||||
### 1. 数据结构优化
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ 推荐:使用 reactive 包装数据
|
|
||||||
const goodsData = reactive({
|
|
||||||
price: 399,
|
|
||||||
title: "商品标题",
|
|
||||||
});
|
|
||||||
|
|
||||||
// ❌ 避免:频繁的深层对象更新
|
|
||||||
const goodsData = ref({
|
|
||||||
nested: {
|
|
||||||
deep: {
|
|
||||||
value: "data",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 设施列表优化
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ 推荐:预定义设施列表
|
|
||||||
const FACILITY_PRESETS = {
|
|
||||||
spa: [
|
|
||||||
{ icon: "home", name: "48个泡池" },
|
|
||||||
{ icon: "water", name: "恒温泳池" },
|
|
||||||
],
|
|
||||||
hotel: [
|
|
||||||
{ icon: "bed", name: "豪华客房" },
|
|
||||||
{ icon: "car", name: "免费停车" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用预设
|
|
||||||
const goodsData = {
|
|
||||||
facilities: FACILITY_PRESETS.spa,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 图标优化
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ 推荐:使用常见图标
|
|
||||||
const commonIcons = ["home", "person", "heart", "star"];
|
|
||||||
|
|
||||||
// ❌ 避免:使用过多不同图标增加包体积
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 数据验证
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 添加数据验证
|
|
||||||
const validateGoodsInfo = (data) => {
|
|
||||||
return {
|
|
||||||
price: Number(data.price) || 0,
|
|
||||||
title: String(data.title || ""),
|
|
||||||
facilities: Array.isArray(data.facilities) ? data.facilities : [],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 错误处理
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<GoodInfo :goodsInfo="goodsData" @error="handleError" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const handleError = (error) => {
|
|
||||||
console.error("GoodInfo Error:", error);
|
|
||||||
// 错误上报或用户提示
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 无障碍访问
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="good-info"
|
|
||||||
role="article"
|
|
||||||
:aria-label="`商品信息:${goodsInfo.title}`"
|
|
||||||
>
|
|
||||||
<!-- 组件内容 -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **图标依赖**: 组件依赖 `uni-icons`,请确保项目中已安装
|
|
||||||
2. **单位适配**: 样式使用 `rpx` 单位,适配小程序和H5
|
|
||||||
3. **性能考虑**: 设施列表较多时建议分页或虚拟滚动
|
|
||||||
4. **主题适配**: 支持暗色模式,但需要系统支持
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0 (2024-01-XX)
|
|
||||||
|
|
||||||
- ✨ 初始版本发布
|
|
||||||
- 🎨 现代化UI设计
|
|
||||||
- ⚡ 性能优化
|
|
||||||
- 📱 响应式布局
|
|
||||||
- 🌙 暗色模式支持
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
- **框架**: Vue 3 Composition API
|
|
||||||
- **样式**: SCSS
|
|
||||||
- **图标**: uni-icons
|
|
||||||
- **构建**: Vite/Webpack
|
|
||||||
|
|
||||||
## 浏览器支持
|
|
||||||
|
|
||||||
- ✅ Chrome 80+
|
|
||||||
- ✅ Firefox 75+
|
|
||||||
- ✅ Safari 13+
|
|
||||||
- ✅ Edge 80+
|
|
||||||
- ✅ 微信小程序
|
|
||||||
- ✅ 支付宝小程序
|
|
||||||
- ✅ H5
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,24 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="good-info border-box pl-12 pr-12">
|
<div class="bg-white px-[12px]">
|
||||||
<!-- 标题区域 -->
|
<!-- 标题区域 -->
|
||||||
<div class="title-section">
|
<div class="flex items-center mb-[12px]">
|
||||||
<span class="title">
|
<span
|
||||||
{{ goodsData.commodityName || "【成人票】云从朵花温泉门票" }}
|
class="text-[16px] font-semibold text-[#333] leading-[1.4] flex-0-[280px] text-ellipsis overflow-hidden whitespace-nowrap">
|
||||||
|
{{ goodsData.commodityName }}
|
||||||
</span>
|
</span>
|
||||||
<div class="tag-wrapper" v-if="goodsData.timeTag">
|
<div class="flex items-center" v-if="goodsData.timeTag">
|
||||||
<div class="time-tag">{{ goodsData.timeTag || "随时可退" }}</div>
|
<div
|
||||||
|
class="text-[9px] text-semibold text-[#f55726] border-[1px] border-[#f55726] rounded-[6px] px-[6px] py-[3px]">
|
||||||
|
{{ goodsData.timeTag }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设施信息区域 -->
|
<!-- 设施信息区域 -->
|
||||||
<div class="facilities-section" v-if="facilitiesList.length">
|
<div class="mt-[12px]" v-if="facilitiesList.length">
|
||||||
<div class="facilities-grid">
|
<div class="flex flex-wrap gap-[8px]">
|
||||||
<div
|
<div class="flex items-center gap-[4px] p-[8px] bg-[#fafafa] rounded-[6px]"
|
||||||
class="facility-item"
|
v-for="(facility, index) in facilitiesList" :key="index">
|
||||||
v-for="(facility, index) in facilitiesList"
|
<span class="text-[12px] text-[#333] leading-[1] white-space-nowrap overflow-hidden text-ellipsis">
|
||||||
:key="index"
|
{{ facility }}
|
||||||
>
|
</span>
|
||||||
<span class="facility-span">{{ facility }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +51,3 @@ const facilitiesList = computed(() => {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "./styles/index.scss";
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,40 +1,5 @@
|
|||||||
.good-info {
|
.good-info {
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
// 标题区域
|
// 标题区域
|
||||||
.title-section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
flex: 0 280px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.time-tag {
|
|
||||||
color: #f55726;
|
|
||||||
padding: 3px 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 9px;
|
|
||||||
border: 1px solid #f55726;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.calender-icon {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设施信息区域
|
// 设施信息区域
|
||||||
.facilities-section {
|
.facilities-section {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="border-box border-top-8 pl-12 pr-12 pt-6 pb-12">
|
<div class="border-top border-[#E5E5E5] border-[8px] px-[12px] pt-[6px] pb-[12px]">
|
||||||
<ModuleTitle v-if="showTitle" title="套餐包含内容" />
|
<ModuleTitle v-if="showTitle" title="套餐包含内容" />
|
||||||
<div
|
<div class="flex flex-col items-start " v-for="(item, index) in goodsData.commodityPackageConfig" :key="index">
|
||||||
class="flex flex-items-start flex-col"
|
<div class="flex items-center justify-start py-[4px] ml-[4px]">
|
||||||
v-for="(item, index) in goodsData.commodityPackageConfig"
|
<span class="whitespace-nowrap flex-auto text-[14px] text-[#171717]">
|
||||||
:key="index"
|
{{ item.name }}
|
||||||
>
|
</span>
|
||||||
<div class="title-row py-4 ml-4">
|
<div
|
||||||
<span class="left font-size-14 color-171717">{{ item.name }}</span>
|
class="flex-1 h-px mx-2 bg-[repeating-linear-gradient(to_right,#CACFD8_0_10px,transparent_10px_16px)] bg-repeat-x bg-center">
|
||||||
<div class="sep" aria-hidden="true"></div>
|
</div>
|
||||||
<span class="right font-size-14 color-171717"
|
<span class="whitespace-nowrap flex-auto text-[14px] text-[#171717]">
|
||||||
>{{ item.count }}{{ item.unit }}</span
|
{{ item.count }}{{ item.unit }}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +33,3 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "./styles/index.scss";
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
.title-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-row .left,
|
|
||||||
.title-row .right {
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-row .sep {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
height: 1px;
|
|
||||||
margin: 0 8px;
|
|
||||||
background-image: repeating-linear-gradient(to right, #CACFD8 0 10px, transparent 10px 16px);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
53
src/pages/goods/components/LocationCard/index.vue
Normal file
53
src/pages/goods/components/LocationCard/index.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center mt-[12px] p-[12px] bg-white border 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-[10px] ml-[16px]">
|
||||||
|
<div>
|
||||||
|
<div class="actions-btn">
|
||||||
|
<van-icon type="paperplane-filled" 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>
|
||||||
|
<span class="text-[12px] text-ink-600">电话</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
orderData: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拨打电话
|
||||||
|
const getPhoneNumber = () => {
|
||||||
|
const o = props.orderData || {};
|
||||||
|
return o.commodityPhone || o.phone || o.contactPhone;
|
||||||
|
};
|
||||||
|
|
||||||
|
const callPhone = () => {
|
||||||
|
const phone = getPhoneNumber();
|
||||||
|
|
||||||
|
if (!phone) {
|
||||||
|
showToast("未提供电话号码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = `tel:${phone}`;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 585 KiB |
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="goods-container bg-gray">
|
<div class="goods-container">
|
||||||
<TopNavBar :title="navOpacity < 0.5 ? '' : '商品详情'" :background="`rgba(217, 238, 255, ${navOpacity})`"
|
<TopNavBar :title="navOpacity < 0.5 ? '' : '商品详情'" :background="`rgba(217, 238, 255, ${navOpacity})`"
|
||||||
:titleColor="navOpacity < 0.5 ? '#ffffff' : '#000000'"
|
:titleColor="navOpacity < 0.5 ? '#ffffff' : '#000000'"
|
||||||
:backIconColor="navOpacity < 0.5 ? '#ffffff' : '#000000'" />
|
:backIconColor="navOpacity < 0.5 ? '#ffffff' : '#000000'" />
|
||||||
|
|
||||||
<!-- 滚动区域 -->
|
<!-- 滚动区域 -->
|
||||||
<scroll-div class="content-wrapper" scroll-y @scroll="handleScroll">
|
<div class="content-wrapper" @scroll="handleScroll">
|
||||||
<imgSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
<imgSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
||||||
|
|
||||||
<div class="goods-content">
|
<div class="goods-content">
|
||||||
@@ -23,19 +23,19 @@
|
|||||||
<GoodPackage v-if="
|
<GoodPackage v-if="
|
||||||
goodsData.orderType != 0 &&
|
goodsData.orderType != 0 &&
|
||||||
goodsData.commodityPackageConfig &&
|
goodsData.commodityPackageConfig &&
|
||||||
goodsData.commodityPackageConfig.length > 0
|
goodsData.commodityPackageConfig.length
|
||||||
" :goodsData="goodsData" />
|
" :goodsData="goodsData" />
|
||||||
|
|
||||||
<!-- 商品设施组件 -->
|
<!-- 商品设施组件 -->
|
||||||
<GoodFacility v-if="
|
<GoodFacility v-if="
|
||||||
goodsData.commodityEquipment &&
|
goodsData.commodityEquipment &&
|
||||||
goodsData.commodityEquipment.length > 0
|
goodsData.commodityEquipment.length
|
||||||
" :goodsData="goodsData" />
|
" :goodsData="goodsData" />
|
||||||
|
|
||||||
<!-- 商品详情组件 -->
|
<!-- 商品详情组件 -->
|
||||||
<GoodDetail :goodsData="goodsData" />
|
<GoodDetail :goodsData="goodsData" />
|
||||||
</div>
|
</div>
|
||||||
</scroll-div>
|
</div>
|
||||||
|
|
||||||
<!-- 立即抢购 -->
|
<!-- 立即抢购 -->
|
||||||
<div class="footer border-top">
|
<div class="footer border-top">
|
||||||
@@ -57,19 +57,23 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { goodsDetail, commodityDailyPriceList } from "@/api/goods";
|
import { goodsDetail, commodityDailyPriceList } from "@/api/goods";
|
||||||
import { DateUtils } from "@/utils";
|
import { DateUtils } from "@/utils/dateUtils";
|
||||||
import TopNavBar from "@/components/TopNavBar/index.vue";
|
import TopNavBar from "@/components/TopNavBar/index.vue";
|
||||||
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
||||||
import GoodInfo from "./components/GoodInfo/index.vue";
|
import GoodInfo from "./components/GoodInfo/index.vue";
|
||||||
import Calender from "@/components/Calender/index.vue";
|
import Calender from "@/components/Calender/index.vue";
|
||||||
import LocationCard from "@/components/LocationCard/index.vue";
|
import LocationCard from "./components/LocationCard/index.vue";
|
||||||
import DateSelector from "./components/DateSelector/index.vue";
|
import DateSelector from "./components/DateSelector/index.vue";
|
||||||
import GoodDetail from "@/components/GoodDetail/index.vue";
|
import GoodDetail from "@/components/GoodDetail/index.vue";
|
||||||
import GoodFacility from "./components/GoodFacility/index.vue";
|
import GoodFacility from "./components/GoodFacility/index.vue";
|
||||||
import GoodPackage from "./components/GoodPackage/index.vue";
|
import GoodPackage from "./components/GoodPackage/index.vue";
|
||||||
import { useSelectedDateStore } from "@/store";
|
import { useSelectedDateStore } from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
||||||
const navOpacity = ref(0);
|
const navOpacity = ref(0);
|
||||||
const calendarVisible = ref(false);
|
const calendarVisible = ref(false);
|
||||||
@@ -113,14 +117,14 @@ const goodsInfo = async (params) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (goodsData.value.commodityStatus !== "1") {
|
if (goodsData.value.commodityStatus !== "1") {
|
||||||
uni.showModal({
|
showDialog({
|
||||||
title: "温馨提示",
|
title: "温馨提示",
|
||||||
content: "您选的商品暂不可预订,请重新选择。",
|
message: "您选的商品暂不可预订,请重新选择。",
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
}).then(() => {
|
||||||
uni.navigateBack({ delta: 1 });
|
router.back()
|
||||||
}
|
}).catch(() => {
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -229,9 +233,12 @@ const handleDateSelect = (data) => {
|
|||||||
|
|
||||||
// 跳转订购
|
// 跳转订购
|
||||||
const navigateToPay = ({ commodityId }) => {
|
const navigateToPay = ({ commodityId }) => {
|
||||||
uni.navigateTo({
|
router.push({
|
||||||
url: `/pages-booking/index?commodityId=${commodityId}`,
|
name: "booking",
|
||||||
});
|
query: {
|
||||||
|
commodityId,
|
||||||
|
},
|
||||||
|
})
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits, computed } from "vue";
|
import { defineProps, defineEmits, computed } from "vue";
|
||||||
import { orderPayNow } from "@/api/order";
|
import { orderPayNow } from "@/api/order";
|
||||||
import { DebounceUtils } from "@/utils";
|
import { DebounceUtils } from "@/utils/DebounceUtils";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
orderData: {
|
orderData: {
|
||||||
|
|||||||
Reference in New Issue
Block a user