diff --git a/components/ImageSwiper/styles/index.scss b/components/ImageSwiper/styles/index.scss index 69d2d85..a7d1329 100644 --- a/components/ImageSwiper/styles/index.scss +++ b/components/ImageSwiper/styles/index.scss @@ -51,7 +51,6 @@ flex-shrink: 0; text-align: center; transition: all 0.3s ease; - cursor: pointer; &.active { image { diff --git a/pages/goods/components/GoodConfirm/README.md b/pages/goods/components/GoodConfirm/README.md new file mode 100644 index 0000000..340cce3 --- /dev/null +++ b/pages/goods/components/GoodConfirm/README.md @@ -0,0 +1,234 @@ +# GoodConfirm 商品确认组件 + +基于 uni-popup 弹出层的商品确认组件,提供优雅的商品购买确认界面。 + +## 功能特性 + +- 🎨 **现代化设计** - 采用底部弹出设计,符合移动端交互习惯 +- 📱 **响应式布局** - 完美适配各种屏幕尺寸 +- 🛒 **商品信息展示** - 支持商品图片、标题、价格、标签展示 +- 🔢 **数量选择** - 提供加减按钮和手动输入两种方式 +- 💰 **实时计算** - 自动计算并显示总价 +- ⚡ **性能优化** - 基于 Vue 3 Composition API,性能卓越 +- 🎭 **动画效果** - 流畅的弹出和交互动画 +- 🔧 **高度可配置** - 支持自定义商品数据和事件处理 + +## 基础用法 + +### 默认使用 + +```vue + + + +``` + +### 自定义商品数据 + +```vue + +``` + +## 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 + +``` + +### 订单处理集成 + +```vue + +``` + +## 注意事项 + +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 \ No newline at end of file diff --git a/pages/goods/components/GoodConfirm/demo.vue b/pages/goods/components/GoodConfirm/demo.vue new file mode 100644 index 0000000..9be3fc8 --- /dev/null +++ b/pages/goods/components/GoodConfirm/demo.vue @@ -0,0 +1,191 @@ + + + + + \ No newline at end of file diff --git a/pages/goods/components/GoodConfirm/images/商品2级 门票.png b/pages/goods/components/GoodConfirm/images/商品2级 门票.png new file mode 100644 index 0000000..25dc8bb Binary files /dev/null and b/pages/goods/components/GoodConfirm/images/商品2级 门票.png differ diff --git a/pages/goods/components/GoodConfirm/index.vue b/pages/goods/components/GoodConfirm/index.vue new file mode 100644 index 0000000..e0062b1 --- /dev/null +++ b/pages/goods/components/GoodConfirm/index.vue @@ -0,0 +1,151 @@ + + + + + \ No newline at end of file diff --git a/pages/goods/components/GoodConfirm/styles/index.scss b/pages/goods/components/GoodConfirm/styles/index.scss new file mode 100644 index 0000000..6de10ed --- /dev/null +++ b/pages/goods/components/GoodConfirm/styles/index.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/pages/goods/index.vue b/pages/goods/index.vue index 877be27..010f430 100644 --- a/pages/goods/index.vue +++ b/pages/goods/index.vue @@ -12,8 +12,21 @@ + + + + + + + + @@ -25,8 +38,11 @@ import TopNavBar from "@/components/TopNavBar/index.vue"; import ImageSwiper from "@/components/ImageSwiper/index.vue"; import GoodInfo from "./components/GoodInfo/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue"; +import GoodConfirm from "./components/GoodConfirm/index.vue"; const goodsData = ref({}); +const goodConfirmRef = ref(null); + // 获取商品详情数据 const goodsInfo = async (params) => { const res = await goodsDetail(params); @@ -34,6 +50,29 @@ const goodsInfo = async (params) => { 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" }) => { goodsInfo({ commodityId }); }); diff --git a/pages/goods/styles/index.scss b/pages/goods/styles/index.scss index eb6424c..0b2010e 100644 --- a/pages/goods/styles/index.scss +++ b/pages/goods/styles/index.scss @@ -1,3 +1,6 @@ +$button-color: #00a6ff; +$button-hover-color: darken($button-color, 8%); + .goods-container { min-height: 100vh; background-color: #fff; @@ -5,6 +8,8 @@ .content-wrapper { // 为固定导航栏预留空间 padding-top: calc(var(--status-bar-height, 44px) + 68px); + // 为安全区预留空间 + padding-bottom: calc(var(--safe-area-inset-bottom, 0px) + 100px); } .goods-content { @@ -15,4 +20,77 @@ margin-top: -30px; 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); + } + } + } } diff --git a/uni_modules/uni-popup/changelog.md b/uni_modules/uni-popup/changelog.md new file mode 100644 index 0000000..20b1e8c --- /dev/null +++ b/uni_modules/uni-popup/changelog.md @@ -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 端 diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js new file mode 100644 index 0000000..a747b9f --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js @@ -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 diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000..0295df0 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000..7f27a1e --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,143 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000..049cd5c --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,188 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/en.json b/uni_modules/uni-popup/components/uni-popup/i18n/en.json new file mode 100644 index 0000000..8c0f5f3 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/en.json @@ -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" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/index.js b/uni_modules/uni-popup/components/uni-popup/i18n/index.js new file mode 100644 index 0000000..fa8f0f3 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/index.js @@ -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 +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json new file mode 100644 index 0000000..8e5b99f --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "确定", + "uni-popup.placeholder": "请输入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json new file mode 100644 index 0000000..06ce162 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "確定", + "uni-popup.placeholder": "請輸入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/keypress.js b/uni_modules/uni-popup/components/uni-popup/keypress.js new file mode 100644 index 0000000..16a5818 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/keypress.js @@ -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 diff --git a/uni_modules/uni-popup/components/uni-popup/popup.js b/uni_modules/uni-popup/components/uni-popup/popup.js new file mode 100644 index 0000000..a37fb9f --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/popup.js @@ -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; + }, + } +} diff --git a/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue b/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue new file mode 100644 index 0000000..5eb8d5b --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue @@ -0,0 +1,90 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-popup/components/uni-popup/uni-popup.vue b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000..5af55e0 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue @@ -0,0 +1,518 @@ + + + + diff --git a/uni_modules/uni-popup/package.json b/uni_modules/uni-popup/package.json new file mode 100644 index 0000000..ae01918 --- /dev/null +++ b/uni_modules/uni-popup/package.json @@ -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": "√" + } + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-popup/readme.md b/uni_modules/uni-popup/readme.md new file mode 100644 index 0000000..fdad4b3 --- /dev/null +++ b/uni_modules/uni-popup/readme.md @@ -0,0 +1,17 @@ + + +## Popup 弹出层 +> **组件名:uni-popup** +> 代码块: `uPopup` +> 关联组件:`uni-transition` + + +弹出层组件,在应用中弹出一个消息提示窗口、提示框等 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/uni_modules/uni-transition/changelog.md b/uni_modules/uni-transition/changelog.md new file mode 100644 index 0000000..01bfb58 --- /dev/null +++ b/uni_modules/uni-transition/changelog.md @@ -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 目录规范 diff --git a/uni_modules/uni-transition/components/uni-transition/createAnimation.js b/uni_modules/uni-transition/components/uni-transition/createAnimation.js new file mode 100644 index 0000000..8f89b18 --- /dev/null +++ b/uni_modules/uni-transition/components/uni-transition/createAnimation.js @@ -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) +} diff --git a/uni_modules/uni-transition/components/uni-transition/uni-transition.vue b/uni_modules/uni-transition/components/uni-transition/uni-transition.vue new file mode 100644 index 0000000..baea9df --- /dev/null +++ b/uni_modules/uni-transition/components/uni-transition/uni-transition.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/uni_modules/uni-transition/package.json b/uni_modules/uni-transition/package.json new file mode 100644 index 0000000..0542c52 --- /dev/null +++ b/uni_modules/uni-transition/package.json @@ -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": "-" + } + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-transition/readme.md b/uni_modules/uni-transition/readme.md new file mode 100644 index 0000000..2f8a77e --- /dev/null +++ b/uni_modules/uni-transition/readme.md @@ -0,0 +1,11 @@ + + +## Transition 过渡动画 +> **组件名:uni-transition** +> 代码块: `uTransition` + + +元素过渡动画 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file