| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- <template>
- <view class="ay-toast-root" v-if="visible">
- <!-- 遮罩层 -->
- <view class="ay-toast-mask" v-if="mask" @click="onMaskClick"></view>
- <!-- Toast主体 -->
- <view class="ay-toast-container">
- <view class="ay-toast-box" ref="toastBox">
- <!-- 图标区域 -->
- <view class="ay-toast-icon-wrap" v-if="type !== 'text'">
- <!-- 成功图标 -->
- <view class="ay-toast-icon-success" v-if="type === 'success'">
- <view class="success-bg"></view>
- <view class="success-line1"></view>
- <view class="success-line2"></view>
- </view>
- <!-- 失败图标 -->
- <view class="ay-toast-icon-error" v-if="type === 'error'">
- <view class="error-bg"></view>
- <view class="error-line1"></view>
- <view class="error-line2"></view>
- </view>
- <!-- 加载图标 -->
- <view class="ay-toast-icon-loading" v-if="type === 'loading'">
- <view class="loading-ring" ref="loadingRef"></view>
- </view>
- </view>
- <!-- 文字区域 -->
- <text class="ay-toast-text" :class="textColorClass" v-if="title">{{ title }}</text>
- </view>
- </view>
- </view>
- </template>
- <script>
- // #ifdef APP-NVUE
- const animation = weex.requireModule('animation')
- // #endif
- export default {
- name: 'ayToast',
- data() {
- return {
- visible: false,
- type: 'text', // success | error | loading | text
- title: '',
- duration: 2000,
- mask: false,
- timer: null,
- loadingAnimTimer: null
- }
- },
- computed: {
- textColorClass() {
- if (this.type === 'success') return 'text-success'
- if (this.type === 'error') return 'text-error'
- return 'text-default'
- }
- },
- created() {
- uni.$on('ayToast:show', this.show)
- uni.$on('ayToast:hide', this.hide)
- },
- beforeUnmount() {
- this._cleanup()
- },
- beforeDestroy() {
- this._cleanup()
- },
- methods: {
- _cleanup() {
- uni.$off('ayToast:show', this.show)
- uni.$off('ayToast:hide', this.hide)
- this.clearTimer()
- this._stopLoadingAnim()
- },
- show(options) {
- this.clearTimer()
- this._stopLoadingAnim()
- if (typeof options === 'string') {
- options = { title: options, type: 'text' }
- }
- const {
- type = 'text',
- title = '',
- duration,
- mask
- } = options
- this.type = type
- this.title = title
- this.mask = mask !== undefined ? mask : (type === 'loading')
- if (duration !== undefined) {
- this.duration = duration
- } else {
- this.duration = type === 'loading' ? 0 : 2000
- }
- this.visible = true
- // 入场动画 + loading旋转
- this.$nextTick(() => {
- this._playEnterAnim()
- if (type === 'loading') {
- this._startLoadingAnim()
- }
- })
- if (this.duration > 0) {
- this.timer = setTimeout(() => {
- this.hide()
- }, this.duration)
- }
- },
- success(title, options = {}) {
- this.show({ type: 'success', title, ...options })
- },
- error(title, options = {}) {
- this.show({ type: 'error', title, ...options })
- },
- loading(title = '加载中...', options = {}) {
- this.show({ type: 'loading', title, duration: 0, mask: true, ...options })
- },
- hide() {
- this.clearTimer()
- this._stopLoadingAnim()
- this.visible = false
- },
- clearTimer() {
- if (this.timer) {
- clearTimeout(this.timer)
- this.timer = null
- }
- },
- // 入场缩放动画
- _playEnterAnim() {
- // #ifdef APP-NVUE
- const el = this.$refs.toastBox
- if (!el) return
- animation.transition(el, {
- styles: { transform: 'scale(0.8)', opacity: 0 },
- duration: 0
- }, () => {
- animation.transition(el, {
- styles: { transform: 'scale(1)', opacity: 1 },
- duration: 200,
- timingFunction: 'ease-out'
- })
- })
- // #endif
- },
- // loading 旋转动画
- _startLoadingAnim() {
- // #ifdef APP-NVUE
- const el = this.$refs.loadingRef
- if (!el) return
- const doRotate = () => {
- if (!this.visible || this.type !== 'loading') return
- animation.transition(el, {
- styles: { transform: 'rotate(360deg)' },
- duration: 900,
- timingFunction: 'linear'
- }, () => {
- if (!this.visible || this.type !== 'loading') return
- animation.transition(el, {
- styles: { transform: 'rotate(0deg)' },
- duration: 0
- }, () => {
- doRotate()
- })
- })
- }
- doRotate()
- // #endif
- },
- _stopLoadingAnim() {
- if (this.loadingAnimTimer) {
- clearTimeout(this.loadingAnimTimer)
- this.loadingAnimTimer = null
- }
- },
- onMaskClick() {
- // loading状态下防止误操作
- }
- }
- }
- </script>
- <style scoped>
- .ay-toast-root {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 99999;
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- width: 100%;
- height: 100%;
- /* #endif */
- }
- .ay-toast-mask {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.15);
- /* #ifndef APP-NVUE */
- width: 100%;
- height: 100%;
- /* #endif */
- }
- .ay-toast-container {
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- flex-direction: column;
- /* #endif */
- }
- .ay-toast-box {
- background-color: #FFFFFF;
- border-radius: 20rpx;
- padding-top: 36rpx;
- padding-bottom: 32rpx;
- padding-left: 44rpx;
- padding-right: 44rpx;
- min-width: 220rpx;
- max-width: 360rpx;
- justify-content: center;
- align-items: center;
- /* #ifdef APP-NVUE */
- border-width: 1px;
- border-style: solid;
- border-color: #f0f0f0;
- /* #endif */
- /* #ifndef APP-NVUE */
- display: flex;
- flex-direction: column;
- box-shadow: 0 16rpx 48rpx rgba(56, 149, 136, 0.12);
- /* #endif */
- }
- /* 图标容器 */
- .ay-toast-icon-wrap {
- width: 76rpx;
- height: 76rpx;
- margin-bottom: 20rpx;
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- position: relative;
- /* #endif */
- }
- /* ===== 成功图标 ===== */
- .ay-toast-icon-success {
- width: 76rpx;
- height: 76rpx;
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- position: relative;
- /* #endif */
- }
- .success-bg {
- position: absolute;
- width: 76rpx;
- height: 76rpx;
- border-radius: 38rpx;
- background-color: #1a9e78;
- /* #ifndef APP-NVUE */
- top: 0;
- left: 0;
- /* #endif */
- }
- .success-line1 {
- position: absolute;
- width: 18rpx;
- height: 4rpx;
- background-color: #ffffff;
- border-radius: 2rpx;
- top: 42rpx;
- left: 20rpx;
- transform: rotate(45deg);
- }
- .success-line2 {
- position: absolute;
- width: 32rpx;
- height: 4rpx;
- background-color: #ffffff;
- border-radius: 2rpx;
- top: 37rpx;
- left: 28rpx;
- transform: rotate(-45deg);
- }
- /* ===== 错误图标 ===== */
- .ay-toast-icon-error {
- width: 76rpx;
- height: 76rpx;
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- position: relative;
- /* #endif */
- }
- .error-bg {
- position: absolute;
- width: 76rpx;
- height: 76rpx;
- border-radius: 38rpx;
- background-color: #F65252;
- /* #ifndef APP-NVUE */
- top: 0;
- left: 0;
- /* #endif */
- }
- .error-line1 {
- position: absolute;
- width: 32rpx;
- height: 4rpx;
- background-color: #ffffff;
- border-radius: 2rpx;
- transform: rotate(45deg);
- /* #ifndef APP-NVUE */
- top: 50%;
- left: 50%;
- margin-top: -2.5rpx;
- margin-left: -20rpx;
- /* #endif */
- }
- .error-line2 {
- position: absolute;
- width: 32rpx;
- height: 4rpx;
- background-color: #ffffff;
- border-radius: 2rpx;
- transform: rotate(-45deg);
- /* #ifndef APP-NVUE */
- top: 50%;
- left: 50%;
- margin-top: -2.5rpx;
- margin-left: -20rpx;
- /* #endif */
- }
- /* ===== 加载图标 ===== */
- .ay-toast-icon-loading {
- width: 76rpx;
- height: 76rpx;
- justify-content: center;
- align-items: center;
- /* #ifndef APP-NVUE */
- display: flex;
- position: relative;
- /* #endif */
- }
- .loading-ring {
- width: 56rpx;
- height: 56rpx;
- border-radius: 28rpx;
- border-width: 4rpx;
- border-style: solid;
- border-color: #e8f5f2;
- /* #ifndef APP-NVUE */
- border-top-color: #1a9e78;
- box-sizing: border-box;
- animation: loading-spin 0.9s linear infinite;
- /* #endif */
- /* #ifdef APP-NVUE */
- border-top-color: #1a9e78;
- /* #endif */
- }
- /* 文字 */
- .ay-toast-text {
- font-size: 28rpx;
- text-align: center;
- /* #ifdef APP-NVUE */
- lines: 2;
- /* #endif */
- /* #ifndef APP-NVUE */
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- word-break: break-all;
- /* #endif */
- max-width: 320rpx;
- }
- .text-default {
- color: #545F71;
- }
- .text-success {
- color: #1a9e78;
- }
- .text-error {
- color: #F65252;
- }
- /* #ifndef APP-NVUE */
- @keyframes loading-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
- /* #endif */
- </style>
|