9433cfb9创建于 2025年12月31日历史提交
<template>
  <view class="mask flex-center">
    <view class="content">
      <view class="content-top">
        <text class="content-top-text">{{title}}</text>
        <image class="content-top-image" mode="widthFix"
          src="/uni_modules/uni-upgrade-center-app/static/app/bg_top.png"></image>
      </view>

      <view class="content-space"></view>

      <view class="content-body">
        <view class="content-body-title">
          <text class="text title">{{subTitle}}</text>
          <text class="text version">v{{version}}</text>
        </view>
        <view class="body">
          <scroll-view class="box-des-scroll" scroll-y="true">
            <text class="text box-des">
              {{contents}}
            </text>
          </scroll-view>
        </view>
        <view class="footer flex-center">
          <template v-if="isiOS || isHarmony">
            <button class="content-button" style="border: none;color: #fff;" type="primary" plain
              @click="jumpToAppStore">
              {{downLoadBtnTextiOS}}
            </button>
          </template>
          <template v-else>
            <template v-if="!downloadSuccess">
              <view class="progress-box flex-column" v-if="downloading">
                <progress class="progress" :percent="downLoadPercent" activeColor="#3DA7FF" :show-info="true"
                  :stroke-width="10" />
                <view style="width:100%;display: flex;justify-content: space-around;flex-direction: row;">
                  <text class="text" style="font-size: 14px;">{{downLoadingText}}</text>
                  <text class="text" style="font-size: 14px;">({{downloadedSize}}/{{packageFileSize}}M)</text>
                </view>
              </view>

              <button v-else class="content-button" @click="updateApp">
                {{downLoadBtnText}}
              </button>
            </template>

            <button v-else-if="downloadSuccess && !installed" class="content-button" :loading="installing"
              :disabled="installing" @click="installPackage">
              {{installing ? '正在安装……' : '下载完成,立即安装'}}
            </button>

            <button v-else-if="installed" class="content-button" @click="installPackage">
              安装未完成,点击安装
            </button>
          </template>
        </view>
      </view>

      <view class="content-bottom">
        <image v-if="!is_mandatory" class="close-img" mode="widthFix"
          src="/uni_modules/uni-upgrade-center-app/static/app/app_update_close.png" @click="closeUpdate">
        </image>
      </view>
    </view>
  </view>
</template>

