Ver Fonte

Merge branch 'main' of http://git.izouma.com/panhui/zouma-short

panhui há 1 ano atrás
pai
commit
816577cee3

+ 2 - 2
android/app/build.gradle

@@ -15,8 +15,8 @@ android {
         applicationId "com.hasiwlas.shorts"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 2
-        versionName "1.0"
+        versionCode 7
+        versionName "1.0.0-7"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 13 - 4
android/app/src/main/AndroidManifest.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
@@ -9,12 +10,12 @@
         android:theme="@style/AppTheme"
         android:usesCleartextTraffic="true">
         <activity
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
             android:name=".MainActivity"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
+            android:exported="true"
             android:label="@string/title_activity_main"
-            android:theme="@style/AppTheme.NoActionBarLaunch"
             android:launchMode="singleTask"
-            android:exported="true">
+            android:theme="@style/AppTheme.NoActionBarLaunch">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
@@ -26,7 +27,9 @@
             android:authorities="${applicationId}.fileprovider"
             android:exported="false"
             android:grantUriPermissions="true">
-            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
         </provider>
     </application>
 
@@ -34,4 +37,10 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="com.android.vending.BILLING" />
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
 </manifest>

+ 2 - 0
package.json

@@ -43,6 +43,7 @@
     "cordova-plugin-purchase": "^13.10.0",
     "date-fns": "^2.29.3",
     "date-fns-tz": "^2.0.0",
+    "decimal.js": "^10.4.3",
     "eruda": "^3.0.1",
     "eruda-benchmark": "^2.0.0",
     "eruda-code": "^2.1.0",
@@ -53,6 +54,7 @@
     "eruda-orientation": "^2.0.0",
     "eruda-timing": "^2.0.1",
     "eruda-touches": "^2.0.0",
+    "fast-uri": "^2.3.0",
     "ionicons": "^7.2.1",
     "less": "^4.1.3",
     "mathjs": "^12.1.0",

+ 0 - 100
src/components/BuyPage.vue

@@ -1,100 +0,0 @@
-<template>
-    <van-popup class="buy-page" v-model:show="show" closeable round position="bottom" :style="{ maxHeight: '70%' }">
-        <div class="news-title text-white text-lg AlimamaShuHeiTi relative">
-            <span class="z-[1] relative">{{ $t('mine.buy') }}</span>
-            <img class="w-[78px] h-[16px] absolute bottom-0 left-0 z-0" src="@/assets/png-xiantiao1.png" alt="" />
-        </div>
-        <div class="money text-xs text-white mt-[5px]">帳戶餘額:789金豆</div>
-
-        <div class="choose-list flex flex-wrap justify-between">
-            <div class="choose-btn active flex flex-col items-center justify-center">
-                <div class="val text-white">
-                    <span class="text-[16px]">¥</span>
-                    <span class="text-[26px]">19.9</span>
-                </div>
-                <div class="sub text-xs text-white">1990金豆+<span class="text-[#FFEF00]">送888金豆</span></div>
-                <div class="btn-tag">首充福利</div>
-            </div>
-            <div v-for="i in 3" :key="i" class="choose-btn flex flex-col items-center justify-center">
-                <div class="val text-white">
-                    <span class="text-[16px]">¥</span>
-                    <span class="text-[26px]">19.9</span>
-                </div>
-                <div class="sub text-xs text-white">1990金豆+<span class="text-[#FFEF00]">送888金豆</span></div>
-                <div class="btn-tag">粉丝福利</div>
-            </div>
-        </div>
-
-        <div class="tips text-[10px] text-[#61657A] text-center pt-[90px] pb-[18px]">
-            虛擬商品購買後不可退換,青少年請在家長陪同下購買
-        </div>
-
-        <div class="btn">
-            <van-button class="AlimamaShuHeiTi" type="primary" block round>立即支付</van-button>
-        </div>
-    </van-popup>
-</template>
-
-<script setup>
-import { ref } from 'vue'
-const show = ref(false)
-// const props = defineProps({
-//     show: {
-//         type: Boolean,
-//         default: false
-//     }
-// })
-// const emit = defineEmits(['update:show'])
-function changeShow(val) {
-    show.value = val
-}
-defineExpose({ changeShow })
-</script>
-
-<style lang="less" scoped>
-.buy-page {
-    --van-popup-background: #20223c;
-    padding: 21px 18px;
-}
-
-.choose-btn {
-    width: calc(50vw - 27px);
-    height: 88px;
-    background: #2d3059;
-    border-radius: 8px;
-    border: 1px solid #525686;
-    margin-top: 18px;
-    display: inline-flex;
-    position: relative;
-
-    .btn-tag {
-        font-size: 12px;
-        color: #ffffff;
-        line-height: 18px;
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 62px;
-        height: 18px;
-        background: #7f82af;
-        border-radius: 8px 0px 8px 0px;
-        text-align: center;
-    }
-
-    &.active {
-        background: #2d3059 linear-gradient(318deg, rgba(255, 87, 186, 0.1) 0%, rgba(255, 87, 186, 0.2) 100%);
-        border-radius: 8px;
-        border: 2px solid #ff57ba;
-        .sub {
-            span {
-                color: #fff !important;
-            }
-        }
-        .btn-tag {
-            background: #2d3059 linear-gradient(-318deg, #ff3b20, #ff57ba);
-            left: -2px;
-            top: -1px;
-        }
-    }
-}
-</style>

+ 32 - 14
src/components/PosterPage.vue

@@ -4,16 +4,12 @@
             <img :src="posterUrl" alt="" />
 
             <div class="btns">
-                <van-button
-                    class="copy"
-                    @click="copyText(shareUrl)"
-                    round
-                    color="linear-gradient(180deg, #FFD1D8 , #FFEAD0 )"
-                    >{{ $t('copy') }}</van-button
-                >
-                <van-button class="save" @click="save" round color="linear-gradient(180deg, #FF7340 , #FF3E3E )">{{
-                    $t('save')
-                }}</van-button>
+                <van-button class="copy" @click="share()" 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>
@@ -30,6 +26,7 @@ import { useClipboard } from '@vueuse/core'
 import toast from '@/utils/toast'
 import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'
 import { Media } from '@capacitor-community/media'
+import { Share } from '@capacitor/share'
 
 const show = ref(false)
 
@@ -59,9 +56,9 @@ const props = defineProps({
 const { user } = useUserStore()
 async function getInviteUrl() {
     if (props.pageType === 'mine') {
-        return 'https://shorts.izouma.com/share2.html?referrer=' + user.id
+        return 'https://shorts.izouma.com/static/share2.html?referrer=' + user.id
     } else {
-        return `https://shorts.izouma.com/share1.html?referrer=${user.id}&num=${props.episodeNum}&title=${
+        return `https://shorts.izouma.com/static/share1.html?referrer=${user.id}&num=${props.episodeNum}&title=${
             props.title
         }&cover=${encodeURIComponent(props.poster)}`
     }
@@ -127,11 +124,15 @@ async function save() {
             const savedFile = await Filesystem.writeFile({
                 path: 'share.png',
                 data: posterUrl.value,
-                directory: Directory.Data
+                directory: Directory.Cache
             })
+            const albums = (await Media.getAlbums()).albums
+            console.log(albums)
+            const album = albums?.length ? albums.find(a => a.name === 'Camera') || albums[0] : null
             await Media.savePhoto({
                 path: savedFile.uri,
-                album: 'FirstCash'
+                albumIdentifier: album.identifier,
+                name: 'share'
             })
             toast.success('保存成功')
         } catch (e) {
@@ -144,6 +145,23 @@ async function save() {
         link.click()
     }
 }
+
+async function share() {
+    if (Capacitor.isNativePlatform()) {
+        const savedFile = await Filesystem.writeFile({
+            path: 'share.png',
+            data: posterUrl.value,
+            directory: Directory.Cache
+        })
+        await Share.share({
+            title: '走馬短劇',
+            text: '海量短劇先睹為快',
+            url: savedFile.uri
+        })
+    } else {
+        copyText(shareUrl.value)
+    }
+}
 </script>
 
 <style lang="less" scoped>

+ 134 - 0
src/components/PurchaseModal.vue

@@ -0,0 +1,134 @@
+<template>
+    <ion-modal :is-open="show" :initial-breakpoint="0.7" :breakpoints="[0.7]" :handle="false" @didDismiss="dismiss">
+        <ion-content class="ion-padding">
+            <div class="news-title text-white text-lg AlimamaShuHeiTi relative">
+                <span class="z-[1] relative">購買金豆</span>
+                <img class="w-[78px] h-[16px] absolute bottom-0 left-0 z-0" src="@/assets/png-xiantiao1.png" alt="" />
+            </div>
+            <div class="money text-xs text-white mt-[5px]">帳戶餘額:{{ balance }}金豆</div>
+
+            <div class="choose-list flex flex-wrap justify-between overflow-auto" style="height: 350px">
+                <!-- <div class="choose-btn  flex flex-col items-center justify-center">
+                <div class="val text-white">
+                    <span class="text-[16px]">¥</span>
+                    <span class="text-[26px]">19.9</span>
+                </div>
+                <div class="sub text-xs text-white">1990金豆+<span class="text-[#FFEF00]">送888金豆</span></div>
+                <div class="btn-tag">首充福利</div>
+            </div> -->
+                <div
+                    v-for="(item, i) in products"
+                    :key="item.id"
+                    class="choose-btn flex flex-col items-center justify-center"
+                    :class="{ active: selected === i }"
+                    @click="selected = i"
+                >
+                    <div class="val text-white">
+                        <!-- <span class="text-[16px]">¥</span> -->
+                        <span class="text-[16px]">{{ item.pricing.price }}</span>
+                    </div>
+                    <div class="sub text-xs text-[#FFEF00]">
+                        {{ item.title }}
+                        <!-- <span class="text-[#FFEF00]">送888金豆</span> -->
+                    </div>
+                    <!-- <div class="btn-tag">粉丝福利</div> -->
+                </div>
+            </div>
+
+            <div class="tips text-[10px] text-[#61657A] text-center py-4">
+                虛擬商品購買後不可退換,青少年請在家長陪同下購買
+            </div>
+
+            <div class="btn">
+                <van-button class="AlimamaShuHeiTi" type="primary" block round @click="buy">立即支付</van-button>
+            </div>
+        </ion-content>
+    </ion-modal>
+</template>
+
+<script setup>
+import { IonModal, IonContent } from '@ionic/vue'
+import { ref } from 'vue'
+import { useIAPStore } from '@/stores/IAP'
+import { storeToRefs } from 'pinia'
+import uri from 'fast-uri'
+import { useUserStore } from '@/stores/user'
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false
+    }
+})
+const emit = defineEmits(['update:show'])
+function dismiss() {
+    emit('update:show', false)
+}
+const userStore = useUserStore()
+const { user, balance } = storeToRefs(userStore)
+const { getBalance } = userStore
+const { products } = storeToRefs(useIAPStore())
+const selected = ref(0)
+
+function buy() {
+    const { store, ProductType, Platform } = CdvPurchase
+    store.when().finished(() => {
+        getBalance()
+    })
+    store.validator = uri.resolve(import.meta.env.VITE_HTTP_BASE_URL, '/api/userBalances/recharge/' + user.id)
+    // store.validator = 'http://192.168.6.215:3333/api/userBalances/recharge/1'
+    console.log(products.value[selected.value].id)
+    const product = store.get(products.value[selected.value].id, Platform.GOOGLE_PLAY)
+    const myTransaction = store.findInLocalReceipts(product)
+    console.log('myTransaction', myTransaction)
+    console.log('product', product)
+    product.getOffer().order()
+}
+</script>
+
+<style lang="less" scoped>
+.buy-page {
+    --van-popup-background: #20223c;
+    padding: 21px 18px;
+}
+
+.choose-btn {
+    width: calc(50vw - 27px);
+    height: 88px;
+    background: #2d3059;
+    border-radius: 8px;
+    border: 1px solid #525686;
+    margin-top: 18px;
+    display: inline-flex;
+    position: relative;
+
+    .btn-tag {
+        font-size: 12px;
+        color: #ffffff;
+        line-height: 18px;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 62px;
+        height: 18px;
+        background: #7f82af;
+        border-radius: 8px 0px 8px 0px;
+        text-align: center;
+    }
+
+    &.active {
+        background: #2d3059 linear-gradient(318deg, rgba(255, 87, 186, 0.1) 0%, rgba(255, 87, 186, 0.2) 100%);
+        border-radius: 8px;
+        border: 2px solid #ff57ba;
+        .sub {
+            span {
+                color: #fff !important;
+            }
+        }
+        .btn-tag {
+            background: #2d3059 linear-gradient(-318deg, #ff3b20, #ff57ba);
+            left: -2px;
+            top: -1px;
+        }
+    }
+}
+</style>

+ 3 - 0
src/main.js

@@ -25,6 +25,7 @@ import qs from 'qs'
 import { App as AppPlugin } from '@capacitor/app'
 import { emitter } from '@/utils/eventBus'
 import { checkUpdate } from '@/plugins/updater'
+import { useIAPStore } from './stores/IAP'
 
 import 'normalize.css/normalize.css'
 
@@ -132,6 +133,8 @@ if (Capacitor.isNativePlatform()) {
             checkUpdate()
         }
     })
+
+    useIAPStore().initialize()
 }
 
 if (useStorage('showConsole', 0).value > new Date().getTime()) {

+ 1 - 1
src/plugins/updater_meta.json

@@ -1 +1 @@
-{"version":39}
+{"version":44}

+ 1 - 1
src/router/index.js

@@ -88,7 +88,7 @@ const router = createRouter({
     ]
 })
 router.beforeEach(async (to, from, next) => {
-    const { user, get: getUser } = useUserStore()
+    const { user, getUser } = useUserStore()
     if (!to.meta.allowGuest && !user) {
         try {
             await getUser()

+ 54 - 0
src/stores/IAP.js

@@ -0,0 +1,54 @@
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+import { Capacitor } from '@capacitor/core'
+const useIAPStore = defineStore('iap', () => {
+    const products = ref([])
+    async function initialize() {
+        if (Capacitor.isNativePlatform()) {
+            const { store, ProductType, Platform } = CdvPurchase
+            store.verbosity = CdvPurchase.LogLevel.DEBUG
+            store.register([
+                {
+                    type: ProductType.CONSUMABLE,
+                    id: '1',
+                    platform: Platform.GOOGLE_PLAY
+                },
+                {
+                    type: ProductType.CONSUMABLE,
+                    id: '30',
+                    platform: Platform.GOOGLE_PLAY
+                },
+                {
+                    type: ProductType.CONSUMABLE,
+                    id: '50',
+                    platform: Platform.GOOGLE_PLAY
+                },
+                {
+                    type: ProductType.CONSUMABLE,
+                    id: '100',
+                    platform: Platform.GOOGLE_PLAY
+                },
+                {
+                    type: ProductType.CONSUMABLE,
+                    id: '200',
+                    platform: Platform.GOOGLE_PLAY
+                }
+            ])
+            store
+                .when()
+                .productUpdated(product => {
+                    console.log('CdvPurchase: productUpdated', product)
+                    products.value = store.products
+                })
+                .approved(transaction => transaction.verify())
+                .verified(receipt => receipt.finish())
+            store.error(function (error) {
+                console.error('CdvPurchase ERROR: ' + error.message)
+            })
+            store.initialize([Platform.GOOGLE_PLAY])
+        }
+    }
+    initialize()
+    return { initialize, products }
+})
+export { useIAPStore }

+ 12 - 4
src/stores/user.js

@@ -1,6 +1,7 @@
 import { ref } from 'vue'
 import { defineStore } from 'pinia'
 import { http } from '@/plugins/http'
+import Decimal from 'decimal.js'
 async function setAfCuid(cuid) {
     // if (Capacitor.isNativePlatform()) {
     //     AppsFlyer.setCustomerUserId({ cuid })
@@ -15,7 +16,7 @@ export const useUserStore = defineStore('user', () => {
             http.post('/api/auth/login', { username, password })
                 .then(res => {
                     http.setToken(res.token)
-                    return get()
+                    return getUser()
                 })
                 .then(res => {
                     resolve()
@@ -41,7 +42,7 @@ export const useUserStore = defineStore('user', () => {
             })
                 .then(res => {
                     http.setToken(res.token)
-                    return get()
+                    return getUser()
                 })
                 .then(res => {
                     resolve()
@@ -69,7 +70,7 @@ export const useUserStore = defineStore('user', () => {
             //     })
         })
     }
-    const get = async () => {
+    const getUser = async () => {
         if (!http.token.value) return Promise.reject()
         return http.get('/api/users/my').then(res => {
             user.value = res
@@ -79,5 +80,12 @@ export const useUserStore = defineStore('user', () => {
         http.setToken(null)
         user.value = null
     }
-    return { user, login, get, register, logout }
+
+    const balance = ref(new Decimal(0))
+    const getBalance = () => {
+        http.get('/api/userBalances/' + user.value.id).then(res => {
+            balance.value = new Decimal(res.balance)
+        })
+    }
+    return { user, login, getUser, register, logout, balance, getBalance }
 })

+ 14 - 42
src/views/MinePage.vue

@@ -23,7 +23,7 @@
                         </div>
 
                         <div class="wallet-val flex items-center">
-                            <div class="wallet-money">{{ amount }}</div>
+                            <div class="wallet-money">{{ displayBalance }}</div>
                             <div @click="buy" class="wallet-btn">
                                 <img src="@/assets/icon-anniu.png" alt="" />
                                 <span>{{ $t('mine.buy') }}</span>
@@ -100,7 +100,7 @@
                 </div>
             </div>
 
-            <buy-page ref="buyPageRef"></buy-page>
+            <purchase-modal v-model:show="showPurchaseModal"></purchase-modal>
 
             <connect-page ref="connectPageRef"></connect-page>
 
@@ -113,10 +113,10 @@
 import { ConfigProvider as VantConfigProvider, showConfirmDialog } from 'vant'
 import defaultImg from '@/assets/png-touxiang.png'
 import SignInfo from '@/components/SignInfo.vue'
-import BuyPage from '@/components/BuyPage.vue'
+import PurchaseModal from '@/components/PurchaseModal.vue'
 import ConnectPage from '@/components/ConnectPage.vue'
 import PosterPage from '@/components/PosterPage.vue'
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, computed } from 'vue'
 import { useUserStore } from '../stores/user'
 import { http } from '@/plugins/http'
 import { showNotify } from 'vant'
@@ -125,41 +125,20 @@ import { isSameDay } from 'date-fns'
 import toast from '@/utils/toast'
 import { onIonViewWillEnter } from '@ionic/vue'
 import { useIonRouter } from '@ionic/vue'
+import { storeToRefs } from 'pinia'
 
-const buyPageRef = ref(null)
+const showPurchaseModal = ref(false)
 function buy() {
-    toast(t('wait'))
-    // buyPageRef.value.changeShow(true)
-    const { store, ProductType, Platform } = CdvPurchase
-    store.verbosity = CdvPurchase.LogLevel.DEBUG
-    store.register([
-        {
-            type: ProductType.CONSUMABLE,
-            id: '50',
-            platform: Platform.GOOGLE_PLAY
-        }
-    ])
-    store
-        .when()
-        .productUpdated(() => {
-            console.log('productUpdated')
-            const p = store.get('50', Platform.GOOGLE_PLAY)
-            console.log(p)
-            p.getOffer().order()
-        })
-        .approved(() => {
-            console.log('approved')
-        })
-    store.initialize([Platform.GOOGLE_PLAY])
+    showPurchaseModal.value = true
 }
 
 const connectPageRef = ref(null)
 function connect() {
     connectPageRef.value.changeShow(true)
 }
-
-const { user, get: getUser, logout } = useUserStore()
-
+const userStore = useUserStore()
+const { user, logout, balance } = storeToRefs(userStore)
+const { getUser, getBalance } = userStore
 // onMounted(async () => {
 //     if (user) {
 //         await getUser()
@@ -172,7 +151,7 @@ onIonViewWillEnter(async () => {
     if (user) {
         await getUser()
         getSign()
-        await getAmount()
+        getBalance()
     }
 })
 
@@ -189,16 +168,9 @@ 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 displayBalance = computed(() => {
+    return balance.value.toString()
+})
 
 const { t } = useI18n()
 function sign() {

+ 6 - 1
yarn.lock

@@ -3356,7 +3356,7 @@ decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
 
 decimal.js@^10.4.3:
   version "10.4.3"
-  resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz"
+  resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
   integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
 
 decode-uri-component@^0.2.0:
@@ -4323,6 +4323,11 @@ fast-levenshtein@^2.0.6:
   resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
   integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
 
+fast-uri@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/fast-uri/-/fast-uri-2.3.0.tgz#bdae493942483d299e7285dcb4627767d42e2793"
+  integrity sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==
+
 fast-xml-parser@^4.1.3:
   version "4.3.2"
   resolved "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz"