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
|
||||
export {}
|
||||
declare global {
|
||||
const showDialog: typeof import('vant/es').showDialog
|
||||
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>
|
||||
<div class="date-selector" @click="showCalendar">
|
||||
<div class="date-item">
|
||||
<div class="date-label">入住日期</div>
|
||||
<div class="date-box">
|
||||
<div class="date-content">
|
||||
<span class="date-span">{{ checkInDate }}</span>
|
||||
<span class="day-span">{{ checkInDay }}</span>
|
||||
<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="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>
|
||||
<span class="text-[10px] text-[#666]">{{ checkInDay }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nights-info">
|
||||
<span class="nights-span">{{ nights }}晚</span>
|
||||
<div class="flex items-center justify-center min-w-[40px] mx-[8px] mb-[10px]">
|
||||
<span class="text-[12px] text-[#666]">{{ nights }}晚</span>
|
||||
</div>
|
||||
|
||||
<div class="date-item">
|
||||
<div class="date-label">退房日期</div>
|
||||
<div class="date-box">
|
||||
<div class="date-content">
|
||||
<span class="date-span">{{ checkOutDate }}</span>
|
||||
<span class="day-span">{{ checkOutDay }}</span>
|
||||
<div class="flex-1 relative">
|
||||
<div class="absolute top-[-6px] left-[12px] text-[100px] 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>
|
||||
<span class="text-[10px] text-[#666]">{{ checkOutDay }}</span>
|
||||
</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>
|
||||
<div class="border-box border-top-8">
|
||||
<div class="border-box pt-12 pl-12 pr-12" v-for="(moduleItem, index) in goodsData.commodityEquipment" :key="index">
|
||||
<div class="flex flex-items-start flex-col" :class="{
|
||||
<div class="border-top-8">
|
||||
<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-bottom': index < goodsData.commodityEquipment.length - 1,
|
||||
}">
|
||||
<div class="flex flex-items-center flex-row flex-shrink-0">
|
||||
<uni-icons fontFamily="znicons" size="20" color="#171717">
|
||||
{{ zniconsMap[moduleItem.icon] }}
|
||||
</uni-icons>
|
||||
<span class="ml-4 font-size-12 color-171717 line-height-20">
|
||||
<div class="flex items-center flex-row shrink-0">
|
||||
<van-icon fontFamily="znicons" size="20" color="#171717">
|
||||
|
||||
</van-icon>
|
||||
<span class="ml-[4px] text-[12px] text-[#171717] leading-[20px]">
|
||||
{{ moduleItem.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="border-box flex flex-items-center flex-row mt-4 pb-12">
|
||||
<span class="font-size-12 color-525866 line-height-20 mr-4" v-for="(span, index) in moduleItem.desc"
|
||||
<div class="flex items-center flex-row mt-[4px] pb-[12px]">
|
||||
<span class="text-[12px] text-[#525866] leading-[20px] mr-[4px]" v-for="(span, index) in moduleItem.desc"
|
||||
:key="index">
|
||||
{{ span }}
|
||||
</span>
|
||||
@@ -35,10 +35,3 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
</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>
|
||||
<div class="good-info border-box pl-12 pr-12">
|
||||
<div class="bg-white px-[12px]">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<span class="title">
|
||||
{{ goodsData.commodityName || "【成人票】云从朵花温泉门票" }}
|
||||
<div class="flex items-center mb-[12px]">
|
||||
<span
|
||||
class="text-[16px] font-semibold text-[#333] leading-[1.4] flex-0-[280px] text-ellipsis overflow-hidden whitespace-nowrap">
|
||||
{{ goodsData.commodityName }}
|
||||
</span>
|
||||
<div class="tag-wrapper" v-if="goodsData.timeTag">
|
||||
<div class="time-tag">{{ goodsData.timeTag || "随时可退" }}</div>
|
||||
<div class="flex items-center" v-if="goodsData.timeTag">
|
||||
<div
|
||||
class="text-[9px] text-semibold text-[#f55726] border-[1px] border-[#f55726] rounded-[6px] px-[6px] py-[3px]">
|
||||
{{ goodsData.timeTag }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设施信息区域 -->
|
||||
<div class="facilities-section" v-if="facilitiesList.length">
|
||||
<div class="facilities-grid">
|
||||
<div
|
||||
class="facility-item"
|
||||
v-for="(facility, index) in facilitiesList"
|
||||
:key="index"
|
||||
>
|
||||
<span class="facility-span">{{ facility }}</span>
|
||||
<div class="mt-[12px]" v-if="facilitiesList.length">
|
||||
<div class="flex flex-wrap gap-[8px]">
|
||||
<div class="flex items-center gap-[4px] p-[8px] bg-[#fafafa] rounded-[6px]"
|
||||
v-for="(facility, index) in facilitiesList" :key="index">
|
||||
<span class="text-[12px] text-[#333] leading-[1] white-space-nowrap overflow-hidden text-ellipsis">
|
||||
{{ facility }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +51,3 @@ const facilitiesList = computed(() => {
|
||||
return [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
.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 {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<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="套餐包含内容" />
|
||||
<div class="flex flex-col items-start " v-for="(item, index) in goodsData.commodityPackageConfig" :key="index">
|
||||
<div class="flex items-center justify-start py-[4px] ml-[4px]">
|
||||
<span class="whitespace-nowrap flex-auto text-[14px] text-[#171717]">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<div
|
||||
class="flex flex-items-start flex-col"
|
||||
v-for="(item, index) in goodsData.commodityPackageConfig"
|
||||
:key="index"
|
||||
>
|
||||
<div class="title-row py-4 ml-4">
|
||||
<span class="left font-size-14 color-171717">{{ item.name }}</span>
|
||||
<div class="sep" aria-hidden="true"></div>
|
||||
<span class="right font-size-14 color-171717"
|
||||
>{{ item.count }}{{ item.unit }}</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>
|
||||
<span class="whitespace-nowrap flex-auto text-[14px] text-[#171717]">
|
||||
{{ item.count }}{{ item.unit }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,3 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
</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>
|
||||
<div class="goods-container bg-gray">
|
||||
<div class="goods-container">
|
||||
<TopNavBar :title="navOpacity < 0.5 ? '' : '商品详情'" :background="`rgba(217, 238, 255, ${navOpacity})`"
|
||||
:titleColor="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" />
|
||||
|
||||
<div class="goods-content">
|
||||
@@ -23,19 +23,19 @@
|
||||
<GoodPackage v-if="
|
||||
goodsData.orderType != 0 &&
|
||||
goodsData.commodityPackageConfig &&
|
||||
goodsData.commodityPackageConfig.length > 0
|
||||
goodsData.commodityPackageConfig.length
|
||||
" :goodsData="goodsData" />
|
||||
|
||||
<!-- 商品设施组件 -->
|
||||
<GoodFacility v-if="
|
||||
goodsData.commodityEquipment &&
|
||||
goodsData.commodityEquipment.length > 0
|
||||
goodsData.commodityEquipment.length
|
||||
" :goodsData="goodsData" />
|
||||
|
||||
<!-- 商品详情组件 -->
|
||||
<GoodDetail :goodsData="goodsData" />
|
||||
</div>
|
||||
</scroll-div>
|
||||
</div>
|
||||
|
||||
<!-- 立即抢购 -->
|
||||
<div class="footer border-top">
|
||||
@@ -57,19 +57,23 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from 'vue-router'
|
||||
import { goodsDetail, commodityDailyPriceList } from "@/api/goods";
|
||||
import { DateUtils } from "@/utils";
|
||||
import { DateUtils } from "@/utils/dateUtils";
|
||||
import TopNavBar from "@/components/TopNavBar/index.vue";
|
||||
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
||||
import GoodInfo from "./components/GoodInfo/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 GoodDetail from "@/components/GoodDetail/index.vue";
|
||||
import GoodFacility from "./components/GoodFacility/index.vue";
|
||||
import GoodPackage from "./components/GoodPackage/index.vue";
|
||||
import { useSelectedDateStore } from "@/store";
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
||||
const navOpacity = ref(0);
|
||||
const calendarVisible = ref(false);
|
||||
@@ -113,14 +117,14 @@ const goodsInfo = async (params) => {
|
||||
}
|
||||
|
||||
if (goodsData.value.commodityStatus !== "1") {
|
||||
uni.showModal({
|
||||
showDialog({
|
||||
title: "温馨提示",
|
||||
content: "您选的商品暂不可预订,请重新选择。",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
}
|
||||
},
|
||||
message: "您选的商品暂不可预订,请重新选择。",
|
||||
|
||||
}).then(() => {
|
||||
router.back()
|
||||
}).catch(() => {
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -229,9 +233,12 @@ const handleDateSelect = (data) => {
|
||||
|
||||
// 跳转订购
|
||||
const navigateToPay = ({ commodityId }) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages-booking/index?commodityId=${commodityId}`,
|
||||
});
|
||||
router.push({
|
||||
name: "booking",
|
||||
query: {
|
||||
commodityId,
|
||||
},
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from "vue";
|
||||
import { orderPayNow } from "@/api/order";
|
||||
import { DebounceUtils } from "@/utils";
|
||||
import { DebounceUtils } from "@/utils/DebounceUtils";
|
||||
|
||||
const props = defineProps({
|
||||
orderData: {
|
||||
|
||||
Reference in New Issue
Block a user