<script setup lang="uts">
  import { openSchema as utsOpenSchema } from '@/uni_modules/uts-openSchema'
  import { UniUpgradeCenterResult, StoreListItem } from '../../utils/call-check-version'
  import { platform_iOS, platform_Android, platform_Harmony } from '../../utils/utils'
  // #ifdef APP-ANDROID
  import { createNotificationProgress, cancelNotificationProgress, finishNotificationProgress } from '@/uni_modules/uts-progressNotification'
  import { type CreateNotificationProgressOptions, type FinishNotificationProgressOptions } from '@/uni_modules/uts-progressNotification/utssdk/interface.uts'
  // #endif

  const requiredKey = ['version', 'url', 'type']
  let downloadTask : DownloadTask | null = null;
  let openSchemePromise : Promise<boolean> | null = null;

  const openSchema = (url : string) : Promise<boolean> => new Promise<boolean>((resolve, reject) => {
    try {
      utsOpenSchema(url)
      resolve(true)
    } catch (e) {
      reject(false)
    }
  })

  // 从之前下载安装
  const installForBeforeFilePath = ref<string>('')

  // 安装
  const installed = ref<boolean>(false)
  const installing = ref<boolean>(false)

  // 下载
  const downloadSuccess = ref<boolean>(false)
  const downloading = ref<boolean>(false)

  const downLoadPercent = ref<number>(0)
  const downloadedSize = ref<number>(0)
  const packageFileSize = ref<number>(0)

  // 要安装的本地包地址
  const tempFilePath = ref<string>('')

  // 默认安装包信息
  const title = ref<string>('更新日志')
  const contents = ref<string>('')
  const version = ref<string>('')
  const is_mandatory = ref<boolean>(false)
  const url = ref<string>("")
  const platform = ref<string[]>([])
  const store_list = ref<StoreListItem[] | null>(null)

  // 可自定义属性
  const subTitle = ref<string>('发现新版本')
  const downLoadBtnTextiOS = ref<string>('立即跳转更新')
  const downLoadBtnText = ref<string>('立即下载更新')
  const downLoadingText = ref<string>('安装包下载中,请稍后')

  const isiOS = computed(() : boolean => platform.value.includes(platform_iOS))
  const isHarmony = computed(() : boolean => platform.value.includes(platform_Harmony))
  const isAndroid = computed(() : boolean => platform.value.includes(platform_Android))
  const needNotificationProgress = computed(() : boolean => isAndroid.value && !is_mandatory.value)

  function getCurrentDialogPage() : UniPage | null {
    const pages = getCurrentPages()
    if (pages.length > 0) {
      const dialogPages = pages[pages.length - 1].getDialogPages()
      if (dialogPages.length > 0) {
        return dialogPages[dialogPages.length - 1]
      }
    }
    return null
  }

  function closePopup() {
    downloadSuccess.value = false
    downloading.value = false
    downLoadPercent.value = 0
    downloadedSize.value = 0
    packageFileSize.value = 0
    tempFilePath.value = ''

    installing.value = false
    installed.value = false

    uni.closeDialogPage({
      dialogPage: getCurrentDialogPage(),
      fail(e) {
        console.log('e: ', e);
      }
    })
  }

  function askAbortDownload() {
    uni.showModal({
      title: '是否取消下载?',
      cancelText: '否',
      confirmText: '是',
      success: res => {
        if (res.confirm) {
          if (downloadTask !== null) downloadTask!.abort()
          if (needNotificationProgress.value) {
            // #ifdef APP-ANDROID
            cancelNotificationProgress();
            // #endif
          }
          closePopup()
        }
      }
    });
  }

  function closeUpdate() {
    if (downloading.value && !needNotificationProgress.value) {
      askAbortDownload()
      return;
    }
    closePopup()
  }

  function jumpToAppStore() {
    openSchema(url.value)
  }

  function show(localPackageInfo : UniUpgradeCenterResult | null) {
    if (localPackageInfo === null) return;
    for (let key in localPackageInfo) {
      if (requiredKey.indexOf(key) != -1 && localPackageInfo[key] === null) {
        console.error(`参数 ${key} 必填,请检查后重试`)
        closePopup()
        return;
      }
    }
    title.value = localPackageInfo.title
    url.value = localPackageInfo.url
    contents.value = localPackageInfo.contents
    is_mandatory.value = localPackageInfo.is_mandatory
    platform.value = localPackageInfo.platform
    version.value = localPackageInfo.version
    store_list.value = localPackageInfo.store_list
  }

  function checkStoreScheme() : Promise<boolean> | null {
    if (store_list.value !== null) {
      const storeList : StoreListItem[] = store_list.value!.filter((item : StoreListItem) : boolean => item.enable)
      if (storeList.length > 0) {
        if (openSchemePromise === null) {
          openSchemePromise = Promise.reject() as Promise<boolean>
        }
        storeList
          .sort((cur : StoreListItem, next : StoreListItem) : number => next.priority - cur.priority)
          .map((item : StoreListItem) : string => item.scheme)
          .reduce((promise : Promise<boolean>, cur : string) : Promise<boolean> => {
            openSchemePromise = promise.catch<boolean>(() : Promise<boolean> => openSchema(cur))
            return openSchemePromise!
          }, openSchemePromise!)
        return openSchemePromise!
      }
    }
    return null
  }

  function installPackage() {
    installing.value = true;
    // #ifdef APP
    uni.installApk({
      filePath: tempFilePath.value,
      success: _ => {
        installing.value = false;
        installed.value = true;
      },
      fail: err => {
        console.error('installApk fail', err);
        // 安装失败需要重新下载安装包
        installing.value = false;
        installed.value = false;
        uni.showModal({
          title: '更新失败,请重新下载',
          content: `uni.installApk 错误码 ${err.errCode}`,
          showCancel: false
        });
      }
    });
    // 安装跳出覆盖安装,此处直接返回上一页
    if (!is_mandatory.value) {
      uni.navigateBack()
    }
    // #endif
  }

  function downloadFail() {
    const errMsg = '下载失败,请点击重试'
    downloadSuccess.value = false;
    downloading.value = false;
    downLoadPercent.value = 0;
    downloadedSize.value = 0;
    packageFileSize.value = 0;
    downLoadBtnText.value = errMsg
    downloadTask = null;
    if (needNotificationProgress.value) {
      // #ifdef APP-ANDROID
      finishNotificationProgress({
        title: '升级包下载失败',
        content: '请重新检查更新',
        onClick() { }
      } as FinishNotificationProgressOptions);
      // #endif
    }
  }

  function downLoadComplete() {
    downloadSuccess.value = true;
    downloading.value = false;
    downLoadPercent.value = 0
    downloadedSize.value = 0
    packageFileSize.value = 0
    downloadTask = null;
    if (needNotificationProgress.value) {
      // #ifdef APP-ANDROID
      finishNotificationProgress({
        title: "安装升级包",
        content: "下载完成",
        onClick() { }
      } as FinishNotificationProgressOptions)
      installPackage();
      // #endif
      return
    }
    // 强制更新,直接安装
    if (is_mandatory.value) {
      installPackage();
    }
  }

  function downloadPackage() {
    //下载包
    downloadTask = uni.downloadFile({
      url: url.value,
      success: res => {
        if (res.statusCode == 200) {
          tempFilePath.value = res.tempFilePath
          downLoadComplete()
        } else {
          console.log('downloadFile err: ', res);
          downloadFail()
        }
      },
      fail: err => {
        console.log('downloadFile err: ', err);
        downloadFail()
      }
    });
    if (downloadTask !== null) {
      downloading.value = true;
      if (needNotificationProgress.value) {
        closePopup()
      }
      downloadTask!.onProgressUpdate(res => {
        downLoadPercent.value = parseFloat(res.progress.toFixed(0));
        downloadedSize.value = parseFloat((res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2));
        packageFileSize.value = parseFloat((res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2));
        if (needNotificationProgress.value) {
          // #ifdef APP-ANDROID
          createNotificationProgress({
            title: "升级中心正在下载安装包……",
            content: `${downLoadPercent.value}%`,
            progress: downLoadPercent.value,
            onClick: () => {
              if (!downloadSuccess.value) {
                askAbortDownload()
              }
            }
          } as CreateNotificationProgressOptions)
          // #endif
        }
      });
    }
  }

  function updateApp() {
    const checkStoreSchemeResult = checkStoreScheme()
    if (checkStoreSchemeResult !== null) {
      checkStoreSchemeResult
        .then(_ => { })
        .catch(() => { downloadPackage() })
        .finally(() => {
          openSchemePromise = null
        })
    } else { downloadPackage() }
  }

  onUnload(() => {
    if (needNotificationProgress.value) {
      // #ifdef APP-ANDROID
      cancelNotificationProgress()
      // #endif
    }
  })

  onLoad((onLoadOptions : OnLoadOptions) => {
    const local_storage_key : string | null = onLoadOptions['local_storage_key']
    if (local_storage_key == null) {
      console.error('local_storage_key为空,请检查后重试')
      closePopup()
      return;
    };
    const localPackageInfo = uni.getStorageSync(local_storage_key);
    if (localPackageInfo == null) {
      console.error('安装包信息为空,请检查后重试')
      closePopup()
      return;
    };
    show(JSON.parse<UniUpgradeCenterResult>(JSON.stringify(localPackageInfo)) as UniUpgradeCenterResult)
  })

  onBackPress((options : OnBackPressOptions) : boolean | null => {
    if (is_mandatory.value) return true
    if (!needNotificationProgress.value) {
      if (downloadTask !== null) {
        downloadTask!.abort()
      }
    }
    return false
  })
