Преглед на файлове

Squashed commit of the following:

commit 308c5d1153125e4b8a9f8fc3ebf959bc937a5cbc
Author: xiongzhu <692949348@qq.com>
Date:   Sun Mar 19 23:27:25 2023 +0800

    update

commit 1514417adc068b6e6c00fdc660d7707fa4253654
Author: xiongzhu <692949348@qq.com>
Date:   Sun Mar 19 22:37:43 2023 +0800

    blf

commit 3b53c1edb62ad2e8eeb7564f26ebdf0845be1e93
Author: xiongzhu <692949348@qq.com>
Date:   Sun Mar 19 21:37:14 2023 +0800

    .

commit 9884ffb13fa5ef6c4e2d83bb8009937c1f695c79
Author: xiongzhu <692949348@qq.com>
Date:   Sun Mar 19 21:29:22 2023 +0800

    更新

commit ebbe36cd1266927df6a2943bd565b6617f6af393
Author: xiongzhu <692949348@qq.com>
Date:   Fri Mar 17 16:53:16 2023 +0800

    update

commit 891e4bfaa523a8b29078c5a972db6374b07b107b
Author: xiongzhu <692949348@qq.com>
Date:   Fri Mar 17 16:22:40 2023 +0800

    eslint

commit 7fe5c843c65f6c4c36dedb5c9a7ddfb19f38f9f4
Author: xiongzhu <692949348@qq.com>
Date:   Fri Mar 17 16:19:48 2023 +0800

    meta

commit ffee2dd932e7557e848d0da888cbc0b87161678e
Author: xiongzhu <692949348@qq.com>
Date:   Fri Mar 17 16:17:45 2023 +0800

    .

commit cbed79e6f85903f976f5761aac129397e0cfbbf3
Author: xiongzhu <692949348@qq.com>
Date:   Fri Mar 17 16:13:49 2023 +0800

    meta

commit 595871f921835e248b9fe2b662415a5a9a9a05c2
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 15:50:18 2023 +0800

    商品列表

commit ffab3144357891dd6ceb402eaf968adb24f35274
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 15:32:35 2023 +0800

    刷新

commit 65e6b9af46a870909f179f798ae5121f40ae4f09
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 15:05:51 2023 +0800

    .

commit 1779b1242ef2fdc287675bbbf9b70b14835a03e3
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 14:36:07 2023 +0800

    详情

commit 044cf6be39bfe81872ae2459ec6a3fe35d8e88e9
Merge: f219b2f 6d0eaa7
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 14:05:25 2023 +0800

    Merge branch 'dev' of http://git.izouma.com/xiongzhu/paimaide into dev

commit f219b2f3bc8fcc255185dc070c785de54a12fb56
Author: panhui <1529378564@qq.com>
Date:   Fri Mar 17 14:04:53 2023 +0800

    已售罄

commit 6d0eaa7ddbfab7cb802301e434b0c35fc6466455
Author: xiongzhu <692949348@qq.com>
Date:   Thu Mar 16 20:15:25 2023 +0800

    首次打开逻辑

commit ac22476cfb680d1270dad038c5e0dfd67f7f6a5f
Author: panhui <1529378564@qq.com>
Date:   Thu Mar 16 13:38:23 2023 +0800

    .
xiongzhu преди 2 години
родител
ревизия
c94fd767ae

+ 1 - 1
.env.development

@@ -1,3 +1,3 @@
 BASE_URL=/h5/
 VITE_BASE_URL=/h5/
-VITE_HTTP_BASE_URL=http://35.76.121.189/
+VITE_HTTP_BASE_URL=https://paimaide.izouma.com/

+ 4 - 3
.env.test

@@ -1,3 +1,4 @@
-BASE_URL=/h5/
-VITE_BASE_URL=/h5/
-VITE_HTTP_BASE_URL=http://192.168.6.215:8080
+BASE_URL=/
+VITE_BASE_URL=/
+VITE_HTTP_BASE_URL=https://paimaide.izouma.com
+VITE_APP=true

+ 1 - 1
.eslintrc.cjs

@@ -17,5 +17,5 @@ module.exports = {
         tidioChatApi: true,
         $: true
     },
-    ignorePatterns: ['index.html', 'public/error.html']
+    ignorePatterns: ['index.html', 'public/error.html', '/capacitor.config.ts']
 }

+ 1 - 0
.gitignore

@@ -27,3 +27,4 @@ coverage
 *.njsproj
 *.sln
 *.sw?
