panhui пре 2 година
родитељ
комит
525ff33556

BIN
public/fonts/YouSheBiaoTiHei.ttf


BIN
src/assets/png-duihuan-bg.png


BIN
src/assets/png-yaoqinghaoyou-tanchuang.png


+ 0 - 109
src/components/AddressItem.vue

@@ -1,109 +0,0 @@
-<template>
-    <div class="address">
-        <div class="radio" v-if="type == 'submit'" @click="chooseAddress">
-            <img class="radio-img" :src="isChoose ? activeIcon : inactiveIcon" />
-        </div>
-        <div class="content">
-            <div class="address-item">
-                <div class="address-name">收货人</div>
-                <div class="address-val">{{ info.name }} {{ info.phone }}</div>
-            </div>
-            <div class="address-item">
-                <div class="address-name">收货地址</div>
-                <div class="address-val">{{ info.fullAddress }}</div>
-            </div>
-
-            <div class="btn-list">
-                <van-button plain color="#000" size="small" round disabled v-if="info.isDefault">默认</van-button>
-                <van-button plain color="#FF8F00" size="small" round @click="goNext('editAddress', { id: info.id })">
-                    编辑
-                </van-button>
-                <van-button v-if="type == 'list'" plain type="danger" size="small" round>删除</van-button>
-            </div>
-        </div>
-    </div>
-</template>
-<script>
-import { useUserStore } from '@/stores/user'
-import { mapState } from 'pinia'
-import activeIcon from '@/assets/icon_xuanzhong_pre.png'
-import inactiveIcon from '@/assets/icon_xuanzhong.png'
-export default {
-    name: 'addressItem',
-    props: {
-        info: {
-            type: Object,
-            default: () => {
-                return {}
-            }
-        },
-        type: {
-            type: String,
-            default: 'list'
-        },
-        isChoose: {
-            type: Boolean,
-            default: false
-        }
-    },
-    data() {
-        return {
-            activeIcon,
-            inactiveIcon
-        }
-    },
-    computed: {
-        ...mapState(useUserStore, ['user'])
-    },
-    methods: {
-        chooseAddress() {
-            this.$emit('chooseAddress', this.info.id)
-        }
-    }
-}
-</script>
-<style lang="less" scoped>
-.address {
-    background: rgba(var(--ion-text-color-rgb), 1);
-    border-radius: 2px;
-    padding: 15px;
-    display: flex;
-    align-items: center;
-}
-.radio {
-    padding: 10px 10px 10px 0;
-}
-.radio-img {
-    width: 16px;
-    height: 16px;
-    margin-right: 10px;
-}
-.content {
-    flex-grow: 1;
-}
-
-.address-item {
-    display: flex;
-    padding: 0 0 10px;
-    .address-name {
-        font-size: 13px;
-        color: rgba(170, 172, 173, 1);
-        line-height: 20px;
-        min-width: 72px;
-    }
-
-    .address-val {
-        font-size: 14px;
-        color: rgba(var(--ion-color-light-contrast-rgb), 1);
-        line-height: 20px;
-    }
-}
-
-.btn-list {
-    display: flex;
-    justify-content: flex-end;
-    .van-button:not(:last-child) {
-        margin-right: 10px;
-    }
-}
-</style>

+ 0 - 71
src/components/ChatInfo.vue

@@ -1,71 +0,0 @@
-<template>
-    <div class="chat-info" :class="{ isLeft: isLeft }">
-        <div class="chat-time">今天12:10</div>
-        <div class="chat-content">
-            <div class="chat-message">你好,这个藏品怎么支付?</div>
-
-            <img src="../assets/png_moren.png" alt="" class="icon" />
-        </div>
-    </div>
-</template>
-<script>
-export default {
-    props: {
-        info: {
-            type: Object,
-            default: () => {
-                return {}
-            }
-        },
-        isLeft: {
-            type: Boolean,
-            default: false
-        }
-    }
-}
-</script>
-
-<style lang="less" scoped>
-.chat-info {
-    padding: 16px;
-    &.isLeft {
-        .chat-content {
-            flex-direction: row-reverse;
-            .chat-message {
-                background: var(--ion-text-color);
-                border-radius: 2px 12px 12px 12px;
-                color: #000000;
-            }
-        }
-    }
-}
-.chat-time {
-    font-size: 12px;
-    color: #878d99;
-    line-height: 17px;
-    text-align: center;
-    margin-bottom: 10px;
-}
-.chat-content {
-    .f();
-    justify-content: flex-end;
-    .icon {
-        width: 36px;
-        height: 36px;
-        box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.04);
-        border-radius: 4px;
-    }
-
-    .chat-message {
-        background: #000000;
-        border-radius: 12px 2px 12px 12px;
-        font-size: 16px;
-        font-family: PingFangSC-Regular, PingFang SC;
-        font-weight: 400;
-        color: var(--ion-text-color);
-        line-height: 26px;
-        padding: 10px;
-        margin: 0 8px;
-    }
-}
-</style>

+ 0 - 139
src/components/CommissionItem.vue

@@ -1,139 +0,0 @@
-<template>
-    <div class="commission-item">
-        <div class="head">
-            <div class="name">{{ detail.junior.nickname }}</div>
-            <div class="num">{{ $t('distribution.commission') }}: +{{ detail.amount }}</div>
-        </div>
-        <div class="order">
-            <van-image :src="detail.order.pic[0]" class="cover" radius="2" fit="cover" lazy-load> </van-image>
-            <div class="info">
-                <div class="name">
-                    {{ getLocaleString(detail.order.name) }}
-                </div>
-                <div class="price">
-                    <div class="value">{{ $t('balance.symbol') }}{{ (detail.order || {}).sellPrice }}</div>
-                </div>
-            </div>
-        </div>
-        <div class="bottom">
-            <div class="num">×1</div>
-            <div class="props">
-                <div class="prop">{{ $t('order.id') }}: {{ detail.orderId }}</div>
-                <div class="prop">{{ $t('order.createdAt') }}: {{ detail.createdAt }}</div>
-            </div>
-        </div>
-    </div>
-</template>
-<script setup>
-import { ref, computed, onMounted } from 'vue'
-const props = defineProps({
-    detail: {
-        type: Object,
-        default: () => {
-            return {
-                junior: {},
-                order: { pic: [] }
-            }
-        }
-    }
-})
-</script>
-<style lang="less" scoped>
-.commission-item {
-    margin: 0 16px 15px 16px;
-    background: #2c302f;
-    border-radius: 8px;
-    padding-bottom: 12px;
-    .head {
-        height: 44px;
-        background: #3d403f;
-        padding: 0 15px;
-        color: #c7f2df;
-        border-radius: 8px 8px 0 0;
-        .f();
-        .avatar {
-            width: 24px;
-            height: 24px;
-            border-radius: 2px;
-        }
-        .name {
-            flex-grow: 1;
-            font-size: 16px;
-            font-weight: bold;
-        }
-        .num {
-            font-size: 14px;
-            font-weight: bold;
-        }
-    }
-    .order {
-        .f();
-        margin-top: 14px;
-        margin-bottom: 10px;
-        padding: 0 15px;
-        .cover {
-            width: 50px;
-            height: 50px;
-            min-width: 50px;
-            --border-radius: 4px;
-        }
-        .info {
-            height: 50px;
-            margin-left: 10px;
-            flex-basis: 0;
-            flex-grow: 1;
-            min-width: 0;
-            .name {
-                max-width: 100%;
-                font-size: 14px;
-                color: #c7f2df;
-                font-weight: bold;
-                line-height: 20px;
-                .ellipsis();
-            }
-            .price {
-                .f();
-                margin-top: 5px;
-                line-height: 17px;
-                font-size: 14px;
-                width: 100%;
-                color: #c7f2df;
-                font-weight: bold;
-                .value {
-                    flex-grow: 1;
-                }
-            }
-        }
-    }
-    .prop {
-        font-size: 10px;
-        line-height: 14px;
-        color: #9fbfb1;
-        margin-top: 4px;
-    }
-}
-.commission-item.darker {
-    background: #1c1c1c;
-    .head {
-        background: #202020;
-    }
-}
-.bottom {
-    padding: 0 15px;
-    .f();
-    align-items: stretch;
-    .num {
-        width: 50px;
-        .f;
-        justify-content: center;
-        font-size: 14px;
-        color: #c7f2df;
-        font-weight: bold;
-    }
-    .props {
-        margin-left: 10px;
-        border-top: 0.5px solid #a6a6a6;
-        flex-grow: 1;
-    }
-}
-</style>

+ 33 - 2
src/components/ConnectPage.vue

@@ -18,7 +18,14 @@
                         <div class="text-[#121325] text-[13px]">客服</div>
                         <div class="text-[#386459] text-xs">whatsapp:+16272899</div>
                     </div>
-                    <van-button color="#121325" class="w-[60px]" size="mini" round>複製</van-button>
+                    <van-button
+                        color="#121325"
+                        class="w-[60px]"
+                        @click="copyText('whatsapp:+16272899')"
+                        size="mini"
+                        round
+                        >複製</van-button
+                    >
                 </div>
             </div>
             <div class="connect-info">
@@ -29,7 +36,14 @@
                         <div class="text-[#121325] text-[13px]">郵箱</div>
                         <div class="text-[#386459] text-xs">11727272636378@139.com</div>
                     </div>
-                    <van-button color="#121325" class="w-[60px]" size="mini" round>複製</van-button>
+                    <van-button
+                        color="#121325"
+                        @click="copyText('11727272636378@139.com')"
+                        class="w-[60px]"
+                        size="mini"
+                        round
+                        >複製</van-button
+                    >
                 </div>
             </div>
         </div>
@@ -38,6 +52,11 @@
 
 <script setup>
 import { ref } from 'vue'
+import { Clipboard as NativeClipboard } from '@capacitor/clipboard'
+import { useClipboard } from '@vueuse/core'
+import toast from '@/utils/toast'
+import { Capacitor } from '@capacitor/core'
+
 const show = ref(false)
 // const props = defineProps({
 //     show: {
@@ -50,6 +69,18 @@ function changeShow(val) {
     show.value = val
 }
 defineExpose({ changeShow })
+
+const { copy } = useClipboard({ legacy: true })
+async function copyText(text) {
+    if (Capacitor.isNativePlatform()) {
+        await NativeClipboard.write({
+            string: text
+        })
+    } else {
+        copy(text)
+    }
+    toast.success('复制成功')
+}
 </script>
 
 <style lang="less" scoped>

+ 0 - 49
src/components/ExploreContainer.vue

@@ -1,49 +0,0 @@
-<template>
-    <div id="container">
-        <strong>{{ name }}</strong>
-        <p>
-            Explore
-            <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">
-                UI Components
-            </a>
-        </p>
-    </div>
-</template>
-
-<script>
-import { defineComponent } from 'vue'
-
-export default defineComponent({
-    name: 'ExploreContainer',
-    props: {
-        name: String
-    }
-})
-</script>
-
-<style scoped>
-#container {
-    text-align: center;
-    position: absolute;
-    left: 0;
-    right: 0;
-    top: 50%;
-    transform: translateY(-50%);
-}
-
-#container strong {
-    font-size: 20px;
-    line-height: 26px;
-}
-
-#container p {
-    font-size: 16px;
-    line-height: 22px;
-    color: #8c8c8c;
-    margin: 0;
-}
-
-#container a {
-    text-decoration: none;
-}
-</style>

+ 0 - 108
src/components/IntroductionModal.vue

