This commit is contained in:
2025-10-27 17:56:52 +08:00
25 changed files with 549 additions and 3358 deletions

View File

@@ -0,0 +1,102 @@
<template>
<uni-popup
ref="popupRef"
type="bottom"
:safe-area="false"
@maskClick="handleClose"
>
<view class="refund-popup bg-F5F7FA border-box">
<view
class="border-box flex flex-items-center justify-between pt-12 pb-12 relative"
>
<view
class="flex-full font-size-16 color-171717 line-height-24 text-center"
>明细详情</view
>
<!-- 关闭按钮 -->
<uni-icons
class="close absolute"
type="close"
size="20"
color="#CACFD8"
@click="handleClose"
/>
</view>
<!-- 内容区域 -->
<view class="rounded-12 bg-white ml-12 mr-12 mb-40">
<view
class="border-box border-bottom flex flex-items-center flex-justify-between pt-12 pb-12 ml-12 mr-12"
>
<text class="font-size-16 font-500 color-171717">在线支付</text>
<text class="font-size-14 color-171717">239</text>
</view>
<view
class="border-box flex flex-items-center flex-justify-between pt-12 pb-12 ml-12 mr-12"
>
<text class="font-size-16 font-500 color-171717">房费</text>
<text class="font-size-14 color-171717">239</text>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed, watch } from "vue";
// Props定义
const props = defineProps({
// 弹窗显示状态
modelValue: {
type: Boolean,
default: false,
},
// 订单数据
orderData: {
type: Object,
default: () => {},
},
});
// Events定义
const emit = defineEmits(["update:modelValue"]);
// 弹窗引用
const popupRef = ref(null);
// 获取退款模板
const commodityPurchaseInstruction = computed(() => {
if (props.orderData.commodityPurchaseInstruction) {
return props.orderData.commodityPurchaseInstruction;
}
return {};
});
// 方法定义
const show = () => popupRef.value && popupRef.value.open();
const hide = () => popupRef.value && popupRef.value.close();
// 监听modelValue变化
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
show();
} else {
hide();
}
},
{ immediate: true }
);
const handleClose = () => {
emit("update:modelValue", false);
emit("close");
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,9 @@
.refund-popup {
border-radius: 15px 15px 0 0;
padding-bottom: 40px;
}
.close {
top: 14px;
right: 12px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

View File

@@ -1,52 +0,0 @@
<template>
<view class="service-order-item">
<view class="order-header">
<image class="order-icon" src="./images/icon_order.png"></image>
<text class="order-title">温泉早鸟票</text>
<text :class="['order-status', `status-${orderStatus}`]">{{
orderStatusText
}}</text>
</view>
<view class="order-details">
<view class="detail-item">
<text class="detail-label">订单编号</text>
<text class="detail-value">{{ orderId }}</text>
</view>
<view class="detail-item">
<text class="detail-label">游客人数</text>
<text class="detail-value">{{ touristCount }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from "vue";
// Sample data
const orderId = ref("7378400483776544");
const touristCount = ref(7);
const orderStatus = ref("pending"); // Options: 'canceled', 'pending', 'refundProcessing', 'refunded', 'completed'
// Computed property for order status text
const orderStatusText = computed(() => {
switch (orderStatus.value) {
case "canceled":
return "已取消";
case "pending":
return "待确认";
case "refundProcessing":
return "退款中";
case "refunded":
return "已退款";
case "completed":
return "已完成";
default:
return "";
}
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,16 +0,0 @@
## 订单 Item 组件
组件名称:订单卡片组件
## 提示词:
使用 uniapp + vue3 组合式 api 开发微信小程序,要求如下:
1、按照提供的图片高度还原交互设计
2、要求布局样式结构简洁明了class 命名请按照模块名称来命名,例如:.service-order-item
3、可以使用 uniapp 内置的组件
4、订单状态有已取消、待确认、退款中、已退款、已完成状态用颜色区分
5、订单卡片有点击跳转订单详情交互
## 备注
仅供学习、交流使用,请勿用于商业用途。

View File

@@ -1,81 +0,0 @@
.service-order-item {
background-color: #fff;
border-radius: 10px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.order-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.order-icon {
width: 20px;
height: 20px;
border-radius: $uni-border-radius-circle;
background-color: #ffa500;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.order-title {
font-size: $uni-font-size-lg;
color: $uni-text-color;
}
.order-status {
font-size: $uni-font-size-base;
padding: 5px 10px;
border-radius: 15px;
}
.status-canceled {
color: $uni-text-color-grey;
border: 1px solid #999;
}
.status-pending {
color: #ff4d94;
border: 1px solid #ff4d94;
}
.status-refundProcessing {
color: #ff9900;
border: 1px solid #ff9900;
}
.status-refunded {
color: #ff4d4f;
border: 1px solid #ff4d4f;
}
.status-completed {
color: #28a745;
border: 1px solid #28a745;
}
.order-details {
margin-top: 10px;
}
.detail-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.detail-label {
font-size: $uni-font-size-base;
color: #666;
margin-right: 10px;
}
.detail-value {
font-size: $uni-font-size-base;
color: $uni-text-color;
}

View File

@@ -0,0 +1,101 @@
<template>
<uni-popup
ref="popupRef"
type="bottom"
:safe-area="false"
@maskClick="handleClose"
>
<view class="refund-popup bg-F5F7FA border-box">
<view
class="border-box flex flex-items-center justify-between pt-12 pb-12 relative"
>
<view
class="flex-full font-size-16 color-171717 line-height-24 text-center"
>取消政策</view
>
<!-- 关闭按钮 -->
<uni-icons
class="close absolute"
type="close"
size="20"
color="#CACFD8"
@click="handleClose"
/>
</view>
<!-- 内容区域 -->
<view class="border-box rounded-12 bg-white p-12 ml-12 mr-12 mb-40">
<view class="flex flex-items-center mb-8">
<uni-icons fontFamily="znicons" size="20" color="#333">
{{ zniconsMap["zn-refund"] }}
</uni-icons>
<text class="font-size-14 font-600 color-171717 ml-8">退订规则</text>
</view>
<view class="font-size-14 color-525866 line-height-16">
<!-- {{ commodityPurchaseInstruction.refundContent }} -->
·不予退款12小时以内取消或未入住不予退款
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { zniconsMap } from "@/static/fonts/znicons";
// Props定义
const props = defineProps({
// 弹窗显示状态
modelValue: {
type: Boolean,
default: false,
},
// 订单数据
orderData: {
type: Object,
default: () => {},
},
});
// Events定义
const emit = defineEmits(["update:modelValue"]);
// 弹窗引用
const popupRef = ref(null);
// 获取退款模板
const commodityPurchaseInstruction = computed(() => {
if (props.orderData.commodityPurchaseInstruction) {
return props.orderData.commodityPurchaseInstruction;
}
return {};
});
// 方法定义
const show = () => popupRef.value && popupRef.value.open();
const hide = () => popupRef.value && popupRef.value.close();
// 监听modelValue变化
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
show();
} else {
hide();
}
},
{ immediate: true }
);
const handleClose = () => {
emit("update:modelValue", false);
emit("close");
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,9 @@
.refund-popup {
border-radius: 15px 15px 0 0;
padding-bottom: 40px;
}
.close {
top: 14px;
right: 12px;
}

View File

@@ -32,7 +32,7 @@ const props = defineProps({
});
// Emit
const emit = defineEmits(["update:modelValue"]);
const emit = defineEmits(["update:modelValue", "decrease", "increase"]);
// Local state
const value = ref(props.modelValue);
@@ -53,6 +53,7 @@ const decrease = () => {
if (value.value > props.min) {
value.value--;
emit("update:modelValue", value.value);
emit("decrease");
}
};
@@ -60,6 +61,7 @@ const increase = () => {
if (value.value < props.max) {
value.value++;
emit("update:modelValue", value.value);
emit("increase");
}
};
</script>

View File

@@ -0,0 +1,73 @@
<template>
<view class="border-box flex flex-items-center flex-justify-between mb-12">
<view class="left flex flex-items-center">
<text class="font-size-12 color-99A0AE mr-4">入住</text>
<text class="font-size-12 color-171717 mr-16">
{{ selectedDate.startDate }}
</text>
<text
class="total border-box rounded-50 flex flex-items-center font-size-11 color-43669A relative"
>1</text
>
<text class="font-size-12 color-99A0AE ml-16">离店</text>
<text class="font-size-12 color-171717 ml-4">
{{ selectedDate.endDate }}
</text>
</view>
<view class="flex flex-items-center" @click="emit('click')">
<text class="font-size-12 color-2D91FF line-height-16">房间详情</text>
<uni-icons type="right" size="15" color="#99A0AE" />
</view>
</view>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
// Props
const props = defineProps({
selectedDate: {
type: Object,
default: () => {
return {
startDate: "",
endDate: "",
};
},
},
});
const emit = defineEmits(["click"]);
</script>
<style scoped lang="scss">
.total {
border: 1px solid #43669a;
padding: 3px 6px;
&::after,
&::before {
content: "";
width: 8px;
height: 1px;
position: absolute;
}
&::before {
background: linear-gradient(
270deg,
rgba(67, 102, 154, 1),
rgba(67, 102, 154, 0)
);
left: -9px;
}
&::after {
background: linear-gradient(
270deg,
rgba(67, 102, 154, 0),
rgba(67, 102, 154, 1)
);
right: -9px;
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<view
class="booking-footer border-box bg-white flex flex-items-center font-family-misans-vf"
>
<text class="font-size-14 font-500 color-525866 mr-4"> 在线付 </text>
<text
class="amt font-size-20 font-bold color-FF3D60 line-height-28 flex flex-items-center mr-8"
>88</text
>
<!-- <view class="flex flex-items-center" @click="emit('detailClick')">
<text class="font-size-12 color-A3A3A3 mr-4">明细</text>
<uni-icons type="up" size="16" color="#A3A3A3" />
</view> -->
<view
class="btn border-box rounded-10 flex flex-items-center ml-auto pl-8"
@click="handleBooking"
>
<image
class="icon"
src="https://oss.nianxx.cn/mp/static/version_101/common/btn.png"
/>
<text class="font-size-16 font-500 color-white">立即预定</text>
</view>
</view>
</template>
<script setup>
import { defineEmits } from "vue";
const emit = defineEmits(["detailClick"]);
</script>
<style scoped lang="scss">
.booking-footer {
border-radius: 15px 15px 0 0;
padding: 12px 12px 42px;
}
.amt {
&::before {
content: "¥";
font-size: 16px;
font-weight: 500;
}
}
.btn {
width: 120px;
height: 48px;
background: linear-gradient(90deg, #ff3d60 57%, #ff990c 100%);
}
.icon {
height: 48px;
width: 34px;
}
</style>

View File

@@ -10,7 +10,11 @@
</view>
<view class="border-box pl-12 pr-12">
<view class="border-box border-bottom pt-12 pb-12 flex flex-items-center">
<view
class="border-box border-bottom pt-12 pb-12 flex flex-items-center"
v-for="item in quantity"
:key="item"
>
<view class="font-size-14 font-500 color-525866 mr-12"> 住客姓名 </view>
<view class="right">
<input

View File

@@ -14,14 +14,33 @@
<view class="booking-content flex-full border-box p-12">
<!-- 预约内容 -->
<view class="border-box bg-white p-12 rounded-12 mb-12">
<view class="font-size-16 font-500 color-000 line-height-24"
>温泉季戏水单人票 +单人简餐</view
>
<!-- 酒店类型入住离店日期部分 -->
<DateRangeSection
v-if="orderData.commodityTypeCode === '0'"
:selectedDate="selectedDate"
@click="navigateToDetail(orderData)"
/>
<view
class="border-box border-bottom font-size-12 color-99A0AE line-height-16 pb-12"
>温泉早鸟票2张 黄南武辣子鸡2人套餐1份</view
>
<view class="font-size-16 font-500 color-000 line-height-24 ellipsis-1">
{{ orderData.commodityName }}
</view>
<view class="border-box border-bottom">
<view class="font-size-12 color-99A0AE line-height-16 pb-12">
{{ orderData.commodityDescription }}
</view>
<!-- 权益部分 -->
<view class="flex flex-items-center mb-8">
<text
class="bg-F7F7F7 border-box rounded-4 font-size-11 color-525866 mr-4 pt-4 pb-4 pl-6 pr-6"
v-for="(item, index) in orderData.commodityFacilityList"
:key="index"
>
{{ item }}
</text>
</view>
</view>
<view
class="border-box flex flex-items-center flex-justify-between pt-12"
@@ -30,7 +49,9 @@
>使用时间:周一至周日9:00-22:00</text
>
<view class="flex flex-items-center">
<text class="font-size-12 color-2D91FF line-height-16"
<text
class="font-size-12 color-2D91FF line-height-16"
@click="refundVisible = true"
>取消政策</text
>
<uni-icons type="right" size="15" color="#99A0AE" />
@@ -39,21 +60,73 @@
</view>
<!-- 非酒店类型 -->
<ContactSection v-if="false" />
<ContactSection v-if="orderData.commodityTypeCode !== '0'" />
<!-- 酒店类型 -->
<UserSection />
<UserSection v-if="orderData.commodityTypeCode === '0'" />
</view>
<!-- 底部 -->
<FooterSection @detailClick="detailVisible = true" />
<!-- 取消政策弹窗 -->
<RefundPopup v-model="refundVisible" :orderData="orderData" />
<!-- 明细弹窗 -->
<DetailPopup v-model="detailVisible" :orderData="orderData" />
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import TopNavBar from "@/components/TopNavBar/index.vue";
import DateRangeSection from "./components/DateRangeSection/index.vue";
import ContactSection from "./components/ConactSection/index.vue";
import UserSection from "./components/UserSection/index.vue";
import RefundPopup from "@/components/RefundPopup/index.vue";
import DetailPopup from "@/components/DetailPopup/index.vue";
import FooterSection from "./components/FooterSection/index.vue";
import { goodsDetail } from "@/request/api/GoodsApi";
const title = ref("预约");
const refundVisible = ref(false);
const detailVisible = ref(false);
const orderData = ref({});
const selectedDate = ref({
startDate: "",
endDate: "",
totalDays: 1,
});
onLoad((options) => {
const { commodityId, startDate, endDate, totalDays } = options;
selectedDate.value.startDate = startDate;
selectedDate.value.endDate = endDate;
selectedDate.value.totalDays = totalDays;
getGoodsDetail(commodityId);
});
const getGoodsDetail = async (commodityId) => {
const res = await goodsDetail({ commodityId });
console.log("获取商品详情", res);
orderData.value = res.data;
// 取commodityFacilityList前3个
orderData.value.commodityFacilityList = res.data.commodityFacilityList.slice(
0,
3
);
};
// 跳转商品详情
const navigateToDetail = ({ commodityId }) => {
uni.navigateTo({
url: `/pages/goods/index?commodityId=${commodityId}`,
});
};
</script>
<style scoped lang="scss">

View File

@@ -1,158 +0,0 @@
# RefundPopup 退款弹窗组件
## 组件概述
`RefundPopup` 是一个用于处理订单退款相关操作的弹窗组件,支持多种退款场景和状态展示。
## 功能需求分析
### 界面设计规范
1. **弹窗容器**
- 使用圆角矩形容器,背景色为白色
- 弹窗宽度适中,居中显示
- 支持遮罩层,点击遮罩可关闭弹窗
2. **头部区域**
- 显示可爱的花朵卡通形象作为视觉元素
- 卡通形象位于弹窗顶部中央位置
3. **内容区域**
- 主标题:根据不同场景显示相应提示文字
- 副标题:显示详细的退款规则和说明
- 金额显示:突出显示可退金额(橙色字体)
- 退款政策:详细列出各种退款条件和规则
4. **按钮区域**
- 双按钮布局:左侧为次要操作,右侧为主要操作
- 按钮样式:圆角矩形,左侧蓝色,右侧橙色
- 按钮文字:根据场景显示不同的操作文字
### 交互功能
1. **弹窗显示/隐藏**
- 支持通过方法调用显示弹窗
- 支持点击遮罩层关闭弹窗
- 支持点击按钮后关闭弹窗
2. **退款场景处理**
- **不予退款场景**:显示"不予退款12小时以内取消或未入住不予退款"
- **免费取消场景**:显示"免费取消提前48小时以上全额退还房费"
- **部分退款场景**:显示退款政策详情和可退金额
- **商品限制场景**:显示"该商品未使用随时可退"
3. **按钮交互**
- 左侧按钮:"退订政策"或"退订政策"(查看详情)
- 右侧按钮:"我知道了"或"点击退款"(确认操作)
- 支持按钮点击事件回调
4. **数据展示**
- 动态显示可退金额
- 展示详细的退款规则列表
- 支持不同退款类型的文案切换
### 技术要求
1. **组件架构**
- 使用 Vue 3 Composition API
- 支持 TypeScript可选
- 使用 uni-popup 作为弹窗基础组件
2. **样式处理**
- 使用 SASS 预处理器
- 支持响应式设计
- 遵循设计规范的颜色和字体
3. **性能优化**
- 懒加载弹窗内容
- 合理使用计算属性
- 避免不必要的重渲染
4. **代码规范**
- 清晰的组件结构
- 详细的注释说明
- 统一的命名规范
### 样式规范
1. **颜色规范**
- 主色调:蓝色 #007AFF(左侧按钮)
- 强调色:橙色 #FF9500(右侧按钮、金额)
- 文字色:黑色 #000000(主要文字)
- 辅助色:灰色 #666666(辅助文字)
- 背景色:白色 #FFFFFF
2. **字体规范**
- 主标题16px加粗
- 副标题14px常规
- 金额18px加粗橙色
- 按钮文字16px加粗
- 说明文字12px常规
3. **间距规范**
- 弹窗内边距20px
- 元素间距12px
- 按钮间距12px
- 按钮高度44px
### 组件接口
#### Props
```typescript
interface RefundPopupProps {
// 弹窗显示状态
visible: boolean
// 退款类型:'no_refund' | 'free_cancel' | 'partial_refund' | 'anytime_refund'
refundType: string
// 可退金额
refundAmount?: number
// 退款规则列表
refundRules?: string[]
// 自定义标题
title?: string
// 自定义描述
description?: string
}
```
#### Events
```typescript
interface RefundPopupEvents {
// 弹窗关闭事件
'update:visible': (visible: boolean) => void
// 查看政策按钮点击
'policy-click': () => void
// 确认按钮点击
'confirm-click': () => void
// 弹窗关闭事件
'close': () => void
}
```
#### Methods
```typescript
interface RefundPopupMethods {
// 显示弹窗
show(): void
// 隐藏弹窗
hide(): void
}
```
### 使用场景
1. **订单详情页面**:用户查看退款政策
2. **退款申请流程**:确认退款操作
3. **客服咨询场景**:展示退款规则
4. **订单管理后台**:处理退款申请
### 文件结构
```
RefundPopup/
├── index.vue # 主组件文件
├── styles/
│ └── index.scss # 样式文件
├── demo.vue # 演示页面
└── README.md # 组件文档
```
### 开发注意事项
1. 确保弹窗在不同屏幕尺寸下的适配
2. 处理长文本的换行和显示
3. 考虑无障碍访问支持
4. 添加适当的动画效果
5. 确保组件的可复用性和可扩展性

View File

@@ -1,389 +0,0 @@
<template>
<view class="demo-container">
<view class="demo-header">
<text class="demo-title">RefundPopup 退款弹窗组件演示</text>
</view>
<view class="demo-content">
<!-- 场景选择 -->
<view class="demo-section">
<text class="section-title">退款场景</text>
<view class="scenario-buttons">
<button
class="scenario-btn"
:class="{ active: currentScenario === 'no_refund' }"
@click="setScenario('no_refund')"
>
不予退款
</button>
<button
class="scenario-btn"
:class="{ active: currentScenario === 'free_cancel' }"
@click="setScenario('free_cancel')"
>
免费取消
</button>
<button
class="scenario-btn"
:class="{ active: currentScenario === 'partial_refund' }"
@click="setScenario('partial_refund')"
>
部分退款
</button>
<button
class="scenario-btn"
:class="{ active: currentScenario === 'anytime_refund' }"
@click="setScenario('anytime_refund')"
>
随时可退
</button>
</view>
</view>
<!-- 金额设置 -->
<view class="demo-section">
<text class="section-title">退款金额</text>
<view class="amount-input">
<input
class="amount-field"
type="number"
v-model="refundAmount"
placeholder="请输入退款金额"
/>
<text class="amount-unit"></text>
</view>
</view>
<!-- 操作按钮 -->
<view class="demo-section">
<button class="demo-btn primary" @click="showPopup">
显示退款弹窗
</button>
</view>
<!-- 事件日志 -->
<view class="demo-section">
<text class="section-title">事件日志</text>
<view class="event-log">
<view
class="log-item"
v-for="(log, index) in eventLogs"
:key="index"
>
<text class="log-time">{{ log.time }}</text>
<text class="log-event">{{ log.event }}</text>
</view>
<view class="log-empty" v-if="eventLogs.length === 0">
暂无事件日志
</view>
</view>
<button class="demo-btn secondary" @click="clearLogs">
清空日志
</button>
</view>
</view>
<!-- RefundPopup 组件 -->
<RefundPopup
v-model="popupVisible"
:refund-type="currentScenario"
:refund-amount="refundAmount"
:refund-rules="customRules"
@policy-click="handlePolicyClick"
@confirm-click="handleConfirmClick"
@close="handleClose"
/>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import RefundPopup from './index.vue'
// 响应式数据
const popupVisible = ref(false)
const currentScenario = ref('no_refund')
const refundAmount = ref(399)
const eventLogs = ref([])
// 自定义退款规则(可选)
const customRules = ref([])
// 方法定义
const setScenario = (scenario) => {
currentScenario.value = scenario
addLog(`切换到场景: ${getScenarioName(scenario)}`)
// 根据场景设置默认金额
switch (scenario) {
case 'no_refund':
refundAmount.value = 0
break
case 'free_cancel':
refundAmount.value = 399
break
case 'partial_refund':
refundAmount.value = 199
break
case 'anytime_refund':
refundAmount.value = 399
break
}
}
const getScenarioName = (scenario) => {
const names = {
'no_refund': '不予退款',
'free_cancel': '免费取消',
'partial_refund': '部分退款',
'anytime_refund': '随时可退'
}
return names[scenario] || scenario
}
const showPopup = () => {
popupVisible.value = true
addLog('显示退款弹窗')
}
const handlePolicyClick = () => {
addLog('点击了退订政策按钮')
// 这里可以跳转到政策详情页面
uni.showToast({
title: '查看退订政策',
icon: 'none'
})
}
const handleConfirmClick = () => {
addLog(`确认操作 - 场景: ${getScenarioName(currentScenario.value)}, 金额: ¥${refundAmount.value}`)
// 根据不同场景执行不同操作
if (currentScenario.value === 'no_refund') {
uni.showToast({
title: '已知晓退款政策',
icon: 'success'
})
} else {
uni.showToast({
title: '退款申请已提交',
icon: 'success'
})
}
}
const handleClose = () => {
addLog('关闭退款弹窗')
}
const addLog = (event) => {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
eventLogs.value.unshift({
time,
event
})
// 限制日志数量
if (eventLogs.value.length > 10) {
eventLogs.value = eventLogs.value.slice(0, 10)
}
}
const clearLogs = () => {
eventLogs.value = []
addLog('清空事件日志')
}
// 初始化
addLog('演示页面已加载')
</script>
<style lang="scss" scoped>
.demo-container {
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.demo-header {
text-align: center;
margin-bottom: 30px;
.demo-title {
font-size: 20px;
font-weight: 600;
color: #333333;
}
}
.demo-content {
.demo-section {
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
display: block;
}
}
}
.scenario-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;
.scenario-btn {
flex: 1;
min-width: 80px;
height: 40px;
border: 2px solid #e0e0e0;
border-radius: 20px;
background: #ffffff;
color: #666666;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
&.active {
border-color: #007aff;
background: #007aff;
color: #ffffff;
}
&:active {
transform: scale(0.95);
}
}
}
.amount-input {
display: flex;
align-items: center;
gap: 8px;
.amount-field {
flex: 1;
height: 44px;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 0 16px;
font-size: 16px;
background: #ffffff;
&:focus {
border-color: #007aff;
outline: none;
}
}
.amount-unit {
font-size: 16px;
color: #666666;
font-weight: 500;
}
}
.demo-btn {
width: 100%;
height: 48px;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
&.primary {
background: #007aff;
color: #ffffff;
&:active {
background: #0056cc;
transform: scale(0.98);
}
}
&.secondary {
background: #f0f0f0;
color: #666666;
margin-top: 12px;
&:active {
background: #e0e0e0;
transform: scale(0.98);
}
}
}
.event-log {
max-height: 200px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 12px;
background: #f9f9f9;
.log-item {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.log-time {
font-size: 12px;
color: #999999;
font-family: monospace;
min-width: 60px;
}
.log-event {
font-size: 14px;
color: #333333;
flex: 1;
}
}
.log-empty {
text-align: center;
color: #999999;
font-size: 14px;
padding: 20px 0;
}
}
// 响应式适配
@media screen and (max-width: 375px) {
.demo-container {
padding: 16px;
}
.demo-content {
.demo-section {
padding: 16px;
margin-bottom: 16px;
}
}
.scenario-buttons {
.scenario-btn {
min-width: 70px;
height: 36px;
font-size: 13px;
}
}
}
</style>

View File

@@ -1,274 +0,0 @@
<template>
<view class="example-page">
<view class="page-header">
<text class="page-title">订单详情</text>
</view>
<view class="order-info">
<view class="order-item">
<text class="item-name">鲜牛肉红烧牛肉面二两+例汤1份</text>
<text class="item-price">¥128</text>
</view>
<view class="order-item">
<text class="item-name">红油干拌鲜肉云吞+例汤1份</text>
<text class="item-price">¥50</text>
</view>
<view class="order-total">
<text class="total-label">总计</text>
<text class="total-price">¥178</text>
</view>
</view>
<view class="order-actions">
<button class="action-button refund-btn" @click="showRefundPopup">
申请退款
</button>
<button class="action-button contact-btn" @click="contactService">
联系客服
</button>
</view>
<!-- 退款弹窗组件 -->
<RefundPopup
v-model="refundVisible"
:refund-type="refundType"
:refund-amount="refundAmount"
@policy-click="viewRefundPolicy"
@confirm-click="handleRefundConfirm"
@close="handleRefundClose"
/>
</view>
</template>
<script setup>
import { ref } from 'vue'
import RefundPopup from './index.vue'
// 响应式数据
const refundVisible = ref(false)
const refundType = ref('partial_refund')
const refundAmount = ref(89) // 50% 退款
// 方法定义
const showRefundPopup = () => {
// 根据订单状态和时间判断退款类型
const orderTime = new Date('2024-01-15 10:00:00')
const currentTime = new Date()
const hoursDiff = (currentTime - orderTime) / (1000 * 60 * 60)
if (hoursDiff < 12) {
refundType.value = 'no_refund'
refundAmount.value = 0
} else if (hoursDiff < 24) {
refundType.value = 'partial_refund'
refundAmount.value = 89 // 50% 退款
} else if (hoursDiff < 48) {
refundType.value = 'free_cancel'
refundAmount.value = 178 // 全额退款
} else {
refundType.value = 'anytime_refund'
refundAmount.value = 178
}
refundVisible.value = true
}
const viewRefundPolicy = () => {
// 跳转到退款政策页面
uni.navigateTo({
url: '/pages/policy/refund'
})
}
const handleRefundConfirm = () => {
if (refundType.value === 'no_refund') {
uni.showToast({
title: '已了解退款政策',
icon: 'success'
})
return
}
// 提交退款申请
uni.showLoading({
title: '提交中...'
})
// 模拟API调用
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '退款申请已提交',
icon: 'success'
})
// 可以跳转到退款状态页面
// uni.navigateTo({
// url: '/pages/refund/status'
// })
}, 2000)
}
const handleRefundClose = () => {
console.log('退款弹窗已关闭')
}
const contactService = () => {
uni.showActionSheet({
itemList: ['在线客服', '电话客服', '意见反馈'],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 打开在线客服
uni.navigateTo({
url: '/pages/service/chat'
})
break
case 1:
// 拨打客服电话
uni.makePhoneCall({
phoneNumber: '400-123-4567'
})
break
case 2:
// 打开意见反馈
uni.navigateTo({
url: '/pages/feedback/index'
})
break
}
}
})
}
</script>
<style lang="scss" scoped>
.example-page {
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.page-header {
text-align: center;
margin-bottom: 20px;
.page-title {
font-size: 18px;
font-weight: 600;
color: #333333;
}
}
.order-info {
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-of-type {
border-bottom: none;
}
.item-name {
flex: 1;
font-size: 14px;
color: #333333;
line-height: 1.4;
}
.item-price {
font-size: 16px;
color: #ff6b35;
font-weight: 600;
}
}
.order-total {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 12px;
margin-top: 12px;
border-top: 2px solid #f0f0f0;
.total-label {
font-size: 16px;
color: #333333;
font-weight: 600;
}
.total-price {
font-size: 20px;
color: #ff6b35;
font-weight: 700;
margin-left: 8px;
}
}
}
.order-actions {
display: flex;
gap: 16px;
.action-button {
flex: 1;
height: 48px;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
&.refund-btn {
background: #ff6b35;
color: #ffffff;
&:active {
background: #e55a2b;
transform: scale(0.98);
}
}
&.contact-btn {
background: #007aff;
color: #ffffff;
&:active {
background: #0056cc;
transform: scale(0.98);
}
}
}
}
// 响应式适配
@media screen and (max-width: 375px) {
.example-page {
padding: 16px;
}
.order-info {
padding: 16px;
margin-bottom: 16px;
}
.order-actions {
gap: 12px;
.action-button {
height: 44px;
font-size: 15px;
}
}
}
</style>