+/*.zip

+ 13 - 1
android/app/build.gradle

@@ -6,7 +6,7 @@ android {
         applicationId "com.bigauction.mobile"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 116
+        versionCode 141
         versionName "1.0.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
@@ -21,6 +21,18 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
+    flavorDimensions "environment"
+    productFlavors {
+        dev {
+            dimension "environment"
+            applicationIdSuffix ".dev"
+            manifestPlaceholders = [displayName:"FirstCashDev"]
+        }
+        prod {
+            dimension "environment"
+            manifestPlaceholders = [displayName:"FirstCash"]
+        }
+    }
 }
 
 repositories {

+ 2 - 2
android/app/src/main/AndroidManifest.xml

@@ -6,7 +6,7 @@
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
+        android:label="${displayName}"
         android:supportsRtl="true"
         android:theme="@style/AppTheme"
         android:hardwareAccelerated="true"
@@ -17,7 +17,7 @@
         <activity
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
             android:name="com.bigauction.mobile.MainActivity"
-            android:label="@string/title_activity_main"
+            android:label="${displayName}"
             android:theme="@style/AppTheme.NoActionBarLaunch"
             android:launchMode="singleInstance"
             android:exported="true">

BIN
android/app/src/main/assets/cdvasset.manifest


+ 0 - 36
capacitor.config.json

@@ -1,36 +0,0 @@
-{
-    "appId": "com.bigauction.mobile",
-    "appName": "FirstCash",
-    "webDir": "dist",
-    "bundledWebRuntime": true,
-    "backgroundColor": "#1C1C1C",
-    "errorPath": "error.html",
-    "android": {
-        "buildOptions": {
-            "keystorePath": "../zouma.jks",
-            "keystorePassword": "zouma123",
-            "keystoreAlias": "zouma",
-            "keystoreAliasPassword": "zouma123",
-            "releaseType": "APK"
-        },
-        "releaseType": "APK",
-        "minWebViewVersion": 60
-    },
-    "ios": {
-        "allowsLinkPreview": false
-    },
-    "plugins": {
-        "SplashScreen": {
-            "launchShowDuration": 2000,
-            "androidScaleType": "CENTER_CROP"
-        },
-        "CodePush": {
-            "IOS_DEPLOY_KEY": "eAdEIJJkMhuRwBKVpDXvbay6Ay0yEJAbzi0Ur",
-            "ANDROID_DEPLOY_KEY": "ZrEsHcngd89oYtHZYQxLRUSMJ8trafzoNUVDN",
-            "SERVER_URL": "https://codepush.appcenter.ms/"
-        }
-    },
-    "server": {
-        "errorPath": "error.html"
-    }
-}

+ 49 - 0
capacitor.config.ts

@@ -0,0 +1,49 @@
+import { CapacitorConfig } from '@capacitor/cli';
+
+let config: CapacitorConfig;
+const baseConfig: CapacitorConfig = {
+    appId: "com.bigauction.mobile",
+    appName: "FirstCash",
+    webDir: "dist",
+    bundledWebRuntime: true,
+    backgroundColor: "#1C1C1C",
+    android: {
+        buildOptions: {
+            keystorePath: "../zouma.jks",
+            keystorePassword: "zouma123",
+            keystoreAlias: "zouma",
+            keystoreAliasPassword: "zouma123",
+            releaseType: "APK"
+        },
+        minWebViewVersion: 60
+    },
+    ios: {
+        allowsLinkPreview: false
+    },
+    plugins: {
+        SplashScreen: {
+            launchShowDuration: 2000,
+            androidScaleType: "CENTER_CROP"
+        },
+        CodePush: {
+            IOS_DEPLOY_KEY: "eAdEIJJkMhuRwBKVpDXvbay6Ay0yEJAbzi0Ur",
+            ANDROID_DEPLOY_KEY: "ZrEsHcngd89oYtHZYQxLRUSMJ8trafzoNUVDN",
+            SERVER_URL: "https://codepush.appcenter.ms/"
+        }
+    },
+    server: {
+        errorPath: "error.html"
+    }
+}
+config = { ...baseConfig }
+switch (process.env.NODE_ENV) {
+    case 'dev':
+        config.android!.flavor = 'dev';
+        config.plugins!.CodePush!.ANDROID_DEPLOY_KEY = '6yDUHefjATEf9hCyLrEKBXb6uITu9u_fIA1bS'
+        break;
+    default:
+        config.android!.flavor = 'prod';
+        break;
+}
+
+export default config;

+ 24 - 13
codePush.mjs

@@ -15,6 +15,13 @@ const iosName = 'ASNFTNGFHP-iOS'
 
 inquirer
     .prompt([
+        {
+            name: 'type',
+            type: 'list',
+            message: 'Deployment Type',
+            choices: ['Development', 'Release'],
+            default: 'Development'
+        },
         {
             name: 'platform',
             type: 'checkbox',
@@ -22,10 +29,15 @@ inquirer
             choices: ['Android', 'iOS'],
             default: ['Android']
         },
-        { name: 'build', type: 'list', message: 'build', choices: ['YES', 'NO'], default: 'YES' }
+        { name: 'build', type: 'list', message: 'Build Package', choices: ['YES', 'NO'], default: 'YES' }
     ])
     .then(async answers => {
         console.log(answers)
+        let mode = 'app'
+        if (answers.type === 'Development') {
+            process.env.NODE_ENV = 'dev'
+            mode = 'test'
+        }
         const capacitorConfig = await loadConfig()
         const codePush = new CodePush(token)
         const projectConfig = {
@@ -38,28 +50,27 @@ inquirer
         }
         const project = new MobileProject(process.cwd(), projectConfig)
         await project.load()
-        await project.android.incrementVersionCode()
-        await project.ios.incrementBuild('App')
+        if (answers.platform.includes('Android')) await project.android.incrementVersionCode()
+        if (answers.platform.includes('iOS')) await project.ios.incrementBuild('App')
         project.commit()
 
-        let version = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'src', 'version.json')))
-        version.android.version = await project.android.getVersionName()
-        version.android.build = await project.android.getVersionCode()
-        version.ios.version = await project.ios.getVersion('App')
-        version.ios.build = await project.ios.getBuild('App')
+        let meta = String(fs.readFileSync(path.resolve(process.cwd(), 'public', 'meta.js')))
         if (answers.build === 'YES') {
-            version.www++
-            fs.writeFileSync(path.resolve(process.cwd(), 'src', 'version.json'), JSON.stringify(version, null, 4))
+            meta = meta.replace(
+                /(window.www_version = )\d+/,
+                `$1${parseInt(/window.www_version = (\d+)/.exec(meta)[1]) + 1}`
+            )
+            fs.writeFileSync(path.resolve(process.cwd(), 'public', 'meta.js'), meta)
             await build({
                 configFile: 'vite.config.js',
-                mode: 'app'
+                mode
             })
         }
         if (answers.platform.includes('Android')) {
             var bar1 = new ProgressBar('Upload Android Bundle [:bar] :percent', { total: 100 })
             await sync(capacitorConfig, 'android', null, false)
             await codePush
-                .release(androidName, 'Release', 'android/app/src/main/assets/public/', '~1.0.0', {}, percent => {
+                .release(androidName, answers.type, 'android/app/src/main/assets/public/', '~1.0.0', {}, percent => {
                     bar1.tick(percent)
                 })
                 .then(res => {
@@ -70,7 +81,7 @@ inquirer
             var bar2 = new ProgressBar('Upload iOS Bundle [:bar] :percent', { total: 100 })
             await sync(capacitorConfig, 'ios', null, false)
             await codePush
-                .release(androidName, 'Release', 'ios/App/App/public/', '~1.0.0', {}, percent => {
+                .release(androidName, answers.type, 'ios/App/App/public/', '~1.0.0', {}, percent => {
                     bar2.tick(percent)
                 })
                 .then(res => {

+ 1 - 0
index.html

@@ -22,6 +22,7 @@
         onload="if(media!='all')media='all'">
     <!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans"> -->
     <title>FirstCash</title>
+    <script src="meta.js"></script>
 </head>
 
 <body>

+ 4 - 1
package.json

@@ -36,7 +36,7 @@
     "@ionic/vue-router": "^6.3.8",
     "@vant/area-data": "^1.3.2",
     "@vueuse/components": "^9.10.0",
-    "@vueuse/core": "^9.6.0",
+    "@vueuse/core": "^9.13.0",
     "appsflyer-capacitor-plugin": "^6.9.2",
     "axios": "^1.2.0",
     "capacitor-codepush": "https://github.com/x1ongzhu/capacitor-codepush.git",
@@ -63,6 +63,7 @@
     "pinia": "^2.0.26",
     "qrcode": "^1.5.1",
     "qs": "^6.11.0",
+    "resolve-url": "^0.2.1",
     "swiper": "^8.4.5",
     "vant": "^4.0.2",
     "vue": "^3.2.45",
@@ -77,12 +78,14 @@
     "@vitejs/plugin-legacy": "^4.0.1",
     "@vitejs/plugin-vue": "^4.0.0",
     "@vue/eslint-config-prettier": "^7.0.0",
+    "@vue/eslint-config-typescript": "^11.0.2",
     "code-push": "^4.1.0",
     "eslint": "^8.22.0",
     "eslint-plugin-vue": "^9.3.0",
     "inquirer": "^9.1.4",
     "prettier": "^2.7.1",
     "progress": "^2.0.3",
+    "typescript": "^4.9.5",
     "vite": "^4.1.4",
     "vite-plugin-imagemin": "^0.6.1",
     "vite-plugin-pwa": "^0.14.1"

+ 1 - 0
public/meta.js

@@ -0,0 +1 @@
+window.www_version = 1112

+ 0 - 1
src/App.vue

@@ -126,7 +126,6 @@ onMounted(() => {
     http.get('/sysConfig/get/customer_json').then(res => {
         customers.value = JSON.parse(res.value)
         http.get('/user/myBroker').then(res => {
-            console.log(res)
             customers.value.splice(0, 0, {
                 type: 'Telegram',
                 account: res.account,

BIN
src/assets/icon_close2.png


BIN
src/assets/icon_gouxuan_pre2.png


BIN
src/assets/solded.png


+ 53 - 0
src/components/NewsModal.vue

@@ -0,0 +1,53 @@
+<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>

+ 40 - 16
src/components/ProductItem.vue

@@ -1,6 +1,9 @@
 <template>
     <div class="product-info" :class="{ product2: list }" @click="goDetail">
         <van-image :width="width" :height="width" fit="cover" :src="getImg(info.pic)" />
+        <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">
@@ -11,7 +14,7 @@
             <div class="flex1"></div>
             <div class="datas">
                 <div class="data">
-                    <div class="val" v-if="!notStock">
+                    <div class="val" v-if="!noSale">
                         <span class="nor">{{ $t('balance.symbol') }}</span>
                         <span>{{ info.currentPrice }}</span>
                     </div>
@@ -21,14 +24,14 @@
                     <div class="name">{{ $t('product.priceNow') }}</div>
                 </div>
                 <div class="data">
-                    <div class="val" v-if="!notStock">{{ riseRatePercent }}%</div>
+                    <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="!notStock">
+                    <div class="val" v-if="!noSale">
                         <span>{{ $t('balance.symbol') }}</span>
                         <span>{{ nextPrice || 0 }}</span>
                     </div>
@@ -45,7 +48,12 @@
             <span> <van-count-down :time="diffTime" format="HH:mm:ss" @finish="getDiffTime" /></span>
         </div>
 
-        <div class="not" v-if="notStock">
+        <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>
@@ -60,8 +68,8 @@ 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 } from 'date-fns'
-import { ConfigProvider as VantConfigProvider, showDialog } from 'vant'
+import { isAfter, addDays, differenceInMilliseconds, format } from 'date-fns'
+import { showDialog } from 'vant'
 
 const { t } = useI18n()
 const props = defineProps({
@@ -78,6 +86,10 @@ const props = defineProps({
     stopOfficial: {
         type: Boolean,
         default: false
+    },
+    batchStatus: {
+        type: String,
+        default: ''
     }
 })
 const category = computed(() => {
@@ -95,19 +107,18 @@ const user = computed(() => {
     return userStore.user
 })
 
-const notStock = computed(() => {
-    if (user.value) {
-        return (
-            props.stopOfficial &&
-            props.info.sales === 0 &&
-            !isAfter(addDays(new Date(user.value.createdAt), 2), new Date())
-        )
-    }
+const notStart = computed(() => {
+    return props.batchStatus !== '抢购中' && props.info.status === 'IN_STOCK'
+})
+
+const noSale = computed(() => {
     return props.stopOfficial && props.info.sales === 0
 })
 
 const goDetail = () => {
-    if (notStock.value) {
+    if (notStart.value) {
+        toast(t('product.start', { time: props.batchStatus }))
+    } else if (noSale.value) {
         toast(t('common.notAvailable') + '...')
     } else if (diffTime.value) {
         showDialog({
@@ -158,6 +169,19 @@ function getDiffTime() {
     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;
@@ -201,7 +225,7 @@ function getDiffTime() {
         font-size: 10px;
         line-height: 15px;
         font-weight: bold;
-        color: rgba(194, 136, 0, 1);
+        color: #fff;
         margin-left: 2px;
     }
 

+ 41 - 14
src/components/ProductItemSmall.vue

@@ -1,6 +1,9 @@
 <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)" />
+        <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">
@@ -10,7 +13,7 @@
             </div>
             <div class="datas">
                 <div class="data">
-                    <div class="val" v-if="!notStock">
+                    <div class="val" v-if="!noSale">
                         <span class="nor">{{ $t('balance.symbol') }}</span>
                         <span>{{ info.currentPrice }}</span>
                     </div>
@@ -27,8 +30,12 @@
             <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="notStock">
+        <div class="not" v-if="noSale">
             <img src="@/assets/not-ava.png" alt="" />
             <span>{{ $t('common.notAvailable') }}...</span>
         </div>
@@ -43,8 +50,8 @@ 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, getTime } from 'date-fns'
-import { ConfigProvider as VantConfigProvider, showDialog } from 'vant'
+import { isAfter, addDays, differenceInMilliseconds, format } from 'date-fns'
+import { showDialog } from 'vant'
 
 const { t } = useI18n()
 const props = defineProps({
@@ -61,6 +68,10 @@ const props = defineProps({
     stopOfficial: {
         type: Boolean,
         default: false
+    },
+    batchStatus: {
+        type: String,
+        default: ''
     }
 })
 const category = computed(() => {
@@ -72,27 +83,28 @@ const user = computed(() => {
     return userStore.user
 })
 
-const notStock = computed(() => {
-    if (user.value) {
-        return (
-            props.stopOfficial &&
-            props.info.sales === 0 &&
-            !isAfter(addDays(new Date(user.value.createdAt), 2), new Date())
-        )
-    }
+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 (notStock.value) {
+    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',
@@ -137,6 +149,21 @@ function getDiffTime() {
     }
 }
 
+.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;
@@ -180,7 +207,7 @@ function getDiffTime() {
         font-size: 10px;
         line-height: 15px;
         font-weight: bold;
-        color: rgba(194, 136, 0, 1);
+        color: #fff;
         margin-left: 2px;
     }
 

+ 1 - 22
src/components/TutorialModal.vue

@@ -43,21 +43,7 @@
 
 <script setup>
 import { ref, onMounted } from 'vue'
-import {
-    createAnimation,
-    IonButtons,
-    IonButton,
-    IonModal,
-    IonHeader,
-    IonContent,
-    IonToolbar,
-    IonTitle,
-    IonItem,
-    IonList,
-    IonAvatar,
-    IonImg,
-    IonLabel
-} from '@ionic/vue'
+import { createAnimation } from '@ionic/vue'
 
 const show = ref(false)
 const modalRef = ref(false)
@@ -92,13 +78,6 @@ const enterAnimation = baseEl => {
 const leaveAnimation = baseEl => {
     return enterAnimation(baseEl).direction('reverse')
 }
-onMounted(() => {
-    if (localStorage.getItem('showTuTorial') !== '1') {
-        showClose.value = false
-        show.value = true
-        localStorage.setItem('showTuTorial', '1')
-    }
-})
 
 function init() {
     showClose.value = true

+ 66 - 0
src/components/VideoModal.vue

@@ -0,0 +1,66 @@
+<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>

+ 14 - 0
src/components/videoModal.js

@@ -0,0 +1,14 @@
+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 }

+ 10 - 3
src/locales/en.json

@@ -69,7 +69,11 @@
         "pending": "Coming soon",
         "pic": "illustrate",
         "sucess": "successful purchase",
-        "tips": "Pledge your balance through the overall pledge balance ratio of the platform. Get 4‰ of the pledge amount of that day."
+        "tips": "Stake your balance through the overall stake balance ratio of the platform. Get {rate}‰ of the stake amount of that day.",
+        "amount": "Enter stake amount",
+        "minAmount": "minimum stake amount: ",
+        "sure": "Confirm",
+        "time": "Choose a stake duration"
     },
     "common": {
         "alert": "Hint",
@@ -263,7 +267,8 @@
         "start": "Start at {time}",
         "tag": "Digital Artwork",
         "tomorrowBuy": "Can be sold for ",
-        "delayTips": "This product can only be purchased after the cooling time. If you need to buy multiple high-quality products in a concentrated manner, you can enter a higher level session."
+        "delayTips": "This product can only be purchased after the cooling time. If you need to buy multiple high-quality products in a concentrated manner, you can enter a higher level session.",
+        "soldOut": "Sold out"
     },
     "rank": {
         "inviteNum": "New recruits",
@@ -281,7 +286,9 @@
         "checkUpdate": "Check for update",
         "darkMode": "Dark Mode",
         "updating": "Updating",
-        "upToDate": "Already up to date"
+        "upToDate": "Already up to date",
+        "newUpdate": "New version available",
+        "updateNow": "Update now"
     },
     "title": {
         "balanceRecord": "Balance Records",

+ 12 - 3
src/locales/es.json

@@ -34,7 +34,9 @@
         "withdrawSuccess": "La solicitud de retiro ha sido enviada y se espera que llegue dentro de las 24 horas",
         "withdrawing": "Retiro",
         "insufficientBalance": "Saldo insuficiente",
-        "withdrawMin": "El monto mínimo de retiro es {value}"
+        "withdrawMin": "El monto mínimo de retiro es {value}",
+        "credits": "integral",
+        "totalRechargeAmount": "recarga acumulada"
     },
     "bank": {
         "account": "número de tarjeta bancaria",
@@ -67,7 +69,13 @@
         "pending": "Muy pronto",
         "pic": "ilustrar",
         "sucess": "compra exitosa",
-        "tips": "Prometa su saldo y disfrute de los dividendos de hoy a través de la relación de saldo de compromiso general de la plataforma, y ​​el ingreso químico diario más alto puede obtener 1%"
+        "tips": "Prometa su saldo y disfrute de los dividendos de hoy a través de la relación de saldo de compromiso general de la plataforma, y ​​el ingreso químico diario más alto puede obtener 1%",
+        "amount": "Ingrese el monto de la promesa",
+        "minAmount": "oferta mínima",
+        "sure": "Confirmar compromiso",
+        "time": "Elija un tiempo de compromiso",
+        "tips1": "Comprometa su saldo, disfrute de los dividendos de hoy a través de la relación de saldo de compromiso general de la plataforma y obtenga los ingresos químicos diarios más altos",
+        "tips2": "‰"
     },
     "common": {
         "alert": "pista",
@@ -261,7 +269,8 @@
         "start": "{hora} abierto",
         "tag": "ilustraciones digitales",
         "tomorrowBuy": "Disponible mañana",
-        "delayTips": "Esta colección se está enfriando, ¡puedes ir a una sesión superior para verla!"
+        "delayTips": "Esta colección se está enfriando, ¡puedes ir a una sesión superior para verla!",
+        "soldOut": "Agotado"
     },
     "rank": {
         "inviteNum": "nuevos reclutas",

+ 10 - 3
src/locales/zh.json

@@ -69,7 +69,11 @@
         "pending": "即将开售",
         "pic": "图片说明",
         "sucess": "购买成功",
-        "tips": "质押您的余额,通过平台整体质押余额比例,享受今日分红,最高日化收益可得1%"
+        "tips": "质押您的余额,通过平台整体质押余额比例,享受今日分红,最高日化收益可得{rate}‰",
+        "amount": "输入质押金额",
+        "minAmount": "最低质押金额:",
+        "time": "选择质押时长",
+        "sure": "确认"
     },
     "common": {
         "alert": "提示",
@@ -263,7 +267,8 @@
         "start": "{time} 开启",
         "tag": "数字艺术品",
         "tomorrowBuy": "明日可卖",
-        "delayTips": "该藏品正在冷却中,可以去更高的场次看看哦!"
+        "delayTips": "该藏品正在冷却中,可以去更高的场次看看哦!",
+        "soldOut": "已售罄"
     },
     "rank": {
         "inviteNum": "拉新人数",
@@ -281,7 +286,9 @@
         "checkUpdate": "检查更新",
         "darkMode": "黑暗模式",
         "updating": "正在更新...",
-        "upToDate": "当前已是最新版本"
+        "upToDate": "当前已是最新版本",
+        "newUpdate": "发现新版本",
+        "updateNow": "立即更新"
     },
     "title": {
         "balanceRecord": "交易明细",

+ 94 - 52
src/main.js

@@ -1,4 +1,4 @@
-import { createApp } from 'vue'
+import { createApp, ref } from 'vue'
 import { createPinia } from 'pinia'
 import App from './App.vue'
 import router from './router'
@@ -17,7 +17,7 @@ import { useBackButton } from '@ionic/vue'
 import { SplashScreen } from '@capacitor/splash-screen'
 // import { Openinstall } from 'capacitor-openinstall'
 import { AppsFlyer, AFConstants } from 'appsflyer-capacitor-plugin'
-import { Locale } from 'vant'
+import { Locale as VantLocale } from 'vant'
 import vantEnUS from 'vant/es/locale/lang/en-US'
 import { useStorage } from '@vueuse/core'
 import { init as initEruda } from '@/utils/console'
@@ -27,6 +27,8 @@ import { Network } from '@capacitor/network'
 import { Facebook } from 'capacitor-facebook'
 import { SafeArea } from 'capacitor-plugin-safe-area'
 import qs from 'qs'
+import { App as AppPlugin } from '@capacitor/app'
+import { emitter } from '@/utils/eventBus'
 
 import 'normalize.css/normalize.css'
 
@@ -56,10 +58,10 @@ import common from './mixins/common'
 
 const invitor = useStorage('invitor', null, localStorage)
 const urlParams = qs.parse(location.search.slice(1))
+console.log('urlParams', urlParams)
 if (urlParams.invitor) {
     invitor.value = urlParams.invitor
 }
-console.log(urlParams)
 
 const app = createApp(App)
 app.use(i18n)
@@ -70,7 +72,9 @@ app.use(http)
 app.use(Vant)
 app.use(toast)
 app.mixin(common)
-Locale.use('en-US', vantEnUS)
+const packageSynced = ref(false)
+app.provide('packageSynced', packageSynced)
+VantLocale.use('en-US', vantEnUS)
 
 // ionic components
 Object.keys(IonComponents).forEach(key => {
@@ -89,29 +93,68 @@ if (navigator.language === 'zh-CN') {
 }
 const firstRun = useStorage('firstRun', true)
 const checkUpdate = async () => {
-    if (!location.origin.includes('localhost')) return
-    codePush
-        .sync(
-            {
-                updateDialog: false,
-                installMode: InstallMode.IMMEDIATE,
-                ignoreFailedUpdates: false
-            },
-            downloadProgress => {
-                console.log(downloadProgress)
-                if (downloadProgress) {
-                    console.log(`Downloading ${downloadProgress.receivedBytes} of ${downloadProgress.totalBytes}`)
+    if (!location.origin.includes('localhost')) {
+        packageSynced.value = true
+        return
+    }
+    await codePush.notifyApplicationReady()
+    try {
+        console.log('codePush -> checking for update')
+        const update = await new Promise((resolve, reject) => {
+            codePush.checkForUpdate(
+                p => {
+                    resolve(p)
+                },
+                err => {
+                    reject(err)
                 }
-            }
-        )
-        .then(status => {
-            console.log('codePush sync status:', SyncStatus[status])
-            if (status === SyncStatus.UP_TO_DATE) {
-                initAF()
-            }
+            )
         })
+        if (!update) {
+            console.log('codePush -> no update available')
+        } else {
+            console.log('codePush -> there is an update, start download', update)
+            const downloadedPackage = await update.download(downloadProgress => {
+                console.log(`Downloading ${downloadProgress.receivedBytes} of ${downloadProgress.totalBytes} bytes.`)
+            })
+            console.log('codePush -> package downloaded at: ' + downloadedPackage.localPath)
+            await downloadedPackage.install({
+                installMode: InstallMode.IMMEDIATE,
+                minimumBackgroundDuration: 120,
+                mandatoryInstallMode: InstallMode.IMMEDIATE
+            })
+            console.log('codePush -> install update success')
+        }
+    } catch (e) {
+        console.log('codePush -> an error occurred while checking for updates', e)
+    }
+    packageSynced.value = true
+    // codePush
+    //     .sync(
+    //         {
+    //             updateDialog: false,
+    //             installMode: InstallMode.IMMEDIATE,
+    //             ignoreFailedUpdates: false
+    //         },
+    //         downloadProgress => {
+    //             console.log(downloadProgress)
+    //             if (downloadProgress) {
+    //                 console.log(`Downloading ${downloadProgress.receivedBytes} of ${downloadProgress.totalBytes}`)
+    //             }
+    //         }
+    //     )
+    //     .then(status => {
+    //         console.log('codePush sync status:', SyncStatus[status])
+    //         if (status === SyncStatus.UP_TO_DATE) {
+    //             initAF()
+    //         }
+    //     })
 }
-const initAF = () => {
+const initAF = async () => {
+    const appInfo = await AppPlugin.getInfo()
+    if (/\.dev$/.test(appInfo.id)) {
+        return
+    }
     const afConfig = {
         appID: 'id1665426567', // replace with your app ID.
         devKey: Capacitor.getPlatform() === 'android' ? 'xiQnptYGJ44kMBgmR3esZC' : 'QFfmCXsWbhqgRiXsWx7b2o', // replace with your dev key.
@@ -210,36 +253,35 @@ const initOpeninstall = () => {
 Facebook.init({ appId: '683776590201632', pixelId: '557748303040652', autoLogEvent: true }).then(() => {
     console.log('FacebookSDK initialized')
 })
-document.addEventListener('deviceready', () => {
-    console.log('deviceready')
-    if (Capacitor.isNativePlatform()) {
-        StatusBar.setStyle({ style: Style.Dark })
-        if (Capacitor.getPlatform() === 'android') {
-            StatusBar.setOverlaysWebView({ overlay: true })
-            const style = document.documentElement.style
-            if (Capacitor.isPluginAvailable('SafeArea')) {
-                SafeArea.getSafeAreaInsets().then(({ insets }) => {
-                    style.setProperty('--ion-safe-area-top', insets.top + 'px')
-                    // style.setProperty('--ion-safe-area-bottom', insets.bottom + 'px')
-                })
-            } else {
-                window.AndroidNotch.getInsetTop(
-                    px => {
-                        style.setProperty('--ion-safe-area-top', px + 'px')
-                    },
-                    err => console.error('Failed to get insets top:', err)
-                )
-            }
+if (Capacitor.isNativePlatform()) {
+    StatusBar.setStyle({ style: Style.Dark })
+    if (Capacitor.getPlatform() === 'android') {
+        StatusBar.setOverlaysWebView({ overlay: true })
+        const style = document.documentElement.style
+        if (Capacitor.isPluginAvailable('SafeArea')) {
+            SafeArea.getSafeAreaInsets().then(({ insets }) => {
+                style.setProperty('--ion-safe-area-top', insets.top + 'px')
+                // style.setProperty('--ion-safe-area-bottom', insets.bottom + 'px')
+            })
+        } else {
+            window.AndroidNotch.getInsetTop(
+                px => {
+                    style.setProperty('--ion-safe-area-top', px + 'px')
+                },
+                err => console.error('Failed to get insets top:', err)
+            )
         }
-        initAF()
-        Network.getStatus().then(status => {
-            console.log('networkStatus:', status)
-            if (status.connected) {
-                checkUpdate()
-            }
-        })
     }
-})
+    initAF()
+    Network.getStatus().then(status => {
+        console.log('networkStatus:', status)
+        if (status.connected) {
+            checkUpdate()
+        }
+    })
+} else {
+    packageSynced.value = true
+}
 
 if (useStorage('showConsole', 0).value > new Date().getTime()) {
     initEruda()

+ 3 - 0
src/utils/console.js

@@ -45,6 +45,9 @@ async function init() {
             // import('eruda-memory').then(res => {
             //     eruda.add(res.default)
             // })
+            let console = eruda.get('console')
+            // console.config.set('overrideConsole', false)
+
             eruda.remove('sources')
             let snippets = eruda.get('snippets')
             snippets.add(

+ 0 - 12
src/version.json

@@ -1,12 +0,0 @@
-{
-    "ios": {
-        "version": "1.0.0",
-        "build": 118
-    },
-    "android": {
-        "version": "1.0.0",
-        "build": 116,
-        "versionCode": 12
-    },
-    "www": 1101
-}

+ 180 - 22
src/views/BLFPage.vue

@@ -6,7 +6,7 @@
             <div class="box">
                 <img src="@/assets/blfimg3.png" alt="" class="box-img" />
                 <div class="box-text">
-                    <div class="text2">{{ $t('blf.tips') }}</div>
+                    <div class="text2">{{ $t('blf.tips', { rate }) }}</div>
                 </div>
                 <div class="num">
                     <div class="num-title">{{ $t('blf.has') }}</div>
@@ -45,9 +45,7 @@
                             <van-count-down class="text2" :time="time" @finish="onFinish" />
                         </div>
                     </van-button>
-                    <van-button type="primary" v-else-if="productInfo.stock" block @click="buy">{{
-                        $t('blf.buy')
-                    }}</van-button>
+                    <van-button type="primary" v-else-if="now" block @click="buy">{{ $t('blf.buy') }}</van-button>
                     <van-button type="primary" v-else class="not" block> {{ $t('blf.finish') }} </van-button>
                 </template>
 
@@ -56,6 +54,39 @@
                 }}</van-button>
             </div>
         </ion-content>
+        <ion-modal :is-open="showPayModal" class="pay-modal" @didDismiss="showPayModal = false">
+            <ion-content :scrollY="false">
+                <div class="pay-box">
+                    <div class="title">
+                        <div class="text1">{{ $t('blf.amount') }}</div>
+                        <div class="text2">{{ $t('blf.minAmount') + $t('balance.symbol') + minAmount }}</div>
+                    </div>
+
+                    <div class="close" @click="showPayModal = false">
+                        <img src="@/assets/icon_close2.png" alt="" />
+                    </div>
+                    <van-stepper
+                        :min="minAmount"
+                        :max="now"
+                        :show-plus="false"
+                        :show-minus="false"
+                        v-model="stakeAmount"
+                    />
+                    <div class="title">
+                        <div class="text1">{{ $t('blf.time') }}</div>
+                    </div>
+                    <div class="time-list">
+                        <div class="time-item" v-for="item in stakeTimes" :key="item" @click="stakeTime = item">
+                            <img :src="item === stakeTime ? activeIcon : icon" alt="" />
+                            <span>{{ getHours(item) + $t('delegate.tips3') }}</span>
+                        </div>
+                    </div>
+                    <div class="footer">
+                        <van-button type="primary" block @click="pay">{{ $t('blf.sure') }}</van-button>
+                    </div>
+                </div>
+            </ion-content>
+        </ion-modal>
     </ion-page>
 </template>
 <script setup>
@@ -65,10 +96,15 @@ import Toast from '@/utils/toast'
 import '@/styles/animate.css'
 import http from '@/plugins/http'
 import { useIonRouter } from '@ionic/vue'
+import { useWindowSize } from '@vueuse/core'
 import { onIonViewWillEnter } from '@ionic/vue'
 import { differenceInMilliseconds, differenceInSeconds } from 'date-fns'
 import { useUserStore } from '@/stores/user'
 import { accDiv, accMul } from '@/plugins/calc'
+import { closeOutline } from 'ionicons/icons'
+import { IonModal, IonContent } from '@ionic/vue'
+import activeIcon from '@/assets/icon_gouxuan_pre2.png'
+import icon from '@/assets/icon_gouxuan_huise.png'
 
 const userStore = useUserStore()
 const user = computed(() => {
@@ -84,28 +120,41 @@ const progress = computed(() => {
 })
 
 const router = useIonRouter()
+const showPayModal = ref(false)
 const buy = () => {
     if (user.value == null) {
         router.push({ name: 'login' })
     } else {
-        Toast.loading({
-            message: t('common.loading') + '...',
-            forbidClick: true
-        })
-        http.http
-            .post('/financeOrder/create', {
-                productId: productInfo.value.id
-            })
-            .then(() => {
-                Toast.clear()
-                init()
-                Toast.success(t('blf.sucess'))
-            })
-            .catch(e => {
-                Toast(e.error)
-            })
+        showPayModal.value = true
     }
 }
+const stakeAmount = ref(0)
+const stakeTime = ref(720)
+const stakeTimes = ref([720, 1080, 1440])
+function pay() {
+    Toast.loading({
+        message: t('common.loading') + '...',
+        forbidClick: true
+    })
+    http.http
+        .post('/financeOrder/create/v1', {
+            productId: productInfo.value.id,
+            stakeAmount: stakeAmount.value,
+            stakeTime: stakeTime.value
+        })
+        .then(() => {
+            Toast.clear()
+            init()
+            showPayModal.value = false
+            Toast.success(t('blf.sucess'))
+        })
+        .catch(e => {
+            Toast(e.error)
+        })
+}
+function getHours(time) {
+    return Math.ceil(time / 60)
+}
 
 function onFinish() {
     init()
@@ -113,13 +162,15 @@ function onFinish() {
 
 const productInfo = ref({})
 const time = ref(0)
+const rate = ref(0)
+const minAmount = ref(1)
 function init() {
     http.http
         .get('/financeProduct/today')
         .then(res => {
             productInfo.value = res
-            total.value = res.total
-            now.value = res.stock
+            total.value = res.amount
+            now.value = res.amount - res.saleAmount
             if (res.status === 'PENDING') {
                 // res.startTime
                 time.value = differenceInMilliseconds(new Date(res.startTime), new Date())
@@ -130,6 +181,14 @@ function init() {
         .catch(e => {
             getOrder()
         })
+
+    http.http.get('/sysConfig/get/min_stake_time')
+    http.http.get('/sysConfig/get/min_stake_amount').then(res => {
+        minAmount.value = Number(res.value)
+    })
+    http.http.get('/sysConfig/get/f_product_profit_rate').then(res => {
+        rate.value = Number(res.value) * 1000
+    })
 }
 
 const profit = ref(0)
@@ -300,4 +359,103 @@ ion-content {
         margin-top: 20px;
     }
 }
+
+ion-modal {
+    --height: 330px;
+    --border-radius: 8px;
+    --width: 80vw;
+
+    :deep(ion-content) {
+        --background: linear-gradient(90deg, rgba(219, 255, 206, 1), rgba(61, 243, 188, 1));
+        position: relative;
+        padding: 2px;
+        box-sizing: border-box;
+
+        .pay-box {
+            background-color: #2c302f;
+            border-radius: 8px;
+            position: absolute;
+            top: 2px;
+            left: 2px;
+            right: 2px;
+            bottom: 2px;
+            padding: 24px 16px;
+
+            .title {
+                .text1 {
+                    font-size: 18px;
+                    font-weight: bold;
+                    color: #ffffff;
+                    line-height: 24px;
+                }
+
+                .text2 {
+                    font-size: 12px;
+                    color: #39f3b9;
+                    line-height: 17px;
+                    margin-top: 5px;
+                }
+            }
+
+            .close {
+                position: absolute;
+                right: 16px;
+                top: 16px;
+                img {
+                    width: 22px;
+                    height: 22px;
+                }
+            }
+
+            .van-stepper {
+                width: 100%;
+                --van-stepper-input-width: 100%;
+                --van-stepper-input-height: 60px;
+                margin: 14px 0 24px;
+                .van-stepper__input {
+                    margin: 0 0;
+                    background: #3c403f;
+                    border-radius: 8px;
+                    border: 1px solid #4f5352;
+                    font-size: 36px;
+                    font-weight: bold;
+                    color: #39f3b9;
+                    line-height: 36px;
+                    text-align: left;
+                    padding: 0 12px;
+                }
+            }
+
+            .time-list {
+                .f();
+                padding: 16px 0 24px;
+                .time-item {
+                    width: 33%;
+                    .f();
+                    img {
+                        width: 24px;
+                        height: 24px;
+                        display: block;
+                    }
+                    span {
+                        font-size: 14px;
+                        color: #959797;
+                        line-height: 24px;
+                        margin-left: 4px;
+                    }
+                }
+            }
+        }
+    }
+}
+
+ion-modal::part(backdrop) {
+    background: rgba(0, 0, 0, 0.5);
+    opacity: 1;
+}
+
+ion-modal ion-toolbar {
+    --background: rgb(14 116 144);
+    --color: white;
+}
 </style>

+ 111 - 163
src/views/HomePage.vue

@@ -24,21 +24,21 @@
                     class="tutorial-btn animate__animated animate__heartBeat animate__infinite"
                     alt=""
                     srcset=""
-                    @click="agreement"
+                    @click="showIntroVideo"
                 />
                 <img
                     src="@/assets/OPNAVI.png"
                     class="tutorial-btn animate__animated animate__heartBeat animate__infinite"
                     alt=""
                     srcset=""
-                    @click="showTutorial"
+                    @click="showTutorialVideo"
                 />
                 <img
                     src="@/assets/TUTORIAL.png"
                     class="tutorial-btn animate__animated animate__heartBeat animate__infinite"
                     alt=""
                     srcset=""
-                    @click="tutorial"
+                    @click="showGraphicTutorial"
                 />
             </div>
             <!-- 通知 -->
@@ -49,7 +49,7 @@
                             dot
                             :offset="[-4, 4]"
                             class="animate__animated animate__rubberBand animate__infinite"
-                            v-if="hasNews"
+                            v-if="recentNews"
                         >
                             <img class="bar-icon" src="../assets/home_icon_redian.png" alt="" @click="showConsoleEve" />
                         </van-badge>
@@ -70,7 +70,11 @@
                         :touchable="false"
                         :show-indicators="false"
                     >
-                        <van-swipe-item v-for="(item, index) in news" :key="index" @click="goNews">
+                        <van-swipe-item
+                            v-for="(item, index) in news"
+                            :key="index"
+                            @click="router.push({ name: 'news' })"
+                        >
                             <div class="notic-news">
                                 <span class="van-ellipsis">{{ item.title }}</span>
                                 <ion-icon class="right-icon" :icon="intoIcon"></ion-icon>
@@ -82,9 +86,9 @@
 
             <div class="rank-content">
                 <img class="rank-bg" src="../assets/png-kuang.png" alt="" />
-                <div class="rank-box" @click="goRank">
+                <div class="rank-box" @click="router.push({ name: 'rank' })">
                     <div class="rank-list">
-                        <div class="rank-info" v-for="(item, index) in profitList" :key="index">
+                        <div class="rank-info" v-for="(item, index) in recentWidthdraw" :key="index">
                             <div class="rank-info-left">
                                 <div class="name">{{ item.nickname }}</div>
                                 <!-- <div class="text">
@@ -99,7 +103,7 @@
 
                             <ion-icon class="right-icon" :icon="intoIcon"></ion-icon>
                         </div>
-                        <div class="rank-info" v-for="(item, index) in profitList" :key="index">
+                        <div class="rank-info" v-for="(item, index) in recentWidthdraw" :key="index">
                             <div class="rank-info-left">
                                 <div class="name">{{ item.nickname }}</div>
                                 <!-- <div class="text">
@@ -167,16 +171,13 @@
                     <img src="../assets/home_icon_liucheng.png" alt="" />
                     <span>{{ $t('common.guide') }}</span>
                 </div> -->
-                <div class="tool-info" @click="showTutorial">
+                <div class="tool-info" @click="showTutorialVideo">
                     <img src="../assets/png-TUTORIAL.png" alt="" />
                 </div>
-                <div class="tool-info" @click="agreement">
+                <div class="tool-info" @click="showIntroVideo">
                     <img src="../assets/png-AGREEMENT.png" alt="" />
                 </div>
             </div>
-
-            <tutorial-modal ref="tutorialModalRef"></tutorial-modal>
-            <introduction-modal ref="introductionModalRef"></introduction-modal>
         </ion-content>
     </ion-page>
 </template>
@@ -186,23 +187,23 @@ import '@/styles/animate.css'
 import 'swiper/swiper.min.css'
 import 'swiper/swiper-bundle.min.css'
 import intoIcon from '@/assets/svgs/icon_inter.svg'
-import { useRoute, useRouter } from 'vue-router'
-import { ref, watch, computed } from 'vue'
-import { useIonRouter, onIonViewWillEnter } from '@ionic/vue'
+import { useRouter } from 'vue-router'
+import { ref, computed, onMounted, inject } from 'vue'
+import { modalController } from '@ionic/vue'
 import { isAfter, isBefore, parse, getYear, getMonth, getDate, addDays, getTime, format } from 'date-fns'
 import { http } from '@/plugins/http'
 import toast from '@/utils/toast'
 import { useI18n } from 'vue-i18n'
-import TutorialModal from '../components/TutorialModal.vue'
-import IntroductionModal from '../components/IntroductionModal.vue'
-import { useStorage } from '@vueuse/core'
+import { useStorage, until, promiseTimeout } from '@vueuse/core'
 import { init as initEruda } from '@/utils/console'
 import { emitter } from '@/utils/eventBus'
 import { getUtcTime } from '@/plugins/time'
 import { useUserStore } from '@/stores/user'
+import { showVideoModal } from '@/components/videoModal'
+import NewsModal from '@/components/NewsModal.vue'
 
 const router = useRouter()
-
+const i18n = useI18n()
 
 const topBanners = ref([])
 function getBanner() {
@@ -248,7 +249,69 @@ function getSaleBatch() {
     })
 }
 
-const i18n = useI18n()
+const recentWidthdraw = ref([])
+function getRecentWidthdraw() {
+    http.get('/withdrawApply/recentWithdraw').then(res => {
+        recentWidthdraw.value = res
+    })
+}
+
+const news = ref([])
+function getNews() {
+    http.post(
+        '/article/all',
+        {
+            query: {
+                del: false
+            },
+            sort: 'createdAt,desc',
+            size: 1,
+            page: 0
+        },
+        { body: 'json' }
+    ).then(res => {
+        news.value = res.content
+    })
+}
+
+const recentNews = computed(() => {
+    return news.value.find(item => {
+        return isAfter(addDays(parse(item.createdAt, 'yyyy-MM-dd HH:mm:ss', 0), 1), new Date()) || null
+    })
+})
+const refresh = () => {
+    getRecentWidthdraw()
+    getBanner()
+    getSaleBatch()
+    getNews()
+}
+const turtorialShown = useStorage('turtorialShown', false)
+const newsShown = useStorage('newsShown', null)
+const packageSynced = inject('packageSynced', false)
+async function showNewsModal(news) {
+    const modal = await modalController.create({
+        component: NewsModal,
+        componentProps: { detail: news }
+    })
+    modal.present()
+    return await modal.onWillDismiss()
+}
+onMounted(async () => {
+    refresh()
+    await until(packageSynced).toBe(true)
+    if (turtorialShown.value == false) {
+        await until(turtorialVideoSrc).not.toBeNull()
+        await showVideoModal({ src: turtorialVideoSrc.value, skip: true })
+        turtorialShown.value = true
+        await promiseTimeout(500)
+    }
+    await until(recentNews).toMatch(v => !!v)
+    if (newsShown.value != `${recentNews.value.id}`) {
+        await showNewsModal(recentNews.value)
+        newsShown.value = `${recentNews.value.id}`
+    }
+})
+
 const goList = info => {
     if (info.id == 11547) {
         const { user } = useUserStore()
@@ -268,94 +331,23 @@ const goList = info => {
         }
         return
     }
-    if (getStatus(info) === '抢购中') {
-        router.push({
-            name: 'productList',
-            query: {
-                batchId: info.id
-            }
-        })
-    } else {
-        toast(i18n.t('common.wait') + '...')
-    }
-}
-
-function goNews() {
     router.push({
-        name: 'news'
-    })
-}
-
-onIonViewWillEnter(() => {
-    // toast.loading({
-    //     message: '加载中...',
-    //     forbidClick: true
-    // })
-    getRank()
-    getBanner()
-    getSaleBatch()
-    // Promise.all([]).then(() => {
-    //     toast.dismiss()
-    // })
-    getNotice()
-})
-
-const news = ref([])
-let first = true
-function getNotice() {
-    http.post(
-        '/article/all',
-        {
-            query: {
-                del: false
-            },
-            sort: 'id,desc',
-            size: 5,
-            page: 0
-        },
-        { body: 'json' }
-    ).then(res => {
-        news.value = res.content
-        if (first) {
-            let newsStorages = window.localStorage.getItem('newsStorage')
-                ? JSON.parse(window.localStorage.getItem('newsStorage'))
-                : []
-            first = false
-            let info = news.value.find(item => {
-                let date1 = addDays(new Date(item.createdAt), 1)
-                return isAfter(date1, new Date())
-            })
-            if (info && !newsStorages.includes(info.id.toString())) {
-                newsStorages.push(info.id.toString())
-                window.localStorage.setItem('newsStorage', JSON.stringify(newsStorages))
-                router.push({
-                    name: 'newsDetail',
-                    state: {
-                        ...info
-                    },
-                    query: {
-                        id: info.id
-                    }
-                })
-            }
+        name: 'productList',
+        query: {
+            batchId: info.id
         }
     })
+    // if (getStatus(info) === '抢购中') {
+    //     router.push({
+    //         name: 'productList',
+    //         query: {
+    //             batchId: info.id
+    //         }
+    //     })
+    // } else {
+    //     toast(i18n.t('common.wait') + '...')
+    // }
 }
-const hasNews = computed(() => {
-    let info = news.value.find(item => {
-        let date1 = addDays(new Date(item.createdAt), 1)
-        return isAfter(date1, new Date())
-    })
-    return !!info
-})
-
-const noticeBarRef = ref(null)
-const route = useRoute()
-watch(route, value => {
-    if (value.name === 'home' && noticeBarRef.value) {
-        noticeBarRef.value.reset()
-    }
-})
 
 const getStatus = info => {
     let date1 = getYear(new Date()) + '/' + (getMonth(new Date()) + 1) + '/' + getDate(new Date())
@@ -368,75 +360,31 @@ const getStatus = info => {
             return getTime(startDate, 1) - getTime(new Date())
         } else if (info.empty && isBefore(new Date(), endDate)) {
             return '已抢光'
-        }
-        {
+        } else {
             return getTime(addDays(startDate, 1)) - getTime(new Date())
         }
     }
     return ''
 }
 
-const profitList = ref([])
-function randomS() {
-    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-    return chars[Math.floor(Math.random() * chars.length)]
-}
-function getRank() {
-    http.get('/withdrawApply/recentWithdraw').then(res => {
-        profitList.value = res
-    })
-    // let profitListJson = localStorage.getItem('profitList')
-    // if (profitListJson) {
-    //     profitListJson = JSON.parse(profitListJson)
-    //     if (new Date().getTime() < profitListJson.expire) {
-    //         profitList.value = profitListJson.data
-    //         return
-    //     }
-    // }
-    // profitList.value = Array.from({ length: 10 }, (v, i) => {
-    //     return {
-    //         nickname: randomS() + '***' + randomS(),
-    //         val: randomNum(100, 1000)
-    //     }
-    // })
-    // localStorage.setItem(
-    //     'profitList',
-    //     JSON.stringify({
-    //         data: profitList.value,
-    //         expire: new Date().getTime() + 1000 * 60 * 60 * 24
-    //     })
-    // )
-}
-
-function randomNum(minNum, maxNum) {
-    switch (arguments.length) {
-        case 1:
-            return parseInt(Math.random() * minNum + 1, 10)
-        case 2:
-            return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
-        default:
-            return 0
-    }
-}
-
-function goRank() {
-    router.push({ name: 'rank' })
-}
-
-const introductionModalRef = ref(null)
-function agreement() {
-    introductionModalRef.value.init()
+const turtorialVideoSrc = ref(null)
+const introVideoSrc = ref(null)
+http.post('/sysConfig/multipleGet', ['tutorial_video', 'introduce_video'], {
+    body: 'json'
+}).then(res => {
+    turtorialVideoSrc.value = res.find(item => item.name === 'tutorial_video')?.value
+    introVideoSrc.value = res.find(item => item.name === 'introduce_video')?.value
+})
+async function showIntroVideo() {
+    await showVideoModal({ src: introVideoSrc.value })
 }
 
-function tutorial() {
+function showGraphicTutorial() {
     router.push({ name: 'turtorial' })
-    // Browser.open({ url: `${location.origin}/static/PrivacyPolicy.html` })
 }
 
-const tutorialModalRef = ref(null)
-function showTutorial() {
-    console.log(tutorialModalRef.value)
-    tutorialModalRef.value.init()
+async function showTutorialVideo() {
+    await showVideoModal({ src: turtorialVideoSrc.value })
 }
 
 let clickNum = 0

+ 93 - 23
src/views/ProductListPage.vue

@@ -19,16 +19,14 @@
                     </ion-button>
                 </ion-buttons>
             </ion-toolbar>
-            <!-- <div class="status hot" v-if="status === '抢购中'">
-                <img src="../assets/info_icon_qianggouzhong.png" alt="" />
-                <span>{{ $t('product.hot') }}</span>
-            </div>
-            <div class="status" v-else-if="status">
+
+            <div class="status" v-if="batchStatus && batchStatus !== '抢购中'">
                 <img src="../assets/info_icon_shijian.png" alt="" />
-                <span><van-count-down :time="status" /> {{ $t('product.start') }}</span>
-            </div> -->
+                <span>{{ $t('product.start', { time: formatTime(batchInfo.saleStart) }) }} </span>
+                <span v-if="batchStatus === '已抢光'">({{ $t('blf.finish') }})</span>
+            </div>
         </ion-header>
-        <ion-content :scrollEvents="true" @ionScrollEnd="handleScrollEnd()">
+        <ion-content :scrollEvents="true" @ionScroll="handleScroll()">
             <ion-refresher slot="fixed" @ionRefresh="handleRefresh($event)">
                 <ion-refresher-content></ion-refresher-content>
             </ion-refresher>
@@ -50,12 +48,22 @@
             >
                 <template v-if="listType === 'list'">
                     <div class="product-list" v-for="item in list" :key="item.id">
-                        <product-info list :info="item" :stopOfficial="stopOfficial"></product-info>
+                        <product-info
+                            list
+                            :info="item"
+                            :stopOfficial="stopOfficial"
+                            :batchStatus="batchStatus"
+                        ></product-info>
                     </div>
                 </template>
                 <template v-else>
                     <div class="product" v-for="item in list" :key="item.id">
-                        <product-small-info list :info="item" :stopOfficial="stopOfficial"></product-small-info>
+                        <product-small-info
+                            list
+                            :info="item"
+                            :stopOfficial="stopOfficial"
+                            :batchStatus="batchStatus"
+                        ></product-small-info>
                     </div>
                 </template>
             </van-list>
@@ -69,9 +77,9 @@ import productInfo from '../components/ProductItem.vue'
 import { ref, getCurrentInstance, onMounted, computed } from 'vue'
 import { useRoute } from 'vue-router'
 import useList from '../plugins/list'
-import { isAfter, isBefore, parse, getYear, getTime, addDays, getMonth, getDate } from 'date-fns'
-import { searchOutline } from 'ionicons/icons'
+import { isAfter, isBefore, format, parse, getYear, getMonth, getDate } from 'date-fns'
 import { useStorage } from '@vueuse/core'
+import { getUtcTime } from '@/plugins/time'
 
 const route = useRoute()
 const {
@@ -84,12 +92,22 @@ const batchInfo = ref({})
 const batchId = route.query.batchId
 
 const { empty, size, loading, finished, list, getData } = useList('/product/list', () => {
-    return {
-        query: {
-            batchId: route.query.batchId,
-            status: 'IN_STOCK'
-        },
-        sort: 'delayTo,asc;sales,desc;'
+    if (batchStatus.value !== '抢购中' && batchInfo.value.id && batchInfo.value.empty) {
+        return {
+            query: {
+                batchId: route.query.batchId,
+                status: 'IN_STOCK,SOLD_OUT'
+            },
+            sort: 'status,asc;delayTo,asc;sales,desc;'
+        }
+    } else {
+        return {
+            query: {
+                batchId: route.query.batchId,
+                status: 'IN_STOCK'
+            },
+            sort: 'delayTo,asc;sales,desc;'
+        }
     }
 })
 const handleRefresh = event => {
@@ -98,10 +116,62 @@ const handleRefresh = event => {
     })
 }
 function getBatch() {
-    global.$http.get('/saleBatch/get/' + batchId).then(res => {
-        batchInfo.value = res
-    })
+    global.$http
+        .get('/saleBatch/get/' + batchId)
+        .then(res => {
+            batchInfo.value = {
+                ...res,
+                empty: false
+            }
+
+            if (batchId === 11549) {
+                return global.$http
+                    .post(
+                        '/product/list',
+                        {
+                            query: {
+                                batchId: batchId,
+                                status: 'IN_STOCK'
+                            },
+                            size: 1,
+                            sort: 'modifiedAt,desc'
+                        },
+                        { body: 'json' }
+                    )
+                    .then(res => {
+                        batchInfo.value.empty = res.empty
+                    })
+            } else {
+                return Promise.resolve()
+            }
+        })
+        .then(() => {
+            if (batchStatus.value !== '抢购中' && empty.value) {
+                getData(true)
+            }
+        })
+}
+
+function formatTime(time) {
+    return format(getUtcTime(parse(time, 'HH:mm:ss', new Date())), 'HH:mm a')
 }
+const batchStatus = computed(() => {
+    let date1 = getYear(new Date()) + '/' + (getMonth(new Date()) + 1) + '/' + getDate(new Date())
+    let startDate = getUtcTime(new Date(date1 + ' ' + batchInfo.value.saleStart))
+    let endDate = getUtcTime(new Date(date1 + ' ' + batchInfo.value.saleEnd))
+    if (batchInfo.value && batchInfo.value.saleStart) {
+        if (isAfter(new Date(), startDate) && isBefore(new Date(), endDate) && !batchInfo.value.empty) {
+            return '抢购中'
+        } else if (isBefore(new Date(), startDate)) {
+            return formatTime(batchInfo.value.saleStart)
+        } else if (batchInfo.value.empty && isBefore(new Date(), endDate)) {
+            return '已抢光'
+        } else {
+            return formatTime(batchInfo.value.saleStart)
+        }
+    }
+    return ''
+})
 
 //官方未开售(存在销量>0的藏品,销量为0的不可售)
 const stopOfficial = computed(() => {
@@ -151,7 +221,7 @@ onMounted(() => {
 })
 
 const listRef = ref(null)
-function handleScrollEnd() {
+function handleScroll() {
     listRef.value.check()
 }
 </script>
@@ -197,7 +267,7 @@ ion-button {
 .status {
     .f();
     justify-content: center;
-    background: var(--green);
+    background: #5d7368;
     height: 28px;
     img {
         width: 18px;

+ 71 - 17
src/views/SettingPage.vue

@@ -77,7 +77,7 @@
 
                 <van-cell-group inset>
                     <van-cell :title="$t('settings.checkUpdate')" @click="checkUpdate">
-                        <span slot="value" class="version">v{{ version.www }}</span>
+                        <span slot="value" class="version">{{ version }}</span>
                     </van-cell>
                 </van-cell-group>
                 <!-- <van-cell-group :border="false">
@@ -113,14 +113,17 @@ import { useSettingsStore } from '@/stores/settings'
 import { codePush, InstallMode } from 'capacitor-codepush'
 import { SyncStatus } from 'capacitor-codepush/dist/esm/syncStatus'
 import { Capacitor } from '@capacitor/core'
-import version from '@/version.json'
 import { useClipboard } from '@vueuse/core'
+import resolveUrl from 'resolve-url'
+import axios from 'axios'
+import { showDialog } from 'vant'
+import { promiseTimeout } from '@vueuse/core'
 
 export default {
     data() {
         return {
             showTest: false,
-            version
+            version: window.www_version
         }
     },
     computed: {
@@ -234,23 +237,74 @@ export default {
             this.logout()
             this.$router.back()
         },
-        checkUpdate() {
-            if (Capacitor.isNativePlatform()) {
-                this.$toast.loading(this.$t('settings.checkingUpdate'))
-                codePush
-                    .sync({
-                        updateDialog: false,
-                        installMode: InstallMode.IMMEDIATE,
-                        ignoreFailedUpdates: false
+        async checkUpdate() {
+            this.$toast.loading(this.$t('settings.checkingUpdate'))
+            await promiseTimeout(300)
+            try {
+                if (Capacitor.isNativePlatform()) {
+                    this.$toast.dismiss()
+                    console.log('codePush -> checking for update')
+                    const update = await new Promise((resolve, reject) => {
+                        codePush.checkForUpdate(
+                            p => {
+                                resolve(p)
+                            },
+                            err => {
+                                reject(err)
+                            }
+                        )
                     })
-                    .then(status => {
-                        console.log('codePush sync status:', SyncStatus[status])
-                        if (status === SyncStatus.UP_TO_DATE) {
-                            this.$toast(this.$t('settings.upToDate'))
+                    if (!update) {
+                        console.log('codePush -> no update available')
+                        this.$toast(this.$t('settings.upToDate'))
+                    } else {
+                        console.log('codePush -> there is an update, prompt for download', update)
+                        await showDialog({
+                            message: this.$t('settings.newUpdate'),
+                            confirmButtonText: this.$t('settings.updateNow')
+                        })
+                        console.log('codePush -> start downloading')
+                        this.$toast.loading(this.$t('settings.updating'))
+                        const downloadedPackage = await update.download(downloadProgress => {
+                            console.log(
+                                `Downloading ${downloadProgress.receivedBytes} of ${downloadProgress.totalBytes} bytes.`
+                            )
+                        })
+                        console.log('codePush -> package downloaded at: ' + downloadedPackage.localPath)
+                        await downloadedPackage.install({
+                            installMode: InstallMode.IMMEDIATE,
+                            minimumBackgroundDuration: 120,
+                            mandatoryInstallMode: InstallMode.IMMEDIATE
+                        })
+                        this.$toast.dismiss()
+                        console.log('codePush -> install update success')
+                    }
+                } else {
+                    const url = resolveUrl(import.meta.env.BASE_URL, 'meta.js')
+                    this.$toast.loading(this.$t('settings.checkingUpdate'))
+                    const res = await axios.get(url, {
+                        headers: {
+                            'Cache-Control': 'no-cache'
                         }
                     })
-            } else {
-                this.$toast(this.$t('settings.upToDate'))
+                    this.$toast.dismiss()
+                    const latest = parseInt(/window.www_version = (\d+)/.exec(res.data)[1])
+                    console.log('latest', latest)
+                    if (latest > window.www_version) {
+                        await promiseTimeout(200)
+                        await showDialog({
+                            message: this.$t('settings.newUpdate'),
+                            confirmButtonText: this.$t('settings.updateNow')
+                        })
+                        await promiseTimeout(500)
+                        window.location = resolveUrl(import.meta.env.BASE_URL, '?ts=' + Date.now())
+                    } else {
+                        this.$toast(this.$t('settings.upToDate'))
+                    }
+                }
+            } catch (e) {
+                this.$toast.dismiss()
+                console.log('An error occurred while checking for updates', e)
             }
         }
     }

+ 2 - 1
vite.config.js

@@ -88,6 +88,7 @@ export default defineConfig(({ command, mode }) => {
                     additionalData: '@import "@/styles/common.less";'
                 }
             }
-        }
+        },
+        logLevel: mode === 'app' || mode === 'test' ? 'error' : 'info'
     }
 })

+ 154 - 4
yarn.lock

@@ -1186,6 +1186,18 @@
   resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
   integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
 
+"@eslint-community/eslint-utils@^4.2.0":
+  version "4.2.0"
+  resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz#a831e6e468b4b2b5ae42bf658bea015bf10bc518"
+  integrity sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==
+  dependencies:
+    eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.4.0":
+  version "4.4.0"
+  resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
+  integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
+
 "@eslint/eslintrc@^2.0.0":
   version "2.0.0"
   resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff"
@@ -1730,6 +1742,11 @@
     jest-matcher-utils "^27.0.0"
     pretty-format "^27.0.0"
 
+"@types/json-schema@^7.0.9":
+  version "7.0.11"
+  resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+  integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+
 "@types/lodash@^4.14.175":
   version "4.14.191"
   resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa"
@@ -1770,6 +1787,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/semver@^7.3.12":
+  version "7.3.13"
+  resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
+  integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
+
 "@types/slice-ansi@^4.0.0":
   version "4.0.0"
   resolved "https://registry.npmmirror.com/@types/slice-ansi/-/slice-ansi-4.0.0.tgz#eb40dfbe3ac5c1de61f6bcb9ed471f54baa989d6"
@@ -1797,6 +1819,90 @@
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
   integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
 
+"@typescript-eslint/eslint-plugin@^5.0.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz#bc2400c3a23305e8c9a9c04aa40933868aaaeb47"
+  integrity sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==
+  dependencies:
+    "@eslint-community/regexpp" "^4.4.0"
+    "@typescript-eslint/scope-manager" "5.55.0"
+    "@typescript-eslint/type-utils" "5.55.0"
+    "@typescript-eslint/utils" "5.55.0"
+    debug "^4.3.4"
+    grapheme-splitter "^1.0.4"
+    ignore "^5.2.0"
+    natural-compare-lite "^1.4.0"
+    semver "^7.3.7"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/parser@^5.0.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.55.0.tgz#8c96a0b6529708ace1dcfa60f5e6aec0f5ed2262"
+  integrity sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==
+  dependencies:
+    "@typescript-eslint/scope-manager" "5.55.0"
+    "@typescript-eslint/types" "5.55.0"
+    "@typescript-eslint/typescript-estree" "5.55.0"
+    debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz#e863bab4d4183ddce79967fe10ceb6c829791210"
+  integrity sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==
+  dependencies:
+    "@typescript-eslint/types" "5.55.0"
+    "@typescript-eslint/visitor-keys" "5.55.0"
+
+"@typescript-eslint/type-utils@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz#74bf0233523f874738677bb73cb58094210e01e9"
+  integrity sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==
+  dependencies:
+    "@typescript-eslint/typescript-estree" "5.55.0"
+    "@typescript-eslint/utils" "5.55.0"
+    debug "^4.3.4"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/types@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.55.0.tgz#9830f8d3bcbecf59d12f821e5bc6960baaed41fd"
+  integrity sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==
+
+"@typescript-eslint/typescript-estree@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz#8db7c8e47ecc03d49b05362b8db6f1345ee7b575"
+  integrity sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==
+  dependencies:
+    "@typescript-eslint/types" "5.55.0"
+    "@typescript-eslint/visitor-keys" "5.55.0"
+    debug "^4.3.4"
+    globby "^11.1.0"
+    is-glob "^4.0.3"
+    semver "^7.3.7"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/utils@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.55.0.tgz#34e97322e7ae5b901e7a870aabb01dad90023341"
+  integrity sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==
+  dependencies:
+    "@eslint-community/eslint-utils" "^4.2.0"
+    "@types/json-schema" "^7.0.9"
+    "@types/semver" "^7.3.12"
+    "@typescript-eslint/scope-manager" "5.55.0"
+    "@typescript-eslint/types" "5.55.0"
+    "@typescript-eslint/typescript-estree" "5.55.0"
+    eslint-scope "^5.1.1"
+    semver "^7.3.7"
+
+"@typescript-eslint/visitor-keys@5.55.0":
+  version "5.55.0"
+  resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz#01ad414fca8367706d76cdb94adf788dc5b664a2"
+  integrity sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==
+  dependencies:
+    "@typescript-eslint/types" "5.55.0"
+    eslint-visitor-keys "^3.3.0"
+
 "@vant/area-data@^1.3.2":
   version "1.4.0"
   resolved "https://registry.npmmirror.com/@vant/area-data/-/area-data-1.4.0.tgz#6140c32a3b60e42512a3f428ca9e175b7307d048"
@@ -1885,6 +1991,15 @@
     eslint-config-prettier "^8.3.0"
     eslint-plugin-prettier "^4.0.0"
 
+"@vue/eslint-config-typescript@^11.0.2":
+  version "11.0.2"
+  resolved "https://registry.npmmirror.com/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz#03353f404d4472900794e653450bb6623de3c642"
+  integrity sha512-EiKud1NqlWmSapBFkeSrE994qpKx7/27uCGnhdqzllYDpQZroyX/O6bwjEpeuyKamvLbsGdO6PMR2faIf+zFnw==
+  dependencies:
+    "@typescript-eslint/eslint-plugin" "^5.0.0"
+    "@typescript-eslint/parser" "^5.0.0"
+    vue-eslint-parser "^9.0.0"
+
 "@vue/reactivity-transform@3.2.47":
   version "3.2.47"
   resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
@@ -1942,7 +2057,7 @@
     "@vueuse/shared" "9.13.0"
     vue-demi "*"
 
-"@vueuse/core@9.13.0", "@vueuse/core@^9.6.0":
+"@vueuse/core@9.13.0", "@vueuse/core@^9.13.0":
   version "9.13.0"
   resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
   integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==
@@ -3742,6 +3857,14 @@ eslint-plugin-vue@^9.3.0:
     vue-eslint-parser "^9.0.1"
     xml-name-validator "^4.0.0"
 
+eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
 eslint-scope@^7.1.1:
   version "7.1.1"
   resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
@@ -3841,7 +3964,7 @@ esrecurse@^4.3.0:
   dependencies:
     estraverse "^5.2.0"
 
-estraverse@^4.2.0:
+estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.3.0"
   resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
@@ -4509,7 +4632,7 @@ globby@^10.0.0:
     merge2 "^1.2.3"
     slash "^3.0.0"
 
-globby@^11.0.1:
+globby@^11.0.1, globby@^11.1.0:
   version "11.1.0"
   resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
   integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -5924,6 +6047,11 @@ native-run@^1.6.0:
     tslib "^2.4.0"
     yauzl "^2.10.0"
 
+natural-compare-lite@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.npmmirror.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
+  integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -6925,6 +7053,11 @@ resolve-from@^4.0.0:
   resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
+resolve-url@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+  integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==
+
 resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.1:
   version "1.22.1"
   resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
@@ -7798,11 +7931,23 @@ ts-node@^10.2.1:
     v8-compile-cache-lib "^3.0.1"
     yn "3.1.1"
 
+tslib@^1.8.1:
+  version "1.14.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
 tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
   version "2.5.0"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
   integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
 
+tsutils@^3.21.0:
+  version "3.21.0"
+  resolved "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+  integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
+  dependencies:
+    tslib "^1.8.1"
+
 tunnel-agent@^0.6.0:
   version "0.6.0"
   resolved "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -7873,6 +8018,11 @@ typed-function@^4.1.0:
   resolved "https://registry.npmmirror.com/typed-function/-/typed-function-4.1.0.tgz#da4bdd8a6d19a89e22732f75e4a410860aaf9712"
   integrity sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==
 
+typescript@^4.9.5:
+  version "4.9.5"
+  resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
+  integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+
 uglify-js@^3.1.4:
   version "3.17.4"
   resolved "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
@@ -8101,7 +8251,7 @@ vue-demi@*:
   resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
   integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
 
-vue-eslint-parser@^9.0.1:
+vue-eslint-parser@^9.0.0, vue-eslint-parser@^9.0.1:
   version "9.1.0"
   resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.1.0.tgz#0e121d1bb29bd10763c83e3cc583ee03434a9dd5"
   integrity sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==