@@ -1,108 +0,0 @@
-<template>
-    <ion-modal
-        :is-open="show"
-        ref="modalRef"
-        class="open-modal"
-        @didDismiss="show = false"
-        :enter-animation="enterAnimation"
-        :leave-animation="leaveAnimation"
-    >
-        <ion-content>
-            <ion-header>
-                <ion-toolbar>
-                    <ion-buttons slot="end">
-                        <ion-button @click="dismiss()">{{ $t('common.close') }}</ion-button>
-                    </ion-buttons>
-                </ion-toolbar>
-            </ion-header>
-            <ion-content>
-                <video ref="videoRef" class="video" playsinline="true" webkit-playsinline="true" autoplay loop controls>
-                    <source
-                        src="https://paimaide.s3.ap-northeast-1.amazonaws.com/video/2023-01-30-08-00-18IHzGzzvq.mp4"
-                        type="video/mp4"
-                    />
-                </video>
-            </ion-content>
-        </ion-content>
-    </ion-modal>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import {
-    createAnimation,
-    IonButtons,
-    IonButton,
-    IonModal,
-    IonHeader,
-    IonContent,
-    IonToolbar,
-    IonTitle,
-    IonItem,
-    IonList,
-    IonAvatar,
-    IonImg,
-    IonLabel
-} from '@ionic/vue'
-
-const show = ref(false)
-const modalRef = ref(false)
-const videoRef = ref(false)
-
-function dismiss() {
-    modalRef.value.$el.dismiss()
-}
-
-const enterAnimation = baseEl => {
-    const root = baseEl.shadowRoot
-
-    const backdropAnimation = createAnimation()
-        .addElement(root.querySelector('ion-backdrop'))
-        .fromTo('opacity', '0.01', 'var(--backdrop-opacity)')
-
-    const wrapperAnimation = createAnimation()
-        .addElement(root.querySelector('.modal-wrapper'))
-        .keyframes([
-            { offset: 0, opacity: '0', transform: 'scale(0)' },
-            { offset: 1, opacity: '0.99', transform: 'scale(1)' }
-        ])
-
-    return createAnimation()
-        .addElement(baseEl)
-        .easing('ease-out')
-        .duration(500)
-        .addAnimation([backdropAnimation, wrapperAnimation])
-}
-
-const leaveAnimation = baseEl => {
-    return enterAnimation(baseEl).direction('reverse')
-}
-
-function init() {
-    show.value = true
-}
-
-defineExpose({
-    init
-})
-</script>
-
-<style lang="less" scoped>
-.video {
-    width: 100vw;
-    height: 100%;
-    display: block;
-}
-
-.back {
-    position: fixed;
-    right: 30px;
-    top: calc(var(--ion-safe-area-top) + 30px);
-    z-index: 20;
-    width: 60px;
-    height: 26px;
-    background-color: rgba(255, 255, 255, 0.1);
-    border-width: 0;
-    color: #fff;
-}
-</style>

+ 0 - 53
src/components/NewsModal.vue

@@ -1,53 +0,0 @@
-<template>
-    <ion-page>
-        <ion-header>
-            <ion-toolbar>
-                <ion-buttons slot="end">
-                    <ion-button @click="dismiss()">{{ $t('common.close') }}</ion-button>
-                </ion-buttons>
-            </ion-toolbar>
-        </ion-header>
-        <ion-content>
-            <div class="title">{{ detail.title }}</div>
-            <div class="time">{{ detail.createdAt }}</div>
-            <div class="content-wrapper">
-                <div class="content" v-html="detail.detail"></div>
-            </div>
-        </ion-content>
-    </ion-page>
-</template>
-<script setup>
-import { modalController } from '@ionic/vue'
-const props = defineProps({
-    detail: {
-        type: Object
-    }
-})
-const dismiss = async () => {
-    return await modalController.dismiss()
-}
-</script>
-<style lang="less" scoped>
-.title {
-    text-align: center;
-    padding: 10px 16px;
-    line-height: 24px;
-    font-size: 16px;
-    font-weight: bold;
-}
-.time {
-    line-height: 24px;
-    font-size: 12px;
-    color: var(--ion-color-step-400);
-    text-align: center;
-}
-.content-wrapper {
-    padding: 16px;
-    line-height: 1.5;
-    color: var(--ion-color-step-800);
-    :deep(img) {
-        width: 100%;
-        height: auto;
-    }
-}
-</style>

+ 0 - 676
src/components/OrderItem.vue

@@ -1,676 +0,0 @@
-<template>
-    <div class="order-info">
-        <div class="order-top">
-            <div class="order-no">{{ $t('order.id') }}:{{ info.id }}</div>
-            <div class="order-staus" :class="info.status">
-                {{
-                    info.locked && info.status === 'SELLING'
-                        ? $t('order.stopSale')
-                        : $t('order.status.' + [info.status])
-                }}
-            </div>
-        </div>
-
-        <div class="order-content" @click="goDetail">
-            <van-image class="suk-img" width="80" height="80" fit="cover" :src="pic" lazy-load />
-
-            <div class="order-text">
-                <div class="van-ellipsis text1">{{ getLocaleString(productInfo.name) }}</div>
-                <div class="tags">
-                    <div class="product-tag" v-if="saleBatch">{{ saleBatch.name }}</div>
-                </div>
-                <div class="text3">
-                    <div class="price">{{ $t('balance.symbol') }}{{ info.totalPrice }}</div>
-                    <div class="num">×1</div>
-                </div>
-            </div>
-        </div>
-
-        <div class="order-price">
-            <div class="price">
-                <span>{{ $t('order.totalPayment') }}:&nbsp;</span>
-                <span>{{ $t('balance.symbol') }}{{ info.totalPrice }}</span>
-            </div>
-
-            <div class="order-tips" v-if="info.status == 'NOT_PAID'">{{ $t('order.countDown') }} {{ times }}</div>
-        </div>
-
-        <div class="order-button">
-            <span class="problem" @click="showCS">{{ $t('order.hasProblem') }}</span>
-            <template v-if="info.status == 'NOT_PAID'">
-                <!-- <van-button color="#FF8F00"plain size="small" @click="confirmPayment">我已付款</van-button> -->
-                <van-button type="primary" size="small" @click="pay">{{ $t('order.payNow') }}</van-button>
-            </template>
-
-            <template v-else-if="info.status == 'NOT_CONFIRMED'">
-                <van-button color="#AAACAD" plain size="small" @click="goDetail">
-                    {{ $t('order.viewOrder') }}
-                </van-button>
-            </template>
-
-            <template v-else-if="info.status == 'SOLD_NOT_CONFIRMED'">
-                <!-- <van-button color="#AAACAD"plain size="small" @click="confirmPayment">未收到款</van-button> -->
-                <!-- <van-button type="primary"size="small" @click="confirmReceipt">确认收款</van-button> -->
-            </template>
-
-            <template v-else-if="info.status == 'SELLING'">
-                <!-- <van-button color="#AAACAD"plain size="small" @click="confirmPayment">未收到款</van-button> -->
-                <!-- <van-button type="primary"size="small" @click="confirmReceipt">确认收款</van-button> -->
-                <!-- <van-button color="#AAACAD" plain size="small" @click="applyShip">申请发货</van-button> -->
-                <van-button color="#AAACAD" plain size="small" @click="goDetail">
-                    {{ $t('order.viewOrder') }}
-                </van-button>
-            </template>
-
-            <template v-else-if="info.status == 'CONFIRMED'">
-                <!-- <van-button color="#AAACAD" plain size="small" @click="applyShip">
-                    {{ $t('order.applyShip') }}
-                </van-button> -->
-                <van-button type="primary" size="small" @click="delegate" v-if="delegationActive">
-                    {{ $t('delegate.title') }}
-                </van-button>
-                <van-button color="#aaacad" @click="showTip" v-else>{{ $t('delegate.title') }} </van-button>
-            </template>
-
-            <template v-else-if="info.status == 'SOLD'">
-                <van-button color="#AAACAD" plain size="small" @click="goDetail">
-                    {{ $t('order.viewOrder') }}
-                </van-button>
-            </template>
-            <template v-else-if="info.status == 'NOT_SHIPPED'">
-                <van-button color="#AAACAD" plain size="small" @click="goDetail">
-                    {{ $t('order.viewOrder') }}
-                </van-button>
-            </template>
-            <template v-else-if="info.status == 'RECEIVED'">
-                <van-button color="#AAACAD" plain size="small" @click="goDetail">
-                    {{ $t('order.viewOrder') }}
-                </van-button>
-            </template>
-            <template v-else-if="info.status == 'SHIPPED'">
-                <van-button type="primary" size="small" @click="receive"> {{ $t('order.confirmReceipt') }}</van-button>
-            </template>
-        </div>
-        <ion-modal id="delegate" class="dialog" ref="modal" v-model:is-open="show" @didDismiss="show = false">
-            <div class="getsold">
-                <div class="title">{{ $t('delegate.title') }}</div>
-                <table>
-                    <tr>
-                        <td>{{ $t('delegate.originalPrice') }}</td>
-                        <td>{{ $t('balance.symbol') }}{{ info.totalPrice }}</td>
-                    </tr>
-                    <tr>
-                        <td>{{ $t('delegate.increase') }}</td>
-                        <td>{{ riseRatePercent }}%</td>
-                    </tr>
-                    <tr>
-                        <td>{{ $t('delegate.sellPrice') }}</td>
-                        <td class="price">{{ $t('balance.symbol') }}{{ soldValue }}</td>
-                    </tr>
-                </table>
-                <div class="tips">
-                    {{ $t('delegate.tip1', { riseRatePercent, serviceCharge: serviceValue }) }}
-                </div>
-
-                <van-button class="button" block type="primary" @click="sale" :disabled="loading || serviceCharge <= 0">
-                    {{ $t('delegate.payServiceCharge') }}<br />
-                    {{ $t('balance.symbol') }}{{ serviceCharge }}
-                </van-button>
-            </div>
-        </ion-modal>
-    </div>
-</template>
-<script>
-import { mapState } from 'pinia'
-import { useUserStore } from '@/stores/user'
-import { useSystemStore } from '@/stores/system'
-import { orderStatus } from '../status'
-import { differenceInSeconds, addMinutes, parse } from 'date-fns'
-import { showDialog } from 'vant'
-
-export default {
-    name: 'orderInfo',
-    inject: ['showCS'],
-    props: {
-        info: {
-            type: Object,
-            default: () => {
-                return {}
-            }
-        },
-        delegationActive: {
-            type: Boolean,
-            default: true
-        },
-        delegationTime: {
-            type: String,
-            default: ''
-        }
-    },
-    data() {
-        return {
-            orderStatus,
-            times: 0,
-            show: false,
-            value: 6,
-            loading: false,
-            payType: 'balance'
-        }
-    },
-    computed: {
-        ...mapState(useUserStore, ['user']),
-        ...mapState(useSystemStore, ['platformCommission', 'saleBatches']),
-        riseRate() {
-            let info = this.saleBatches.find(item => item.id == this.info.batchId)
-            let _back = null
-            if (info) {
-                _back = info.extensions.find(item => {
-                    return item.minPrice <= this.info.totalPrice && this.info.totalPrice <= item.maxPrice
-                })
-            }
-            return _back?.riseRate || 0
-        },
-        riseRatePercent() {
-            return this.riseRate * 100
-        },
-        platformCommission() {
-            let info = this.saleBatches.find(item => item.id == this.info.batchId)
-            let _back = null
-            if (info) {
-                _back = info.extensions.find(item => {
-                    return item.minPrice <= this.info.totalPrice && this.info.totalPrice <= item.maxPrice
-                })
-            }
-            return _back?.serviceCharge || 0
-        },
-        productInfo() {
-            return this.info.productInfo || {}
-        },
-        pic() {
-            if (this.productInfo.pic && this.productInfo.pic.length > 0) {
-                return this.productInfo.pic[0]
-            } else {
-                return ''
-            }
-        },
-        fromUserInfo() {
-            return this.info.fromUserInfo || {}
-        },
-        soldValue() {
-            var price = Number(this.info.totalPrice)
-            if (this.riseRatePercent) {
-                var more = this.mul(this.riseRatePercent, price)
-                more = this.mul(more, 0.01)
-                price = this.addNum(more, price)
-            }
-            return price.toFixed(2)
-        },
-        serviceCharge() {
-            let totalPrice = this.info ? this.info.totalPrice || 0 : 0
-            return (totalPrice * this.platformCommission).toFixed(2)
-        },
-        serviceValue() {
-            return this.platformCommission * 100
-        },
-        saleBatch() {
-            if (this.saleBatches && this.saleBatches.length > 0) {
-                return this.saleBatches.find(i => this.info.batchId === i.id)
-            } else {
-                return null
-            }
-        }
-    },
-    created() {
-        if (this.info.status == 'NOT_PAID') {
-            this.getTime()
-        }
-        // this.$http.get('/sysConfig/get/pay_type').then(res => {
-        //     if (res.value == '0') {
-        //         if (this.isWeixin) {
-        //             this.payType = 'weixin';
-        //         } else {
-        //             this.payType = 'alipay';
-        //         }
-        //     } else {
-        //         this.payType = 'third';
-        //     }
-        // });
-    },
-    methods: {
-        goDetail() {
-            this.$router.push({
-                name: 'orderDetail',
-                query: {
-                    id: this.info.id
-                }
-            })
-        },
-        getTime() {
-            var times = differenceInSeconds(
-                addMinutes(parse(this.info.createdAt, 'yyyy-MM-dd HH:mm:ss', new Date()), 30),
-                new Date()
-            )
-            if (times <= 0) {
-                times = 0
-            }
-            var min = parseInt(times / 60)
-            var seconds = parseInt(times % 60)
-            this.times = min + this.$t('order.minute') + ' ' + seconds + this.$t('order.second')
-            if (!times) {
-                return
-            }
-            setTimeout(this.getTime, 1000)
-        },
-        confirmPayment() {
-            this.$http
-                .post('/order/confirmPayment', {
-                    orderId: this.info.id
-                })
-                .then(res => {
-                    this.$toast.success('确认成功')
-                    setTimeout(() => {
-                        this.$emit('updateOrder', res)
-                    }, 1000)
-                })
-                .catch(e => {
-                    return this.$toast.error(e.error)
-                })
-        },
-        confirmReceipt() {
-            this.$http
-                .post('/order/confirmReceipt', {
-                    orderId: this.info.id
-                })
-                .then(res => {
-                    this.$toast.success('确认成功')
-                    setTimeout(() => {
-                        this.$emit('updateOrder', res)
-                    }, 1000)
-                })
-                .catch(e => {
-                    return this.$toast.error(e.error)
-                })
-        },
-        addNum(a, b) {
-            var c, d, e
-            try {
-                c = a.toString().split('.')[1].length
-            } catch (f) {
-                c = 0
-            }
-            try {
-                d = b.toString().split('.')[1].length
-            } catch (f) {
-                d = 0
-            }
-            return (e = Math.pow(10, Math.max(c, d))), (this.mul(a, e) + this.mul(b, e)) / e
-        },
-        mul(a, b) {
-            var c = 0,
-                d = a.toString(),
-                e = b.toString()
-            try {
-                c += d.split('.')[1].length
-            } catch (f) {
-                // eslint-disable-next-line no-empty
-            }
-            try {
-                c += e.split('.')[1].length
-            } catch (f) {
-                // eslint-disable-next-line no-empty
-            }
-            return (Number(d.replace('.', '')) * Number(e.replace('.', ''))) / Math.pow(10, c)
-        },
-        sale() {
-            this.show = false
-            if (this.payType === 'alipay') {
-                window.open(
-                    this.$baseUrl +
-                        `/payDelegation/alipay?userId=${this.user.id}&orderId=${this.info.id}&riseRate=${
-                            this.riseRatePercent / 100
-                        }&returnUrl=${encodeURIComponent(location.href)}`
-                )
-            } else if (this.payType === 'third') {
-                window.open(
-                    this.$baseUrl +
-                        `/payDelegation/third?userId=${this.user.id}&orderId=${this.info.id}&riseRate=${
-                            this.riseRatePercent / 100
-                        }&payType=` +
-                        (this.isWeixin ? 'WXGZH' : 'ZFBH5')
-                )
-            } else if (this.payType === 'weixin') {
-                this.loading = true
-                this.$toast.loading({
-                    mask: false,
-                    message: '加载中...',
-                    duration: 0,
-                    forbidClick: true
-                })
-                this.$http
-                    .get('/payDelegation/wx', {
-                        userId: this.user.id,
-                        orderId: this.info.id,
-                        riseRate: this.riseRatePercent / 100
-                    })
-                    .then(res => {
-                        this.$toast.dismiss()
-                        this.loading = false
-                        wx.chooseWXPay({
-                            appId: res.appId,
-                            timestamp: res.timeStamp,
-                            nonceStr: res.nonceStr,
-                            package: res.packageValue,
-                            signType: res.signType,
-                            paySign: res.paySign,
-                            success(res) {
-                                this.$toast.success(this.$t('order.paySuccess'))
-                            }
-                        })
-                    })
-                    .catch(e => {
-                        this.$toast.dismiss()
-                        this.loading = false
-                        return this.$toast.error(e.error)
-                    })
-            } else if (this.payType === 'balance') {
-                this.$toast.loading(this.$t('order.paying'))
-                this.$http
-                    .post('/payDelegation/balance', {
-                        userId: this.user.id,
-                        orderId: this.info.id,
-                        riseRate: this.riseRatePercent / 100
-                    })
-                    .then(res => {
-                        this.logDelegationPurchase(this.serviceCharge, res)
-                        this.$toast.success(this.$t('order.paySuccess'))
-                        this.$emit('updateOrder', res)
-                    })
-                    .catch(e => {
-                        this.$toast.error(e.error)
-                    })
-            }
-        },
-        applyShip() {
-            this.$dialog
-                .confirm({
-                    title: '提示',
-                    message: '确认申请发货?'
-                })
-                .then(() => {
-                    this.$http
-                        .post('/order/applyShip', {
-                            orderId: this.info.id
-                        })
-                        .then(res => {
-                            this.$toast.success('申请成功')
-                            setTimeout(() => {
-                                this.$emit('updateOrder', res)
-                            }, 1000)
-                        })
-                        .catch(e => {
-                            return this.$toast.error(e.error)
-                        })
-                })
-                .catch(() => {})
-        },
-        receive() {
-            this.$http
-                .post('/order/receive', {
-                    orderId: this.info.id
-                })
-                .then(res => {
-                    this.$toast.success('确认成功')
-                    setTimeout(() => {
-                        this.$emit('updateOrder', res)
-                    }, 1000)
-                })
-                .catch(e => {
-                    return this.$toast.error(e.error)
-                })
-        },
-        pay() {
-            this.$toast.loading(this.$t('order.paying'))
-            this.$http
-                .post('/order/balancePay', { orderId: this.info.id })
-                .then(res => {
-                    this.logOrderPurchase(this.info)
-                    this.$toast.success(this.$t('order.paySuccess'))
-                    this.$emit('updateOrder', this.info.id)
-                })
-                .catch(e => {
-                    this.$toast.error(e.error)
-                })
-        },
-        delegate() {
-            this.$http.get('/saleBatch/get/' + this.info.batchId).then(res => {
-                this.batchInfo = res
-                this.show = true
-            })
-        },
-        showTip() {
-            showDialog({
-                title: this.$t('common.alert'),
-                message: this.$t('delegate.tip', { time: this.delegationTime })
-            })
-        },
-        problem() {
-            document.querySelector('#btn-customer').click()
-        }
-    }
-}
-</script>
-<style lang="less" scoped>
-@status-color: #ff8f00;
-.order-info {
-    padding: 0 15px;
-    background: var(--ion-color-step-0);
-    border-radius: 2px;
-    margin: 12px 16px 0 16px;
-}
-
-.order-top {
-    display: flex;
-    justify-content: space-between;
-    height: 43px;
-    align-items: center;
-    font-size: 13px;
-    color: var(--ion-text-color);
-    .order-staus {
-        color: @status-color;
-        font-size: 13px;
-        font-weight: bold;
-        &.NOT_PAID {
-            color: var(--ion-color-danger);
-        }
-        &.CANCELED,
-        &.SOLD {
-            color: var(--ion-color-step-300);
-        }
-        &.CONFIRMED,
-        &.SELLING {
-            .gt();
-        }
-    }
-}
-
-.text1 {
-    font-size: 14px;
-    line-height: 20px;
-}
-
-.text2 {
-    font-size: 12px;
-    line-height: 22px;
-    height: 22px;
-    background: var(--ion-color-step-50);
-    border-radius: 2px 100px 100px 100px;
-    padding: 0 8px;
-    margin-top: 5px;
-    display: inline-block;
-}
-.tags {
-    .f();
-    margin-top: 8px;
-}
-.text3 {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    margin-top: 13px;
-
-    .price {
-        font-size: 14px;
-        font-weight: bold;
-        line-height: 20px;
-    }
-
-    .num {
-        font-size: 14px;
-        color: var(--ion-color-step-300);
-        line-height: 20px;
-    }
-}
-.order-content {
-    display: flex;
-    padding-top: 10px;
-    border-top: 1px solid var(--ion-color-step-50);
-
-    .order-text {
-        margin-left: 10px;
-        flex-grow: 1;
-        overflow: hidden;
-    }
-}
-
-.order-price {
-    text-align: right;
-
-    .price {
-        font-size: 12px;
-        color: var(--ion-color-step-300);
-        line-height: 17px;
-        margin-top: 18px;
-        span {
-            &:last-child {
-                font-size: 16px;
-                font-weight: bold;
-                color: var(--ion-color-danger);
-                line-height: 22px;
-            }
-        }
-    }
-    .order-tips {
-        font-size: 12px;
-        color: var(--ion-color-danger);
-        line-height: 17px;
-        margin-top: 8px;
-    }
-}
-
-.order-button {
-    padding: 15px 0;
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
-    border-top: 1px solid var(--ion-color-step-50);
-    margin-top: 15px;
-
-    .van-button {
-        margin-left: 10px;
-        height: 28px;
-        border-radius: 4px;
-    }
-    .problem {
-        flex-grow: 1;
-        text-align: left;
-        font-size: 12px;
-        font-weight: 400;
-        color: var(--ion-color-step-300);
-    }
-}
-.getsold {
-    width: calc(100vw - 60px);
-    padding: 15px 20px;
-    box-sizing: border-box;
-    .title {
-        font-size: 16px;
-        font-weight: bold;
-        text-align: center;
-        color: rgba(var(--ion-color-light-contrast-rgb), 1);
-        line-height: 22px;
-    }
-
-    table {
-        width: 100%;
-        margin-top: 20px;
-        border-collapse: collapse;
-        font-size: 14px;
-        font-weight: bold;
-        tr {
-            border-bottom: 1px solid var(--ion-color-step-50);
-        }
-        td {
-            line-height: 56px;
-        }
-        .price {
-            font-size: 16px;
-            color: rgba(255, 59, 48, 1);
-            line-height: 22px;
-        }
-    }
-    .tips {
-        font-size: 13px;
-        color: rgba(170, 172, 173, 1);
-        line-height: 18px;
-        margin-top: 20px;
-    }
-
-    .button {
-        width: 200px;
-        display: block;
-        margin: 50px auto 0;
-    }
-}
-
-ion-modal#delegate {
-    --width: fit-content;
-    --min-width: 250px;
-    --height: fit-content;
-    --border-radius: 6px;
-}
-.dark .order-info {
-    @status-color: #f0ff00;
-    background: var(--ion-color-step-50);
-    .order-top {
-        .order-staus {
-            color: @status-color;
-            &.NOT_PAID {
-                color: @status-color;
-            }
-            &.CANCELED,
-            &.SOLD {
-                color: var(--ion-color-step-300);
-            }
-            &.CONFIRMED,
-            &.SELLING {
-                background: none;
-                background-clip: inherit;
-                -webkit-background-clip: inherit;
-                -webkit-text-fill-color: inherit;
-            }
-        }
-    }
-    .order-price {
-        .price {
-            span {
-                &:last-child {
-                    color: @status-color;
-                }
-            }
-        }
-        .order-tips {
-            color: @status-color;
-        }
-    }
-}
-</style>

+ 162 - 0
src/components/PosterPage.vue