View File

@@ -1,154 +0,0 @@
<template>
<uni-popup ref="popupRef" type="center" @maskClick="handleClose">
<view class="refund-popup">
<!-- 头部卡通形象 -->
<image
:src="commodityPurchaseInstruction.refundLogo"
class="refund-popup__avatar"
mode="aspectFill"
/>
<!-- 内容区域 -->
<view class="refund-popup__content">
<!-- 主标题 -->
<view class="refund-popup__title">
{{ commodityPurchaseInstruction.refundTitle }}
</view>
<!-- 金额显示 -->
<view class="refund-popup__amount" v-if="isRefundable">
<text class="amount-symbol">¥</text>
<text class="amount-value">{{ refundAmount }}</text>
<text class="amount-unit"></text>
</view>
<view class="refund-popup__amount-label" v-if="isRefundable"
>可退金额</view
>
<!-- 退款政策详情 -->
<view class="refund-popup__policy" v-if="showRefundPolicy">
<view class="policy-title">退订政策</view>
<view class="policy-content">
{{ commodityPurchaseInstruction.refundContent }}
</view>
</view>
</view>
<!-- 按钮区域 -->
<view class="refund-popup__actions">
<view
v-if="!showRefundPolicy"
class="action-btn secondary-btn"
@click="handlePolicyClick"
>
退订政策
</view>
<view
class="action-btn primary-btn"
@click="handleConfirmClick(btnText)"
>
{{ btnText }}
</view>
</view>
<!-- 关闭按钮 -->
<uni-icons
class="refund-popup__close"
type="close"
size="40"
color="#fff"
@click="handleClose"
/>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed, watch } from "vue";
// Props定义
const props = defineProps({
// 弹窗显示状态
modelValue: {
type: Boolean,
default: false,
},
// 订单数据
orderData: {
type: Object,
default: () => {},
},
});
// Events定义
const emit = defineEmits(["update:modelValue", "confirm", "close"]);
// 弹窗引用
const popupRef = ref(null);
// 退款政策是否显示
const showRefundPolicy = ref(false);
// 退款金额
const refundAmount = computed(() => {
if (props.orderData.payAmt) {
return parseFloat(Number(props.orderData.payAmt) || 0);
}
return 0;
});
// 是否可退款
const isRefundable = computed(() => props.orderData.refundable);
// 按钮文件
const btnText = computed(() => (isRefundable.value ? "点击退款" : "我知道了"));
// 获取退款模板
const commodityPurchaseInstruction = computed(() => {
if (props.orderData.commodityPurchaseInstruction) {
return props.orderData.commodityPurchaseInstruction;
}
return {};
});
// 方法定义
const show = () => popupRef.value && popupRef.value.open();
const hide = () => popupRef.value && popupRef.value.close();
// 监听modelValue变化
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
show();
} else {
hide();
}
},
{ immediate: true }
);
const handleClose = () => {
showRefundPolicy.value = false;
emit("update:modelValue", false);
emit("close");
};
const handlePolicyClick = () => {
showRefundPolicy.value = true;
};
const handleConfirmClick = (text) => {
if (text === "点击退款") {
emit("confirm", props.orderData);
}
handleClose();
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -1,164 +0,0 @@
// RefundPopup 退款弹窗样式
.refund-popup {
width: 320px;
background: linear-gradient(173deg, #cbf6ff 3%, #ffffff 32%);
border-radius: 12px;
box-sizing: border-box;
padding-top: 64px;
position: relative;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
// 头部区域
&__avatar {
width: 132px;
height: 132px;
position: absolute;
top: -75px;
left: 50%;
transform: translateX(-50%);
}
// 内容区域
&__content {
padding: 12px 20px 20px;
text-align: center;
}
&__title {
font-size: $uni-font-size-lg;
font-weight: 500;
color: $uni-text-color;
line-height: 22px;
margin-bottom: 12px;
text-align: center;
}
&__amount {
display: flex;
justify-content: center;
align-items: baseline;
margin-bottom: 4px;
.amount-symbol {
font-size: $uni-font-size-sm;
color: #ff6a00;
}
.amount-value {
font-size: 24px;
color: #ff6a00;
margin: 0 2px;
}
.amount-unit {
font-size: $uni-font-size-sm;
color: #ff6a00;
}
}
&__amount-label {
font-size: $uni-font-size-sm;
color: $uni-text-color;
margin-bottom: 16px;
}
&__policy {
text-align: left;
margin-bottom: 16px;
.policy-title {
font-size: $uni-font-size-base;
color: #007aff;
font-weight: 600;
margin-bottom: 8px;
}
.policy-content {
font-size: $uni-font-size-sm;
color: $uni-text-color;
line-height: 22px;
text-align: justify;
}
}
// 按钮区域
&__actions {
display: flex;
gap: 12px;
padding: 0 20px 20px;
.action-btn {
flex: 1;
height: 44px;
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
font-size: $uni-font-size-lg;
transition: all 0.3s ease;
outline: none;
&.secondary-btn {
background: #007aff;
color: #ffffff;
&:active {
background: #0056cc;
transform: scale(0.98);
}
}
&.primary-btn {
background: #ff9500;
color: #ffffff;
&:active {
background: #e6850e;
transform: scale(0.98);
}
}
}
}
// 关闭按钮
&__close {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 40px;
}
}
// 动画效果
@keyframes popupFadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes flowerBounce {
0%,
20%,
50%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-4px);
}
60% {
transform: translateY(-2px);
}
}
.refund-popup {
animation: popupFadeIn 0.3s ease-out;
}

