feat: 商品详情支付交互调试
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="checkbox-wrapper" @click="onChange">
|
<view class="checkbox-wrapper" @click.stop="onChange">
|
||||||
<uni-icons
|
<uni-icons
|
||||||
class="checkbox-icon"
|
class="checkbox-icon"
|
||||||
:type="isChecked"
|
:type="isChecked"
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="more-tips">
|
<view class="more-tips">
|
||||||
<view class="more-tips-scroll">
|
<view class="more-tips-scroll">
|
||||||
<view class="more-tips-item" v-for="(item, index) in itemList" :key="index">
|
<view
|
||||||
<text class="more-tips-item-title" @click="sendReply(item)">{{ item }}</text>
|
class="more-tips-item"
|
||||||
|
v-for="(item, index) in itemList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<text class="more-tips-item-title" @click="sendReply(item)">{{
|
||||||
|
item
|
||||||
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -10,33 +16,31 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps } from "vue";
|
import { defineProps } from "vue";
|
||||||
const emits = defineEmits(['replySent']);
|
const emits = defineEmits(["replySent"]);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
itemList: {
|
itemList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: [
|
default: [
|
||||||
'定温泉票',
|
"定温泉票",
|
||||||
'定酒店',
|
"定酒店",
|
||||||
'优惠套餐',
|
"优惠套餐",
|
||||||
'亲子玩法',
|
"亲子玩法",
|
||||||
'了解交通',
|
"了解交通",
|
||||||
'看看酒店',
|
"看看酒店",
|
||||||
'看看美食'
|
"看看美食",
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const sendReply = (text) => {
|
const sendReply = (text) => {
|
||||||
emits('replySent', text); // 向父组件传递数据
|
emits("replySent", text); // 向父组件传递数据
|
||||||
}
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.more-tips {
|
.more-tips {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
&-scroll {
|
&-scroll {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -44,6 +48,8 @@
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
/* 隐藏滚动条 */
|
/* 隐藏滚动条 */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@@ -56,7 +62,7 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
box-shadow: 0 2px 5px 0px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 5px 0px rgba(0, 0, 0, 0.1);
|
||||||
background-color: #FFFFFF;
|
background-color: #ffffff;
|
||||||
padding: 2px 12px;
|
padding: 2px 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -74,7 +80,7 @@
|
|||||||
font-family: PingFang SC, PingFang SC;
|
font-family: PingFang SC, PingFang SC;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #00A6FF;
|
color: #00a6ff;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,11 +66,7 @@
|
|||||||
|
|
||||||
<!-- 数量选择区域 -->
|
<!-- 数量选择区域 -->
|
||||||
<view class="quantity-section">
|
<view class="quantity-section">
|
||||||
<ModuleTitle
|
<ModuleTitle :title="sectionTitle" />
|
||||||
:title="
|
|
||||||
goodsData.commodityTypeCode === '0' ? '订房信息' : '游客信息'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stepper v-model="quantity" />
|
<Stepper v-model="quantity" />
|
||||||
</view>
|
</view>
|
||||||
@@ -83,11 +79,7 @@
|
|||||||
>
|
>
|
||||||
<FormCard
|
<FormCard
|
||||||
v-for="(item, index) in userFormList"
|
v-for="(item, index) in userFormList"
|
||||||
:title="
|
:title="userCardTitle(index)"
|
||||||
goodsData.commodityTypeCode === '0'
|
|
||||||
? `房间${index + 1}`
|
|
||||||
: `游客${index + 1}`
|
|
||||||
"
|
|
||||||
:form="item"
|
:form="item"
|
||||||
:showDeleteIcon="userFormList.length > 1"
|
:showDeleteIcon="userFormList.length > 1"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -119,59 +111,119 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, defineProps, defineEmits } from "vue";
|
import { ref, computed, watch, onMounted, nextTick } from "vue";
|
||||||
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
||||||
import ModuleTitle from "@/components/ModuleTitle/index.vue";
|
import ModuleTitle from "@/components/ModuleTitle/index.vue";
|
||||||
import Stepper from "@/components/Stepper/index.vue";
|
import Stepper from "@/components/Stepper/index.vue";
|
||||||
import FormCard from "@/components/FormCard/index.vue";
|
import FormCard from "@/components/FormCard/index.vue";
|
||||||
import SumCard from "@/components/SumCard/index.vue";
|
import SumCard from "@/components/SumCard/index.vue";
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const showToast = (title, icon = "none", duration = 2000) => {
|
||||||
|
uni.showToast({ title, icon, duration });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidUserForm = (user) => {
|
||||||
|
return (
|
||||||
|
user &&
|
||||||
|
typeof user.name === "string" &&
|
||||||
|
user.name.trim() !== "" &&
|
||||||
|
typeof user.phone === "string" &&
|
||||||
|
user.phone.trim() !== ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
const COMMODITY_TYPES = {
|
||||||
|
HOTEL: "0",
|
||||||
|
TICKET: "1",
|
||||||
|
DINING: "2",
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_PRICE = 399;
|
||||||
|
const MIN_USER_COUNT = 1;
|
||||||
|
|
||||||
// Props定义
|
// Props定义
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
goodsData: {
|
goodsData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({
|
default: () => ({
|
||||||
commodityTypeCode: "0", // 商品类型 0-酒店 1-门票 2-餐饮
|
commodityTypeCode: "0",
|
||||||
|
specificationPrice: "",
|
||||||
|
commodityName: "",
|
||||||
|
commodityPhotoList: [],
|
||||||
|
commodityServiceList: [],
|
||||||
}),
|
}),
|
||||||
|
validator: (value) => {
|
||||||
|
return value && typeof value === "object";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emits定义
|
// Emits定义
|
||||||
const emits = defineEmits(["confirm", "close"]);
|
const emits = defineEmits(["confirm", "close"]);
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const createEmptyUserForm = () => {
|
||||||
|
return { name: "", phone: "" };
|
||||||
|
};
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const popup = ref(null);
|
const popup = ref(null);
|
||||||
const quantity = ref(1);
|
const quantity = ref(MIN_USER_COUNT);
|
||||||
const userFormList = ref([{ name: "", phone: "" }]); // 初始化一个表单项
|
const userFormList = ref([createEmptyUserForm()]); // 初始化一个表单项
|
||||||
const isDeleting = ref(false); // 标志位,防止删除时watch冲突
|
const isDeleting = ref(false); // 标志位,防止删除时watch冲突
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const totalPrice = computed(() => {
|
const totalPrice = computed(() => {
|
||||||
const price = props.goodsData.specificationPrice || 399;
|
const price = props.goodsData?.specificationPrice || DEFAULT_PRICE;
|
||||||
return (price * quantity.value).toFixed(0);
|
return (price * quantity.value).toFixed(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isHotelType = computed(() => {
|
||||||
|
return props.goodsData?.commodityTypeCode === COMMODITY_TYPES.HOTEL;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sectionTitle = computed(() => {
|
||||||
|
return isHotelType.value ? "订房信息" : "游客信息";
|
||||||
|
});
|
||||||
|
|
||||||
|
const userCardTitle = computed(() => {
|
||||||
|
return (index) =>
|
||||||
|
isHotelType.value ? `房间${index + 1}` : `游客${index + 1}`;
|
||||||
|
});
|
||||||
|
|
||||||
// 监听 quantity 变化,动态调整 userFormList
|
// 监听 quantity 变化,动态调整 userFormList
|
||||||
watch(
|
watch(
|
||||||
quantity,
|
quantity,
|
||||||
(newQuantity, oldQuantity) => {
|
async (newQuantity) => {
|
||||||
// 如果正在执行删除操作,跳过watch逻辑
|
// 如果正在执行删除操作,跳过watch逻辑
|
||||||
if (isDeleting.value) {
|
if (isDeleting.value) {
|
||||||
isDeleting.value = false;
|
isDeleting.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保数量不小于最小值
|
||||||
|
if (newQuantity < MIN_USER_COUNT) {
|
||||||
|
quantity.value = MIN_USER_COUNT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentLength = userFormList.value.length;
|
const currentLength = userFormList.value.length;
|
||||||
|
|
||||||
if (newQuantity > currentLength) {
|
if (newQuantity > currentLength) {
|
||||||
// 数量增加,添加新的表单项
|
// 数量增加,添加新的表单项
|
||||||
for (let i = currentLength; i < newQuantity; i++) {
|
const newForms = Array.from({ length: newQuantity - currentLength }, () =>
|
||||||
userFormList.value.push({ name: "", phone: "" });
|
createEmptyUserForm()
|
||||||
}
|
);
|
||||||
|
userFormList.value.push(...newForms);
|
||||||
} else if (newQuantity < currentLength) {
|
} else if (newQuantity < currentLength) {
|
||||||
// 数量减少,删除多余的表单项
|
// 数量减少,删除多余的表单项
|
||||||
userFormList.value.splice(newQuantity);
|
userFormList.value.splice(newQuantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 等待DOM更新完成
|
||||||
|
await nextTick();
|
||||||
},
|
},
|
||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
);
|
);
|
||||||
@@ -187,19 +239,33 @@ const closePopup = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateUserForm = (index, field, value) => {
|
const updateUserForm = (index, field, value) => {
|
||||||
if (userFormList.value[index]) {
|
if (!userFormList.value[index]) {
|
||||||
userFormList.value[index][field] = value;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!["name", "phone"].includes(field)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
userFormList.value[index][field] = value?.toString().trim() || "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUserForm = (index) => {
|
const deleteUserForm = (index) => {
|
||||||
|
// 参数验证
|
||||||
|
if (
|
||||||
|
typeof index !== "number" ||
|
||||||
|
index < 0 ||
|
||||||
|
index >= userFormList.value.length
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 确保至少保留一个表单项
|
// 确保至少保留一个表单项
|
||||||
if (userFormList.value.length <= 1) {
|
if (userFormList.value.length <= MIN_USER_COUNT) {
|
||||||
uni.showToast({
|
const message = isHotelType.value
|
||||||
title: "至少需要一位游客信息",
|
? "至少需要一个房间信息"
|
||||||
icon: "none",
|
: "至少需要一位游客信息";
|
||||||
duration: 2000,
|
showToast(message);
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,21 +279,71 @@ const deleteUserForm = (index) => {
|
|||||||
quantity.value = userFormList.value.length;
|
quantity.value = userFormList.value.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateUserForms = () => {
|
||||||
|
const invalidUsers = userFormList.value.filter((user, index) => {
|
||||||
|
return !isValidUserForm(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalidUsers.length > 0) {
|
||||||
|
const message = isHotelType.value
|
||||||
|
? "请填写完整的房客信息"
|
||||||
|
: "请填写完整的游客信息";
|
||||||
|
showToast(message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const confirmOrder = () => {
|
const confirmOrder = () => {
|
||||||
|
try {
|
||||||
|
// 校验用户信息是否填写完整
|
||||||
|
if (!validateUserForms()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建订单数据
|
||||||
const orderData = {
|
const orderData = {
|
||||||
goodsData: props.goodsData,
|
goodsData: props.goodsData,
|
||||||
quantity: quantity.value,
|
quantity: quantity.value,
|
||||||
totalPrice: totalPrice.value,
|
totalPrice: parseFloat(totalPrice.value),
|
||||||
userFormList: userFormList.value,
|
userFormList: userFormList.value.map((user) => ({
|
||||||
};
|
name: user.name.trim(),
|
||||||
emits("confirm", orderData);
|
phone: user.phone.trim(),
|
||||||
closePopup();
|
})),
|
||||||
|
commodityType: props.goodsData?.commodityTypeCode,
|
||||||
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 触发确认事件
|
||||||
|
emits("confirm", orderData);
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
closePopup();
|
||||||
|
} catch (error) {
|
||||||
|
showToast("订单处理失败,请重试");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化用户表单列表
|
||||||
|
if (userFormList.value.length === 0) {
|
||||||
|
userFormList.value.push(createEmptyUserForm());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露给父组件的方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
showPopup,
|
showPopup,
|
||||||
closePopup,
|
closePopup,
|
||||||
|
resetForm: () => {
|
||||||
|
userFormList.value = [createEmptyUserForm()];
|
||||||
|
quantity.value = MIN_USER_COUNT;
|
||||||
|
},
|
||||||
|
validateForms: validateUserForms,
|
||||||
|
getUserFormList: () => userFormList.value,
|
||||||
|
getTotalPrice: () => totalPrice.value,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ import Calender from "@/components/Calender/index.vue";
|
|||||||
const calendarVisible = ref(false);
|
const calendarVisible = ref(false);
|
||||||
const goodsData = ref({});
|
const goodsData = ref({});
|
||||||
const goodConfirmRef = ref(null);
|
const goodConfirmRef = ref(null);
|
||||||
const selectedDate = ref("");
|
const selectedDate = ref();
|
||||||
const priceData = ref([]);
|
const priceData = ref([]);
|
||||||
|
|
||||||
// 获取商品详情数据
|
// 获取商品详情数据
|
||||||
@@ -117,25 +117,40 @@ const showConfirmPopup = () => {
|
|||||||
|
|
||||||
// 处理确认订单
|
// 处理确认订单
|
||||||
const handleConfirmOrder = async (orderData) => {
|
const handleConfirmOrder = async (orderData) => {
|
||||||
console.log("确认订单:", orderData);
|
console.log("确认订单---1:", orderData);
|
||||||
// const commodityId = orderData.commodityId;
|
const { goodsData } = orderData;
|
||||||
// const purchaseAmount = orderData.purchaseAmount;
|
// 购买的商品id
|
||||||
// const checkInData = orderData.checkInData;
|
const commodityId = goodsData.commodityId;
|
||||||
// const checkOutData = orderData.checkOutData;
|
// 消费者信息
|
||||||
// const consumerInfoEntityList = orderData.consumerInfoEntityList;
|
const consumerInfoEntityList = orderData.userFormList;
|
||||||
// const payWay = "0";
|
// 购买数量
|
||||||
// const paySource = "1";
|
const purchaseAmount = orderData.userFormList.length;
|
||||||
|
// 支付方式 0-微信 1-支付宝 2-云闪付
|
||||||
|
const payWay = "0";
|
||||||
|
// 支付渠道 0-app 1-小程序 2-h5
|
||||||
|
const paySource = "1";
|
||||||
|
|
||||||
// const params = {
|
const params = {
|
||||||
// commodityId,
|
commodityId,
|
||||||
// purchaseAmount,
|
purchaseAmount,
|
||||||
// payWay,
|
payWay,
|
||||||
// paySource,
|
paySource,
|
||||||
// consumerInfoEntityList,
|
consumerInfoEntityList,
|
||||||
// checkInData,
|
};
|
||||||
// checkOutData,
|
|
||||||
// };
|
//酒店类型添加入住时间、离店时间
|
||||||
// const res = await orderPay(params);
|
if (goodsData.commodityTypeCode === "0" && selectedDate.value) {
|
||||||
|
const { startDate, endDate } = selectedDate.value;
|
||||||
|
// 入住时间
|
||||||
|
params.checkInData = startDate;
|
||||||
|
// 离店时间
|
||||||
|
params.checkOutData = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 购买数量
|
||||||
|
|
||||||
|
const res = await orderPay(params);
|
||||||
|
console.log("确认订单---2:", res);
|
||||||
|
|
||||||
// 仅作为示例,非真实参数信息。
|
// 仅作为示例,非真实参数信息。
|
||||||
// uni.requestPayment({
|
// uni.requestPayment({
|
||||||
|
|||||||
71
pages/login/components/AgreePopup/README.md
Normal file
71
pages/login/components/AgreePopup/README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# AgreePopup 用户协议同意弹窗组件
|
||||||
|
|
||||||
|
## 组件概述
|
||||||
|
|
||||||
|
AgreePopup 是一个用于登录流程中的用户协议同意弹窗组件,用于向用户展示隐私政策和用户协议,并获取用户的同意确认。
|
||||||
|
|
||||||
|
## 功能需求
|
||||||
|
|
||||||
|
### 界面设计
|
||||||
|
- **弹窗标题**:显示"温馨提示"标题,居中显示
|
||||||
|
- **关闭按钮**:右上角显示"×"关闭按钮,点击可关闭弹窗
|
||||||
|
- **内容区域**:
|
||||||
|
- 主要说明文字:"您在使用朵花温泉服务前,请仔细阅读用户隐私条款及用户注册须知,当您点击同意,即表示您已经理解并同意该条款,该条款将构成对您具有法律约束力的文件。"
|
||||||
|
- 注意事项:"请您注意:如果您不同意上述用户注册须知、隐私政策或其中任何约定,请您停止注册。如您阅读并点击同意即表示您已充分阅读理解并接受其全部内容,并表明您也同意朵花温泉可以依据以上隐私政策来处理您的个人信息。"
|
||||||
|
|
||||||
|
### 交互功能
|
||||||
|
- **复选框**:
|
||||||
|
- 显示蓝色勾选框
|
||||||
|
- 文字说明:"本人已仔细阅读《用户协议》和《隐私协议》,知悉并诺遵守该内容。"
|
||||||
|
- 支持点击切换选中/未选中状态
|
||||||
|
- **确认按钮**:
|
||||||
|
- 显示"我知道了"按钮
|
||||||
|
- 蓝色背景,白色文字
|
||||||
|
- 圆角设计
|
||||||
|
- 点击后触发同意事件并关闭弹窗
|
||||||
|
|
||||||
|
### 技术要求
|
||||||
|
- 使用 Vue 3 Composition API
|
||||||
|
- 支持弹窗显示/隐藏控制
|
||||||
|
- 提供事件回调:同意、关闭
|
||||||
|
- 响应式设计,适配移动端
|
||||||
|
- 使用 uni-app 框架
|
||||||
|
|
||||||
|
### 样式规范
|
||||||
|
- 弹窗背景:白色
|
||||||
|
- 圆角设计
|
||||||
|
- 文字颜色:深灰色
|
||||||
|
- 按钮:蓝色主题色
|
||||||
|
- 复选框:蓝色选中状态
|
||||||
|
- 适当的内边距和间距
|
||||||
|
|
||||||
|
### 使用场景
|
||||||
|
- 用户首次登录时显示
|
||||||
|
- 隐私政策更新后重新确认
|
||||||
|
- 注册流程中的协议确认
|
||||||
|
|
||||||
|
## 组件接口
|
||||||
|
|
||||||
|
### Props
|
||||||
|
- `visible`: Boolean - 控制弹窗显示/隐藏
|
||||||
|
- `title`: String - 弹窗标题,默认"温馨提示"
|
||||||
|
|
||||||
|
### Events
|
||||||
|
- `@agree`: 用户点击同意时触发
|
||||||
|
- `@close`: 用户关闭弹窗时触发
|
||||||
|
- `@cancel`: 用户取消操作时触发
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
- `show()`: 显示弹窗
|
||||||
|
- `hide()`: 隐藏弹窗
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
```
|
||||||
|
AgreePopup/
|
||||||
|
├── README.md # 组件说明文档
|
||||||
|
├── index.vue # 组件主文件
|
||||||
|
├── styles/
|
||||||
|
│ └── index.scss # 组件样式文件
|
||||||
|
└── images/
|
||||||
|
└── 登录授权1.png # 设计稿参考图
|
||||||
|
```
|
||||||
133
pages/login/components/AgreePopup/demo.vue
Normal file
133
pages/login/components/AgreePopup/demo.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<view class="demo-container">
|
||||||
|
<view class="demo-header">
|
||||||
|
<text class="demo-title">AgreePopup 组件演示</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="demo-content">
|
||||||
|
<button class="demo-btn" @click="showPopup">显示用户协议弹窗</button>
|
||||||
|
|
||||||
|
<view class="demo-info">
|
||||||
|
<text class="info-title">组件状态:</text>
|
||||||
|
<text class="info-text">弹窗可见:{{ popupVisible }}</text>
|
||||||
|
<text class="info-text">用户操作:{{ userAction }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- AgreePopup 组件 -->
|
||||||
|
<AgreePopup
|
||||||
|
:visible="popupVisible"
|
||||||
|
title="温馨提示"
|
||||||
|
@agree="handleAgree"
|
||||||
|
@close="handleClose"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import AgreePopup from './index.vue'
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const popupVisible = ref(false)
|
||||||
|
const userAction = ref('无')
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
const showPopup = () => {
|
||||||
|
popupVisible.value = true
|
||||||
|
userAction.value = '显示弹窗'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAgree = () => {
|
||||||
|
popupVisible.value = false
|
||||||
|
userAction.value = '用户同意协议'
|
||||||
|
console.log('用户同意了协议')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
popupVisible.value = false
|
||||||
|
userAction.value = '用户关闭弹窗'
|
||||||
|
console.log('用户关闭了弹窗')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
popupVisible.value = false
|
||||||
|
userAction.value = '用户取消操作'
|
||||||
|
console.log('用户取消了操作')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.demo-container {
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
|
.demo-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.demo-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.demo-btn {
|
||||||
|
width: 200px;
|
||||||
|
height: 44px;
|
||||||
|
background: #007AFF;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #0056CC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #004499;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-info {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
.info-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
pages/login/components/AgreePopup/images/登录授权1.png
Normal file
BIN
pages/login/components/AgreePopup/images/登录授权1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
91
pages/login/components/AgreePopup/index.vue
Normal file
91
pages/login/components/AgreePopup/index.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup ref="popup" type="center" :mask-click="false">
|
||||||
|
<view class="agree-popup">
|
||||||
|
<!-- 弹窗头部 -->
|
||||||
|
<view class="popup-header">
|
||||||
|
<view class="popup-title">{{ title }}</view>
|
||||||
|
<view class="close-btn" @click="handleClose">
|
||||||
|
<uni-icons type="closeempty" size="24" color="#999" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 弹窗内容 -->
|
||||||
|
<view class="popup-content">
|
||||||
|
<view class="content-text">
|
||||||
|
<text class="main-text">
|
||||||
|
您在使用朵花温泉服务前,请仔细阅读用户隐私条款及用户注册须知,当您点击同意,即表示您已经理解并同意该条款,该条款将构成对您具有法律约束力的文件。
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="notice-text">
|
||||||
|
<text>
|
||||||
|
请您注意:如果您不同意上述用户注册须知、隐私政策或其中任何约定,请您停止注册。如您阅读并点击同意即表示您已充分阅读理解并接受其全部内容,并表明您也同意朵花温泉可以依据以上隐私政策来处理您的个人信息。
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认按钮 -->
|
||||||
|
<view class="button-area">
|
||||||
|
<view class="confirm-btn" @click="handleClose"> 我知道了 </view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, defineProps, defineEmits, defineExpose } from "vue";
|
||||||
|
|
||||||
|
// Props定义
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "温馨提示",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Events定义
|
||||||
|
const emits = defineEmits(["agree", "close", "cancel"]);
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const popup = ref(null);
|
||||||
|
|
||||||
|
// 监听visible变化
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
show();
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
const show = () => {
|
||||||
|
popup.value?.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hide = () => {
|
||||||
|
popup.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
hide();
|
||||||
|
emits("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./styles/index.scss";
|
||||||
|
</style>
|
||||||
95
pages/login/components/AgreePopup/styles/index.scss
Normal file
95
pages/login/components/AgreePopup/styles/index.scss
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// AgreePopup 组件样式
|
||||||
|
.agree-popup {
|
||||||
|
width: 327px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
// 弹窗头部
|
||||||
|
.popup-header {
|
||||||
|
position: relative;
|
||||||
|
padding: 20px 20px 0 20px;
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗内容
|
||||||
|
.popup-content {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.content-text {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.main-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 22px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-text {
|
||||||
|
text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮区域
|
||||||
|
.button-area {
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
width: 148px;
|
||||||
|
height: 44px;
|
||||||
|
background: linear-gradient(90deg, #22a7ff 0%, #2567ff 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #0056cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #004499;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,29 +42,33 @@
|
|||||||
<view class="login-agreement">
|
<view class="login-agreement">
|
||||||
<CheckBox v-model="isAgree">
|
<CheckBox v-model="isAgree">
|
||||||
<text class="login-agreement-text">阅读并同意</text>
|
<text class="login-agreement-text">阅读并同意</text>
|
||||||
<navigator
|
<text
|
||||||
url="/pages/service-agreement/service-agreement"
|
|
||||||
class="login-agreement-link"
|
class="login-agreement-link"
|
||||||
>《服务协议》</navigator
|
@click.stop="handleAgreeClick('service')"
|
||||||
|
>《服务协议》</text
|
||||||
>
|
>
|
||||||
<text class="login-agreement-text">和</text>
|
<text class="login-agreement-text">和</text>
|
||||||
<navigator
|
<text
|
||||||
url="/pages/privacy-policy/privacy-policy"
|
|
||||||
class="login-agreement-link"
|
class="login-agreement-link"
|
||||||
>《隐私协议》</navigator
|
@click.stop="handleAgreeClick('privacy')"
|
||||||
|
>《隐私协议》</text
|
||||||
>
|
>
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<AgreePopup ref="agreePopup" :visible="visible" @close="visible = false" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import CheckBox from "@/components/CheckBox/index.vue";
|
import CheckBox from "@/components/CheckBox/index.vue";
|
||||||
|
import AgreePopup from "./components/AgreePopup/index.vue";
|
||||||
import { loginAuth, bindPhone, checkPhone } from "@/manager/LoginManager";
|
import { loginAuth, bindPhone, checkPhone } from "@/manager/LoginManager";
|
||||||
import { goHome } from "@/hooks/useGoHome";
|
import { goHome } from "@/hooks/useGoHome";
|
||||||
|
|
||||||
const isAgree = ref(false);
|
const isAgree = ref(false);
|
||||||
|
const visible = ref(false);
|
||||||
|
|
||||||
// 同意隐私协议并获取手机号
|
// 同意隐私协议并获取手机号
|
||||||
const handleAgreeAndGetPhone = () => {
|
const handleAgreeAndGetPhone = () => {
|
||||||
@@ -103,6 +107,11 @@ const onLogin = (e) => {
|
|||||||
console.error("登录失败", err);
|
console.error("登录失败", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理同意协议点击事件
|
||||||
|
const handleAgreeClick = (type) => {
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user