</script>

<style>
  .flex-center {
    /* #ifndef APP-NVUE | UNI-APP-X */
    display: flex;
    /* #endif */
    justify-content: center;
    align-items: center;
  }

  .mask {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, .65);
  }

  .content {
    position: relative;
    top: 0;
    width: 600rpx;
    background-color: transparent;
  }

  .text {
    font-family: Source Han Sans CN;
  }

  .content-top {
    width: 100%;
    border-bottom-color: #fff;
    border-bottom-width: 15px;
    border-bottom-style: solid;
  }

  .content-space {
    width: 100%;
    height: 120px;
    background-color: #fff;
    position: absolute;
    top: 30%;
    z-index: -1;
  }

  .content-top-image {
    width: 100%;
    position: relative;
    bottom: -10%;
  }

  .content-top-text {
    font-size: 22px;
    font-weight: bold;
    color: #F8F8FA;
    position: absolute;
    width: 65%;
    top: 50%;
    left: 25px;
    z-index: 1;
  }

  .content-body {
    box-sizing: border-box;
    padding: 0 25px;
    width: 100%;
    background-color: #fff;
    border-bottom-left-radius: 15px;
    border-bottom-right-radius: 15px;
  }

  .content-body-title {
    flex-direction: row;
    align-items: center;
  }

  .content-body-title .version {
    padding-left: 10px;
    color: #fff;
    font-size: 10px;
    margin-left: 5px;
    padding: 2px 4px;
    border-radius: 10px;
    background: #50aefd;
  }

  .title {
    font-size: 16px;
    font-weight: bold;
    color: #3DA7FF;
    line-height: 38px;
  }

  .footer {
    height: 75px;
    display: flex;
    align-items: center;
    justify-content: space-around;
  }

  .box-des-scroll {
    box-sizing: border-box;
    padding: 0 15px;
    height: 100px;
  }

  .box-des {
    font-size: 13px;
    color: #000000;
    line-height: 25px;
  }

  .progress-box {
    width: 100%;
  }

  .progress {
    width: 90%;
    height: 20px;
  }

  .content-bottom {
    height: 75px;
  }

  .close-img {
    width: 35px;
    height: 35px;
    z-index: 1000;
    position: relative;
    bottom: -30%;
    left: 50%;
    margin-left: -17px;
  }

  .content-button {
    width: 100%;
    height: 40px;
    line-height: 40px;

    font-size: 15px;
    font-weight: 400;
    border-radius: 20px;
    border: none;
    color: #fff;

    text-align: center;

    background-color: #1785ff;
  }

  .flex-column {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
</style>