@@ -0,0 +1,162 @@
+<template>
+    <ion-modal :is-open="show" ref="modalRef" class="open-modal" @didDismiss="dismiss">
+        <div class="modal-box">
+            <img :src="posterUrl" alt="" />
+
+            <div class="btns">
+                <van-button
+                    class="copy"
+                    @click="copyText(shareUrl)"
+                    round
+                    color="linear-gradient(180deg, #FFD1D8 , #FFEAD0 )"
+                    >复制链接</van-button
+                >
+                <van-button class="save" @click="save" round color="linear-gradient(180deg, #FF7340 , #FF3E3E )"
+                    >保存图片</van-button
+                >
+            </div>
+        </div>
+    </ion-modal>
+</template>
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import VueQrcode from '@chenfengyuan/vue-qrcode'
+import qrcode from 'qrcode'
+import { Capacitor } from '@capacitor/core'
+import { AppsFlyer } from 'appsflyer-capacitor-plugin'
+import { useUserStore } from '../stores/user'
+import shareImg from '@/assets/png-yaoqinghaoyou-tanchuang.png'
+import { Clipboard as NativeClipboard } from '@capacitor/clipboard'
+import { useClipboard } from '@vueuse/core'
+import toast from '@/utils/toast'
+import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'
+import { Media } from '@capacitor-community/media'
+
+const show = ref(false)
+
+function dismiss() {
+    show.value = false
+}
+
+const { user } = useUserStore()
+async function getInviteUrl() {
+    console.log(user)
+    console.log(user.id)
+    if (Capacitor.isNativePlatform()) {
+        const { link } = await AppsFlyer.generateInviteLink({
+            addParameters: { invitor: `${user.id}`, af_sub1: `${user.id}` }
+        })
+        return link
+    } else {
+        return `https://ifirstcash.onelink.me/cQAJ?af_js_web=true&af_ss_ver=2_3_0&pid=af_user_invite&invitor=${user.id}&af_sub1=${user.id}&af_ss_ui=true`
+    }
+}
+
+const shareUrl = ref('')
+const posterUrl = ref('')
+async function onOpenInviteModal() {
+    let qrImg = new Image()
+    let bgImg = new Image()
+    const url = await getInviteUrl()
+    shareUrl.value = url
+    function loadQR() {
+        return new Promise((resolve, reject) => {
+            qrcode.toDataURL(url, { width: 335, height: 335, margin: 0 }, function (err, url) {
+                if (err) {
+                    reject(err)
+                }
+                qrImg.onload = () => {
+                    resolve()
+                }
+                qrImg.src = url
+            })
+        })
+    }
+    function loadBg() {
+        return new Promise((resolve, reject) => {
+            bgImg.onload = () => {
+                resolve()
+            }
+            bgImg.src = shareImg
+        })
+    }
+    await Promise.all([loadQR(), loadBg()])
+    let canvas = document.createElement('canvas')
+    canvas.width = 900
+    canvas.height = 1200
+    let ctx = canvas.getContext('2d')
+    ctx.drawImage(bgImg, 0, 0)
+    ctx.drawImage(qrImg, 230, 420, 450, 450)
+    let dataUrl = canvas.toDataURL('image/png')
+    posterUrl.value = dataUrl
+    show.value = true
+}
+defineExpose({ onOpenInviteModal })
+
+const { copy } = useClipboard({ legacy: true })
+async function copyText(text) {
+    if (Capacitor.isNativePlatform()) {
+        await NativeClipboard.write({
+            string: text
+        })
+    } else {
+        copy(text)
+    }
+    toast.success('复制成功')
+}
+
+async function save() {
+    if (Capacitor.isNativePlatform()) {
+        //
+        try {
+            const savedFile = await Filesystem.writeFile({
+                path: 'share.png',
+                data: posterUrl.value,
+                directory: Directory.Data
+            })
+            await Media.savePhoto({
+                path: savedFile.uri,
+                album: 'FirstCash'
+            })
+            toast.success('保存成功')
+        } catch (e) {
+            console.log(e)
+        }
+    } else {
+        var link = document.createElement('a')
+        link.setAttribute('download', 'Share.png')
+        link.setAttribute('href', posterUrl.value.replace('image/png', 'image/octet-stream'))
+        link.click()
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.open-modal {
+    --height: fit-content;
+    --border-radius: 8px;
+    --width: 300px;
+    --background: transparent;
+}
+
+.modal-box {
+    position: relative;
+    .btns {
+        display: flex;
+        justify-content: space-between;
+        padding: 40px 0;
+        .copy {
+            width: 100px;
+            color: #ff4034 !important;
+            font-family: AlimamaShuHeiTi;
+            text-shadow: 0px 2px 1px #ffffff;
+        }
+
+        .save {
+            width: 184px;
+            font-family: AlimamaShuHeiTi;
+            text-shadow: 0px 2px 1px #e71d0c;
+        }
+    }
+}
+</style>

+ 0 - 232
src/components/ProductBanner.vue

@@ -1,232 +0,0 @@
-<template>
-    <div class="detail-top">
-        <img src="../assets/png-bg-shangping.png" class="bg-img" alt="" />
-        <div class="detail-info">
-            <swiper @swiper="setSwiperRef" class="mySwiper" v-if="banners.length > 0">
-                <swiper-slide v-for="(item, index) in banners" :key="index">
-                    <!-- <img :src="item" /> -->
-
-                    <!-- <div class="video-box" v-if="isVideo(item)">
-                        <van-icon color="#fff" size="60" name="play-circle" />
-                    </div> -->
-                    <van-image
-                        @click="preview(index, banners)"
-                        :src="item"
-                        width="calc(100vw - 134px)"
-                        height="calc(100vw - 134px)"
-                        fit="cover"
-                    />
-                </swiper-slide>
-            </swiper>
-        </div>
-    </div>
-</template>
-
-<script>
-import { Swiper, SwiperSlide } from 'swiper/vue'
-
-import 'swiper/swiper.min.css'
-import 'swiper/swiper-bundle.min.css'
-import { showImagePreview } from 'vant'
-import { watch, ref, computed } from 'vue'
-import { useWindowSize } from '@vant/use'
-export default {
-    setup() {
-        const { width, height } = useWindowSize()
-
-        let swiperRef = null
-
-        const setSwiperRef = swiper => {
-            swiperRef = swiper
-        }
-
-        watch([width, height], () => {
-            swiperRef.update(true)
-        })
-
-        return { swiperRef: null, setSwiperRef }
-    },
-    props: {
-        info: {
-            type: Object,
-            default: () => {
-                return {}
-            }
-        }
-    },
-    components: {
-        Swiper,
-        SwiperSlide
-    },
-    computed: {
-        banners() {
-            return this.info.pic || []
-        }
-    },
-    methods: {
-        preview(index = 0, list = []) {
-            // showImagePreview({
-            //     images: [...list],
-            //     closeable: true
-            // })
-        }
-    }
-}
-</script>
-
-<style lang="less" scoped>
-@radius: 30px;
-.detail-top {
-    position: relative;
-    width: 100vw;
-    // height: 100vw;
-    .detail-info {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        padding-bottom: 50px;
-    }
-    .bg-img {
-        display: block;
-        width: 100vw;
-    }
-    .mySwiper {
-        border-radius: @radius;
-        border: 2px solid var(--ion-text-color);
-        padding: 5px;
-        overflow: hidden;
-        .detail-animate();
-        width: calc(100vw - 124px);
-        .swiper-slide {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            position: relative;
-            overflow: hidden;
-            :deep(.van-image) {
-                border: 2px solid #2f2f2f;
-                border-radius: @radius;
-                overflow: hidden;
-                background-color: transparent;
-            }
-        }
-    }
-}
-
-.share-content {
-    display: flex;
-    margin-top: 20px;
-}
-.share-icon {
-    img {
-        width: 18px;
-        height: 18px;
-        display: inline-block;
-        vertical-align: middle;
-    }
-    span {
-        font-size: 14px;
-        color: #949699;
-        line-height: 24px;
-        vertical-align: middle;
-        margin-left: 2px;
-    }
-
-    &.shareLeft {
-        margin-left: 30px;
-    }
-}
-
-.video-box {
-    position: absolute;
-    left: 0;
-    top: 0;
-    right: 0;
-    bottom: 0;
-    background-color: rgba(var(--ion-text-color-rgb), 0.5);
-    .f();
-    justify-content: center;
-
-    .van-icon {
-        color: rgba(0, 0, 0, 0.5) !important;
-    }
-}
-
-.setAvatar {
-    .f();
-    img {
-        width: 18px;
-        height: 18px;
-        display: block;
-    }
-
-    span {
-        font-size: 12px;
-        color: #949699;
-        line-height: 24px;
-        margin-left: 3px;
-    }
-}
-
-@keyframes flipY {
-    0% {
-        transform: perspective(500px) rotateX(0deg) rotateY(15deg);
-    }
-    50% {
-        transform: perspective(500px) rotateX(0deg) rotateY(-15deg);
-    }
-    100% {
-        transform: perspective(500px) rotateX(0deg) rotateY(15deg);
-    }
-}
-@keyframes flipYLight {
-    0% {
-        left: -100vw;
-        opacity: 0.3;
-        width: 25px;
-    }
-    25% {
-        left: 45vw;
-        opacity: 0;
-        width: 45px;
-    }
-    50% {
-        left: 150vw;
-        opacity: 0.3;
-        width: 25px;
-    }
-    75% {
-        left: 45vw;
-        opacity: 0;
-        width: 45px;
-    }
-    100% {
-        left: -100vw;
-        opacity: 0.3;
-        width: 25px;
-    }
-}
-
-.detail-animate() {
-    animation: flipY 6s ease-in-out infinite;
-    position: relative;
-    &::after {
-        content: '';
-        background-color: #fff;
-        position: absolute;
-        background: #fff;
-        width: 25px;
-        height: 100vw;
-        top: 0;
-        left: 45px;
-        opacity: 0;
-        transform: skewX(-25deg);
-        z-index: 2;
-        animation: flipYLight 6s ease-in-out infinite;
-    }
-}
-</style>

+ 0 - 387
src/components/ProductItem.vue

@@ -1,387 +0,0 @@
-<template>
-    <div class="product-info" :class="{ product2: list }" @click="goDetail">
-        <van-image :width="width" :height="width" fit="cover" :src="getImg(info.pic)" lazy-load />
-        <div class="img_box" v-if="info.status === 'SOLD_OUT'" :style="{ width: width + 'px', height: width + 'px' }">
-            <img src="@/assets/solded.png" alt="" />
-        </div>
-        <div class="content">
-            <div class="text1 van-ellipsis">{{ getLocaleString(info.name) }}</div>
-            <div class="badges" v-if="list">
-                <div class="badge">{{ category.name }}</div>
-                <div class="badge">{{ riseRatePercent }}%&nbsp;{{ $t('product.riseDesc') }}</div>
-                <div class="badge">{{ $t('product.tag') }}</div>
-            </div>
-            <div class="flex1"></div>
-            <div class="datas">
-                <div class="data">
-                    <div class="val" v-if="!noSale">
-                        <span class="nor">{{ $t('balance.symbol') }}</span>
-                        <span>{{ info.currentPrice }}</span>
-                    </div>
-                    <div class="val" v-else>
-                        <span>***</span>
-                    </div>
-                    <div class="name">{{ $t('product.priceNow') }}</div>
-                </div>
-                <!-- <div class="data">
-                    <div class="val" v-if="!noSale">{{ riseRatePercent }}%</div>
-                    <div class="val" v-else>
-                        <span>***</span>
-                    </div>
-                    <div class="name">{{ $t('product.dailyEarning') }}</div>
-                </div> -->
-                <div class="data">
-                    <div class="val" v-if="!noSale">
-                        <span>{{ $t('balance.symbol') }}</span>
-                        <span>{{ nextPrice || 0 }}</span>
-                    </div>
-                    <div class="val" v-else>
-                        <span>***</span>
-                    </div>
-                    <div class="name">{{ $t('product.tomorrowBuy') }}</div>
-                </div>
-            </div>
-        </div>
-
-        <div class="status" v-if="diffTime">
-            <img src="../assets/info_icon_shijian.png" alt="" />
-            <span> <van-count-down :time="diffTime" format="HH:mm:ss" @finish="getDiffTime" /></span>
-        </div>
-
-        <div class="status" v-if="notStart">
-            <!-- <img src="../assets/info_icon_shijian.png" alt="" /> -->
-            <span>{{ $t('product.start', { time: batchStatus }) }}</span>
-        </div>
-
-        <div class="not" v-if="noSale">
-            <img src="@/assets/not-ava.png" alt="" />
-            <span>{{ $t('common.notAvailable') }}...</span>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { computed, ref, onMounted } from 'vue'
-import { useIonRouter } from '@ionic/vue'
-import { useSystemStore } from '../stores/system'
-import { accAdd, accMul } from '../plugins/calc'
-import toast from '@/utils/toast'
-import { useI18n } from 'vue-i18n'
-import { useUserStore } from '@/stores/user'
-import { isAfter, addDays, differenceInMilliseconds, format } from 'date-fns'
-import { showDialog } from 'vant'
-
-const { t } = useI18n()
-const props = defineProps({
-    info: {
-        type: Object,
-        default: () => {
-            return {}
-        }
-    },
-    list: {
-        type: Boolean,
-        default: false
-    },
-    stopOfficial: {
-        type: Boolean,
-        default: false
-    },
-    batchStatus: {
-        type: String,
-        default: ''
-    }
-})
-const category = computed(() => {
-    return props.info.category || {}
-})
-
-const router = useIonRouter()
-
-const width = computed(() => {
-    return props.list ? 104 : 78
-})
-
-const userStore = useUserStore()
-const user = computed(() => {
-    return userStore.user
-})
-
-const notStart = computed(() => {
-    return props.batchStatus !== '抢购中' && props.info.status === 'IN_STOCK'
-})
-
-const noSale = computed(() => {
-    return props.stopOfficial && props.info.sales === 0
-})
-
-const goDetail = () => {
-    if (notStart.value) {
-        toast(t('product.start', { time: props.batchStatus }))
-    } else if (noSale.value) {
-        toast(t('common.notAvailable') + '...')
-    } else if (diffTime.value) {
-        showDialog({
-            title: t('common.alert'),
-            message: t('product.delayTips')
-        })
-    } else {
-        router.push({
-            path: '/productDetail',
-            query: {
-                id: props.info.id
-            }
-        })
-    }
-}
-
-const riseRate = computed(() => {
-    return useSystemStore().saleBatches.find(item => item.id == props.info.batchId)?.riseRate || 0
-})
-
-const riseRatePercent = computed(() => {
-    return useSystemStore().saleBatches.find(item => item.id == props.info.batchId)?.riseRate * 100 || 0
-})
-
-const nextPrice = computed(() => {
-    return accMul(props.info.currentPrice, accAdd(1, riseRate.value)).toFixed(2)
-})
-
-const diffTime = ref(0)
-onMounted(() => {
-    getDiffTime()
-})
-function getDiffTime() {
-    if (props.info.delayTo && isAfter(new Date(props.info.delayTo), new Date())) {
-        diffTime.value = differenceInMilliseconds(new Date(props.info.delayTo), new Date())
-    } else {
-        diffTime.value = 0
-    }
-}
-</script>
-
-<style lang="less" scoped>
-.product-info {
-    .f();
-    .van-image {
-        flex-shrink: 0;
-    }
-    position: relative;
-}
-
-.img_box {
-    position: absolute;
-    top: 0;
-    left: 0;
-    background: rgba(17, 17, 17, 0.7);
-    z-index: 30;
-    .f();
-    justify-content: center;
-    img {
-        width: 80%;
-    }
-}
-
-.not {
-    .f();
-    background-color: #ffe6b8;
-    height: 20px;
-    padding: 2px 6px;
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: 20;
-    border-radius: 8px 0 8px 0;
-    img {
-        width: 12px;
-        height: 12px;
-    }
-
-    span {
-        font-size: 10px;
-        line-height: 15px;
-        font-weight: bold;
-        color: rgba(194, 136, 0, 1);
-        margin-left: 5px;
-    }
-}
-
-.status {
-    .f();
-    background-color: #5d7368;
-    height: 20px;
-    padding: 2px 6px;
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: 20;
-    border-radius: 8px 0 8px 0;
-    img {
-        width: 16px;
-        height: 16px;
-    }
-
-    span {
-        font-size: 10px;
-        line-height: 15px;
-        font-weight: bold;
-        color: #fff;
-        margin-left: 2px;
-    }
-
-    .van-count-down {
-        --van-count-down-font-size: 12px;
-    }
-}
-
-.content {
-    align-self: stretch;
-    flex-grow: 1;
-    overflow: hidden;
-    padding: 8px;
-    .f-col();
-
-    .text1 {
-        font-size: 13px;
-        color: var(--ion-color-step-900);
-        line-height: 18px;
-        font-weight: bold;
-    }
-
-    .badges {
-        .f();
-        margin-top: 4px;
-        .badge {
-            font-size: 10px;
-            color: #c085ff;
-            // background: #2d2d56;
-            line-height: 16px;
-            border-radius: 2px;
-            padding: 0 4px;
-        }
-        .badge + .badge {
-            margin-left: 6px;
-        }
-    }
-}
-
-.flex1 {
-    flex-grow: 1;
-}
-
-.datas {
-    .f();
-    justify-content: space-between;
-    .data {
-        .f-col();
-        align-items: center;
-        .val {
-            font-size: 13px;
-            font-weight: bold;
-            color: var(--ion-color-light-contrast);
-            line-height: 18px;
-            .nor {
-                font-weight: normal;
-            }
-        }
-        .name {
-            font-size: 10px;
-            color: rgba(var(--ion-color-light-contrast-rgb), 0.6);
-            line-height: 10px;
-            white-space: nowrap;
-            margin-top: 2px;
-        }
-
-        &:nth-child(1) {
-            align-items: flex-start;
-            .val {
-                color: var(--red);
-            }
-        }
-
-        &:nth-child(2) {
-            .val {
-                color: var(--green);
-            }
-        }
-    }
-}
-
-.product2 {
-    .content {
-        padding: 6px 10px;
-
-        .text1 {
-            font-size: 14px;
-            color: #c7f2df;
-            line-height: 24px;
-            font-weight: bold;
-        }
-        .badges {
-            .badge {
-                color: #c085ff;
-                font-weight: bold;
-                border: 0.5px solid #c085ff;
-
-                &:nth-child(2) {
-                    color: #39f3bb;
-                    border: 0.5px solid #39f3bb;
-                }
-
-                &:nth-child(3) {
-                    color: #fff43e;
-                    border: 0.5px solid #fff43e;
-                }
-            }
-        }
-    }
-
-    .datas {
-        border-top: 0.5px solid #9fbfb1;
-        .f();
-        .data {
-            width: 33%;
-            .f-col();
-            align-items: center;
-            .val {
-                font-size: 13px;
-                font-weight: bold;
-                color: var(--ion-color-step-800);
-                line-height: 24px;
-                small {
-                    font-size: 10px;
-                    line-height: 24px;
-                }
-            }
-            .name {
-                font-size: 10px;
-                color: #9fbfb1;
-                line-height: 10px;
-                white-space: nowrap;
-                margin-top: 0;
-            }
-
-            &:nth-child(1) {
-                align-items: flex-start;
-                .val {
-                    color: var(--yellow);
-                }
-            }
-
-            &:nth-child(2) {
-                align-items: flex-start;
-                .val {
-                    color: var(--green);
-                }
-            }
-
-            &:nth-child(3) {
-                align-items: flex-start;
-                .val {
-                    color: #c7f2df;
-                }
-            }
-        }
-    }
-}
-</style>

+ 0 - 294
src/components/ProductItemSmall.vue

@@ -1,294 +0,0 @@
-<template>
-    <div class="product-info" :class="{ product2: list }" @click="goDetail">
-        <van-image width="calc(50vw - 24px)" height="calc(50vw - 24px)" fit="cover" :src="getImg(info.pic)" lazy-load />
-        <div class="img_box" v-if="info.status === 'SOLD_OUT'" :style="{ width: width + 'px', height: width + 'px' }">
-            <img src="@/assets/solded.png" alt="" />
-        </div>
-        <div class="content">
-            <div class="text1 van-ellipsis">{{ getLocaleString(info.name) }}</div>
-            <div class="badges">
-                <div class="badge">{{ category.name }}</div>
-                <!-- <div class="badge">{{ riseRatePercent }}%&nbsp;{{ $t('product.riseDesc') }}</div> -->
-                <!-- <div class="badge">{{ $t('product.tag') }}</div> -->
-            </div>
-            <div class="datas">
-                <div class="data">
-                    <div class="val" v-if="!noSale">
-                        <span class="nor">{{ $t('balance.symbol') }}</span>
-                        <span>{{ info.currentPrice }}</span>
-                    </div>
-
-                    <div class="val" v-else>
-                        <span>***</span>
-                    </div>
-                    <div class="name">{{ $t('product.priceNow') }}</div>
-                </div>
-            </div>
-        </div>
-
-        <div class="status" v-if="diffTime">
-            <img src="../assets/info_icon_shijian.png" alt="" />
-            <span> <van-count-down :time="diffTime" format="HH:mm:ss" @finish="getDiffTime" /></span>
-        </div>
-        <div class="status" v-if="notStart">
-            <img src="../assets/info_icon_shijian.png" alt="" />
-            <span>{{ $t('product.start', { time: batchStatus }) }}</span>
-        </div>
-
-        <div class="not" v-if="noSale">
-            <img src="@/assets/not-ava.png" alt="" />
-            <span>{{ $t('common.notAvailable') }}...</span>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { computed, ref, onMounted } from 'vue'
-import { useIonRouter } from '@ionic/vue'
-import { useSystemStore } from '../stores/system'
-import { accAdd, accMul } from '../plugins/calc'
-import toast from '@/utils/toast'
-import { useI18n } from 'vue-i18n'
-import { useUserStore } from '@/stores/user'
-import { isAfter, addDays, differenceInMilliseconds, format } from 'date-fns'
-import { showDialog } from 'vant'
-
-const { t } = useI18n()
-const props = defineProps({
-    info: {
-        type: Object,
-        default: () => {
-            return {}
-        }
-    },
-    list: {
-        type: Boolean,
-        default: false
-    },
-    stopOfficial: {
-        type: Boolean,
-        default: false
-    },
-    batchStatus: {
-        type: String,
-        default: ''
-    }
-})
-const category = computed(() => {
-    return props.info.category || {}
-})
-
-const userStore = useUserStore()
-const user = computed(() => {
-    return userStore.user
-})
-
-const notStart = computed(() => {
-    return props.batchStatus !== '抢购中' && props.info.status === 'IN_STOCK'
-})
-
-const noSale = computed(() => {
-    return props.stopOfficial && props.info.sales === 0
-})
-
-const router = useIonRouter()
-const goDetail = () => {
-    getDiffTime()
-    if (notStart.value) {
-        toast(t('product.start', { time: props.batchStatus }))
-    } else if (noSale.value) {
-        toast(t('common.notAvailable') + '...')
-    } else if (diffTime.value) {
-        showDialog({
-            title: t('common.alert'),
-            message: t('product.delayTips')
-        })
-    } else if (props.info.status === 'SOLD_OUT') {
-        toast(t('product.soldOut'))
-    } else {
-        router.push({
-            path: '/productDetail',
-            query: {
-                id: props.info.id
-            }
-        })
-    }
-}
-
-const riseRate = computed(() => {
-    return useSystemStore().saleBatches.find(item => item.id == props.info.batchId)?.riseRate || 0
-})
-
-const riseRatePercent = computed(() => {
-    return useSystemStore().saleBatches.find(item => item.id == props.info.batchId)?.riseRate * 100 || 0
-})
-
-const nextPrice = computed(() => {
-    return accMul(props.info.currentPrice, accAdd(1, riseRate.value)).toFixed(2)
-})
-
-const diffTime = ref(0)
-onMounted(() => {
-    getDiffTime()
-})
-function getDiffTime() {
-    if (props.info.delayTo && isAfter(new Date(props.info.delayTo), new Date())) {
-        diffTime.value = differenceInMilliseconds(new Date(props.info.delayTo), new Date())
-    } else {
-        diffTime.value = 0
-    }
-}
-</script>
-
-<style lang="less" scoped>
-.product-info {
-    .f-col();
-    position: relative;
-    .van-image {
-        flex-shrink: 0;
-    }
-}
-
-.img_box {
-    width: calc(50vw - 24px);
-    height: calc(50vw - 24px);
-    position: absolute;
-    top: 0;
-    left: 0;
-    background: rgba(17, 17, 17, 0.7);
-    z-index: 30;
-    .f();
-    justify-content: center;
-    img {
-        width: 80%;
-    }
-}
-
-.not {
-    .f();
-    background-color: #ffe6b8;
-    height: 20px;
-    padding: 2px 6px;
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: 20;
-    border-radius: 8px 0 8px 0;
-    img {
-        width: 12px;
-        height: 12px;
-    }
-
-    span {
-        font-size: 10px;
-        line-height: 15px;
-        font-weight: bold;
-        color: rgba(194, 136, 0, 1);
-        margin-left: 5px;
-    }
-}
-
-.status {
-    .f();
-    background-color: #5d7368;
-    height: 20px;
-    padding: 2px 6px;
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: 20;
-    border-radius: 8px 0 8px 0;
-    img {
-        width: 16px;
-        height: 16px;
-    }
-
-    span {
-        font-size: 10px;
-        line-height: 15px;
-        font-weight: bold;
-        color: #fff;
-        margin-left: 2px;
-    }
-
-    .van-count-down {
-        --van-count-down-font-size: 12px;
-    }
-}
-
-.content {
-    align-self: stretch;
-    padding: 6px 8px;
-    flex-grow: 1;
-    overflow: hidden;
-    .f-col();
-
-    .text1 {
-        font-size: 14px;
-        color: #c7f2df;
-        font-weight: bold;
-        line-height: 24px;
-        font-weight: bold;
-    }
-
-    .badges {
-        .f();
-        margin-top: 4px;
-        overflow: hidden;
-        .badge {
-            font-size: 10px;
-            line-height: 16px;
-            border-radius: 2px;
-            font-weight: bold;
-            color: #c085ff;
-            padding: 0 4px;
-            white-space: nowrap;
-            border: 0.5px solid #c085ff;
-            &:nth-child(2) {
-                color: #39f3bb;
-                border: 0.5px solid #39f3bb;
-            }
-        }
-        .badge + .badge {
-            margin-left: 10px;
-        }
-    }
-}
-
-.flex1 {
-    flex-grow: 1;
-}
-
-.datas {
-    .f();
-    margin-top: 12px;
-    .data {
-        width: 50%;
-        .f();
-        .val {
-            .f();
-            align-items: flex-end;
-            font-size: 18px;
-            font-weight: bold;
-            color: var(--ion-color-light-contrast);
-            color: var(--yellow);
-            .nor {
-                font-weight: normal;
-            }
-        }
-        .name {
-            font-size: 10px;
-            color: #9fbfb1;
-            line-height: 10px;
-            white-space: nowrap;
-            margin-left: 8px;
-        }
-        &:nth-child(2) {
-            align-items: flex-end;
-            .val {
-                color: var(--ion-color-light-contrast);
-            }
-        }
-    }
-}
-</style>

+ 0 - 56
src/components/ProductTitle.vue

@@ -1,56 +0,0 @@
-<template>
-    <div class="product-title" :class="{ isSmall }">
-        <span ref="content">{{ title }}</span>
-        <img class="img1" src="../assets/png-biaokuag-01.png" alt="" />
-        <img class="img2" src="../assets/png-biaokuag-02.png" alt="" />
-    </div>
-</template>
-
-<script>
-export default {
-    props: { title: { type: String } },
-    data() {
-        return {
-            observer: null,
-            isSmall: false
-        }
-    },
-    watch: {
-        title(val) {
-            this.isSmall = val && val.length > 16
-        }
-    }
-}
-</script>
-
-<style lang="less" scoped>
-.product-title {
-    font-size: 20px;
-    color: var(--ion-text-color);
-    line-height: 44px;
-    padding: 0 30px;
-    position: relative;
-    min-height: 44px;
-    font-weight: bold;
-    img {
-        width: 20px;
-        height: 44px;
-        position: absolute;
-        top: 0;
-        &.img1 {
-            left: 0;
-        }
-        &.img2 {
-            right: 0;
-        }
-    }
-
-    &.isSmall {
-        font-size: 18px;
-        color: var(--ion-text-color);
-        line-height: 24px;
-        text-align: center;
-        .f();
-    }
-}
-</style>

+ 0 - 87
src/components/RankItem.vue

@@ -1,87 +0,0 @@
-<template>
-    <div class="rankItem">
-        <div class="num" v-html="numContent"></div>
-        <van-image width="40" height="40" radius="100" fit="fill" :src="info.avatar" />
-        <div class="name">{{ info.nickname }}</div>
-        <div class="money">{{ info.money.toFixed(2) }}</div>
-    </div>
-</template>
-<script>
-export default {
-    name: 'rankItem',
-    props: {
-        rank: {
-            type: [Number, String],
-            default: 1
-        },
-        info: {
-            type: Object,
-            default: () => {
-                return {};
-            }
-        }
-    },
-    data() {
-        return {};
-    },
-    computed: {
-        numContent() {
-            if (this.rank > 3) {
-                return this.rank;
-            } else if (this.rank == 1) {
-                return '<img src="' + require('../assets/paihang_icon_no1.png') + '" style="width:20px"/>';
-            } else if (this.rank == 2) {
-                return '<img src="' + require('../assets/paihang_icon_no2.png') + '" style="width:20px"/>';
-            } else if (this.rank == 3) {
-                return '<img src="' + require('../assets/paihang_icon_no3.png') + '" style="width:20px"/>';
-            }
-            return '';
-        }
-    }
-};
-</script>
-<style lang="less" scoped>
-.rankItem {
-    height: 68px;
-    background: rgba(var(--ion-text-color-rgb), 0.2);
-    border-radius: 6px;
-    display: flex;
-    padding: 0 14px;
-    align-items: center;
-    margin-top: 10px;
-}
-
-.num {
-    font-size: 14px;
-    font-weight: bold;
-    text-align: center;
-    color: rgba(var(--ion-text-color-rgb), 1);
-    line-height: 20px;
-    width: 20px;
-    margin-right: 15px;
-    img {
-        width: 20px;
-        height: 25px;
-    }
-}
-
-.name {
-    font-size: 14px;
-    color: rgba(93, 52, 0, 1);
-    line-height: 20px;
-    margin-left: 10px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    margin-right: 10px;
-}
-
-.money {
-    flex-grow: 1;
-    text-align: right;
-    font-size: 15px;
-    font-weight: bold;
-    color: rgba(203, 41, 0, 1);
-    line-height: 21px;
-}
-</style>

+ 0 - 76
src/components/TeamMember.vue

@@ -1,76 +0,0 @@
-<template>
-    <div class="team-member">
-        <ion-thumbnail class="avatar">
-            <img :src="detail.junior.avatar" />
-        </ion-thumbnail>
-        <div class="info">
-            <div class="name">{{ detail.junior.nickname }}</div>
-            <div class="time">{{ $t('distribution.joinTeamAt') }}: {{ detail.createdAt.substr(0, 11) }}</div>
-        </div>
-        <div class="divider"></div>
-        <div class="profit">
-            <div class="value">+{{ detail.commissionSum }}</div>
-            <div class="label">{{ $t('distribution.totalProfit') }}</div>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted } from 'vue'
-const props = defineProps({
-    detail: {
-        type: Object,
-        default: () => {}
-    }
-})
-</script>
-<style lang="less" scoped>
-.team-member {
-    .f();
-    margin: 10px 16px 0 16px;
-    height: 68px;
-    background: var(--ion-color-step-0);
-    border-radius: 2px;
-    padding: 0 15px;
-    .avatar {
-        width: 36px;
-        height: 36px;
-        border-radius: 2px;
-    }
-    .info {
-        .f-col();
-        margin-left: 10px;
-        flex: 1;
-        .name {
-            font-size: 14px;
-            line-height: 20px;
-        }
-        .time {
-            font-size: 12px;
-            color: var(--ion-color-step-600);
-            line-height: 17px;
-        }
-    }
-    .divider {
-        width: 1px;
-        height: 36px;
-        background-color: var(--ion-color-step-50);
-    }
-    .profit {
-        width: 68px;
-        .f-col();
-        align-items: flex-end;
-        font-size: 12px;
-        line-height: 17px;
-        .value {
-            font-size: 12px;
-            line-height: 17px;
-        }
-    }
-}
-.dark {
-    .team-member {
-        background: var(--ion-color-step-50) !important;
-    }
-}
-</style>

+ 0 - 112
src/components/TutorialModal.vue

@@ -1,112 +0,0 @@
-<template>
-    <ion-modal
-        :is-open="show"
-        ref="modalRef"
-        class="open-modal"
-        @didDismiss="show = false"
-        :enter-animation="enterAnimation"
-        :leave-animation="leaveAnimation"
-    >
-        <ion-content>
-            <ion-header>
-                <ion-toolbar>
-                    <ion-buttons slot="end">
-                        <ion-button @click="dismiss()">{{
-                            showClose ? $t('common.close') : $t('common.skip')
-                        }}</ion-button>
-                    </ion-buttons>
-                </ion-toolbar>
-            </ion-header>
-            <ion-content>
-                <video
-                    ref="videoRef"
-                    class="video"
-                    webkit-playsinline="true"
-                    x5-video-player-type="h5"
-                    x5-video-orientation="portraint"
-                    autoplay
-                    loop
-                    controls
-                >
-                    <source
-                        src="https://paimaide.s3.ap-northeast-1.amazonaws.com/video/2023-01-30-07-43-43NAoUVgVk.mp4"
-                        type="video/mp4"
-                    />
-                </video>
-            </ion-content>
-
-            <!-- <van-button class="back" v-if="showClose" round @click="show = false">{{ $t('common.close') }}</van-button>
-            <van-button class="back" v-else round @click="show = false">{{ $t('common.skip') }}</van-button> -->
-        </ion-content>
-    </ion-modal>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { createAnimation } from '@ionic/vue'
-
-const show = ref(false)
-const modalRef = ref(false)
-const videoRef = ref(false)
-const showClose = ref(true)
-
-function dismiss() {
-    modalRef.value.$el.dismiss()
-}
-
-const enterAnimation = baseEl => {
-    const root = baseEl.shadowRoot
-
-    const backdropAnimation = createAnimation()
-        .addElement(root.querySelector('ion-backdrop'))
-        .fromTo('opacity', '0.01', 'var(--backdrop-opacity)')
-
-    const wrapperAnimation = createAnimation()
-        .addElement(root.querySelector('.modal-wrapper'))
-        .keyframes([
-            { offset: 0, opacity: '0', transform: 'scale(0)' },
-            { offset: 1, opacity: '0.99', transform: 'scale(1)' }
-        ])
-
-    return createAnimation()
-        .addElement(baseEl)
-        .easing('ease-out')
-        .duration(500)
-        .addAnimation([backdropAnimation, wrapperAnimation])
-}
-
-const leaveAnimation = baseEl => {
-    return enterAnimation(baseEl).direction('reverse')
-}
-
-function init() {
-    showClose.value = true
-    show.value = true
-}
-
-defineExpose({
-    init
-})
-</script>
-
-<style lang="less" scoped>
-.video {
-    width: 100vw;
-    height: 100%;
-    display: block;
-    position: relative;
-    z-index: 1;
-}
-
-.back {
-    position: fixed;
-    right: 30px;
-    top: calc(var(--ion-safe-area-top) + 30px);
-    z-index: 999;
-    width: 60px;
-    height: 26px;
-    background-color: rgba(255, 255, 255, 0.1);
-    border-width: 0;
-    color: #fff;
-}
-</style>

+ 228 - 0
src/components/VideoBuy.vue

@@ -0,0 +1,228 @@
+<template>
+    <ion-modal :is-open="show" ref="modalRef" class="open-modal" @didDismiss="dismiss">
+        <div class="relative">
+            <img src="@/assets/png-duihuan-bg.png" class="model-bg" alt="" />
+            <div class="model-box">
+                <div class="title">
+                    <span class="text-[56px]">{{ buyEpInfo.price }}</span>
+                    <span class="mt-[5px]"> 个金豆兑换 </span>
+                </div>
+
+                <div class="info">
+                    <van-image :src="file" width="90" height="58" fit="cover" radius="8" />
+                    <div class="info-text">
+                        <div class="text1 van-ellipsis">{{ seriesInfo.title }}</div>
+                        <div class="text2">
+                            <span>兑换第{{ buyEpInfo.episodeNum }}集</span>
+                            <span>{{ category ? category + '·' : '' }}{{ seriesInfo.totalEpisodes }}集</span>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="btn w-[240px] m-auto mt-[18px]">
+                    <van-button @click="buy" block round color="linear-gradient(180deg, #FF7340 , #FF3E3E )"
+                        >立即兑换</van-button
+                    >
+                </div>
+            </div>
+        </div>
+    </ion-modal>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import {
+    createAnimation,
+    IonButtons,
+    IonButton,
+    IonModal,
+    IonHeader,
+    IonContent,
+    IonToolbar,
+    IonTitle,
+    IonItem,
+    IonList,
+    IonAvatar,
+    IonImg,
+    IonLabel
+} from '@ionic/vue'
+import resolveUrl from 'resolve-url'
+const fileURL = import.meta.env.VITE_HTTP_FILE_URL
+import { http } from '@/plugins/http'
+import toast from '@/utils/toast'
+
+const show = ref(false)
+const modalRef = ref(false)
+const videoRef = ref(false)
+
+const props = defineProps({
+    buyEpInfo: {
+        type: Object,
+        default: () => {
+            return {}
+        }
+    },
+    seriesInfo: {
+        type: Object,
+        default: () => {
+            return {}
+        }
+    }
+})
+
+const file = computed(() => {
+    return resolveUrl(fileURL, props.seriesInfo.cover)
+})
+
+const category = computed(() => {
+    if (props.seriesInfo.categories && props.seriesInfo.categories.length > 0) {
+        return props.seriesInfo.categories[0].name
+    }
+    return ''
+})
+
+function dismiss() {
+    show.value = false
+    emit('videopause', false)
+}
+
+const enterAnimation = baseEl => {
+    const root = baseEl.shadowRoot
+
+    const backdropAnimation = createAnimation()
+        .addElement(root.querySelector('ion-backdrop'))
+        .fromTo('opacity', '0.01', 'var(--backdrop-opacity)')
+
+    const wrapperAnimation = createAnimation()
+        .addElement(root.querySelector('.modal-wrapper'))
+        .keyframes([
+            { offset: 0, opacity: '0', transform: 'scale(0)' },
+            { offset: 1, opacity: '0.99', transform: 'scale(1)' }
+        ])
+
+    return createAnimation()
+        .addElement(baseEl)
+        .easing('ease-out')
+        .duration(500)
+        .addAnimation([backdropAnimation, wrapperAnimation])
+}
+
+const leaveAnimation = baseEl => {
+    return enterAnimation(baseEl).direction('reverse')
+}
+const emit = defineEmits(['videopause', 'playVideo'])
+function init() {
+    show.value = true
+    emit('videopause')
+}
+
+defineExpose({
+    init
+})
+
+function buy() {
+    http.post(
+        '/api/orders',
+        {
+            episodeId: props.buyEpInfo.id
+        },
+        {
+            body: 'json'
+        }
+    )
+        .then(res => {
+            toast.success('购买成功')
+            show.value = false
+            emit('playVideo', props.buyEpInfo.episodeNum)
+        })
+        .catch(e => {
+            toast(e.message)
+        })
+}
+</script>
+
+<style lang="less" scoped>
+.back {
+    position: fixed;
+    right: 30px;
+    top: calc(var(--ion-safe-area-top) + 30px);
+    z-index: 20;
+    width: 60px;
+    height: 26px;
+    background-color: rgba(255, 255, 255, 0.1);
+    border-width: 0;
+    color: #fff;
+}
+
+.open-modal {
+    --height: fit-content;
+    --border-radius: 8px;
+    --width: 300px;
+    --background: transparent;
+}
+
+.model-bg {
+    width: 300px;
+    display: block;
+    height: auto;
+}
+
+.model-box {
+    position: absolute;
+    top: 130px;
+    left: 0;
+    right: 0;
+    .title {
+        color: #bb2c2c;
+        font-size: 24px;
+        font-family: YouSheBiaoTiHei;
+        color: #bb2c2c;
+        line-height: 30px;
+        display: flex;
+        justify-content: center;
+        margin-top: 15px;
+    }
+}
+
+.info {
+    display: flex;
+    padding: 9px 10px;
+    margin: 14px 12px;
+    background: #ffffff linear-gradient(133deg, #ffcd61 0%, #fffffb 50%, #ffe9b9 100%);
+    border-radius: 8px;
+    .van-image {
+        flex-shrink: 0;
+    }
+
+    .info-text {
+        flex-grow: 1;
+        overflow: hidden;
+        margin-left: 12px;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+
+        .text1 {
+            font-size: 14px;
+            font-weight: 500;
+            color: #000000;
+            line-height: 18px;
+        }
+
+        .text2 {
+            display: flex;
+            justify-content: space-between;
+            font-size: 12px;
+            color: #61657a;
+            line-height: 18px;
+
+            span {
+                &:first-child {
+                    color: #bb2c2c;
+                    text-decoration: underline;
+                }
+            }
+        }
+    }
+}
+</style>

+ 0 - 66
src/components/VideoModal.vue

@@ -1,66 +0,0 @@
-<template>
-    <ion-header>
-        <ion-toolbar>
-            <ion-buttons slot="end">
-                <ion-button @click="dismiss()">{{ skip ? $t('common.skip') : $t('common.close') }}</ion-button>
-            </ion-buttons>
-        </ion-toolbar>
-    </ion-header>
-    <ion-content>
-        <video
-            ref="videoRef"
-            class="video"
-            webkit-playsinline="true"
-            x5-video-player-type="h5"
-            x5-video-orientation="portraint"
-            autoplay
-            loop
-            controls
-        >
-            <source :src="src" type="video/mp4" />
-        </video>
-    </ion-content>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { modalController } from '@ionic/vue'
-
-const props = defineProps({
-    skip: {
-        type: Boolean,
-        default: false
-    },
-    src: {
-        type: String,
-        required: true
-    }
-})
-const videoRef = ref(false)
-
-async function dismiss() {
-    return await modalController.dismiss()
-}
-</script>
-
-<style lang="less" scoped>
-.video {
-    width: 100vw;
-    height: 100%;
-    display: block;
-    position: relative;
-    z-index: 1;
-}
-
-.back {
-    position: fixed;
-    right: 30px;
-    top: calc(var(--ion-safe-area-top) + 30px);
-    z-index: 999;
-    width: 60px;
-    height: 26px;
-    background-color: rgba(255, 255, 255, 0.1);
-    border-width: 0;
-    color: #fff;
-}
-</style>

+ 0 - 14
src/components/videoModal.js

@@ -1,14 +0,0 @@
-import { modalController, createAnimation } from '@ionic/vue'
-import VideoModal from './VideoModal.vue'
-
-async function showVideoModal(props) {
-    const modal = await modalController.create({
-        component: VideoModal,
-        componentProps: props || {}
-    })
-    modal.present()
-
-    return await modal.onWillDismiss()
-}
-
-export { showVideoModal }

+ 2 - 1
src/locales/zh-tw.json

@@ -51,5 +51,6 @@
   },
   "user": {
     "notLogin": "登录查看更多精彩"
-  }
+  },
+  "loadingFinished": "加载完成"
 }