View File

@@ -50,7 +50,7 @@ import GoodsInfo from "./components/GoodsInfo/index.vue";
import UserInfo from "./components/UserInfo/index.vue";
import NoticeInfo from "./components/NoticeInfo/index.vue";
import OrderInfo from "./components/OrderInfo/index.vue";
import RefundPopup from "./components/RefundPopup/index.vue";
import RefundPopup from "@/components/RefundPopup/index.vue";
const refundVisible = ref(false);
const orderData = ref({});

View File

@@ -61,6 +61,10 @@ const props = defineProps({
stockUnitLabel: "", // 库存单位
}),
},
selectedDate: {
type: Object,
default: () => {},
},
});
const handleClick = ({ commodityId }) => {
@@ -68,7 +72,10 @@ const handleClick = ({ commodityId }) => {
};
const handleBooking = ({ commodityId }) => {
uni.navigateTo({ url: `/pages/booking/index?commodityId=${commodityId}` });
const { startDate, endDate, totalDays } = props.selectedDate;
uni.navigateTo({
url: `/pages-booking/index?commodityId=${commodityId}&startDate=${startDate}&endDate=${endDate}&totalDays=${totalDays}`,
});
};
</script>

View File

@@ -50,7 +50,12 @@
</view>
</template>
<Card v-for="(item, index) in dataList" :key="index" :item="item" />
<Card
v-for="(item, index) in dataList"
:key="index"
:item="item"
:selectedDate="selectedDate"
/>
</z-paging>
<!-- 日历组件 -->

View File

@@ -7,6 +7,10 @@
background-color: #f5f5f5;
}
.bg-F7F7F7 {
background-color: #f7f7f7;
}
.bg-17294E {
background-color: #17294e;
}

View File

@@ -1,4 +1,12 @@
// 内边距
.pt-4 {
padding-top: 4px;
}
.pb-4 {
padding-bottom: 4px;
}
.p-6 {
padding: 6px;
}