refactor: migrate login and agree popup to Vant and Tailwind

- replace uni-popup/uni-icons with Vant van-popup/van-icon in AgreePopup
- switch markdown rendering to vue3-markdown-it from zero-markdown-div
- remove unused AgreePopup demo, styles, README and image assets
- refactor login page to use Tailwind utilities instead of legacy SCSS
- replace uni.showToast with auto-imported vant showToast, add global type definition
- update package dependencies and refresh yarn.lock
- clean up unused uni-app specific imports and code
This commit is contained in:
DEV_DSW
2026-05-27 12:04:24 +08:00
parent df2f158018
commit 3ab5c596cf
10 changed files with 178 additions and 432 deletions

View File

@@ -1,71 +0,0 @@
# 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 # 设计稿参考图
```

View File

@@ -1,127 +0,0 @@
<template>
<div class="demo-container">
<div class="demo-header">
<span class="demo-title">AgreePopup 组件演示</span>
</div>
<div class="demo-content">
<button class="demo-btn" @click="showPopup">显示用户协议弹窗</button>
<div class="demo-info">
<span class="info-title">组件状态</span>
<span class="info-span">弹窗可见{{ popupVisible }}</span>
<span class="info-span">用户操作{{ userAction }}</span>
</div>
</div>
<!-- AgreePopup 组件 -->
<AgreePopup :visible="popupVisible" title="温馨提示" @agree="handleAgree" @close="handleClose" @cancel="handleCancel" />
</div>
</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 {
span-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-color: #fff;
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-span {
font-size: 14px;
color: #666666;
display: block;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,31 +1,35 @@
<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<div class="agree-popup">
<van-popup ref="popup" position="center" v-model:show="show">
<div class="w-[327px] bg-white rounded-[12px]">
<!-- 弹窗头部 -->
<div class="popup-header">
<div class="popup-title">{{ title }}</div>
<div class="close-btn" @click="handleClose">
<uni-icons type="closeempty" size="24" color="#999" />
<div class="relative px-[20px] pt-[20px]">
<div class="text-[18px] font-[600] text-[#333] text-center leading-[24px]">{{ title }}</div>
<div class="absolute top-[16px] right-[16px] w-[28px] h-[28px] flex items-center justify-center"
@click="handleClose">
<van-icon name="cross" size="24" color="#999" />
</div>
</div>
<!-- 弹窗内容 -->
<div class="popup-content">
<div class="p-[12px] max-h-[400px] overflow-y-auto">
<div class="content-span">
<zero-markdown-div :markdown="agreement" />
<vue3-markdown-it :source="agreement" :html="true" :linkify="true" />
</div>
</div>
<!-- 确认按钮 -->
<div class="button-area">
<div class="confirm-btn" @click="handleClose">我知道了</div>
<div class="p-[20px] flex justify-center items-center">
<div
class="w-[148px] h-[44px] bg-[#22a7ff] rounded-[50px] flex items-center justify-center text-white text-[16px] font-[500]"
@click="handleClose">我知道了</div>
</div>
</div>
</uni-popup>
</van-popup>
</template>
<script setup>
import { ref, watch, defineProps, defineEmits, defineExpose } from "vue";
import Vue3MarkdownIt from 'vue3-markdown-it';
// Props定义
const props = defineProps({
@@ -48,40 +52,25 @@ const emits = defineEmits(["agree", "close", "cancel"]);
// 响应式数据
const popup = ref(null);
// 监听visible变化
watch(
() => props.visible,
(newVal) => {
if (newVal) {
show();
} else {
hide();
}
},
);
const show = ref(false)
// 方法定义
const show = () => {
popup.value?.open();
const open = () => {
show.value = true;
};
const hide = () => {
popup.value?.close();
const close = () => {
show.value = false
};
const handleClose = () => {
hide();
close();
emits("close");
};
// 暴露方法给父组件
defineExpose({
show,
hide,
open,
close,
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,83 +0,0 @@
// AgreePopup 组件样式
.agree-popup {
width: 327px;
background-color: #fff;
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: #333;
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: 12px;
max-height: 400px; // 设置最大高度
overflow-y: auto; // 启用垂直滚动
// 自定义滚动条样式
&::-webkit-scrollbar {
display: none;
}
}
// 按钮区域
.button-area {
padding: 20px;
box-sizing: border-box;
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);
}
}
}
}

View File

@@ -1,62 +1,36 @@
<template>
<div class="login-wrapper bg-liner">
<!-- 返回按钮 -->
<div class="back-btn" @click="goBack">
<uni-icons fontFamily="znicons" size="24" color="#333">
{{ zniconsMap["zn-nav-arrow-left"] }}
</uni-icons>
</div>
<!-- 头部内容 -->
<div class="login-header">
<!-- 卡通形象 -->
<img class="w-full h-full" :src="logo" mode="widthFix" />
</div>
<div class="h-screen flex flex-col items-center justify-center">
<!-- 卡通形象 -->
<img class="w-[200px] h-[200px]" :src="logo" />
<!-- 协议勾选 -->
<div class="login-agreement flex flex-items-center">
<div class="w-[304px] mt-[80px] flex items-center">
<CheckBox v-model="isAgree">
<span class="font-size-12 color-525866">我已阅读并同意</span>
<span class="font-size-12 theme-color-500 ml-4 mr-4" @click.stop="handleAgreeClick('service')">服务协议</span>
<span class="font-size-12 color-525866"></span>
<span class="font-size-12 theme-color-500 ml-4 mr-4" @click.stop="handleAgreeClick('privacy')">隐私协议</span>
<span class="font-size-12 color-525866">\n</span>
<span class="font-size-12 color-525866 ml-30">授权与账号关联操作</span>
<span class="text-[12px] text-ink-600">我已阅读并同意</span>
<span class="text-[12px] text-[#2D91FF] mx-[4px]" @click.stop="handleAgreeClick('service')">服务协议</span>
<span class="text-[12px] text-ink-600"></span>
<span class="text-[12px] text-[#2D91FF] mx-[4px]" @click.stop="handleAgreeClick('privacy')">隐私协议</span>
<span class="text-[12px] text-ink-600 ml-[30px]">授权与账号关联操作</span>
</CheckBox>
</div>
<!-- 按钮区域 -->
<div class="login-btn-area">
<!-- 同意隐私协议并获取手机号按钮 -->
<button v-if="needWxAuthLogin && !isAppleLogin" class="login-btn" type="primary" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber">
{{ loginButtonspan }}
</button>
<button v-else class="login-btn" type="primary" @click="handleAgreeAndGetPhone">
{{ loginButtonspan }}
<div class="w-[304px] mt-[20px]">
<button class="w-full h-[44px] bg-[#0ccd58] text-white rounded-[10px] text-center" @click="getPhoneNumber">
手机号快捷登录
</button>
</div>
<AgreePopup ref="agreePopup" :visible="visible" :agreement="computedAgreement" @close="visible = false" />
<AgreePopup ref="agreePopup" :agreement="computedAgreement" />
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import {
getServiceAgreement,
getPrivacyAgreement,
} from "@/request/api/LoginApi";
import {
onLogin,
goBack,
onCheckPhoneLogin,
onAppleLogin,
} from "@/hooks/useGoLogin";
import { getServiceAgreement, getPrivacyAgreement } from "@/api/login";
import { onLogin, goBack, onCheckPhoneLogin, onAppleLogin } from "@/hooks/useGoLogin";
import CheckBox from "@/components/CheckBox/index.vue";
import AgreePopup from "./components/AgreePopup/index.vue";
import { zniconsMap } from "@/static/fonts/znicons";
import { getCurrentConfig } from "@/constants/base";
// 是否需要微信手机号授权登录
const needWxAuthLogin = ref(false);
@@ -66,16 +40,9 @@ const serviceAgreement = ref("");
const privacyAgreement = ref("");
// 协议类型
const AgreeType = ref("service");
const logo = computed(() => getCurrentConfig().logo);
const logo = ref('');
const isAppleLogin = ref(false);
const loginButtonspan = computed(() =>
isAppleLogin.value ? "Apple ID快捷登录" : "手机号快捷登录",
);
// #ifdef APP-PLUS
const systemInfo = uni.getSystemInfoSync();
isAppleLogin.value = systemInfo.platform === "ios";
// #endif
// 同意隐私协议并获取手机号
const handleAgreeAndGetPhone = () => {
@@ -85,7 +52,7 @@ const handleAgreeAndGetPhone = () => {
}
if (!isAgree.value) {
uni.showToast({
showToast({
title: "请先同意服务协议和隐私协议",
icon: "none",
});
@@ -100,7 +67,7 @@ const handleAgreeAndGetPhone = () => {
}
})
.catch(() => {
uni.showToast({
showToast({
title: "Apple ID登录失败",
icon: "none",
});
@@ -113,7 +80,7 @@ const handleAgreeAndGetPhone = () => {
}
})
.catch(() => {
uni.showToast({
showToast({
title: "手机号登录失败",
icon: "none",
});
@@ -124,7 +91,7 @@ const handleAgreeAndGetPhone = () => {
/// 获取授权后绑定手机号登录
const getPhoneNumber = (e) => {
if (!isAgree.value) {
uni.showToast({
showToast({
title: "请先同意服务协议和隐私协议",
icon: "none",
});
@@ -133,7 +100,7 @@ const getPhoneNumber = (e) => {
onLogin(e)
.then(() => loginSuccess())
.catch(() => {
uni.showToast({
showToast({
title: "获取登录手机号失败",
icon: "none",
});
@@ -142,7 +109,7 @@ const getPhoneNumber = (e) => {
/// 登录成功返回上一页
const loginSuccess = () => {
uni.showToast({
showToast({
title: "登录成功",
icon: "success",
});
@@ -180,21 +147,4 @@ const getPrivacyAgreementData = async () => {
};
getPrivacyAgreementData();
// TODO
// onShow(async () => {
// if (!isAppleLogin.value) {
// // 页面显示时检查微信授权登录状态
// const res = await onCheckPhoneLogin();
// needWxAuthLogin.value = res;
// }
// });
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
@font-face {
font-family: znicons;
src: url("@/assets/fonts/znicons.ttf");
}
</style>

View File

@@ -1,43 +0,0 @@
.login-wrapper {
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
height: 100vh;
padding-top: 100px;
position: relative;
.back-btn {
position: absolute;
top: 44px;
left: 4px;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.login-header {
margin-top: 40px;
width: 200px;
height: 200px;
}
.login-btn-area {
margin-top: 20px;
width: 304px;
.login-btn {
background: #0ccd58;
width: 100%;
border-radius: 10px;
}
}
.login-agreement {
margin-top: 80px;
width: 304px;
}
}