+ 13 - 13
src/plugins/list.js

@@ -11,21 +11,21 @@ function useList(url, beforeData = null, httpType) {
     const empty = ref(false)
     const loading = ref(false)
     const finished = ref(false)
-    const page = ref(0)
+    const page = ref(1)
     const totalElements = ref(0)
     const size = ref(20)
     const list = ref([])
 
     const getData = (isRefresh = false) => {
         if (isRefresh) {
-            page.value = 0
+            page.value = 1
             list.value = []
         }
         finished.value = false
         empty.value = false
         loading.value = true
 
-        let data = { page: page.value, size: size.value, sort: 'createdAt,desc' }
+        let data = { page: page.value }
         if (beforeData) {
             data = {
                 ...data,
@@ -37,16 +37,16 @@ function useList(url, beforeData = null, httpType) {
             return global.$http
                 .get(url, data, { body: 'json' })
                 .then(res => {
-                    if (res.first) {
+                    if (res.current_page === 1) {
                         list.value = []
                     }
-                    list.value = [...list.value, ...res.content]
-                    empty.value = res.empty
-                    finished.value = res.last
+                    list.value = [...list.value, ...res.data]
+                    empty.value = res.meta.total == 0
+                    finished.value = res.meta.current_page === res.meta.last_page
                     if (!finished.value) {
                         page.value = page.value + 1
                     }
-                    totalElements.value = Number(res.totalElements)
+                    totalElements.value = Number(res.meta.total)
                     loading.value = false
                 })
                 .catch(e => {
@@ -56,16 +56,16 @@ function useList(url, beforeData = null, httpType) {
             return global.$http
                 .post(url, data, { body: 'json' })
                 .then(res => {
-                    if (res.first) {
+                    if (res.current_page === 1) {
                         list.value = []
                     }
-                    list.value = [...list.value, ...res.content]
-                    empty.value = res.empty
-                    finished.value = res.last
+                    list.value = [...list.value, ...res.data]
+                    empty.value = res.meta.total == 0
+                    finished.value = res.meta.current_page === res.meta.last_page
                     if (!finished.value) {
                         page.value = page.value + 1
                     }
-                    totalElements.value = Number(res.totalElements)
+                    totalElements.value = Number(res.meta.total)
                     loading.value = false
                 })
                 .catch(e => {

+ 2 - 1
src/stores/user.js

@@ -36,7 +36,8 @@ export const useUserStore = defineStore('user', () => {
             http.post('api/auth/register', {
                 email,
                 username,
-                password
+                password,
+                invitor
             })
                 .then(res => {
                     http.setToken(res.token)

+ 5 - 0
src/styles/fonts.less

@@ -8,6 +8,11 @@
     src: url(/fonts/alibabaPuHui.ttf);
 }
 
+@font-face {
+    font-family: 'YouSheBiaoTiHei';
+    src: url(/fonts/YouSheBiaoTiHei.ttf);
+}
+
 @font-face {
     font-family: 'AlimamaShuHeiTi';
     src: url(/fonts/AlimamaShuHeiTi-Bold.ttf);

+ 16 - 17
src/views/HistoryPage.vue

@@ -14,17 +14,24 @@
                 </van-sticky>
 
                 <div class="pb-6 px-[18px]">
-                    <van-empty description="暂无记录" v-if="histories.length == 0 && !loading" />
-
-                    <div class="hot-list" v-else>
+                    <van-empty description="暂无记录" v-if="empty" />
+                    <van-list
+                        class="hot-list"
+                        v-model:loading="loading"
+                        :finished="finished"
+                        :finished-text="$t('loadingFinished')"
+                        @load="getData"
+                        :immediate-check="false"
+                        ref="listRef"
+                    >
                         <video-line
-                            v-for="(item, index) in histories"
+                            v-for="(item, index) in list"
                             :key="index"
                             :info="item.series"
                             history
                             :episodeNum="item.episode.episodeNum"
                         ></video-line>
-                    </div>
+                    </van-list>
                 </div>
             </div>
         </ion-content>
@@ -36,6 +43,7 @@ import { useRouter } from 'vue-router'
 import { ref } from 'vue'
 import { http } from '@/plugins/http'
 import { onIonViewWillEnter } from '@ionic/vue'
+import useList from '../plugins/list'
 
 const router = useRouter()
 function back() {
@@ -47,20 +55,11 @@ function changeSticky(fixed) {
     isFixed.value = fixed
 }
 
-const histories = ref([])
-const loading = ref(true)
-
-function init() {
-    http.get('/api/playHistories').then(res => {
-        histories.value = res.data
-        loading.value = false
-    })
-}
-
-init()
 onIonViewWillEnter(() => {
-    init()
+    getData(true)
 })
+
+const { empty, size, loading, finished, list, getData } = useList('/api/playHistories', () => {}, 'get')
 </script>
 
 <style lang="less" scoped>

+ 14 - 7
src/views/HomePage.vue

@@ -69,9 +69,17 @@
                         alt=""
                     />
                 </div>
-                <div class="perfect-list">
+                <van-list
+                    class="perfect-list"
+                    v-model:loading="loading"
+                    :finished="finished"
+                    :finished-text="$t('loadingFinished')"
+                    @load="getData"
+                    :immediate-check="false"
+                    ref="listRef"
+                >
                     <video-small v-for="(item, index) in allList" :key="index" :info="item"></video-small>
-                </div>
+                </van-list>
             </div>
         </ion-content>
     </ion-page>
@@ -82,14 +90,13 @@ import VideoSmall from '../components/VideoSmall.vue'
 import VideoLine from '../components/VideoLine.vue'
 import { http } from '@/plugins/http'
 import { ref, onMounted, nextTick } from 'vue'
+import useList from '../plugins/list'
 
 const seriesList = ref([])
 const hotList = ref([])
-const allList = ref([])
-http.get('/api/series').then(res => {
-    allList.value = res.data
-    // hotList.value = res.data.slice(0, 3)
-})
+
+const { empty, size, loading, finished, list: allList, getData } = useList('/api/series', () => {}, 'get')
+getData(true)
 
 async function getSeries(data = {}) {
     return http.get('/api/series', data).then(res => {

+ 22 - 2
src/views/MinePage.vue

@@ -23,7 +23,7 @@
                         </div>
 
                         <div class="wallet-val flex items-center">
-                            <div class="wallet-money">0</div>
+                            <div class="wallet-money">{{ amount }}</div>
                             <div @click="buy" class="wallet-btn">
                                 <img src="@/assets/icon-anniu.png" alt="" />
                                 <span>{{ $t('mine.buy') }}</span>
@@ -60,7 +60,7 @@
 
                 <div class="menu">
                     <van-cell-group>
-                        <van-cell :title="$t('mine.share')" is-link>
+                        <van-cell :title="$t('mine.share')" @click="share" is-link>
                             <template #icon>
                                 <img src="@/assets/icon_yaoqinghaoyou.png" class="w-[24px] h-[24px] mr-[10px]" alt="" />
                             </template>
@@ -98,6 +98,8 @@
             <buy-page ref="buyPageRef"></buy-page>
 
             <connect-page ref="connectPageRef"></connect-page>
+
+            <poster-page ref="posterRef"></poster-page>
         </ion-content>
     </ion-page>
 </template>
@@ -107,6 +109,7 @@ import defaultImg from '@/assets/png-touxiang.png'
 import SignInfo from '@/components/SignInfo.vue'
 import BuyPage from '@/components/BuyPage.vue'
 import ConnectPage from '@/components/ConnectPage.vue'
+import PosterPage from '@/components/PosterPage.vue'
 import { ref, onMounted } from 'vue'
 import { useUserStore } from '../stores/user'
 import { http } from '@/plugins/http'
@@ -130,6 +133,7 @@ const { user } = useUserStore()
 onMounted(() => {
     if (user) {
         getSign()
+        getAmount()
     }
 })
 
@@ -144,6 +148,17 @@ function getSign() {
     })
 }
 
+const amount = ref(0)
+function getAmount() {
+    http.get('/api/userBalances/' + user.id).then(res => {
+        if (Number(res.balance) > 10000) {
+            amount.value = (Number(res.balance) / 10000).toFixed(2) + '万'
+        } else {
+            amount.value = res.balance
+        }
+    })
+}
+
 const { t } = useI18n()
 function sign() {
     http.post('/api/signInRecords')
@@ -157,6 +172,11 @@ function sign() {
             }
         })
 }
+
+const posterRef = ref(null)
+function share() {
+    posterRef.value.onOpenInviteModal()
+}
 </script>
 
 <style lang="less" scoped>

+ 46 - 19
src/views/RecordPage.vue

@@ -12,29 +12,36 @@
             <img src="@/assets/png-zhuiju-bg.png" class="top-bg !absolute w-full h-[200px] top-0 left-0 !z-0" alt="" />
             <div class="watching-content">
                 <div class="py-[14px] px-[18px]">
-                    <div
-                        v-for="i in 10"
-                        :key="i"
-                        class="wallet bg-[#20223C] flex h-[86px] items-center px-[14px] rounded-lg"
+                    <van-list
+                        class="list"
+                        v-model:loading="loading"
+                        :finished="finished"
+                        :finished-text="$t('loadingFinished')"
+                        @load="getData"
+                        :immediate-check="false"
+                        ref="listRef"
                     >
-                        <van-image
-                            width="90"
-                            height="58"
-                            fit="cover"
-                            src="https://p9.itc.cn/images01/20200808/f27ca5bb0a1c4e2d9b9df893f273a83c.jpeg"
-                        />
-                        <div class="flex-1 ml-[12px] h-[58px] flex flex-col justify-between overflow-hidden">
-                            <div class="text-sm text-white van-ellipsis">
-                                少帥,夫人的深你不配少帥,夫人的深你不配少帥,夫人的深你不配
+                        <div
+                            v-for="(item, index) in list"
+                            :key="index"
+                            class="wallet bg-[#20223C] flex h-[86px] items-center px-[14px] rounded-lg"
+                        >
+                            <van-image width="90" height="58" fit="cover" radius="6" :src="getImg(item.series)" />
+                            <div class="flex-1 ml-[12px] h-[58px] flex flex-col justify-between overflow-hidden">
+                                <div class="text-sm text-white van-ellipsis">
+                                    {{ item.series ? item.series.title : '' }}
+                                </div>
+                                <div class="text-xs text-[#797A8A]">{{ getTime(item.updatedAt) }}</div>
                             </div>
-                            <div class="text-xs text-[#797A8A]">11-20 12:10</div>
-                        </div>
-                        <div class="h-[58px] flex flex-col justify-between items-end ml-[12px]">
-                            <div class="text-[#00FFE4] text-xs">兑换第8集</div>
+                            <div class="h-[58px] flex flex-col justify-between items-end ml-[12px]">
+                                <div class="text-[#00FFE4] text-xs">兑换第{{ item.episode.episodeNum }}集</div>
 
-                            <div class="text-[18px] text-white AlimamaShuHeiTi">-10</div>
+                                <div class="text-[18px] text-white AlimamaShuHeiTi">-{{ item.price }}</div>
+                            </div>
                         </div>
-                    </div>
+                    </van-list>
+
+                    <van-empty description="暂无记录" v-if="empty" />
                 </div>
             </div>
         </ion-content>
@@ -43,6 +50,10 @@
 <script setup>
 import { useRouter } from 'vue-router'
 import { ref } from 'vue'
+import useList from '../plugins/list'
+import resolveUrl from 'resolve-url'
+import { format } from 'date-fns'
+const fileURL = import.meta.env.VITE_HTTP_FILE_URL
 
 const router = useRouter()
 function back() {
@@ -53,6 +64,22 @@ const isFixed = ref(false)
 function changeSticky(fixed) {
     isFixed.value = fixed
 }
+
+const { empty, size, loading, finished, list, getData } = useList('/api/orders', () => {}, 'get')
+
+getData(true)
+
+function getImg(series) {
+    if (series) {
+        return resolveUrl(fileURL, series.cover)
+    } else {
+        return ''
+    }
+}
+
+function getTime(day) {
+    return format(new Date(day), 'MM-dd hh:mm', new Date())
+}
 </script>
 
 <style lang="less" scoped>

+ 3 - 2
src/views/RegisterPage.vue

@@ -138,7 +138,8 @@ const form = ref({
     email: '',
     username: '',
     password: '',
-    password2: ''
+    password2: '',
+    invitor: localStorage.getItem('invitor') || ''
 })
 
 const checked = ref(false)
@@ -161,7 +162,7 @@ showLoadingToast({
     forbidClick: true
 })
 function onSubmit() {
-    register(form.value.email, form.value.username, form.value.password).then(res => {
+    register(form.value.email, form.value.username, form.value.password, form.value.invitor).then(res => {
         closeToast()
         toast.success(t('login.register'))
         router.go(-2)

+ 40 - 6
src/views/VideoPage.vue

@@ -9,7 +9,16 @@
             </ion-toolbar>
         </ion-header>
         <ion-content class="view van-safe-area-bottom">
-            <video ref="videoRef" controls muted class="video" :src="curEpInfo.playUrl" :poster="poster"></video>
+            <video
+                ref="videoRef"
+                controls
+                muted
+                playsinline="true"
+                webkit-playsinline="true"
+                class="video"
+                :src="curEpInfo.playUrl"
+                :poster="poster"
+            ></video>
 
             <div class="right-fixed">
                 <div class="right-btn" @click="collect">
@@ -43,6 +52,14 @@
                 :seriesInfo="seriesInfo"
                 :curEpInfo="curEpInfo"
             ></video-history>
+
+            <video-buy
+                ref="videoBuyRef"
+                @videopause="videopause"
+                @playVideo="playVideo"
+                :seriesInfo="seriesInfo"
+                :buyEpInfo="buyEpInfo"
+            ></video-buy>
         </ion-content>
     </ion-page>
 </template>
@@ -51,6 +68,7 @@ import { useRouter } from 'vue-router'
 import { ref, reactive, onUnmounted, onMounted } from 'vue'
 import moreImg from '@/assets/icon_inter_white.png'
 import VideoHistory from '../components/VideoHistory.vue'
+import VideoBuy from '../components/VideoBuy.vue'
 import { http } from '@/plugins/http'
 import { useRoute, onBeforeRouteLeave } from 'vue-router'
 import '@/styles/animate.css'
@@ -116,17 +134,33 @@ async function getEpisodes(episodeNum = 1, duration = 0, episodeId = 0) {
     }
 }
 
+const buyEpInfo = ref({})
+const videoBuyRef = ref(null)
 async function play(curEpId = 0, duration = 0) {
     await http.get('/api/episodes/' + curEpId).then(res => {
-        curEpInfo.value = res
-        nextTick(() => {
-            videoRef.value.currentTime = duration
-            videoRef.value.play()
-        })
+        if (Number(res.price) > 0 && !res.playUrl) {
+            buyEpInfo.value = res
+            videoBuyRef.value.init()
+        } else {
+            curEpInfo.value = res
+            nextTick(() => {
+                videoRef.value.currentTime = duration
+                videoRef.value.play()
+            })
+        }
+
         // poster.value = resolveUrl(fileURL, res.cover)
     })
 }
 
+function videopause(pause = true) {
+    if (pause) {
+        videoRef.value.pause()
+    } else {
+        videoRef.value.play()
+    }
+}
+
 http.get('/api/collections/' + seriesId.value).then(res => {
     isCollected.value = true
 })

+ 34 - 12
src/views/WalletPage.vue

@@ -12,8 +12,16 @@
             <img src="@/assets/png-zhuiju-bg.png" class="top-bg !absolute w-full h-[200px] top-0 left-0 !z-0" alt="" />
             <div class="watching-content">
                 <div class="py-[14px] px-[18px]">
-                    <van-empty description="暂无记录" v-if="list.length == 0 && !loading" />
-                    <template v-else>
+                    <van-empty description="暂无记录" v-if="empty" />
+                    <van-list
+                        class="list"
+                        v-model:loading="loading"
+                        :finished="finished"
+                        :finished-text="$t('loadingFinished')"
+                        @load="getData"
+                        :immediate-check="false"
+                        ref="listRef"
+                    >
                         <div
                             v-for="(item, index) in list"
                             :key="index"
@@ -21,12 +29,12 @@
                         >
                             <img class="w-[36px] h-[36px]" :src="getIcon(item.type)" alt="" />
                             <div class="flex-1 ml-[12px]">
-                                <div class="text-sm text-white">{{ item.description }}</div>
+                                <div class="text-sm text-white">{{ getTitle(item) }}</div>
                                 <div class="text-xs text-[#797A8A]">{{ formateDay(item.createdAt) }}</div>
                             </div>
                             <div class="text-[18px] text-white AlimamaShuHeiTi">{{ getAmount(item.amount) }}</div>
                         </div>
-                    </template>
+                    </van-list>
                 </div>
             </div>
         </ion-content>
@@ -40,6 +48,7 @@ import { format } from 'date-fns'
 import tixianImg from '@/assets/icon-tixian.png'
 import qiandaoImg from '@/assets/icon-qiandao.png'
 import yaoqingImg from '@/assets/icon-yaoqing.png'
+import useList from '../plugins/list'
 
 const router = useRouter()
 function back() {
@@ -51,12 +60,8 @@ function changeSticky(fixed) {
     isFixed.value = fixed
 }
 
-const list = ref([])
-const loading = ref(true)
-http.get('/api/balanceRecords').then(res => {
-    list.value = res.data
-    loading.value = false
-})
+const { empty, size, loading, finished, list, getData } = useList('/api/balanceRecords', () => {}, 'get')
+getData(true)
 
 function formateDay(day) {
     console.log(new Date(day))
@@ -75,14 +80,31 @@ function getIcon(type) {
             img = qiandaoImg
             break
         case 'recharge':
-            img = tixianImg
+            img = yaoqingImg
             break
         case 'purchase':
-            img = yaoqingImg
+            img = tixianImg
             break
     }
     return img
 }
+
+function getTitle(info) {
+    let title = ''
+    if (info.series) {
+        title = '购买《' + info.series.title + '》'
+
+        if (info.episode) {
+            title += '第' + info.episode.episodeNum + '集'
+        }
+    } else if (info.episode) {
+        title = '购买第' + info.episode.episodeNum + '集'
+    }
+    if (info.description) {
+        title = info.description
+    }
+    return title
+}
 </script>
 
 <style lang="less" scoped>