Merge branch 'main' of https://git.brother7.cn/zoujing/YGChatCS
This commit is contained in:
commit
6422530057
@ -51,7 +51,6 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
image {
|
image {
|
||||||
|
|||||||
234
pages/goods/components/GoodConfirm/README.md
Normal file
234
pages/goods/components/GoodConfirm/README.md
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# GoodConfirm 商品确认组件
|
||||||
|
|
||||||
|
基于 uni-popup 弹出层的商品确认组件,提供优雅的商品购买确认界面。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🎨 **现代化设计** - 采用底部弹出设计,符合移动端交互习惯
|
||||||
|
- 📱 **响应式布局** - 完美适配各种屏幕尺寸
|
||||||
|
- 🛒 **商品信息展示** - 支持商品图片、标题、价格、标签展示
|
||||||
|
- 🔢 **数量选择** - 提供加减按钮和手动输入两种方式
|
||||||
|
- 💰 **实时计算** - 自动计算并显示总价
|
||||||
|
- ⚡ **性能优化** - 基于 Vue 3 Composition API,性能卓越
|
||||||
|
- 🎭 **动画效果** - 流畅的弹出和交互动画
|
||||||
|
- 🔧 **高度可配置** - 支持自定义商品数据和事件处理
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
### 默认使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<button @click="showConfirm">显示确认弹窗</button>
|
||||||
|
|
||||||
|
<GoodConfirm
|
||||||
|
ref="confirmRef"
|
||||||
|
:goodsData="goodsData"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
@close="handleClose"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import GoodConfirm from '@/pages/goods/components/GoodConfirm/index.vue'
|
||||||
|
|
||||||
|
const confirmRef = ref(null)
|
||||||
|
const goodsData = ref({
|
||||||
|
commodityName: '【成人票】云从朵花温泉门票',
|
||||||
|
price: 399,
|
||||||
|
timeTag: '随时可退',
|
||||||
|
commodityPhotoList: [
|
||||||
|
{
|
||||||
|
photoUrl: '/static/test/mk_img_1.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const showConfirm = () => {
|
||||||
|
confirmRef.value?.showPopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = (orderData) => {
|
||||||
|
console.log('确认订单:', orderData)
|
||||||
|
// 处理订单确认逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
console.log('关闭弹窗')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义商品数据
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const customGoodsData = ref({
|
||||||
|
commodityName: '【亲子套票】海洋世界一日游',
|
||||||
|
price: 268,
|
||||||
|
timeTag: '限时优惠',
|
||||||
|
commodityPhotoList: [
|
||||||
|
{
|
||||||
|
photoUrl: '/path/to/custom/image.jpg'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 文档
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| goodsData | Object | {} | 商品数据对象 |
|
||||||
|
|
||||||
|
#### goodsData 对象结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GoodsData {
|
||||||
|
commodityName?: string; // 商品名称
|
||||||
|
price?: number; // 商品价格
|
||||||
|
timeTag?: string; // 时间标签(如:随时可退)
|
||||||
|
commodityPhotoList?: Array<{ // 商品图片列表
|
||||||
|
photoUrl: string; // 图片URL
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| 事件名 | 参数 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| confirm | orderData | 确认购买时触发 |
|
||||||
|
| close | - | 关闭弹窗时触发 |
|
||||||
|
|
||||||
|
#### confirm 事件参数
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface OrderData {
|
||||||
|
goodsData: GoodsData; // 商品数据
|
||||||
|
quantity: number; // 购买数量
|
||||||
|
totalPrice: string; // 总价(字符串格式)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
| 方法名 | 参数 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| showPopup | - | 显示弹窗 |
|
||||||
|
| closePopup | - | 关闭弹窗 |
|
||||||
|
|
||||||
|
## 样式定制
|
||||||
|
|
||||||
|
组件使用 SCSS 编写样式,支持以下自定义变量:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
// 主色调
|
||||||
|
$primary-color: #ff6b35;
|
||||||
|
$primary-gradient: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
|
||||||
|
|
||||||
|
// 文字颜色
|
||||||
|
$text-primary: #333;
|
||||||
|
$text-secondary: #666;
|
||||||
|
|
||||||
|
// 背景颜色
|
||||||
|
$bg-white: #fff;
|
||||||
|
$bg-gray: #f8f9fa;
|
||||||
|
$border-color: #f5f5f5;
|
||||||
|
|
||||||
|
// 圆角
|
||||||
|
$border-radius: 8px;
|
||||||
|
$border-radius-large: 20px;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 响应式数据绑定
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const goodsData = computed(() => ({
|
||||||
|
commodityName: store.currentGoods.name,
|
||||||
|
price: store.currentGoods.price,
|
||||||
|
timeTag: store.currentGoods.refundPolicy,
|
||||||
|
commodityPhotoList: store.currentGoods.images
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 订单处理集成
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { createOrder } from '@/api/order'
|
||||||
|
|
||||||
|
const handleConfirm = async (orderData) => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '处理中...' })
|
||||||
|
|
||||||
|
const result = await createOrder({
|
||||||
|
commodityId: goodsData.value.id,
|
||||||
|
quantity: orderData.quantity,
|
||||||
|
totalAmount: orderData.totalPrice
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '订单创建成功', icon: 'success' })
|
||||||
|
|
||||||
|
// 跳转到支付页面
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/payment/index?orderId=${result.orderId}`
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '订单创建失败', icon: 'error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **依赖要求**:组件依赖 `uni-popup` 和 `uni-icons`,请确保项目中已安装相关依赖
|
||||||
|
2. **图片资源**:请确保商品图片路径正确,建议使用绝对路径或网络图片
|
||||||
|
3. **数量限制**:组件默认最小购买数量为 1,可根据业务需求调整
|
||||||
|
4. **价格格式**:价格支持数字类型,组件内部会自动处理格式化
|
||||||
|
5. **事件处理**:建议在 `confirm` 事件中添加适当的错误处理和用户反馈
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.0.0 (2024-01-XX)
|
||||||
|
- ✨ 初始版本发布
|
||||||
|
- 🎨 基于 uni-popup 的底部弹出设计
|
||||||
|
- 🛒 完整的商品信息展示功能
|
||||||
|
- 🔢 数量选择和总价计算
|
||||||
|
- 📱 响应式移动端适配
|
||||||
|
- 🎭 流畅的动画效果
|
||||||
|
- 📚 完整的文档和示例
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **框架**: Vue 3 + Composition API
|
||||||
|
- **UI组件**: uni-app + uni-ui
|
||||||
|
- **样式**: SCSS
|
||||||
|
- **构建工具**: Vite
|
||||||
|
|
||||||
|
## 浏览器支持
|
||||||
|
|
||||||
|
- iOS Safari 10+
|
||||||
|
- Android Chrome 50+
|
||||||
|
- 微信小程序
|
||||||
|
- 支付宝小程序
|
||||||
|
- H5 现代浏览器
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
191
pages/goods/components/GoodConfirm/demo.vue
Normal file
191
pages/goods/components/GoodConfirm/demo.vue
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<view class="demo-container">
|
||||||
|
<TopNavBar title="商品确认组件演示" :fixed="true" />
|
||||||
|
|
||||||
|
<view class="content-wrapper">
|
||||||
|
<view class="demo-section">
|
||||||
|
<view class="section-title">基础用法</view>
|
||||||
|
<view class="demo-item">
|
||||||
|
<button class="demo-btn" @click="showBasicDemo">显示基础确认弹窗</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="demo-section">
|
||||||
|
<view class="section-title">自定义商品数据</view>
|
||||||
|
<view class="demo-item">
|
||||||
|
<button class="demo-btn" @click="showCustomDemo">显示自定义商品弹窗</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="demo-section">
|
||||||
|
<view class="section-title">功能特性</view>
|
||||||
|
<view class="feature-list">
|
||||||
|
<view class="feature-item">✓ 基于 uni-popup 弹出层组件</view>
|
||||||
|
<view class="feature-item">✓ 商品信息展示(图片、标题、价格、标签)</view>
|
||||||
|
<view class="feature-item">✓ 数量选择控制(加减按钮、手动输入)</view>
|
||||||
|
<view class="feature-item">✓ 实时总价计算</view>
|
||||||
|
<view class="feature-item">✓ 确认购买和关闭事件</view>
|
||||||
|
<view class="feature-item">✓ 响应式设计,适配移动端</view>
|
||||||
|
<view class="feature-item">✓ 优雅的动画效果</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 基础演示弹窗 -->
|
||||||
|
<GoodConfirm
|
||||||
|
ref="basicDemoRef"
|
||||||
|
:goodsData="basicGoodsData"
|
||||||
|
@confirm="handleBasicConfirm"
|
||||||
|
@close="handleBasicClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 自定义演示弹窗 -->
|
||||||
|
<GoodConfirm
|
||||||
|
ref="customDemoRef"
|
||||||
|
:goodsData="customGoodsData"
|
||||||
|
@confirm="handleCustomConfirm"
|
||||||
|
@close="handleCustomClose"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import TopNavBar from '@/components/TopNavBar/index.vue'
|
||||||
|
import GoodConfirm from './index.vue'
|
||||||
|
|
||||||
|
// 引用
|
||||||
|
const basicDemoRef = ref(null)
|
||||||
|
const customDemoRef = ref(null)
|
||||||
|
|
||||||
|
// 基础商品数据
|
||||||
|
const basicGoodsData = ref({
|
||||||
|
commodityName: '【成人票】云从朵花温泉门票',
|
||||||
|
price: 399,
|
||||||
|
timeTag: '随时可退',
|
||||||
|
commodityPhotoList: [
|
||||||
|
{
|
||||||
|
photoUrl: '/static/test/mk_img_1.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 自定义商品数据
|
||||||
|
const customGoodsData = ref({
|
||||||
|
commodityName: '【亲子套票】海洋世界一日游',
|
||||||
|
price: 268,
|
||||||
|
timeTag: '限时优惠',
|
||||||
|
commodityPhotoList: [
|
||||||
|
{
|
||||||
|
photoUrl: '/static/test/mk_img_1.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
const showBasicDemo = () => {
|
||||||
|
basicDemoRef.value?.showPopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCustomDemo = () => {
|
||||||
|
customDemoRef.value?.showPopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBasicConfirm = (orderData) => {
|
||||||
|
console.log('基础演示确认订单:', orderData)
|
||||||
|
uni.showModal({
|
||||||
|
title: '订单确认',
|
||||||
|
content: `商品:${orderData.goodsData.commodityName}\n数量:${orderData.quantity}\n总价:¥${orderData.totalPrice}`,
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBasicClose = () => {
|
||||||
|
console.log('基础演示关闭弹窗')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomConfirm = (orderData) => {
|
||||||
|
console.log('自定义演示确认订单:', orderData)
|
||||||
|
uni.showModal({
|
||||||
|
title: '订单确认成功',
|
||||||
|
content: `商品:${orderData.goodsData.commodityName}\n数量:${orderData.quantity}\n总价:¥${orderData.totalPrice}`,
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomClose = () => {
|
||||||
|
console.log('自定义演示关闭弹窗')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.demo-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding: 100px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #ff6b35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: 0 1px 4px rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
.feature-item {
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
pages/goods/components/GoodConfirm/images/商品2级 门票.png
Normal file
BIN
pages/goods/components/GoodConfirm/images/商品2级 门票.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
151
pages/goods/components/GoodConfirm/index.vue
Normal file
151
pages/goods/components/GoodConfirm/index.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup ref="popup" type="bottom">
|
||||||
|
<view class="good-confirm-container">
|
||||||
|
<!-- 头部标题栏 -->
|
||||||
|
<view class="header">
|
||||||
|
<view class="header-title">确认订单</view>
|
||||||
|
<view class="close-btn" @click="closePopup">
|
||||||
|
<uni-icons type="closeempty" size="20" color="#666"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品信息区域 -->
|
||||||
|
<view class="goods-info">
|
||||||
|
<view class="goods-image">
|
||||||
|
<image
|
||||||
|
:src="
|
||||||
|
goodsData.commodityPhotoList?.[0]?.photoUrl ||
|
||||||
|
'/static/test/mk_img_1.png'
|
||||||
|
"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="goods-details">
|
||||||
|
<view class="goods-title">{{
|
||||||
|
goodsData.commodityName || "商品名称"
|
||||||
|
}}</view>
|
||||||
|
<view class="goods-price">
|
||||||
|
<text class="currency">¥</text>
|
||||||
|
<text class="price">{{ goodsData.price || 399 }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="goods-tag" v-if="goodsData.timeTag">
|
||||||
|
{{ goodsData.timeTag }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 数量选择区域 -->
|
||||||
|
<view class="quantity-section">
|
||||||
|
<view class="quantity-label">购买数量</view>
|
||||||
|
<view class="quantity-control">
|
||||||
|
<view
|
||||||
|
class="quantity-btn"
|
||||||
|
:class="{ disabled: quantity <= 1 }"
|
||||||
|
@click="decreaseQuantity"
|
||||||
|
>
|
||||||
|
<uni-icons type="minus" size="16" color="#666"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="quantity-input">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="quantity"
|
||||||
|
@input="handleQuantityInput"
|
||||||
|
:disabled="false"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="quantity-btn" @click="increaseQuantity">
|
||||||
|
<uni-icons type="plus" size="16" color="#666"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 总价区域 -->
|
||||||
|
<view class="total-section">
|
||||||
|
<view class="total-label">合计</view>
|
||||||
|
<view class="total-price">
|
||||||
|
<text class="currency">¥</text>
|
||||||
|
<text class="price">{{ totalPrice }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部按钮区域 -->
|
||||||
|
<view class="footer">
|
||||||
|
<button class="confirm-btn" @click="confirmOrder">确认购买</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, defineProps, defineEmits } from "vue";
|
||||||
|
|
||||||
|
// Props定义
|
||||||
|
const props = defineProps({
|
||||||
|
goodsData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits定义
|
||||||
|
const emits = defineEmits(["confirm", "close"]);
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const popup = ref(null);
|
||||||
|
const quantity = ref(1);
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
const price = props.goodsData.price || 399;
|
||||||
|
return (price * quantity.value).toFixed(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
const showPopup = () => {
|
||||||
|
popup.value?.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closePopup = () => {
|
||||||
|
popup.value?.close();
|
||||||
|
emits("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
const increaseQuantity = () => {
|
||||||
|
quantity.value++;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decreaseQuantity = () => {
|
||||||
|
if (quantity.value > 1) {
|
||||||
|
quantity.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuantityInput = (e) => {
|
||||||
|
const value = parseInt(e.detail.value);
|
||||||
|
if (value && value > 0) {
|
||||||
|
quantity.value = value;
|
||||||
|
} else {
|
||||||
|
quantity.value = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmOrder = () => {
|
||||||
|
const orderData = {
|
||||||
|
goodsData: props.goodsData,
|
||||||
|
quantity: quantity.value,
|
||||||
|
totalPrice: totalPrice.value,
|
||||||
|
};
|
||||||
|
emits("confirm", orderData);
|
||||||
|
closePopup();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
showPopup,
|
||||||
|
closePopup,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./styles/index.scss";
|
||||||
|
</style>
|
||||||
239
pages/goods/components/GoodConfirm/styles/index.scss
Normal file
239
pages/goods/components/GoodConfirm/styles/index.scss
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
.good-confirm-container {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 20px 16px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
display: flex;
|
||||||
|
padding: 20px;
|
||||||
|
gap: 12px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
|
||||||
|
.goods-image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.goods-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
line-height: 22px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #fff2e8;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ffdbcc;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
|
||||||
|
.quantity-label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.quantity-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f8f8f8;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:active:not(.disabled) {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-input {
|
||||||
|
width: 60px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #e8e8e8;
|
||||||
|
border-right: 1px solid #e8e8e8;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
|
||||||
|
.total-label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,8 +12,21 @@
|
|||||||
<ModuleTitle title="购买须知" />
|
<ModuleTitle title="购买须知" />
|
||||||
|
|
||||||
<zero-markdown-view :markdown="goodsData.commodityTip" :fontSize="14" />
|
<zero-markdown-view :markdown="goodsData.commodityTip" :fontSize="14" />
|
||||||
|
|
||||||
|
<!-- 立即抢购 -->
|
||||||
|
<view class="footer">
|
||||||
|
<button class="buy-button" @click="showConfirmPopup">立即抢购</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品确认弹窗 -->
|
||||||
|
<GoodConfirm
|
||||||
|
ref="goodConfirmRef"
|
||||||
|
:goodsData="goodsData"
|
||||||
|
@confirm="handleConfirmOrder"
|
||||||
|
@close="handleCloseConfirm"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -25,8 +38,11 @@ import TopNavBar from "@/components/TopNavBar/index.vue";
|
|||||||
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
||||||
import GoodInfo from "./components/GoodInfo/index.vue";
|
import GoodInfo from "./components/GoodInfo/index.vue";
|
||||||
import ModuleTitle from "@/components/ModuleTitle/index.vue";
|
import ModuleTitle from "@/components/ModuleTitle/index.vue";
|
||||||
|
import GoodConfirm from "./components/GoodConfirm/index.vue";
|
||||||
|
|
||||||
const goodsData = ref({});
|
const goodsData = ref({});
|
||||||
|
const goodConfirmRef = ref(null);
|
||||||
|
|
||||||
// 获取商品详情数据
|
// 获取商品详情数据
|
||||||
const goodsInfo = async (params) => {
|
const goodsInfo = async (params) => {
|
||||||
const res = await goodsDetail(params);
|
const res = await goodsDetail(params);
|
||||||
@ -34,6 +50,29 @@ const goodsInfo = async (params) => {
|
|||||||
goodsData.value = res.data;
|
goodsData.value = res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 显示确认弹窗
|
||||||
|
const showConfirmPopup = () => {
|
||||||
|
goodConfirmRef.value?.showPopup();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理确认订单
|
||||||
|
const handleConfirmOrder = (orderData) => {
|
||||||
|
console.log("确认订单:", orderData);
|
||||||
|
uni.showToast({
|
||||||
|
title: "订单确认成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
// 这里可以跳转到订单页面或支付页面
|
||||||
|
// uni.navigateTo({
|
||||||
|
// url: '/pages/order/detail?orderId=' + orderData.orderId
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理关闭弹窗
|
||||||
|
const handleCloseConfirm = () => {
|
||||||
|
console.log("关闭确认弹窗");
|
||||||
|
};
|
||||||
|
|
||||||
onLoad(({ commodityId = "1950766939442774018" }) => {
|
onLoad(({ commodityId = "1950766939442774018" }) => {
|
||||||
goodsInfo({ commodityId });
|
goodsInfo({ commodityId });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
$button-color: #00a6ff;
|
||||||
|
$button-hover-color: darken($button-color, 8%);
|
||||||
|
|
||||||
.goods-container {
|
.goods-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@ -5,6 +8,8 @@
|
|||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
// 为固定导航栏预留空间
|
// 为固定导航栏预留空间
|
||||||
padding-top: calc(var(--status-bar-height, 44px) + 68px);
|
padding-top: calc(var(--status-bar-height, 44px) + 68px);
|
||||||
|
// 为安全区预留空间
|
||||||
|
padding-bottom: calc(var(--safe-area-inset-bottom, 0px) + 100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-content {
|
.goods-content {
|
||||||
@ -15,4 +20,77 @@
|
|||||||
margin-top: -30px;
|
margin-top: -30px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
// 为安全区预留空间
|
||||||
|
padding-bottom: var(--safe-area-inset-bottom, 0);
|
||||||
|
|
||||||
|
.buy-button {
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(179deg, #00a6ff 0%, #0256ff 100%);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50px;
|
||||||
|
height: 42px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 12px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
|
||||||
|
// 按钮波纹效果
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: width 0.6s, height 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
$button-hover-color 0%,
|
||||||
|
darken($button-hover-color, 5%) 100%
|
||||||
|
);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 16px rgba($button-color, 0.4);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba($button-color, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba($button-color, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
uni_modules/uni-popup/changelog.md
Normal file
100
uni_modules/uni-popup/changelog.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
## 1.9.10(2025-07-18)
|
||||||
|
- 修复 nvue 下弹窗样式错乱的问题 ,更新依赖 uni-transition 组件
|
||||||
|
- 更新 示例取消 borderRadius 属性 ,如需内容圆角,用户应该直接在内容插槽中实现
|
||||||
|
## 1.9.9(2025-06-11)
|
||||||
|
- 修复 uni-popup-dialog 中 setVal 方法报错的问题
|
||||||
|
- 修复 uni-popup-dialog 数据双向绑定问题。
|
||||||
|
## 1.9.8(2025-04-16)
|
||||||
|
- 修复 更新组件示例 ,解决更新数据或保存项目导致弹窗消失的问题
|
||||||
|
## 1.9.7(2025-04-14)
|
||||||
|
- 修复 uni-popup-dialog 弹出框在vue3中双向绑定问题
|
||||||
|
## 1.9.6(2025-01-08)
|
||||||
|
- 修复 示例中过期图片地址
|
||||||
|
## 1.9.5(2024-10-15)
|
||||||
|
- 修复 微信小程序中的getSystemInfo警告
|
||||||
|
## 1.9.2(2024-09-21)
|
||||||
|
- 修复 uni-popup在android上的重复点击弹出位置不正确的bug
|
||||||
|
## 1.9.1(2024-04-02)
|
||||||
|
- 修复 uni-popup-dialog vue3下使用value无法进行绑定的bug(双向绑定兼容旧写法)
|
||||||
|
## 1.9.0(2024-03-28)
|
||||||
|
- 修复 uni-popup-dialog 双向绑定时初始化逻辑修正
|
||||||
|
## 1.8.9(2024-03-20)
|
||||||
|
- 修复 uni-popup-dialog 数据输入时修正为双向绑定
|
||||||
|
## 1.8.8(2024-02-20)
|
||||||
|
- 修复 uni-popup 在微信小程序下出现文字向上闪动的bug
|
||||||
|
## 1.8.7(2024-02-02)
|
||||||
|
- 新增 uni-popup-dialog 新增属性focus:input模式下,是否自动自动聚焦
|
||||||
|
## 1.8.6(2024-01-30)
|
||||||
|
- 新增 uni-popup-dialog 新增属性maxLength:限制输入框字数
|
||||||
|
## 1.8.5(2024-01-26)
|
||||||
|
- 新增 uni-popup-dialog 新增属性showClose:控制关闭按钮的显示
|
||||||
|
## 1.8.4(2023-11-15)
|
||||||
|
- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close`
|
||||||
|
## 1.8.3(2023-04-17)
|
||||||
|
- 修复 uni-popup 重复打开时的 bug
|
||||||
|
## 1.8.2(2023-02-02)
|
||||||
|
- uni-popup-dialog 组件新增 inputType 属性
|
||||||
|
## 1.8.1(2022-12-01)
|
||||||
|
- 修复 nvue 下 v-show 报错
|
||||||
|
## 1.8.0(2022-11-29)
|
||||||
|
- 优化 主题样式
|
||||||
|
## 1.7.9(2022-04-02)
|
||||||
|
- 修复 弹出层内部无法滚动的bug
|
||||||
|
## 1.7.8(2022-03-28)
|
||||||
|
- 修复 小程序中高度错误的bug
|
||||||
|
## 1.7.7(2022-03-17)
|
||||||
|
- 修复 快速调用open出现问题的Bug
|
||||||
|
## 1.7.6(2022-02-14)
|
||||||
|
- 修复 safeArea 属性不能设置为false的bug
|
||||||
|
## 1.7.5(2022-01-19)
|
||||||
|
- 修复 isMaskClick 失效的bug
|
||||||
|
## 1.7.4(2022-01-19)
|
||||||
|
- 新增 cancelText \ confirmText 属性 ,可自定义文本
|
||||||
|
- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
|
||||||
|
- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
|
||||||
|
## 1.7.3(2022-01-13)
|
||||||
|
- 修复 设置 safeArea 属性不生效的bug
|
||||||
|
## 1.7.2(2021-11-26)
|
||||||
|
- 优化 组件示例
|
||||||
|
## 1.7.1(2021-11-26)
|
||||||
|
- 修复 vuedoc 文字错误
|
||||||
|
## 1.7.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
|
||||||
|
## 1.6.2(2021-08-24)
|
||||||
|
- 新增 支持国际化
|
||||||
|
## 1.6.1(2021-07-30)
|
||||||
|
- 优化 vue3下事件警告的问题
|
||||||
|
## 1.6.0(2021-07-13)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.5.0(2021-06-23)
|
||||||
|
- 新增 mask-click 遮罩层点击事件
|
||||||
|
## 1.4.5(2021-06-22)
|
||||||
|
- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
|
||||||
|
## 1.4.4(2021-06-18)
|
||||||
|
- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
|
||||||
|
## 1.4.3(2021-06-08)
|
||||||
|
- 修复 错误的 watch 字段
|
||||||
|
- 修复 safeArea 属性不生效的问题
|
||||||
|
- 修复 点击内容,再点击遮罩无法关闭的Bug
|
||||||
|
## 1.4.2(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.4.1(2021-04-29)
|
||||||
|
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
|
||||||
|
## 1.4.0 (2021-04-29)
|
||||||
|
- 新增 type 属性的 left\right 值,支持左右弹出
|
||||||
|
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
|
||||||
|
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
|
||||||
|
- 新增 safeArea 属性,是否适配底部安全区
|
||||||
|
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
|
||||||
|
- 修复 App 端弹出等待的Bug
|
||||||
|
- 优化 提升低配设备性能,优化动画卡顿问题
|
||||||
|
- 优化 更简单的组件自定义方式
|
||||||
|
## 1.2.9(2021-02-05)
|
||||||
|
- 优化 组件引用关系,通过uni_modules引用组件
|
||||||
|
## 1.2.8(2021-02-05)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
|
## 1.2.7(2021-02-05)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
|
- 新增 支持 PC 端
|
||||||
|
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
// #ifdef H5
|
||||||
|
export default {
|
||||||
|
name: 'Keypress',
|
||||||
|
props: {
|
||||||
|
disable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const keyNames = {
|
||||||
|
esc: ['Esc', 'Escape'],
|
||||||
|
tab: 'Tab',
|
||||||
|
enter: 'Enter',
|
||||||
|
space: [' ', 'Spacebar'],
|
||||||
|
up: ['Up', 'ArrowUp'],
|
||||||
|
left: ['Left', 'ArrowLeft'],
|
||||||
|
right: ['Right', 'ArrowRight'],
|
||||||
|
down: ['Down', 'ArrowDown'],
|
||||||
|
delete: ['Backspace', 'Delete', 'Del']
|
||||||
|
}
|
||||||
|
const listener = ($event) => {
|
||||||
|
if (this.disable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const keyName = Object.keys(keyNames).find(key => {
|
||||||
|
const keyName = $event.key
|
||||||
|
const value = keyNames[key]
|
||||||
|
return value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||||
|
})
|
||||||
|
if (keyName) {
|
||||||
|
// 避免和其他按键事件冲突
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit(keyName, {})
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('keyup', listener)
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
document.removeEventListener('keyup', listener)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: () => {}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-popup-dialog">
|
||||||
|
<view class="uni-dialog-title">
|
||||||
|
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="mode === 'base'" class="uni-dialog-content">
|
||||||
|
<slot>
|
||||||
|
<text class="uni-dialog-content-text">{{content}}</text>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view v-else class="uni-dialog-content">
|
||||||
|
<slot>
|
||||||
|
<input class="uni-dialog-input" :maxlength="maxlength" v-model="val" :type="inputType"
|
||||||
|
:placeholder="placeholderText" :focus="focus">
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view class="uni-dialog-button-group">
|
||||||
|
<view class="uni-dialog-button" v-if="showClose" @click="closeDialog">
|
||||||
|
<text class="uni-dialog-button-text">{{closeText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-dialog-button" :class="showClose?'uni-border-left':''" @click="onOk">
|
||||||
|
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import popup from '../uni-popup/popup.js'
|
||||||
|
import {
|
||||||
|
initVueI18n
|
||||||
|
} from '@dcloudio/uni-i18n'
|
||||||
|
import messages from '../uni-popup/i18n/index.js'
|
||||||
|
const {
|
||||||
|
t
|
||||||
|
} = initVueI18n(messages)
|
||||||
|
/**
|
||||||
|
* PopUp 弹出层-对话框样式
|
||||||
|
* @description 弹出层-对话框样式
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||||
|
* @property {String} value input 模式下的默认值
|
||||||
|
* @property {String} placeholder input 模式下输入提示
|
||||||
|
* @property {Boolean} focus input模式下是否自动聚焦,默认为true
|
||||||
|
* @property {String} type = [success|warning|info|error] 主题样式
|
||||||
|
* @value success 成功
|
||||||
|
* @value warning 提示
|
||||||
|
* @value info 消息
|
||||||
|
* @value error 错误
|
||||||
|
* @property {String} mode = [base|input] 模式、
|
||||||
|
* @value base 基础对话框
|
||||||
|
* @value input 可输入对话框
|
||||||
|
* @showClose {Boolean} 是否显示关闭按钮
|
||||||
|
* @property {String} content 对话框内容
|
||||||
|
* @property {Boolean} beforeClose 是否拦截取消事件
|
||||||
|
* @property {Number} maxlength 输入
|
||||||
|
* @event {Function} confirm 点击确认按钮触发
|
||||||
|
* @event {Function} close 点击取消按钮触发
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "uniPopupDialog",
|
||||||
|
mixins: [popup],
|
||||||
|
emits: ['confirm', 'close', 'update:modelValue', 'input'],
|
||||||
|
props: {
|
||||||
|
inputType: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
showClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// #ifdef VUE2
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
|
||||||
|
placeholder: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'error'
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'base'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
beforeClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
confirmText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
maxlength: {
|
||||||
|
type: Number,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogType: 'error',
|
||||||
|
val: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
okText() {
|
||||||
|
return this.confirmText || t("uni-popup.ok")
|
||||||
|
},
|
||||||
|
closeText() {
|
||||||
|
return this.cancelText || t("uni-popup.cancel")
|
||||||
|
},
|
||||||
|
placeholderText() {
|
||||||
|
return this.placeholder || t("uni-popup.placeholder")
|
||||||
|
},
|
||||||
|
titleText() {
|
||||||
|
return this.title || t("uni-popup.title")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
type(val) {
|
||||||
|
this.dialogType = val
|
||||||
|
},
|
||||||
|
mode(val) {
|
||||||
|
if (val === 'input') {
|
||||||
|
this.dialogType = 'info'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value(val) {
|
||||||
|
this.setVal(val)
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue(val) {
|
||||||
|
this.setVal(val)
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
val(val) {
|
||||||
|
// #ifdef VUE2
|
||||||
|
// TODO 兼容 vue2
|
||||||
|
this.$emit('input', val);
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
// TODO 兼容 vue3
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 对话框遮罩不可点击
|
||||||
|
this.popup.disableMask()
|
||||||
|
// this.popup.closeMask()
|
||||||
|
if (this.mode === 'input') {
|
||||||
|
this.dialogType = 'info'
|
||||||
|
this.val = this.value;
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.val = this.modelValue;
|
||||||
|
// #endif
|
||||||
|
} else {
|
||||||
|
this.dialogType = this.type
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 给val属性赋值
|
||||||
|
*/
|
||||||
|
setVal(val) {
|
||||||
|
if (this.maxlength != -1 && this.mode === 'input') {
|
||||||
|
this.val = val.slice(0, this.maxlength);
|
||||||
|
} else {
|
||||||
|
this.val = val
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 点击确认按钮
|
||||||
|
*/
|
||||||
|
onOk() {
|
||||||
|
if (this.mode === 'input') {
|
||||||
|
this.$emit('confirm', this.val)
|
||||||
|
} else {
|
||||||
|
this.$emit('confirm')
|
||||||
|
}
|
||||||
|
if (this.beforeClose) return
|
||||||
|
this.popup.close()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 点击取消按钮
|
||||||
|
*/
|
||||||
|
closeDialog() {
|
||||||
|
this.$emit('close')
|
||||||
|
if (this.beforeClose) return
|
||||||
|
this.popup.close()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.popup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.uni-popup-dialog {
|
||||||
|
width: 300px;
|
||||||
|
border-radius: 11px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-title {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-title-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-content {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-content-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6C6C6C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-button-group {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
border-top-color: #f5f5f5;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-button {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-border-left {
|
||||||
|
border-left-color: #f0f0f0;
|
||||||
|
border-left-style: solid;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-button-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-button-color {
|
||||||
|
color: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-dialog-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px #eee solid;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__success {
|
||||||
|
color: #4cd964;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__warn {
|
||||||
|
color: #f0ad4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__error {
|
||||||
|
color: #dd524d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__info {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-popup-message">
|
||||||
|
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
|
||||||
|
<slot>
|
||||||
|
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import popup from '../uni-popup/popup.js'
|
||||||
|
/**
|
||||||
|
* PopUp 弹出层-消息提示
|
||||||
|
* @description 弹出层-消息提示
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||||
|
* @property {String} type = [success|warning|info|error] 主题样式
|
||||||
|
* @value success 成功
|
||||||
|
* @value warning 提示
|
||||||
|
* @value info 消息
|
||||||
|
* @value error 错误
|
||||||
|
* @property {String} message 消息提示文字
|
||||||
|
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'uniPopupMessage',
|
||||||
|
mixins:[popup],
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 主题 success/warning/info/error 默认 success
|
||||||
|
*/
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'success'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 消息文字
|
||||||
|
*/
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 显示时间,设置为 0 则不会自动关闭
|
||||||
|
*/
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 3000
|
||||||
|
},
|
||||||
|
maskShow:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.popup.maskShow = this.maskShow
|
||||||
|
this.popup.messageChild = this
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
timerClose(){
|
||||||
|
if(this.duration === 0) return
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = setTimeout(()=>{
|
||||||
|
this.popup.close()
|
||||||
|
},this.duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" >
|
||||||
|
.uni-popup-message {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup-message__box {
|
||||||
|
background-color: #e1f3d8;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-color: #eee;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 500px) {
|
||||||
|
.fixforpc-width {
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex: none;
|
||||||
|
min-width: 380px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
max-width: 50%;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
max-width: 500px;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup-message-text {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__success {
|
||||||
|
background-color: #e1f3d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__success-text {
|
||||||
|
color: #67C23A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__warn {
|
||||||
|
background-color: #faecd8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__warn-text {
|
||||||
|
color: #E6A23C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__error {
|
||||||
|
background-color: #fde2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__error-text {
|
||||||
|
color: #F56C6C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__info {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__info-text {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-popup-share">
|
||||||
|
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
|
||||||
|
<view class="uni-share-content">
|
||||||
|
<view class="uni-share-content-box">
|
||||||
|
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
|
||||||
|
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
|
||||||
|
<text class="uni-share-text">{{item.text}}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uni-share-button-box">
|
||||||
|
<button class="uni-share-button" @click="close">{{cancelText}}</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import popup from '../uni-popup/popup.js'
|
||||||
|
import {
|
||||||
|
initVueI18n
|
||||||
|
} from '@dcloudio/uni-i18n'
|
||||||
|
import messages from '../uni-popup/i18n/index.js'
|
||||||
|
const { t } = initVueI18n(messages)
|
||||||
|
export default {
|
||||||
|
name: 'UniPopupShare',
|
||||||
|
mixins:[popup],
|
||||||
|
emits:['select'],
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
beforeClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// TODO 替换为自己的图标
|
||||||
|
bottomData: [{
|
||||||
|
text: '微信',
|
||||||
|
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
|
||||||
|
name: 'wx'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '支付宝',
|
||||||
|
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
|
||||||
|
name: 'ali'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'QQ',
|
||||||
|
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
|
||||||
|
name: 'qq'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '新浪',
|
||||||
|
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
|
||||||
|
name: 'sina'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// text: '百度',
|
||||||
|
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
|
||||||
|
// name: 'copy'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: '其他',
|
||||||
|
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
|
||||||
|
// name: 'more'
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
computed: {
|
||||||
|
cancelText() {
|
||||||
|
return t("uni-popup.cancel")
|
||||||
|
},
|
||||||
|
shareTitleText() {
|
||||||
|
return this.title || t("uni-popup.shareTitle")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 选择内容
|
||||||
|
*/
|
||||||
|
select(item, index) {
|
||||||
|
this.$emit('select', {
|
||||||
|
item,
|
||||||
|
index
|
||||||
|
})
|
||||||
|
this.close()
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 关闭窗口
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
if(this.beforeClose) return
|
||||||
|
this.popup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" >
|
||||||
|
.uni-popup-share {
|
||||||
|
background-color: #fff;
|
||||||
|
border-top-left-radius: 11px;
|
||||||
|
border-top-right-radius: 11px;
|
||||||
|
}
|
||||||
|
.uni-share-title {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.uni-share-title-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.uni-share-content {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-content-box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-content-item {
|
||||||
|
width: 90px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-content-item:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-image {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-text {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #3B4144;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-button-box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-button {
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 50px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-share-button::after {
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
uni_modules/uni-popup/components/uni-popup/i18n/en.json
Normal file
7
uni_modules/uni-popup/components/uni-popup/i18n/en.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"uni-popup.cancel": "cancel",
|
||||||
|
"uni-popup.ok": "ok",
|
||||||
|
"uni-popup.placeholder": "pleace enter",
|
||||||
|
"uni-popup.title": "Hint",
|
||||||
|
"uni-popup.shareTitle": "Share to"
|
||||||
|
}
|
||||||
8
uni_modules/uni-popup/components/uni-popup/i18n/index.js
Normal file
8
uni_modules/uni-popup/components/uni-popup/i18n/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import en from './en.json'
|
||||||
|
import zhHans from './zh-Hans.json'
|
||||||
|
import zhHant from './zh-Hant.json'
|
||||||
|
export default {
|
||||||
|
en,
|
||||||
|
'zh-Hans': zhHans,
|
||||||
|
'zh-Hant': zhHant
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"uni-popup.cancel": "取消",
|
||||||
|
"uni-popup.ok": "确定",
|
||||||
|
"uni-popup.placeholder": "请输入",
|
||||||
|
"uni-popup.title": "提示",
|
||||||
|
"uni-popup.shareTitle": "分享到"
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"uni-popup.cancel": "取消",
|
||||||
|
"uni-popup.ok": "確定",
|
||||||
|
"uni-popup.placeholder": "請輸入",
|
||||||
|
"uni-popup.title": "提示",
|
||||||
|
"uni-popup.shareTitle": "分享到"
|
||||||
|
}
|
||||||
45
uni_modules/uni-popup/components/uni-popup/keypress.js
Normal file
45
uni_modules/uni-popup/components/uni-popup/keypress.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// #ifdef H5
|
||||||
|
export default {
|
||||||
|
name: 'Keypress',
|
||||||
|
props: {
|
||||||
|
disable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const keyNames = {
|
||||||
|
esc: ['Esc', 'Escape'],
|
||||||
|
tab: 'Tab',
|
||||||
|
enter: 'Enter',
|
||||||
|
space: [' ', 'Spacebar'],
|
||||||
|
up: ['Up', 'ArrowUp'],
|
||||||
|
left: ['Left', 'ArrowLeft'],
|
||||||
|
right: ['Right', 'ArrowRight'],
|
||||||
|
down: ['Down', 'ArrowDown'],
|
||||||
|
delete: ['Backspace', 'Delete', 'Del']
|
||||||
|
}
|
||||||
|
const listener = ($event) => {
|
||||||
|
if (this.disable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const keyName = Object.keys(keyNames).find(key => {
|
||||||
|
const keyName = $event.key
|
||||||
|
const value = keyNames[key]
|
||||||
|
return value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||||
|
})
|
||||||
|
if (keyName) {
|
||||||
|
// 避免和其他按键事件冲突
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit(keyName, {})
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('keyup', listener)
|
||||||
|
// this.$once('hook:beforeDestroy', () => {
|
||||||
|
// document.removeEventListener('keyup', listener)
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
render: () => {}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
26
uni_modules/uni-popup/components/uni-popup/popup.js
Normal file
26
uni_modules/uni-popup/components/uni-popup/popup.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.popup = this.getParent()
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
/**
|
||||||
|
* 获取父元素实例
|
||||||
|
*/
|
||||||
|
getParent(name = 'uniPopup') {
|
||||||
|
let parent = this.$parent;
|
||||||
|
let parentName = parent.$options.name;
|
||||||
|
while (parentName !== name) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
if (!parent) return false
|
||||||
|
parentName = parent.$options.name;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
90
uni_modules/uni-popup/components/uni-popup/uni-popup.uvue
Normal file
90
uni_modules/uni-popup/components/uni-popup/uni-popup.uvue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
|
||||||
|
<view @click.stop>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
type CloseCallBack = ()=> void;
|
||||||
|
let closeCallBack:CloseCallBack = () :void => {};
|
||||||
|
export default {
|
||||||
|
emits:["close","clickMask"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isShow:false,
|
||||||
|
isOpen:false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
maskClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 设置show = true 时,如果没有 open 需要设置为 open
|
||||||
|
isShow:{
|
||||||
|
handler(isShow) {
|
||||||
|
// console.log("isShow",isShow)
|
||||||
|
if(isShow && this.isOpen == false){
|
||||||
|
this.isOpen = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate:true
|
||||||
|
},
|
||||||
|
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
|
||||||
|
isOpen:{
|
||||||
|
handler(isOpen) {
|
||||||
|
// console.log("isOpen",isOpen)
|
||||||
|
if(isOpen && this.isShow == false){
|
||||||
|
this.isShow = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate:true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
open(){
|
||||||
|
// ...funs : CloseCallBack[]
|
||||||
|
// if(funs.length > 0){
|
||||||
|
// closeCallBack = funs[0]
|
||||||
|
// }
|
||||||
|
this.isOpen = true;
|
||||||
|
},
|
||||||
|
clickMask(){
|
||||||
|
if(this.maskClick == true){
|
||||||
|
this.$emit('clickMask')
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close(): void{
|
||||||
|
this.isOpen = false;
|
||||||
|
this.$emit('close')
|
||||||
|
closeCallBack()
|
||||||
|
},
|
||||||
|
hiden(){
|
||||||
|
this.isShow = false
|
||||||
|
},
|
||||||
|
show(){
|
||||||
|
this.isShow = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.popup-root {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 750rpx;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
518
uni_modules/uni-popup/components/uni-popup/uni-popup.vue
Normal file
518
uni_modules/uni-popup/components/uni-popup/uni-popup.vue
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
|
||||||
|
<view @touchstart="touchstart">
|
||||||
|
<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
|
||||||
|
:duration="duration" :show="showTrans" @click="onTap" />
|
||||||
|
<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
|
||||||
|
:show="showTrans" @click="onTap">
|
||||||
|
<view class="uni-popup__wrapper" :style="getStyles" :class="[popupstyle]" @click="clear">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</uni-transition>
|
||||||
|
</view>
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<keypress v-if="maskShow" @esc="onTap" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// #ifdef H5
|
||||||
|
import keypress from './keypress.js'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PopUp 弹出层
|
||||||
|
* @description 弹出层组件,为了解决遮罩弹层的问题
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||||
|
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
|
||||||
|
* @value top 顶部弹出
|
||||||
|
* @value center 中间弹出
|
||||||
|
* @value bottom 底部弹出
|
||||||
|
* @value left 左侧弹出
|
||||||
|
* @value right 右侧弹出
|
||||||
|
* @value message 消息提示
|
||||||
|
* @value dialog 对话框
|
||||||
|
* @value share 底部分享示例
|
||||||
|
* @property {Boolean} animation = [true|false] 是否开启动画
|
||||||
|
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
|
||||||
|
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
|
||||||
|
* @property {String} backgroundColor 主窗口背景色
|
||||||
|
* @property {String} maskBackgroundColor 蒙版颜色
|
||||||
|
* @property {String} borderRadius 设置圆角(左上、右上、右下和左下) 示例:"10px 10px 10px 10px"
|
||||||
|
* @property {Boolean} safeArea 是否适配底部安全区
|
||||||
|
* @event {Function} change 打开关闭弹窗触发,e={show: false}
|
||||||
|
* @event {Function} maskClick 点击遮罩触发
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'uniPopup',
|
||||||
|
components: {
|
||||||
|
// #ifdef H5
|
||||||
|
keypress
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
emits: ['change', 'maskClick'],
|
||||||
|
props: {
|
||||||
|
// 开启动画
|
||||||
|
animation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
||||||
|
// message: 消息提示 ; dialog : 对话框
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'center'
|
||||||
|
},
|
||||||
|
// maskClick
|
||||||
|
isMaskClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
// TODO 2 个版本后废弃属性 ,使用 isMaskClick
|
||||||
|
maskClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'none'
|
||||||
|
},
|
||||||
|
safeArea: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
maskBackgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
},
|
||||||
|
borderRadius:{
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
/**
|
||||||
|
* 监听type类型
|
||||||
|
*/
|
||||||
|
type: {
|
||||||
|
handler: function(type) {
|
||||||
|
if (!this.config[type]) return
|
||||||
|
this[this.config[type]](true)
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
isDesktop: {
|
||||||
|
handler: function(newVal) {
|
||||||
|
if (!this.config[newVal]) return
|
||||||
|
this[this.config[this.type]](true)
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 监听遮罩是否可点击
|
||||||
|
* @param {Object} val
|
||||||
|
*/
|
||||||
|
maskClick: {
|
||||||
|
handler: function(val) {
|
||||||
|
this.mkclick = val
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
isMaskClick: {
|
||||||
|
handler: function(val) {
|
||||||
|
this.mkclick = val
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
// H5 下禁止底部滚动
|
||||||
|
showPopup(show) {
|
||||||
|
// #ifdef H5
|
||||||
|
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
duration: 300,
|
||||||
|
ani: [],
|
||||||
|
showPopup: false,
|
||||||
|
showTrans: false,
|
||||||
|
popupWidth: 0,
|
||||||
|
popupHeight: 0,
|
||||||
|
config: {
|
||||||
|
top: 'top',
|
||||||
|
bottom: 'bottom',
|
||||||
|
center: 'center',
|
||||||
|
left: 'left',
|
||||||
|
right: 'right',
|
||||||
|
message: 'top',
|
||||||
|
dialog: 'center',
|
||||||
|
share: 'bottom'
|
||||||
|
},
|
||||||
|
maskClass: {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
},
|
||||||
|
transClass: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderRadius: this.borderRadius || "0",
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
right: 0
|
||||||
|
},
|
||||||
|
maskShow: true,
|
||||||
|
mkclick: true,
|
||||||
|
popupstyle: 'top'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getStyles() {
|
||||||
|
let res = { backgroundColor: this.bg };
|
||||||
|
if (this.borderRadius || "0") {
|
||||||
|
res = Object.assign(res, { borderRadius: this.borderRadius })
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
isDesktop() {
|
||||||
|
return this.popupWidth >= 500 && this.popupHeight >= 500
|
||||||
|
},
|
||||||
|
bg() {
|
||||||
|
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
|
||||||
|
return 'transparent'
|
||||||
|
}
|
||||||
|
return this.backgroundColor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const fixSize = () => {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const {
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
windowTop,
|
||||||
|
safeArea,
|
||||||
|
screenHeight,
|
||||||
|
safeAreaInsets
|
||||||
|
} = uni.getWindowInfo()
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
const {
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
windowTop,
|
||||||
|
safeArea,
|
||||||
|
screenHeight,
|
||||||
|
safeAreaInsets
|
||||||
|
} = uni.getSystemInfoSync()
|
||||||
|
// #endif
|
||||||
|
this.popupWidth = windowWidth
|
||||||
|
this.popupHeight = windowHeight + (windowTop || 0)
|
||||||
|
// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
|
||||||
|
if (safeArea && this.safeArea) {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.safeAreaInsets = screenHeight - safeArea.bottom
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
this.safeAreaInsets = safeAreaInsets.bottom
|
||||||
|
// #endif
|
||||||
|
} else {
|
||||||
|
this.safeAreaInsets = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixSize()
|
||||||
|
// #ifdef H5
|
||||||
|
// window.addEventListener('resize', fixSize)
|
||||||
|
// this.$once('hook:beforeDestroy', () => {
|
||||||
|
// window.removeEventListener('resize', fixSize)
|
||||||
|
// })
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
// TODO vue2
|
||||||
|
destroyed() {
|
||||||
|
this.setH5Visible()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
// TODO vue3
|
||||||
|
unmounted() {
|
||||||
|
this.setH5Visible()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
activated() {
|
||||||
|
this.setH5Visible(!this.showPopup);
|
||||||
|
},
|
||||||
|
deactivated() {
|
||||||
|
this.setH5Visible(true);
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// this.mkclick = this.isMaskClick || this.maskClick
|
||||||
|
if (this.isMaskClick === null && this.maskClick === null) {
|
||||||
|
this.mkclick = true
|
||||||
|
} else {
|
||||||
|
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
|
||||||
|
}
|
||||||
|
if (this.animation) {
|
||||||
|
this.duration = 300
|
||||||
|
} else {
|
||||||
|
this.duration = 0
|
||||||
|
}
|
||||||
|
// TODO 处理 message 组件生命周期异常的问题
|
||||||
|
this.messageChild = null
|
||||||
|
// TODO 解决头条冒泡的问题
|
||||||
|
this.clearPropagation = false
|
||||||
|
this.maskClass.backgroundColor = this.maskBackgroundColor
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setH5Visible(visible = true) {
|
||||||
|
// #ifdef H5
|
||||||
|
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = visible ? "visible" : "hidden";
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 公用方法,不显示遮罩层
|
||||||
|
*/
|
||||||
|
closeMask() {
|
||||||
|
this.maskShow = false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 公用方法,遮罩层禁止点击
|
||||||
|
*/
|
||||||
|
disableMask() {
|
||||||
|
this.mkclick = false
|
||||||
|
},
|
||||||
|
// TODO nvue 取消冒泡
|
||||||
|
clear(e) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
e.stopPropagation()
|
||||||
|
// #endif
|
||||||
|
this.clearPropagation = true
|
||||||
|
},
|
||||||
|
|
||||||
|
open(direction) {
|
||||||
|
// fix by mehaotian 处理快速打开关闭的情况
|
||||||
|
if (this.showPopup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
|
||||||
|
if (!(direction && innerType.indexOf(direction) !== -1)) {
|
||||||
|
direction = this.type
|
||||||
|
}
|
||||||
|
if (!this.config[direction]) {
|
||||||
|
console.error('缺少类型:', direction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this[this.config[direction]]()
|
||||||
|
this.$emit('change', {
|
||||||
|
show: true,
|
||||||
|
type: direction
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close(type) {
|
||||||
|
this.showTrans = false
|
||||||
|
this.$emit('change', {
|
||||||
|
show: false,
|
||||||
|
type: this.type
|
||||||
|
})
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
// // 自定义关闭事件
|
||||||
|
// this.customOpen && this.customClose()
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.showPopup = false
|
||||||
|
}, 300)
|
||||||
|
},
|
||||||
|
// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
|
||||||
|
touchstart() {
|
||||||
|
this.clearPropagation = false
|
||||||
|
},
|
||||||
|
|
||||||
|
onTap() {
|
||||||
|
if (this.clearPropagation) {
|
||||||
|
// fix by mehaotian 兼容 nvue
|
||||||
|
this.clearPropagation = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('maskClick')
|
||||||
|
if (!this.mkclick) return
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 顶部弹出样式处理
|
||||||
|
*/
|
||||||
|
top(type) {
|
||||||
|
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
|
||||||
|
this.ani = ['slide-top']
|
||||||
|
this.transClass = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: this.bg,
|
||||||
|
borderRadius:this.borderRadius || "0"
|
||||||
|
}
|
||||||
|
// TODO 兼容 type 属性 ,后续会废弃
|
||||||
|
if (type) return
|
||||||
|
this.showPopup = true
|
||||||
|
this.showTrans = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.showPoptrans()
|
||||||
|
if (this.messageChild && this.type === 'message') {
|
||||||
|
this.messageChild.timerClose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 底部弹出样式处理
|
||||||
|
*/
|
||||||
|
bottom(type) {
|
||||||
|
this.popupstyle = 'bottom'
|
||||||
|
this.ani = ['slide-bottom']
|
||||||
|
this.transClass = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
paddingBottom: this.safeAreaInsets + 'px',
|
||||||
|
backgroundColor: this.bg,
|
||||||
|
borderRadius:this.borderRadius || "0",
|
||||||
|
}
|
||||||
|
// TODO 兼容 type 属性 ,后续会废弃
|
||||||
|
if (type) return
|
||||||
|
this.showPoptrans()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 中间弹出样式处理
|
||||||
|
*/
|
||||||
|
center(type) {
|
||||||
|
this.popupstyle = 'center'
|
||||||
|
//微信小程序下,组合动画会出现文字向上闪动问题,再此做特殊处理
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.ani = ['fade']
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
this.ani = ['zoom-out', 'fade']
|
||||||
|
// #endif
|
||||||
|
this.transClass = {
|
||||||
|
position: 'fixed',
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
/* #endif */
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius:this.borderRadius || "0"
|
||||||
|
}
|
||||||
|
// TODO 兼容 type 属性 ,后续会废弃
|
||||||
|
if (type) return
|
||||||
|
this.showPoptrans()
|
||||||
|
},
|
||||||
|
left(type) {
|
||||||
|
this.popupstyle = 'left'
|
||||||
|
this.ani = ['slide-left']
|
||||||
|
this.transClass = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
backgroundColor: this.bg,
|
||||||
|
borderRadius:this.borderRadius || "0",
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
// TODO 兼容 type 属性 ,后续会废弃
|
||||||
|
if (type) return
|
||||||
|
this.showPoptrans()
|
||||||
|
},
|
||||||
|
right(type) {
|
||||||
|
this.popupstyle = 'right'
|
||||||
|
this.ani = ['slide-right']
|
||||||
|
this.transClass = {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
backgroundColor: this.bg,
|
||||||
|
borderRadius:this.borderRadius || "0",
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
// TODO 兼容 type 属性 ,后续会废弃
|
||||||
|
if (type) return
|
||||||
|
this.showPoptrans()
|
||||||
|
},
|
||||||
|
showPoptrans(){
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
this.showPopup = true
|
||||||
|
this.showTrans = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.uni-popup {
|
||||||
|
position: fixed;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
&.top,
|
||||||
|
&.left,
|
||||||
|
&.right {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
top: var(--window-top);
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef H5 */
|
||||||
|
top: 0;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popup__wrapper {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: block;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/* iphonex 等安全区设置,底部安全区适配 */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
// padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
// padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
/* #endif */
|
||||||
|
&.left,
|
||||||
|
&.right {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
padding-top: var(--window-top);
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef H5 */
|
||||||
|
padding-top: 0;
|
||||||
|
/* #endif */
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixforpc-z-index {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
z-index: 999;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixforpc-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
107
uni_modules/uni-popup/package.json
Normal file
107
uni_modules/uni-popup/package.json
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-popup",
|
||||||
|
"displayName": "uni-popup 弹出层",
|
||||||
|
"version": "1.9.10",
|
||||||
|
"description": " Popup 组件,提供常用的弹层",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"弹出层",
|
||||||
|
"弹窗",
|
||||||
|
"popup",
|
||||||
|
"弹框"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "",
|
||||||
|
"uni-app": "^4.06",
|
||||||
|
"uni-app-x": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue",
|
||||||
|
"darkmode": "x",
|
||||||
|
"i18n": "x",
|
||||||
|
"widescreen": "x"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-scss",
|
||||||
|
"uni-transition"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "x",
|
||||||
|
"aliyun": "x",
|
||||||
|
"alipay": "x"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"uni-app": {
|
||||||
|
"vue": {
|
||||||
|
"vue2": "√",
|
||||||
|
"vue3": "√"
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"vue": "√",
|
||||||
|
"nvue": "√",
|
||||||
|
"android": "√",
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "√",
|
||||||
|
"alipay": "√",
|
||||||
|
"toutiao": "√",
|
||||||
|
"baidu": "√",
|
||||||
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "√",
|
||||||
|
"lark": "-"
|
||||||
|
},
|
||||||
|
"quickapp": {
|
||||||
|
"huawei": "-",
|
||||||
|
"union": "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": "√",
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "√"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
uni_modules/uni-popup/readme.md
Normal file
17
uni_modules/uni-popup/readme.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Popup 弹出层
|
||||||
|
> **组件名:uni-popup**
|
||||||
|
> 代码块: `uPopup`
|
||||||
|
> 关联组件:`uni-transition`
|
||||||
|
|
||||||
|
|
||||||
|
弹出层组件,在应用中弹出一个消息提示窗口、提示框等
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
31
uni_modules/uni-transition/changelog.md
Normal file
31
uni_modules/uni-transition/changelog.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
## 1.3.6(2025-07-18)
|
||||||
|
- 修复 nvue 页面,样式错误问题
|
||||||
|
## 1.3.5(2025-06-11)
|
||||||
|
- 修复 第一次执行不显示动画的问题
|
||||||
|
## 1.3.4(2025-04-16)
|
||||||
|
- 修复 页面数据更新到底动画复原的问题
|
||||||
|
- 修复 示例页面打开报错的问题
|
||||||
|
## 1.3.3(2024-04-23)
|
||||||
|
- 修复 当元素会受变量影响自动隐藏的bug
|
||||||
|
## 1.3.2(2023-05-04)
|
||||||
|
- 修复 NVUE 平台报错的问题
|
||||||
|
## 1.3.1(2021-11-23)
|
||||||
|
- 修复 init 方法初始化问题
|
||||||
|
## 1.3.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
|
||||||
|
## 1.2.1(2021-09-27)
|
||||||
|
- 修复 init 方法不生效的 Bug
|
||||||
|
## 1.2.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.1.1(2021-05-12)
|
||||||
|
- 新增 示例地址
|
||||||
|
- 修复 示例项目缺少组件的 Bug
|
||||||
|
## 1.1.0(2021-04-22)
|
||||||
|
- 新增 通过方法自定义动画
|
||||||
|
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
|
||||||
|
- 优化 动画触发逻辑,使动画更流畅
|
||||||
|
- 优化 支持单独的动画类型
|
||||||
|
- 优化 文档示例
|
||||||
|
## 1.0.2(2021-02-05)
|
||||||
|
- 调整为 uni_modules 目录规范
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
// const defaultOption = {
|
||||||
|
// duration: 300,
|
||||||
|
// timingFunction: 'linear',
|
||||||
|
// delay: 0,
|
||||||
|
// transformOrigin: '50% 50% 0'
|
||||||
|
// }
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
const nvueAnimation = uni.requireNativePlugin('animation')
|
||||||
|
// #endif
|
||||||
|
class MPAnimation {
|
||||||
|
constructor(options, _this) {
|
||||||
|
this.options = options
|
||||||
|
// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
|
||||||
|
this.animation = uni.createAnimation({
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
this.currentStepAnimates = {}
|
||||||
|
this.next = 0
|
||||||
|
this.$ = _this
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_nvuePushAnimates(type, args) {
|
||||||
|
let aniObj = this.currentStepAnimates[this.next]
|
||||||
|
let styles = {}
|
||||||
|
if (!aniObj) {
|
||||||
|
styles = {
|
||||||
|
styles: {},
|
||||||
|
config: {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
styles = aniObj
|
||||||
|
}
|
||||||
|
if (animateTypes1.includes(type)) {
|
||||||
|
if (!styles.styles.transform) {
|
||||||
|
styles.styles.transform = ''
|
||||||
|
}
|
||||||
|
let unit = ''
|
||||||
|
if(type === 'rotate'){
|
||||||
|
unit = 'deg'
|
||||||
|
}
|
||||||
|
styles.styles.transform += `${type}(${args+unit}) `
|
||||||
|
} else {
|
||||||
|
styles.styles[type] = `${args}`
|
||||||
|
}
|
||||||
|
this.currentStepAnimates[this.next] = styles
|
||||||
|
}
|
||||||
|
_animateRun(styles = {}, config = {}) {
|
||||||
|
let ref = this.$.$refs['ani'].ref
|
||||||
|
if (!ref) return
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
nvueAnimation.transition(ref, {
|
||||||
|
styles,
|
||||||
|
...config
|
||||||
|
}, res => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_nvueNextAnimate(animates, step = 0, fn) {
|
||||||
|
let obj = animates[step]
|
||||||
|
if (obj) {
|
||||||
|
let {
|
||||||
|
styles,
|
||||||
|
config
|
||||||
|
} = obj
|
||||||
|
this._animateRun(styles, config).then(() => {
|
||||||
|
step += 1
|
||||||
|
this._nvueNextAnimate(animates, step, fn)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.currentStepAnimates = {}
|
||||||
|
typeof fn === 'function' && fn()
|
||||||
|
this.isEnd = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step(config = {}) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.animation.step(config)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
|
||||||
|
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
|
||||||
|
this.next++
|
||||||
|
// #endif
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
run(fn) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.$.animationData = this.animation.export()
|
||||||
|
this.$.timer = setTimeout(() => {
|
||||||
|
typeof fn === 'function' && fn()
|
||||||
|
}, this.$.durationTime)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.isEnd = false
|
||||||
|
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
|
||||||
|
if(!ref) return
|
||||||
|
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
|
||||||
|
this.next = 0
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
|
||||||
|
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
|
||||||
|
'translateZ'
|
||||||
|
]
|
||||||
|
const animateTypes2 = ['opacity', 'backgroundColor']
|
||||||
|
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
|
||||||
|
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
|
||||||
|
MPAnimation.prototype[type] = function(...args) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.animation[type](...args)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this._nvuePushAnimates(type, args)
|
||||||
|
// #endif
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function createAnimation(option, _this) {
|
||||||
|
if(!_this) return
|
||||||
|
clearTimeout(_this.timer)
|
||||||
|
return new MPAnimation(option, _this)
|
||||||
|
}
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createAnimation } from './createAnimation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transition 过渡动画
|
||||||
|
* @description 简单过渡动画组件
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
|
||||||
|
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
|
||||||
|
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
|
||||||
|
* @value fade 渐隐渐出过渡
|
||||||
|
* @value slide-top 由上至下过渡
|
||||||
|
* @value slide-right 由右至左过渡
|
||||||
|
* @value slide-bottom 由下至上过渡
|
||||||
|
* @value slide-left 由左至右过渡
|
||||||
|
* @value zoom-in 由小到大过渡
|
||||||
|
* @value zoom-out 由大到小过渡
|
||||||
|
* @property {Number} duration 过渡动画持续时间
|
||||||
|
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uniTransition',
|
||||||
|
emits: ['click', 'change'],
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
modeClass: {
|
||||||
|
type: [Array, String],
|
||||||
|
default () {
|
||||||
|
return 'fade'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 300
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
onceRender: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isShow: false,
|
||||||
|
transform: '',
|
||||||
|
opacity: 0,
|
||||||
|
animationData: {},
|
||||||
|
durationTime: 300,
|
||||||
|
config: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.open()
|
||||||
|
} else {
|
||||||
|
// 避免上来就执行 close,导致动画错乱
|
||||||
|
if (this.isShow) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 生成样式数据
|
||||||
|
stylesObject() {
|
||||||
|
let styles = {
|
||||||
|
...this.styles,
|
||||||
|
'transition-duration': this.duration / 1000 + 's'
|
||||||
|
}
|
||||||
|
let transform = ''
|
||||||
|
for (let i in styles) {
|
||||||
|
let line = this.toLine(i)
|
||||||
|
transform += line + ':' + styles[i] + ';'
|
||||||
|
}
|
||||||
|
return transform
|
||||||
|
},
|
||||||
|
// 初始化动画条件
|
||||||
|
transformStyles() {
|
||||||
|
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 动画默认配置
|
||||||
|
this.config = {
|
||||||
|
duration: this.duration,
|
||||||
|
timingFunction: 'ease',
|
||||||
|
transformOrigin: '50% 50%',
|
||||||
|
delay: 0
|
||||||
|
}
|
||||||
|
this.durationTime = this.duration
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* ref 触发 初始化动画
|
||||||
|
*/
|
||||||
|
init(obj = {}) {
|
||||||
|
if (obj.duration) {
|
||||||
|
this.durationTime = obj.duration
|
||||||
|
}
|
||||||
|
this.animation = createAnimation(Object.assign(this.config, obj), this)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 点击组件触发回调
|
||||||
|
*/
|
||||||
|
onClick() {
|
||||||
|
this.$emit('click', {
|
||||||
|
detail: this.isShow
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* ref 触发 动画分组
|
||||||
|
* @param {Object} obj
|
||||||
|
*/
|
||||||
|
step(obj, config = {}) {
|
||||||
|
if (!this.animation) return this
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
const value = obj[key]
|
||||||
|
if (typeof this.animation[key] === 'function') {
|
||||||
|
Array.isArray(value) ?
|
||||||
|
this.animation[key](...value) :
|
||||||
|
this.animation[key](value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.animation.step(config)
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* ref 触发 执行动画
|
||||||
|
*/
|
||||||
|
run(fn) {
|
||||||
|
if (!this.animation) return
|
||||||
|
this.animation.run(fn)
|
||||||
|
},
|
||||||
|
// 开始过度动画
|
||||||
|
open() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.isShow = true
|
||||||
|
// 新增初始状态重置逻辑(关键)
|
||||||
|
this.transform = this.styleInit(false).transform || ''
|
||||||
|
this.opacity = this.styleInit(false).opacity || 0
|
||||||
|
|
||||||
|
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.animation = createAnimation(this.config, this)
|
||||||
|
this.tranfromInit(false).step()
|
||||||
|
this.animation.run(() => {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.transform = this.styleInit(false).transform || ''
|
||||||
|
this.opacity = this.styleInit(false).opacity || 1
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.transform = ''
|
||||||
|
this.opacity = this.styleInit(false).opacity || 1
|
||||||
|
// #endif
|
||||||
|
this.$emit('change', {
|
||||||
|
detail: this.isShow
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, 80)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 关闭过度动画
|
||||||
|
close(type) {
|
||||||
|
if (!this.animation) return
|
||||||
|
this.tranfromInit(true)
|
||||||
|
.step()
|
||||||
|
.run(() => {
|
||||||
|
this.isShow = false
|
||||||
|
this.animationData = null
|
||||||
|
this.animation = null
|
||||||
|
let { opacity, transform } = this.styleInit(false)
|
||||||
|
this.opacity = opacity || 1
|
||||||
|
this.transform = transform
|
||||||
|
this.$emit('change', {
|
||||||
|
detail: this.isShow
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 处理动画开始前的默认样式
|
||||||
|
styleInit(type) {
|
||||||
|
let styles = { transform: '', opacity: 1 }
|
||||||
|
const buildStyle = (type, mode) => {
|
||||||
|
const value = this.animationType(type)[mode] // 直接使用 type 控制状态
|
||||||
|
if (mode.startsWith('fade')) {
|
||||||
|
styles.opacity = value
|
||||||
|
} else {
|
||||||
|
styles.transform += value + ' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.modeClass === 'string') {
|
||||||
|
buildStyle(type, this.modeClass)
|
||||||
|
} else {
|
||||||
|
this.modeClass.forEach(mode => buildStyle(type, mode))
|
||||||
|
}
|
||||||
|
return styles
|
||||||
|
},
|
||||||
|
// 处理内置组合动画
|
||||||
|
tranfromInit(type) {
|
||||||
|
let buildTranfrom = (type, mode) => {
|
||||||
|
let aniNum = null
|
||||||
|
if (mode === 'fade') {
|
||||||
|
aniNum = type ? 0 : 1
|
||||||
|
} else {
|
||||||
|
aniNum = type ? '-100%' : '0'
|
||||||
|
if (mode === 'zoom-in') {
|
||||||
|
aniNum = type ? 0.8 : 1
|
||||||
|
}
|
||||||
|
if (mode === 'zoom-out') {
|
||||||
|
aniNum = type ? 1.2 : 1
|
||||||
|
}
|
||||||
|
if (mode === 'slide-right') {
|
||||||
|
aniNum = type ? '100%' : '0'
|
||||||
|
}
|
||||||
|
if (mode === 'slide-bottom') {
|
||||||
|
aniNum = type ? '100%' : '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.animation[this.animationMode()[mode]](aniNum)
|
||||||
|
}
|
||||||
|
if (typeof this.modeClass === 'string') {
|
||||||
|
buildTranfrom(type, this.modeClass)
|
||||||
|
} else {
|
||||||
|
this.modeClass.forEach(mode => {
|
||||||
|
buildTranfrom(type, mode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.animation
|
||||||
|
},
|
||||||
|
animationType(type) {
|
||||||
|
return {
|
||||||
|
fade: type ? 1 : 0,
|
||||||
|
'slide-top': `translateY(${type ? '0' : '-100%'})`,
|
||||||
|
'slide-right': `translateX(${type ? '0' : '100%'})`,
|
||||||
|
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
|
||||||
|
'slide-left': `translateX(${type ? '0' : '-100%'})`,
|
||||||
|
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
|
||||||
|
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 内置动画类型与实际动画对应字典
|
||||||
|
animationMode() {
|
||||||
|
return {
|
||||||
|
fade: 'opacity',
|
||||||
|
'slide-top': 'translateY',
|
||||||
|
'slide-right': 'translateX',
|
||||||
|
'slide-bottom': 'translateY',
|
||||||
|
'slide-left': 'translateX',
|
||||||
|
'zoom-in': 'scale',
|
||||||
|
'zoom-out': 'scale'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 驼峰转中横线
|
||||||
|
toLine(name) {
|
||||||
|
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
112
uni_modules/uni-transition/package.json
Normal file
112
uni_modules/uni-transition/package.json
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-transition",
|
||||||
|
"displayName": "uni-transition 过渡动画",
|
||||||
|
"version": "1.3.6",
|
||||||
|
"description": "元素的简单过渡动画",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"动画",
|
||||||
|
"过渡",
|
||||||
|
"过渡动画"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "",
|
||||||
|
"uni-app": "^4.12",
|
||||||
|
"uni-app-x": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue",
|
||||||
|
"darkmode": "x",
|
||||||
|
"i18n": "x",
|
||||||
|
"widescreen": "x"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-scss"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "x",
|
||||||
|
"aliyun": "x",
|
||||||
|
"alipay": "x"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"uni-app": {
|
||||||
|
"vue": {
|
||||||
|
"vue2": "√",
|
||||||
|
"vue3": "√"
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"vue": "√",
|
||||||
|
"nvue": "√",
|
||||||
|
"android": "√",
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": {
|
||||||
|
},
|
||||||
|
"alipay": {
|
||||||
|
},
|
||||||
|
"toutiao": {
|
||||||
|
},
|
||||||
|
"baidu": {
|
||||||
|
},
|
||||||
|
"kuaishou": {
|
||||||
|
},
|
||||||
|
"jd": {
|
||||||
|
},
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "√",
|
||||||
|
"lark": "-"
|
||||||
|
},
|
||||||
|
"quickapp": {
|
||||||
|
"huawei": "√",
|
||||||
|
"union": "√"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"web": {
|
||||||
|
"safari": "-",
|
||||||
|
"chrome": "-"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": "-",
|
||||||
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
uni_modules/uni-transition/readme.md
Normal file
11
uni_modules/uni-transition/readme.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Transition 过渡动画
|
||||||
|
> **组件名:uni-transition**
|
||||||
|
> 代码块: `uTransition`
|
||||||
|
|
||||||
|
|
||||||
|
元素过渡动画
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
Loading…
Reference in New Issue
Block a user