This commit is contained in:
全栈小学生 2025-09-13 10:26:31 +08:00
parent 8bf3968007
commit 7900632322
53 changed files with 2029 additions and 454 deletions

View File

@ -9,7 +9,7 @@
@click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle"
>
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0' }">
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0', marginBottom : component.margin.bottom < 0 ? (component.margin.bottom * 2) + 'rpx' : '0' }">
<!-- 装修模式下设置负上边距后超出的内容禁止选中设置 -->
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1"
@ -122,9 +122,6 @@
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" />
</template>
<!-- 以下是addon文件夹下的自定义组件 -->
</view>
</view>
</template>
@ -135,7 +132,6 @@
</view>
</template>
<script lang="ts" setup>
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import popAds from '@/components/pop-ads/pop-ads.vue'
import { useDiyGroup } from './useDiyGroup'

View File

@ -131,3 +131,13 @@ export function bind(data: AnyObject) {
export function memberLog(data: AnyObject) {
return request.post('member/log', data, { showErrorMessage: false })
}
/**
* app授权登录
*/
export function wxappLogin(data: AnyObject) {
if (uni.getStorageSync('pid')) {
data.pid = uni.getStorageSync('pid');
}
return request.post('wxapp/login', data, { showErrorMessage: false })
}

View File

@ -157,4 +157,11 @@ export function getInitInfo(params: Record<string, any>) {
*/
export function getMemberMobileExist(params: Record<string, any>) {
return request.get('member_mobile_exist', params)
}
}
/**
*
*/
export function getNewVersion(params: Record<string, any>) {
return request.get('app/newversion', params)
}

View File

@ -65,7 +65,7 @@
<!-- <view class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view> -->
</view>
<view v-for="(item, index) in diyComponent.tab.list" class="scroll-item" :class="[{ active: index == currTabIndex }]" @click="changeData(item,index)" :id="'a' + index" :key="index">
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item.text }}</view>
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item ? item.text : '' }}</view>
<!-- <view class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view> -->
</view>
</scroll-view>
@ -148,8 +148,8 @@
<script setup lang="ts">
//
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
import { img } from '@/utils/common';
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance, onUnmounted } from 'vue';
import { img, getTopFixedStatusName } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import diyGroup from '@/addon/components/diy/group/index.vue';
import { getDiyInfo } from '@/app/api/diy';
@ -164,6 +164,12 @@ const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
// h5
if(uni.getStorageSync(getTopFixedStatusName(props.global))){
diyStore.topFixedStatus = uni.getStorageSync(getTopFixedStatusName(props.global));
}else{
diyStore.topFixedStatus = 'home'
}
return props.component;
}
})
@ -376,8 +382,8 @@ const changeData = (item: any, index: any) => {
//
diyStore.topFixedStatus = 'diy'
getDiyInfoFn(item.diy_id);
}
uni.setStorageSync(getTopFixedStatusName(props.global), diyStore.topFixedStatus);
}
const tabAllPopup = ref(false);
@ -389,6 +395,11 @@ const navbarInnerStyle = ref('')
//
navbarInnerStyle.value += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;';
// #endif
// #ifdef APP-PLUS
navbarInnerStyle.value += 'padding-top:' + systemStore.systemInfo.statusBarHeight + 'px;';
// #endif
onMounted(() => {
refresh();
//
@ -397,6 +408,7 @@ onMounted(() => {
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'CarouselSearch') {
diyStore.topFixedStatus = 'home'; // home
refresh();
}
}
@ -427,11 +439,14 @@ onMounted(() => {
// #endif
});
onUnmounted(() => {
uni.removeStorageSync(getTopFixedStatusName(props.global))
})
const refresh = () => {
setModuleLocation();
//
locationVal.refresh();
changeData({ source: 'home' }, -1)
diyComponent.value.swiper.list.forEach((item: any) => {
if (item.imageUrl == '') {
@ -471,6 +486,7 @@ const getDiyInfoFn = (id: any) => {
diyPageData.value = sources.value;
diyPageData.value.forEach((item: any, index) => {
item.componentIsShow = true //
item.pageStyle = '';
if (item.pageStartBgColor) {
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${ item.pageGradientAngle },${ item.pageStartBgColor },${ item.pageEndBgColor });`;

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -28,7 +28,7 @@
</u-form>
<view class="mt-[100rpx]">
<view v-if="config.agreement_show" class="flex items-center mb-[20rpx] py-[10rpx]" @click.stop="agreeChange">
<u-checkbox-group @change="agreeChange">
<u-checkbox-group @change="agreeChange">
<u-checkbox activeColor="var(--primary-color)" :checked="isAgree" shape="circle" size="24rpx" :customStyle="{ 'marginTop': '4rpx' }" />
</u-checkbox-group>
<view class="text-[24rpx] text-[var(--text-color-light6)] flex items-center flex-wrap">
@ -79,20 +79,24 @@ const isAgree = ref(false)
const formData: any = reactive({
mobile: '',
mobile_code: '',
mobile_key: ''
mobile_key: '',
register_type: ''
})
const real_name_input = ref(true);
onLoad(() => {
onLoad((data) => {
//
setTimeout(() => {
real_name_input.value = false;
}, 800)
data.register_type && (Object.assign(formData, { register_type: data.register_type }))
uni.getStorageSync('openid') && (Object.assign(formData, { openid: uni.getStorageSync('openid') }))
uni.getStorageSync('pid') && (Object.assign(formData, { pid: uni.getStorageSync('pid') }))
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') }))
uni.getStorageSync('nickname') && (Object.assign(formData, { nickname: uni.getStorageSync('nickname') }))
uni.getStorageSync('avatar') && (Object.assign(formData, { avatar: uni.getStorageSync('avatar') }))
});
const rules = {

View File

@ -1,7 +1,7 @@
<template>
<view class="w-screen h-screen" :style="themeColor()">
<view class="w-screen h-screen" :style="warpStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -16,7 +16,7 @@
</view>
<view v-else class="h-[90rpx] w-[300rpx]"></view>
</view>
<view class="text-[var(--text-color-light6)]] text-[28rpx] text-center leading-[34rpx] min-h-[34rpx] mt-[40rpx]">{{ loginConfig.desc }}</view>
<view class="text-[var(--text-color-light6)]] text-[28rpx] text-center leading-[34rpx] min-h-[34rpx] mt-[40rpx]">{{ loginConfig.desc }}</view>
<view class="mt-[181rpx]">
<!-- #ifdef H5 -->
<!-- 微信公众号快捷登录开启自动注册的情况下才能使用 -->
@ -52,11 +52,11 @@
<!-- 手机号登录 -->
<view v-if="loginConfig.is_mobile" class="mb-[40rpx] w-full flex items-center justify-center">
<!-- #ifdef H5 -->
<!-- #ifndef MP -->
<button class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]" @click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">{{ t('mobileLogin') }}</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP -->
<button v-if="authRegisterLogin && loginConfig.is_mobile"
class="w-[630rpx] h-[88rpx] !mx-[0] !bg-[#fff] border-[var(--primary-color)] border-solid border-[2rpx] text-[26rpx] rounded-[44rpx] leading-[84rpx] !text-[var(--primary-color)]"
@click="redirect({ url: '/app/pages/auth/login',param:{type:'mobile'}})">
@ -272,7 +272,7 @@ const oneClickLogin = (callback: any = null, data: any = null) => {
mask: true
});
loginLoading.value = true
if (!callback) {
callback = () => {
loginLoading.value = false

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col " :style="themeColor()" v-if="type">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>
@ -28,9 +28,12 @@
class="!bg-transparent" :disabled="real_name_input" fontSize="26rpx"
placeholderClass="!text-[var(--text-color-light9)] text-[26rpx]">
<template #suffix>
<!-- #ifndef APP-PLUS -->
<view @click="changePassword" v-if="formData.password">
<u-icon :name="isPassword?'eye-off':'eye-fill'" color="#b9b9b9" size="20"></u-icon>
<u-icon name="eye-off" color="#b9b9b9" size="20" v-if="isPassword"></u-icon>
<u-icon name="eye-fill" color="#b9b9b9" size="20" v-else></u-icon>
</view>
<!-- #endif -->
</template>
</u-input>
</u-form-item>
@ -163,7 +166,7 @@ onLoad(async(option: any) => {
// #ifdef MP-WEIXIN
uni.getStorageSync('openid') && (Object.assign(formData, { weapp_openid: uni.getStorageSync('openid') }))
// #endif
if (option.type) {
if (option.type == 'mobile') {
if (configStore.login.is_mobile) {
@ -205,6 +208,12 @@ onLoad(async(option: any) => {
isShowQuickLogin.value = false;
}
// #endif
// #ifdef APP-PLUS
if (systemStore.appConfig.wechat_app_id) {
isShowQuickLogin.value = true;
}
// #endif
})
const formData = reactive({
@ -300,6 +309,7 @@ const handleLogin = () => {
}
const toLink = () => {
// #ifndef APP-PLUS
const pages = getCurrentPages(); //
if (pages.length > 1) {
const currentPage = pages[pages.length - 2].route;
@ -314,7 +324,17 @@ const toLink = () => {
} else {
redirect({ url: '/app/pages/auth/index', mode: 'redirectTo' })
}
// #endif
// #ifdef APP-PLUS
useLogin().getAuthCode({
successCallback: () => {
if (!getToken()) {
redirect({ url: '/app/pages/auth/bind', param: { 'register_type': 'wechat' }, mode: 'redirectTo' })
}
}
})
// #endif
}
const toResetpwd = () =>{

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()" v-if="type">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>

View File

@ -1,6 +1,6 @@
<template>
<view class="w-screen h-screen flex flex-col" :style="themeColor()">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view :style="{'height':headerHeight}">
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
</view>

View File

@ -2,7 +2,7 @@
<view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_money.png') + ') left bottom / cover no-repeat'}" class="pb-[194rpx] overflow-hidden">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view>
@ -37,8 +37,8 @@
<text class="nc-iconfont nc-icon-jichuxinxiV6xx text-[26rpx] "></text>
</view>
</view>
</view>
<view class="card-template sidebar-margin mb-[var(--top-m)]" v-if="friendsInfo.config.pay_info_switch">
<template v-if="JSON.stringify(friendsInfo.trade_info) !== '[]' && friendsInfo.trade_info.item_list.length">
@ -52,11 +52,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between" :class="{' mb-[34rpx]': (index + 1) != friendsInfo.trade_info.length }">
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<template #error>
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
</template>
</u--image>
</up-image>
</view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view>

View File

@ -2,7 +2,7 @@
<view :style="themeColor()">
<view class="bg-[var(--page-bg-color)] min-h-screen overflow-hidden" v-if="Object.keys(friendsInfo).length && !loading">
<view :style="{background: 'url(' + img('static/resource/images/app/friendpay_bg.png') + ') left bottom /100% no-repeat'}" class="pb-[168rpx] overflow-hidden">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<view class="sticky top-0 left-0 right-0 z-100">
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
</view>
@ -45,11 +45,11 @@
<view class="border-0 border-solid border-b-[1rpx] border-[#f6f6f6] mb-[20rpx]">
<view v-for="(item, index) in friendsInfo.trade_info.item_list" class="flex justify-between mb-[30rpx]">
<view class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden flex-shrink-0">
<u--image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<up-image class="overflow-hidden" radius="var(--goods-rounded-big)" width="170rpx" height="170rpx" :src="img(item.item_image ? item.item_image : '')" model="aspectFill">
<template #error>
<image class="w-[170rpx] h-[170rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill" />
</template>
</u--image>
</up-image>
</view>
<view class="ml-[20rpx] flex flex-1 flex-col justify-between">
<view>

View File

@ -19,7 +19,10 @@
<!-- 小程序隐私协议 -->
<wx-privacy-popup ref="wxPrivacyPopupRef"></wx-privacy-popup>
<!-- #endif -->
<!-- 版本更新弹窗 -->
<!-- #ifdef APP -->
<update-version ref="updateVersionRef"></update-version>
<!-- #endif -->
</view>
</template>
@ -29,6 +32,8 @@ import { useDiy } from '@/hooks/useDiy'
import { redirect } from '@/utils/common';
import { useShare } from '@/hooks/useShare'
import diyGroup from '@/addon/components/diy/group/index.vue'
import updateVersion from '@/components/update-version/update-version.vue'
import useSystemStore from '@/stores/system';
const { setShare } = useShare()
@ -42,6 +47,11 @@ const diyGroupRef = ref(null)
const wxPrivacyPopupRef: any = ref(null)
const collectTipRef: any = ref(null)
const updateVersionRef: any = ref(null)
const systemStore = useSystemStore();
const versionInfo = systemStore.versionInfo
//
diy.onLoad();
@ -52,8 +62,17 @@ diy.onShow((data: any) => {
// title: diyData.title
// })
} else if (data.page) {
//
redirect({ url: data.page, mode: 'reLaunch' })
// -- app
if(versionInfo && versionInfo.is_forced_upgrade){
nextTick(() => {
if(updateVersionRef.value){
updateVersionRef.value.open()
}
})
}else{
//
redirect({ url: data.page, mode: 'reLaunch' })
}
}
let share = data.share ? JSON.parse(data.share) : null;
setShare(share);

View File

@ -80,8 +80,10 @@ import { redirect } from '@/utils/common'
import { t } from '@/locale'
import { addAddress, editAddress, getAddressInfo } from '@/app/api/member'
import manifestJson from '@/manifest.json'
import { getAddressByLatlng } from '@/app/api/system'
import { getAddressByLatlng,getAreatree } from '@/app/api/system'
import useSystemStore from '@/stores/system';
const systemStore = useSystemStore();
const formData: any = ref({
id: 0,
name: '',
@ -133,6 +135,7 @@ onLoad((data: any) => {
if (selectAddress) {
addressType.value = selectAddress.delivery == 'express' ? 'address' : 'locationAddress';
}
getAreatreeFn()
// #ifdef MP
nextTick(() => {
if (wxPrivacyPopupRef.value) wxPrivacyPopupRef.value.proactive();
@ -256,8 +259,13 @@ const save = () => {
//
const chooseLocation = () => {
// #ifdef MP
let latitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.latitude : '';
let longitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.longitude : '';
// #ifndef H5
uni.chooseLocation({
latitude,
longitude,
success: (res) => {
res.latitude && (formData.value.lat = res.latitude)
res.longitude && (formData.value.lng = res.longitude)
@ -286,7 +294,7 @@ const chooseLocation = () => {
});
// #endif
// #ifdef H5
// #ifndef H5
const urlencode = formData.value;
uni.setStorageSync('addressInfo', urlencode);
let backurl = location.origin + location.pathname + '?source=' + source.value;
@ -308,7 +316,6 @@ const getAddress = (latlng: any) => {
formData.value.address_name = formData.value.full_address.replace(/-/g, '');
formData.value.area = (res.data.province + res.data.city + res.data.district) || res.data.full_address;
formData.value.province_id = res.data.province_id != undefined ? res.data.province_id : 0;
formData.value.city_id = res.data.city_id != undefined ? res.data.city_id : 0;
formData.value.district_id = res.data.district_id != undefined ? res.data.district_id : 0;
@ -330,20 +337,46 @@ const getQueryVariable = (variable: any) => {
}
return false;
}
const areaTree = ref([]); //
const getAreatreeFn = () => {
getAreatree(3).then(res => {
areaTree.value = res.data
}).catch()
}
// idid
const findIdByNameAndParentId = (list, name, parentId = 0) => {
for (const item of list) {
if (item.name === name && item.pid === parentId) {
return item.id;
}
if (item.child && item.child.length > 0) {
const childId = findIdByNameAndParentId(item.child, name, item.id);
if (childId) {
return childId;
}
}
}
return '';
};
const loadingchoosegAddress = ref(false)
const choosegAddress = () =>{
loadingchoosegAddress.value = true
uni.chooseAddress({
success(res) {
console.log(7744)
loadingchoosegAddress.value = false
// --
// const formattedAddress = `${res.userName}-${res.telNumber}-${res.provinceName}${res.cityName}${res.countyName}${res.detailInfoNew || res.detailInfo}`;
console.log(res)
formData.value.name = res.userName
formData.value.mobile = res.telNumber
formData.value.area = res.provinceName + res.cityName + res.countyName
formData.value.address = res.detailInfo
formData.value.name = res.userName
formData.value.mobile = res.telNumber
formData.value.area = res.provinceName + res.cityName + res.countyName
formData.value.address = res.detailInfo
// id
formData.value.province_id = findIdByNameAndParentId(areaTree.value, res.provinceName);
// idid
formData.value.city_id = findIdByNameAndParentId(areaTree.value, res.cityName, formData.value.province_id);
// idid
formData.value.district_id = findIdByNameAndParentId(areaTree.value, res.countyName, formData.value.cityId);
},
fail(err) {
loadingchoosegAddress.value = false

View File

@ -2,7 +2,7 @@
<view class="min-h-[100vh] !bg-[var(--page-bg-color)]" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[190rpx] text-[#fff] w-full" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" class="top-header" />
<!-- #endif -->
<view class="leading-[38rpx] text-[28rpx] pl-[60rpx] pt-[100rpx]">{{ t('accountBalance') }}</view>
@ -153,6 +153,9 @@ const mescrollTop = computed(() => {
return '718rpx'
}
} else {
// #ifdef APP-PLUS
return (pxToRpx(Number(systemStore.systemInfo.statusBarHeight)) + pxToRpx(8) + 632) + 'rpx'
// #endif
if (Object.keys(systemStore.menuButtonInfo).length) {
return (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + pxToRpx(8) + 632) + 'rpx'
} else {

View File

@ -2,7 +2,7 @@
<view class="bg-[var(--page-bg-color)] min-h-[100vh] w-full" :style="themeColor()" v-if="memberStore.info">
<view class="fixed w-full z-2 !bg-[var(--page-bg-color)]">
<view class="pb-[272rpx]" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" class="top-header" />
<!-- #endif -->
</view>

View File

@ -45,6 +45,9 @@ onMounted(() => {
try {
comp.field.value = JSON.parse(item.field_value)
if (comp.componentName=="FormNumber" || comp.componentName=="FormIdentity") {
comp.field.value =String(item.field_value)
}
} catch (error) {
comp.field.value = item.field_value
}

View File

@ -2,7 +2,7 @@
<view :style="themeColor()" class="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden">
<loading-page :loading="loading && memberInfo"></loading-page>
<view v-if="!loading && memberInfo && list && list.length" class="min-h-[100vh] overflow-hidden flex flex-col" :style="{backgroundColor: currLevelInfo.level_style.bg_color }">
<!-- #ifdef MP -->
<!-- #ifdef MP || APP-PLUS -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" />
<!-- #endif -->
<view>
@ -111,7 +111,7 @@
</view>
</view>
<view v-if="!loading && (!list || !list.length)">
<!-- #ifdef MP -->
<!-- #ifdef MP || APP-PLUS -->
<top-tabbar :data="topTabbarDataEmpty" />
<!-- #endif -->
<view class="empty-page" >
@ -119,7 +119,7 @@
<text class="desc">暂无会员等级</text>
</view>
</view>
</view>
</template>

View File

@ -95,6 +95,9 @@ diy.onLoad((data: any) => {
if(formDetailData[item.id]){
try {
item.field.value = JSON.parse(formDetailData[item.id])
if (item.componentName=="FormNumber" || item.componentName=="FormIdentity") {
item.field.value = String(formDetailData[item.id])
}
} catch (e) {
item.field.value = formDetailData[item.id]
}

View File

@ -3,7 +3,7 @@
<template v-if="!loading">
<view class="w-full bg-[var(--page-bg-color)]">
<view class="pb-[210rpx] relative" :style="headerStyle">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="param" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif -->
<view class="text-[70rpx] leading-[90rpx] text-[#fff] pl-[60rpx] font-500 pt-[77rpx] price-font">{{ pointInfo.point || 0 }}</view>
@ -124,7 +124,10 @@ const headerStyle = computed(() => {
}
})
const topStyle = computed(() => {
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx;' : '50rpx'
let style = Object.keys(systemStore.menuButtonInfo).length ? (pxToRpx(Number(systemStore.menuButtonInfo.height)) + pxToRpx(systemStore.menuButtonInfo.top) + 50) + 'rpx' : '50rpx'
// #ifdef APP-PLUS
style = '120rpx'
// #endif
return style
})

View File

@ -3,7 +3,7 @@
<view class="min-h-screen overflow-hidden" v-if="Object.values(info).length" :class="{ 'bg-[#F6F6F6]' : info && info.is_use }">
<view v-if="info.is_use">
<view class="sigin-header">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifndef H5 -->
<view v-if="info.rule_explain" class="side-tab" :style="{top: topStyle}" @click="signPopup = true">
<text class="nc-iconfont nc-icon-a-meiriqiandaoV6xx-36 icon"></text>
<text class="desc">签到规则</text>
@ -154,7 +154,7 @@
</view>
</view>
<view class="h-[100vh] w-[100vw] flex justify-center items-center" v-else>
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<top-tabbar :data="topTabbarData" :scrollBool="topTabarObj.getScrollBool()" class="top-header" />
<!-- #endif -->
<u-empty text="签到未开启" width="347rpx" height="265rpx" :icon="img('static/resource/images/system/empty.png')" />
@ -470,11 +470,22 @@ let topTabbarData = topTabarObj.setTopTabbarParam({ title: '我的签到' })
/********* 自定义头部 - end ***********/
const headStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 382 + 'rpx;'
// #endif
// #ifdef APP-PLUS
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 382 + 'rpx'
// #endif
return style
})
const topStyle = computed(() => {
// #ifdef MP
let style = pxToRpx(Number(systemStore.menuButtonInfo.height) + systemStore.menuButtonInfo.top + 8) + 50 + 'rpx;'
// #endif
// #ifdef APP-PLUS
let style = pxToRpx(systemStore.systemInfo.statusBarHeight + 8) + 50 + 'rpx'
// #endif
console.log(style)
return style
})
</script>

View File

@ -5,6 +5,14 @@
<u-cell :title="t('personalSettings')" :is-link="true" url="/app/pages/member/personal"></u-cell>
<u-cell :title="t('switchLang')" :is-link="true" :value="lang" @click="langSheetShow = true"></u-cell>
<u-cell :title="t('version')" :value="version"></u-cell>
<!-- #ifdef APP -->
<u-cell :title="'检测到新版本' + versionInfo.version_name" :is-link="true" @click="checkUpdate" v-if="versionInfo">
<template #value>
<text class="u-slot-value flex items-center">立即升级<text class="ml-[5px] w-[10rpx] h-[10rpx] bg-[red] rounded-[10rpx]"></text></text>
</template>
</u-cell>
<u-cell title="版本更新" :is-link="true" value="检查版本更新" @click="checkUpdate" v-else/>
<!-- #endif -->
</u-cell-group>
</view>
<view class="mb-[var(--top-m)] sidebar-margin card-template !py-[20rpx]">
@ -19,6 +27,10 @@
<u-action-sheet :actions="langList" :show="langSheetShow" :closeOnClickOverlay="true"
:safeAreaInsetBottom="true"
@close="langSheetShow = false" @select="switchLang"></u-action-sheet>
<!-- 版本更新弹窗 -->
<!-- #ifdef APP -->
<!-- <update-version ref="updateVersionRef"></update-version> -->
<!-- #endif -->
</view>
</template>
@ -26,10 +38,30 @@
import { ref, reactive, computed } from 'vue'
import useMemberStore from '@/stores/member'
import { t, language } from '@/locale'
import updateVersion from '@/components/update-version/update-version.vue'
import useSystemStore from '@/stores/system';
const memberStore = useMemberStore()
const systemStore = useSystemStore();
const versionInfo = computed(() => {
return systemStore.versionInfo
})
const version = ref(import.meta.env.VITE_APP_VERSION)
// #ifdef APP
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
//
version.value = inf.version
})
// #endif
// #ifndef APP
//app
const systemInfo = uni.getSystemInfoSync();
version.value = systemInfo.appVersion;
// #endif
const updateVersionRef: any = ref(null)
/**
* 支持的语言列表
@ -52,6 +84,18 @@ const lang = computed(() => {
const switchLang = (lang) => {
language.loadAllLocaleMessages('app', lang.value)
}
const checkUpdate = ()=>{
if(versionInfo.value && versionInfo.value.version_name){
systemStore.showUpdateVersion()
// uni.$emit('showUpdateVersion',{})
}else{
uni.showToast({
title: '当前版本已是最新版本!',
icon: 'none'
});
}
}
</script>
<style lang="scss" scoped>

View File

@ -124,7 +124,7 @@ const checkIsVerifier = () => {
}
const scanCode = () => {
// #ifdef MP
// #ifndef H5
uni.scanCode({
onlyFromCamera: true,
success: res => {

View File

@ -21,11 +21,11 @@
<text class="text-[#303133] text-[26rpx] font-400 nc-iconfont nc-icon-fuzhiV6xx1 ml-[11rpx]" @click.stop="copy(item.code)"></text>
</view>
<view class="flex flex-1 mb-2" v-for="(dataItem,dataIndex) in item.value.list" :key="dataIndex">
<u--image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
<up-image width="130rpx" height="130rpx" :radius="'var(--goods-rounded-big)'" :src="img(dataItem.cover ? dataItem.cover : '')" mode="aspectFill">
<template #error>
<image class="w-[130rpx] h-[130rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"/>
</template>
</u--image>
</up-image>
<view class="flex flex-col flex-1 ml-[20rpx] py-[4rpx]">
<view class="max-w-[490rpx] leading-[1.3] truncate text-[28rpx] text-[#303133]">{{ dataItem.name }}</view>
<view class="mt-[14rpx] truncate text-[24rpx] text-[var(--text-color-light9)] max-w-[490rpx] " v-if="item.sub_name">{{ item.sub_name }}</view>

View File

@ -17,8 +17,8 @@
</view>
<view class="flex-1 pr-[10rpx]" v-else></view>
</view>
<scroll-view scroll-y="true" class="h-[50vh]" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
<view class="flex p-[30rpx] pt-[0] text-sm font-500 h-[50vh]">
<scroll-view scroll-y="true" class="h-[700rpx] overflow-y-auto" :scroll-top="scrollTop" scroll-with-animation @touchmove.stop>
<view class="flex p-[30rpx] pt-[0] text-sm font-500">
<view v-if="areaList.province.length" class="flex-1 pr-[10rpx]" :style="{ opacity: currSelect == 'province' ? 1 : 0, pointerEvents: currSelect == 'province' ? 'auto' : 'none',height: currSelect == 'province' ? 'auto' : '0',overflow: currSelect == 'province' ? 'auto' : 'hidden' }">
<view v-for="(item, index) in areaList.province" :key="item.id" class="h-[80rpx] flex items-center" :class="{'text-[var(--primary-color)]': selected.province && selected.province.id == item.id }" @click="handleProvinceClick(item)">{{ item.name }}</view>
</view>
@ -138,6 +138,7 @@ watch(() => selected.city, (nval) => {
}
}
if (!data.length) {
currSelect.value = 'city'
emits('complete', selected)
show.value = false
}
@ -155,40 +156,40 @@ const emits = defineEmits(['complete'])
//
const scrollToSelected = (type: string, selectedIndex: number) => {
//
const itemHeight = 80 // 80rpx
const itemHeight = 40 // 80rpx
const targetScrollTop = Math.max(0, (selectedIndex - 2) * itemHeight) // 3
scrollTop.value = targetScrollTop
}
//
const resetScrollTop = () => {
scrollTop.value = 0
scrollTop.value = 1;
nextTick(() => {
scrollTop.value = 0;
});
}
//
// currSelectnextTickDOM
watch(() => currSelect.value, (newVal) => {
//
resetScrollTop()
setTimeout(() => {
if (newVal === 'province' && selected.province) {
const index = areaList.province.findIndex((item: any) => item.id === selected.province.id)
if (index >= 0) {
scrollToSelected('province', index)
}
} else if (newVal === 'city' && selected.city) {
const index = areaList.city.findIndex((item: any) => item.id === selected.city.id)
if (index >= 0) {
scrollToSelected('city', index)
}
} else if (newVal === 'district' && selected.district) {
const index = areaList.district.findIndex((item: any) => item.id === selected.district.id)
if (index >= 0) {
scrollToSelected('district', index)
}
}
}, 150)
})
nextTick(() => { // DOM
if (newVal === 'province' && selected.province) {
const index = areaList.province.findIndex((item: any) => item.id === selected.province.id);
if (index >= 0) {
scrollToSelected('province', index); //
}
} else if (newVal === 'city' && selected.city) {
const index = areaList.city.findIndex((item: any) => item.id === selected.city.id);
if (index >= 0) {
scrollToSelected('city', index); //
}
} else if (newVal === 'district' && selected.district) {
const index = areaList.district.findIndex((item: any) => item.id === selected.district.id);
if (index >= 0) {
scrollToSelected('district', index); //
}
}
});
}, { immediate: true }); // immediate
/**
* 监听区县变更
@ -204,7 +205,6 @@ watch(() => selected.district, (nval) => {
//
const handleProvinceClick = (item: any) => {
selected.province = item
//
nextTick(() => {
resetScrollTop()
})
@ -213,7 +213,6 @@ const handleProvinceClick = (item: any) => {
//
const handleCityClick = (item: any) => {
selected.city = item
//
nextTick(() => {
resetScrollTop()
})

View File

@ -3,12 +3,12 @@
<view class="carousel-track" :style="trackStyle">
<view
v-for="(item, index) in displayList"
:key="item.key"
:key="item.key"
class="carousel-item"
:style="itemStyle(index)"
:class="{ 'scale-in': item.isNew }"
>
<u--image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
<up-image width="30rpx" height="30rpx" radius="15rpx" :src="img(item.src)" mode="aspectFill">
<template #error>
<image
class="w-[30rpx] h-[30rpx] rounded-full"
@ -16,7 +16,7 @@
mode="aspectFill"
/>
</template>
</u--image>
</up-image>
</view>
</view>
</view>
@ -39,9 +39,9 @@ const EASING = 'cubic-bezier(0.25, 0.1, 0.25, 1)'; // 平滑曲线
const slideDistance = ITEM_WIDTH - OVERLAP;
//
const displayList = ref<{
src: string;
isNew: boolean;
const displayList = ref<{
src: string;
isNew: boolean;
key: string; // keyv-for
}[]>([]);
let currentIndex = 0;
@ -82,8 +82,8 @@ const itemStyle = (index: number) => ({
*/
const trackStyle = computed(() => ({
transform: `translateX(${trackOffset.value}rpx)`,
transition: isTransitioning.value
? `transform ${ANIMATION_DURATION}ms ${EASING}`
transition: isTransitioning.value
? `transform ${ANIMATION_DURATION}ms ${EASING}`
: 'none',
display: 'flex',
//
@ -179,7 +179,7 @@ onUnmounted(() => {
position: relative;
flex-shrink: 0;
&:not(:first-child) {
margin-left: -10rpx; //
margin-left: -15rpx; //
}
transform: scale(0); //
}
@ -188,4 +188,4 @@ onUnmounted(() => {
.carousel-item.scale-in {
transform: scale(0.9);
}
</style>
</style>

View File

@ -20,13 +20,13 @@
</button>
</u-form-item>
<u-form-item :label=" t('nickname')" prop="nickname" :border-bottom="true">
<input type="nickname" v-model="formData.nickname" :placeholder="t('nicknamePlaceholder')" placeholderClass="text-[28rpx]" class="text-[28rpx]" @blur="bindNickname" @click="checkAuth($event, 'nickname')"/>
<input type="nickname" v-model="formData.nickname" :placeholder="t('nicknamePlaceholder')" placeholderClass="text-[28rpx]" placeholder-style="color: #999;" class="text-[28rpx]" @blur="bindNickname" @click="checkAuth($event, 'nickname')"/>
</u-form-item>
<u-form-item :label="t('mobile')" prop="mobile" :border-bottom="true" v-if="isBindMobile || config.login.is_bind_mobile">
<input type="mobile" v-model="formData.mobile" :disabled="true" v-if="formData.mobile">
<input type="mobile" v-model="formData.mobile" :disabled="true" v-if="formData.mobile" placeholderClass="text-[28rpx]" class="text-[28rpx]">
<template v-else>
<u-button v-if="info" :customStyle="{border:'none',color: 'var(--primary-color)',width:'140rpx', textAlign:'left',margin:'0rpx'}" :text="t('getMobile')" open-type="getPhoneNumber" @getphonenumber="memberStore.bindMobile"></u-button>
<u-button v-else :customStyle="{border:'none',color: 'var(--primary-color)',width:'140rpx', textAlign:'left',margin:'0rpx'}" :text="t('getMobile')" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber"></u-button>
<u-button v-if="info" :customStyle="{border:'none',color: '#999',width:'140rpx', textAlign:'left',margin:'0rpx'}" :text="t('getMobile')" open-type="getPhoneNumber" @getphonenumber="memberStore.bindMobile" class="text-[28rpx]"></u-button>
<u-button v-else :customStyle="{border:'none',color: '#999',width:'140rpx', textAlign:'left',margin:'0rpx'}" :text="t('getMobile')" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber" class="text-[28rpx]"></u-button>
</template>
</u-form-item>
</view>

View File

@ -1,25 +1,29 @@
<template>
<view class="music-container" :class="{ playing: isPlaying }" :style="musicStyle">
<view class="music-control" :style="defaultStyleCss" @click="togglePlay">
<view class="music-disc " :class="{ rotate: isPlaying }">
<image v-if="discImage" class="disc-image" :src="discImage" mode="aspectFill"></image>
<block v-else>
<text class="default-style iconfont iconyinfuV6mm" v-if="isPlaying"></text>
<text class="default-style disable iconfont iconyinfuV6mm" v-else></text>
</block>
</view>
<view class="music-icon">
<text v-if="isPlaying" class="iconfont icon-pause"></text>
<text v-else class="iconfont icon-play"></text>
</view>
<view class="music-container" :class="{ playing: isPlaying }" :style="musicStyle">
<!-- 增加透明遮罩层用于后台返回时捕获用户交互 -->
<view class="interactive-mask" v-if="needUserInteraction" @click="handleInteraction"></view>
<view class="music-control" :style="defaultStyleCss" @click="togglePlay">
<view class="music-disc " :class="{ rotate: isPlaying }">
<image v-if="discImage" class="disc-image" :src="discImage" mode="aspectFill"></image>
<block v-else>
<text class="default-style iconfont iconyinfuV6mm" v-if="isPlaying"></text>
<text class="default-style disable iconfont iconyinfuV6mm" v-else></text>
</block>
</view>
<view class="music-icon">
<text v-if="isPlaying" class="iconfont icon-pause"></text>
<text v-else class="iconfont icon-play"></text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue';
import { img } from '@/utils/common';
//
//
const props = defineProps({
//
src: {
@ -54,189 +58,253 @@ const props = defineProps({
default: 20
}
});
//
const defaultDiscImage = img('/addon/seckill/goods/music1.png');
const defaultDiscImage2 = img('/addon/seckill/goods/music2.png');
//
const isPlaying = ref(false);
//
//
const isPlaying = ref(false); //
const userWantsPlay = ref(false); //
const lastPlayPosition = ref(0); //
const needUserInteraction = ref(false); //
let audioContext: any = null;
//
const createAudioContext = () => {
console.log('创建音频实例,音乐地址:', props.src);
let currentPage: any = null;
let backgroundTimer: NodeJS.Timeout | null = null;
// #ifdef MP-WEIXIN
audioContext = uni.createInnerAudioContext();
// #endif
// #ifdef APP-PLUS || H5
audioContext = uni.createInnerAudioContext();
// #endif
if (!audioContext) {
console.error('无法创建音频实例');
return;
}
audioContext.src = props.src;
audioContext.loop = props.loop;
//
audioContext.onPlay(() => {
console.log('音乐开始播放');
isPlaying.value = true;
});
//
audioContext.onPause(() => {
console.log('音乐暂停播放');
isPlaying.value = false;
});
//
audioContext.onStop(() => {
console.log('音乐停止播放');
isPlaying.value = false;
});
//
audioContext.onEnded(() => {
console.log('音乐播放结束');
isPlaying.value = false;
});
//
audioContext.onError((res: any) => {
console.error('音频播放错误:', res.errMsg || res);
isPlaying.value = false;
});
//
if (props.autoplay && props.src) {
// #ifdef H5
// H5
console.log('H5环境不自动播放音乐');
isPlaying.value = false;
// #endif
// #ifndef H5
// H5
console.log('准备自动播放音乐');
//
setTimeout(() => {
playMusic();
}, 300);
// #endif
}
};
//
const defaultStyleCss = computed(() => {
let style = '';
style += `background:${props.defaultBgColor};`;
if(props.defaultTextColor){ style += `color:${props.defaultTextColor};`; }
if(props.defaultRounded){
style += `border-border-top-left-radius:${props.defaultRounded * 2}rpx;`;
style += `border-border-bottom-left-radius:${props.defaultRounded * 2}rpx;`;
}
return style;
});
//
const playMusic = () => {
if (!audioContext || !props.src) {
console.error('无法播放音乐audioContext或src为空');
return;
}
console.log('尝试播放音乐:', props.src);
//
audioContext.play();
//
isPlaying.value = true;
//
// #ifdef MP-WEIXIN
try {
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.platform === 'ios') {
// iOS
uni.showToast({
title: '音乐播放中...',
icon: 'none',
duration: 1000
});
let style = '';
style += `background:${props.defaultBgColor};`;
if (props.defaultTextColor) style += `color:${props.defaultTextColor};`;
if (props.defaultRounded) {
style += `border-top-left-radius:${props.defaultRounded * 2}rpx;`;
style += `border-bottom-left-radius:${props.defaultRounded * 2}rpx;`;
}
} catch (error) {
console.error('获取系统信息失败:', error);
}
// #endif
};
//
const pauseMusic = () => {
if (!audioContext) return;
console.log('暂停音乐');
audioContext.pause();
isPlaying.value = false;
};
//
const togglePlay = () => {
console.log('切换播放状态,当前状态:', isPlaying.value);
if (isPlaying.value) {
pauseMusic();
} else {
playMusic();
}
};
//
watch(() => props.src, (newSrc) => {
console.log('音乐地址变化:', newSrc);
if (!audioContext) return;
const wasPlaying = isPlaying.value;
//
audioContext.src = newSrc;
//
if (wasPlaying && newSrc) {
playMusic();
}
return style;
});
//
watch(() => props.autoplay, (newAutoplay) => {
console.log('自动播放状态变化:', newAutoplay);
if (newAutoplay && audioContext && !isPlaying.value && props.src) {
playMusic();
}
});
//
onMounted(() => {
console.log('音乐组件挂载,初始化音频');
createAudioContext();
});
//
onBeforeUnmount(() => {
console.log('音乐组件卸载,释放资源');
if (audioContext) {
//
if (isPlaying.value) {
audioContext.stop();
//
const createAudioContext = () => {
audioContext = uni.createInnerAudioContext();
if (!audioContext) {
console.error('无法创建音频实例');
return;
}
//
audioContext.destroy();
audioContext = null;
}
audioContext.src = props.src;
audioContext.loop = props.loop;
//
audioContext.onPlay(() => {
console.log('音乐开始播放');
isPlaying.value = true;
needUserInteraction.value = false; //
});
//
audioContext.onPause(() => {
console.log('音乐暂停播放');
lastPlayPosition.value = audioContext.currentTime;
isPlaying.value = false;
});
//
audioContext.onStop(() => {
console.log('音乐停止播放');
lastPlayPosition.value = 0;
isPlaying.value = false;
});
//
audioContext.onEnded(() => {
console.log('音乐播放结束');
lastPlayPosition.value = 0;
isPlaying.value = false;
userWantsPlay.value = false;
});
//
audioContext.onError((res: any) => {
console.error('音频播放错误:', res.errMsg || res);
isPlaying.value = false;
// 1007iOS
if (res.errCode === 1007 || res.errMsg?.includes('user interaction')) {
needUserInteraction.value = true; //
}
//
if (userWantsPlay.value) {
setTimeout(() => playMusic(), 1000);
}
});
//
if (props.autoplay && props.src) {
/* #ifdef H5 */
console.log('H5环境不自动播放音乐');
isPlaying.value = false;
userWantsPlay.value = false;
/* #endif */
/* #ifndef H5 */
console.log('准备自动播放音乐');
userWantsPlay.value = true;
setTimeout(() => playMusic(), 300);
/* #endif */
}
};
//
const playMusic = () => {
if (!audioContext || !props.src) {
console.error('无法播放音乐audioContext或src为空');
return;
}
console.log('尝试播放音乐,进度:', lastPlayPosition.value);
//
if (lastPlayPosition.value > 0) {
audioContext.seek(lastPlayPosition.value);
}
//
const playPromise = audioContext.play();
// /Promise
if (playPromise && playPromise.catch) {
playPromise.catch((err: any) => {
console.warn('播放被系统拦截,需要用户交互:', err);
needUserInteraction.value = true; //
});
}
isPlaying.value = true;
userWantsPlay.value = true;
};
//
const pauseMusic = () => {
if (!audioContext) return;
audioContext.pause();
isPlaying.value = false;
userWantsPlay.value = false;
needUserInteraction.value = false;
};
//
const togglePlay = () => {
if (isPlaying.value) {
pauseMusic();
} else {
playMusic();
}
};
//
const handleInteraction = () => {
console.log('用户交互激活播放');
needUserInteraction.value = false;
if (userWantsPlay.value) {
playMusic();
}
};
//
const initPageLifecycle = () => {
currentPage = getCurrentPages()[getCurrentPages().length - 1];
const originalOnShow = currentPage.onShow;
const originalOnHide = currentPage.onHide;
const originalOnUnload = currentPage.onUnload;
//
currentPage.onHide = function (...args: any[]) {
originalOnHide && originalOnHide.apply(this, args);
console.log('页面切入后台');
//
backgroundTimer = setTimeout(() => {
// 3
if (userWantsPlay.value) {
needUserInteraction.value = true;
}
}, 3000);
};
//
currentPage.onShow = function (...args: any[]) {
originalOnShow && originalOnShow.apply(this, args);
console.log('页面返回前台');
//
if (backgroundTimer) {
clearTimeout(backgroundTimer);
backgroundTimer = null;
}
//
if (userWantsPlay.value && audioContext && props.src) {
//
setTimeout(() => {
playMusic();
}, 300);
}
};
//
currentPage.onUnload = function (...args: any[]) {
originalOnUnload && originalOnUnload.apply(this, args);
if (backgroundTimer) {
clearTimeout(backgroundTimer);
}
};
};
//
watch(() => props.src, (newSrc) => {
if (!audioContext) return;
const wasPlaying = userWantsPlay.value;
audioContext.src = newSrc;
lastPlayPosition.value = 0;
if (wasPlaying && newSrc) {
playMusic();
}
});
//
watch(() => props.autoplay, (newAutoplay) => {
if (newAutoplay && audioContext && !isPlaying.value && props.src) {
userWantsPlay.value = true;
playMusic();
}
});
//
onMounted(() => {
createAudioContext();
initPageLifecycle();
});
//
onBeforeUnmount(() => {
//
if (currentPage) {
const originalOnShow = currentPage.onShow;
const originalOnHide = currentPage.onHide;
const originalOnUnload = currentPage.onUnload;
currentPage.onShow = originalOnShow;
currentPage.onHide = originalOnHide;
currentPage.onUnload = originalOnUnload;
}
//
if (audioContext) {
audioContext.stop();
audioContext.destroy();
audioContext = null;
}
if (backgroundTimer) {
clearTimeout(backgroundTimer);
}
});
const musicStyle = computed(() => {
@ -244,112 +312,125 @@ const musicStyle = computed(() => {
'--color': `${props.defaultTextColor}`
}
})
// 使
//
defineExpose({
play: playMusic,
pause: pauseMusic,
toggle: togglePlay,
isPlaying: () => isPlaying.value
play: playMusic,
pause: pauseMusic,
toggle: togglePlay,
isPlaying: () => isPlaying.value
});
</script>
<style lang="scss" scoped>
.music-container {
position: fixed;
right: 0;
top: calc(120rpx + var(--status-bar-height));
z-index: 999;
z-index: 999;
//
.interactive-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 10; //
background: transparent; //
}
.music-control {
width: 80rpx;
height: 80rpx;
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
border-top-left-radius: 20rpx;
border-bottom-left-radius: 20rpx;
z-index: 1; //
}
.music-disc {
transition: all 0.3s;
&.rotate {
animation: rotate 5s linear infinite;
animation-play-state: running;
}
.disc-image {
width: 60rpx;
height: 60rpx;
}
}
.music-icon {
position: absolute;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 40rpx;
color: #ffffff;
text-shadow: 0 0 5rpx rgba(0, 0, 0, 0.5);
}
}
}
.music-control {
width: 80rpx;
height: 80rpx;
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 平台适配 */
/* #ifdef MP-WEIXIN */
.music-container {
z-index: 900;
}
/* #endif */
/* #ifdef APP-PLUS */
.music-container {
z-index: 999;
}
/* #endif */
/* #ifdef H5 */
.music-container {
position: fixed;
z-index: 999;
}
/* #endif */
.default-style{
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
border-top-left-radius: 20rpx;
border-bottom-left-radius: 20rpx;
}
.music-disc {
transition: all 0.3s;
width: 60rpx;
height: 60rpx;
font-size: 34rpx;
box-sizing: border-box;
&.rotate {
animation: rotate 5s linear infinite;
animation-play-state: running;
}
.disc-image {
width: 60rpx;
height: 60rpx;
// background-color: #ffffff;
&.disable:after{
content: '';
position: absolute;
left: 0;
height: 50rpx;
width: 2rpx;
background: var(--color);
left: 50%;
transform: translateX(-50%) rotate(-45deg);
}
}
.music-icon {
position: absolute;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 40rpx;
color: #ffffff;
text-shadow: 0 0 5rpx rgba(0, 0, 0, 0.5);
}
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 适配不同平台 */
/* #ifdef MP-WEIXIN */
.music-container {
z-index: 900;
}
/* #endif */
/* #ifdef APP-PLUS */
.music-container {
z-index: 999;
}
/* #endif */
/* #ifdef H5 */
.music-container {
position: fixed;
z-index: 999;
}
/* #endif */
.default-style{
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
font-size: 34rpx;
box-sizing: border-box;
&.disable:after{
content: '';
position: absolute;
left: 0;
height: 50rpx;
width: 2rpx;
background: var(--color);
left: 50%;
transform: translateX(-50%) rotate(-45deg);
}
}
</style>
</style>

View File

@ -58,7 +58,7 @@ const siteInfo: any = computed(() => {
const popupShow = ref(false);
const contactService = () => {
// #ifdef H5
// #ifndef MP-WEIXIN
popupShow.value = true;
// #endif
}

View File

@ -145,6 +145,18 @@ const confirmPay = () => {
location.href = res.data.url
}
// #endif
// #ifndef H5
uni.requestPayment({
provider: 'alipay',
...res.data,
success: (res: any) => {
toPayResult()
},
fail: (res: any) => {
loading.value = false
}
})
// #endif
break;
default:
if (res.data.url) {

View File

@ -4,7 +4,7 @@
<u-popup :show="sharePopupShow" type="bottom" @close="sharePopupClose" overlayOpacity="0.8">
<view @touchmove.prevent.stop>
<view class="share-content">
<!-- #ifdef MP || APP-PLUS -->
<!-- #ifdef MP -->
<view class="share-box">
<button class="share-btn" :plain="true" open-type="share">
<view class="text-[#07c160] iconfont iconweixin11"></view>
@ -21,6 +21,22 @@
</button>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareSession">
<view class="text-[#07c160] iconfont iconweixin11"></view>
<text>分享给好友</text>
</button>
</view>
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareWechatMoments">
<image :src="img('static/resource/images/app/wechat_moments.png')"></image>
<text>分享到朋友圈</text>
</button>
</view>
<!-- #endif -->
</view>
<view class="share-footer" @click="sharePopupClose">
<text class="text-[#333]">取消分享</text>
@ -46,6 +62,7 @@
import { ref } from 'vue';
import { img, copy } from '@/utils/common';
import useSystemStore from "@/stores/system";
import { useShare } from '@/hooks/useShare'
const props = defineProps({
copyUrl: { // "/wap/addon/shop_fenxiao/pages/goods"
@ -106,6 +123,16 @@ const sharePopupClose = () => {
emits('close');
}
// #ifdef APP-PLUS
const shareSession = () => {
useShare().onShareAppMessage();
}
const shareWechatMoments = () => {
useShare().onShareTimeline();
}
// #endif
defineExpose({
openShare
})

View File

@ -8,7 +8,7 @@
<image v-if="isPosterImg" class="poster-img" :src="img(poster)" mode="aspectFit" :show-menu-by-longpress="true"/>
</view>
<view class="share-content">
<!-- #ifdef MP || APP-PLUS -->
<!-- #ifdef MP -->
<view class="share-box">
<button class="share-btn" :plain="true" open-type="share">
<view class="text-[#07c160] iconfont iconweixin11"></view>
@ -32,6 +32,29 @@
</button>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareSession">
<view class="text-[#07c160] iconfont iconweixin11"></view>
<text>分享给好友</text>
</button>
</view>
<view class="share-box">
<button class="share-btn" :plain="true" @click="shareWechatMoments">
<image :src="img('static/resource/images/app/wechat_moments.png')"></image>
<text>分享到朋友圈</text>
</button>
</view>
<view class="share-box">
<button class="share-btn" :plain="true" @click="savePoster()">
<view class="text-[#07c160] iconfont iconpengyouquan"></view>
<text>保存海报</text>
</button>
</view>
<!-- #endif -->
</view>
<view class="share-footer" @click="sharePopupClose">
<text>取消分享</text>
@ -58,6 +81,7 @@ import { ref } from 'vue';
import { img, copy } from '@/utils/common';
import { getPoster } from '@/app/api/system'
import useSystemStore from "@/stores/system";
import { useShare } from '@/hooks/useShare'
const props = defineProps({
posterId: {
@ -203,6 +227,16 @@ const savePoster = () => {
}
// #endif
// #ifdef APP-PLUS
const shareSession = () => {
useShare().onShareAppMessage();
}
const shareWechatMoments = () => {
useShare().onShareTimeline();
}
// #endif
const shareTop: any = ref(0)
/************ 获取微信头部-start ****************/
const systemStore = useSystemStore()
@ -264,6 +298,11 @@ defineExpose({
color: #333;
}
}
image {
width: 80rpx;
height: 80rpx;
}
.iconfont {
font-size: 80rpx;

View File

@ -107,10 +107,11 @@ const topStatusBarData = computed(() => {
//
const navbarInnerStyle = computed(() => {
let style = '';
if (props.isBack) {
if (isBackShow) {
style += 'padding-left: 30rpx;';//30=> 44=> 10=>maring
if (topStatusBarData.value.style == 'style-1') //padding
style += 'padding-right:' + (40 + 30 + 10) + 'rpx;'; //30=> 44=> 10=>maring
// style += 'padding-right:' + (40 + 30 + 10) + 'rpx;'; //30=> 44=> 10=>maring
style += 'padding-right:' + (40 + 30) + 'rpx;'; //30=> 44=> 10=>maring
} else {
if (topStatusBarData.value.style == 'style-1') //padding
style += 'padding-right: 30rpx;'; //
@ -122,6 +123,9 @@ const navbarInnerStyle = computed(() => {
style += 'padding-top:' + systemStore.menuButtonInfo.top + 'px;';
style += 'padding-bottom: 8px;';
// #endif
// #ifdef APP-PLUS
style += 'padding-top:' + systemStore.systemInfo.statusBarHeight + 'px;';
// #endif
return style;
})
@ -208,7 +212,13 @@ const goBack = () => {
// +right
const capsuleWidth = computed(() => {
let width = `calc(100vw - ${ systemStore.menuButtonInfo.right }px + ${ systemStore.menuButtonInfo.width }px + 10px)`;
let width = '0px'
// #ifdef MP
width = `calc(100vw - ${ systemStore.menuButtonInfo.right }px + ${ systemStore.menuButtonInfo.width }px + 10px)`;
// #endif
// #ifdef APP-PLUS
width = '10px'
// #endif
return width;
})
@ -358,6 +368,9 @@ defineExpose({
&.style-3 {
.content-wrap {
// #ifdef APP-PLUS
padding-bottom: 20rpx;
// #endif
.title-wrap {
height: 46rpx;
max-width: 300rpx;

View File

@ -0,0 +1,281 @@
<template>
<view @touchmove.prevent.stop class="update-version">
<u-popup :show="show" @close="closeFn" mode="center" :round="10" :closeOnClickOverlay="!editionForce">
<view class="update-version-wrap flex flex-col" :style="{'background-image': 'url('+ img('static/resource/images/version_update_bg.png') +')'}">
<view class="flex flex-col justify-center mx-[10rpx]">
<view class="text-[50rpx] font-bold">
发现新版本
</view>
<view class="text-[24rpx] mt-[12rpx] self-start rounded-bl-[14rpx] rounded-tr-[14rpx] py-[4rpx] px-[16rpx] text-[#FA0F1A] bg-[#FFD3D4]">
{{ versionInfo.version_name }}
</view>
</view>
<scroll-view scroll-y="true" class="mt-[40rpx] mb-[20rpx] mx-[10rpx] max-h-[290rpx]">
<view class="text-[26rpx] text-[#333] text-with-newline">
{{versionInfo.version_desc}}
</view>
</scroll-view>
<view class="flex gap-[20rpx] mt-[auto]">
<view v-if="!editionForce" class="rounded-[60rpx] h-[76rpx] w-[240rpx] flex items-center justify-center bg-[#F5F5F5] text-[28rpx] text-[#666]" @click="closeFn" :disabled="downloading">下次更新</view>
<view class="rounded-[60rpx] h-[76rpx] w-[240rpx] flex items-center justify-center active-btn text-[#fff] text-[28rpx] text-[#999]" @click="confirm" :disabled="downloading">
<text v-if="!downloading">下载更新</text>
<text v-else>下载中({{downloadProgress}}%)</text>
</view>
</view>
<!-- 下载进度条 -->
<view class="mt-[30rpx] mx-[10rpx]" v-if="downloading">
<view class="w-full h-[10rpx] bg-[#E0E0E0] rounded-full overflow-hidden">
<view class="h-full bg-[#F11C0C] rounded-full" :style="{ width: downloadProgress + '%' }" :class="downloading ? 'downloading-animation' : ''"></view>
</view>
</view>
</view>
<view v-if="!editionForce" class="nc-iconfont nc-icon-cuohaoV6xx1 text-[#fff] text-center mt-[40rpx] text-[44rpx]" @click="closeFn"></view>
</u-popup>
</view>
</template>
<script setup lang="ts">
import { ref, version } from 'vue'
import {onBackPress, onHide} from "@dcloudio/uni-app";
import useSystemStore from '@/stores/system';
import { img, getUrl } from '@/utils/common';
const systemStore = useSystemStore();
const show = ref(false)
const downloading = ref(false)
const downloadProgress = ref(0)
const versionInfo = ref({})
const editionForce = ref(0)
const open = () => {
show.value = true
}
const closeFn = () => {
if (!show.value) return
//
if (!downloading.value) {
const lock = uni.getStorageSync('update_version_close_lock') || {}
lock[versionInfo.value.version_code] = true
uni.setStorageSync('update_version_close_lock', lock)
show.value = false
systemStore.updateVersionPopup = false
}else{
uni.showToast({
title: '下载过程中不允许关闭',
icon: 'none'
});
}
}
const initFn = ()=>{
useSystemStore().getVersionInfoFn(async (res) => {
versionInfo.value = res
versionInfo.value.version_desc = versionInfo.value.version_desc.replace(/\\n/g, '\n')
editionForce.value = Number(res.is_forced_upgrade)
})
}
initFn()
onBackPress(()=>{
//
if (editionForce.value) {
return true;
}
})
onHide(() => {
editionForce.value = 0;
show.value = false
})
const confirm = () => {
if(downloading.value) return false
if(versionInfo.value.upgrade_type == 'hot'){
uni.downloadFile({
url: versionInfo.value.package_path,
success: res => {
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath, {
force: true //truefalse
},
function() {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,请重启应用',
showCancel: false,
success: function(res) {
if (res.confirm) {
// console.log('');
plus.runtime.restart()
}
}
});
// console.log('install success...');
},
function(e) {
console.error('install fail...');
}
);
}
}
});
}else{
//apk .apk
if (versionInfo.value.upgrade_type == 'app' && versionInfo.value.package_path.includes('.apk')) {
editionForce.value = 0;
// #ifdef APP-PLUS
plus.runtime.openURL(getUrl(versionInfo.value.package_path));
// #endif
show.value = false
/* downloading.value = true
const downloadTask = plus.downloader.createDownload(getUrl(versionInfo.value.package_path), {
filename: '_downloads/' // downloads
}, function(d, status) {
console.log('整包下载 - d',d,d.filename)
console.log('整包下载 - status',status)
if (status === 200) {
// APK
plus.runtime.openURL(d.filename, function(error) {
console.error('打开文件失败:- error' + error);
console.error('打开文件失败:' + error.message);
uni.showToast({
title: '无法打开此文件',
icon: 'none'
});
})
} else {
console.log('APK下载失败: ' + status);
}
downloading.value = false
show.value = false
downloadProgress.value = 0
})
//
downloadTask.addEventListener('statechanged', function(task, status) {
if (task.state === 3) { // 3
//
const progress = (task.downloadedSize / task.totalSize) * 100
downloadProgress.value = progress.toFixed(0)
console.log(`下载进度: ${progress.toFixed(0)}%`);
}
});
downloadTask.start()
*/
} else if(versionInfo.value.upgrade_type == 'market') {
// h5
editionForce.value = 0; // */
// #ifdef APP-PLUS
plus.runtime.openURL(getUrl(versionInfo.value.package_path));
// #endif
show.value = false
}else {
downloading.value = true
//wgt .wgt
download();
}
}
}
const download = () =>{
// #ifdef APP-PLUS
const downloadTask = uni.downloadFile({
url: versionInfo.value.package_path,
success: res => {
uni.showToast({
title: `下载状态${res.statusCode}`,
icon:'none'
})
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath,
{
force: true //truefalse
},
function() {
if (versionInfo.value.upgrade_type == 'hot') {
plus.runtime.restart();
}
},
function(e) {
//wgt
editionForce.value = 0;
uni.showToast({
title:e.message,
icon:'none',
duration:2500
})
setTimeout(()=>{
show.value = false
},2000)
}
);
if (versionInfo.value.upgrade_type != 'hot') {
// app
editionForce.value = 0;
show.value = false
}
}
},
fail: (err) => {
console.error('下载失败:', err)
uni.showToast({
title: err,
icon: 'none'
})
downloading.value = false
downloadProgress.value = 0
}
});
//
downloadTask.onProgressUpdate(res => {
downloadProgress.value = res.progress;
});
// #endif
}
defineExpose({
open,
close: closeFn
})
</script>
<style lang="scss" scoped>
.update-version {
:deep(.u-popup__content) {
background-color: transparent;
.update-version-wrap{
background-size: contain;
width: 564rpx;
height: 686rpx;
padding: 130rpx 30rpx 40rpx;
box-sizing: border-box;
}
}
.active-btn{
background: linear-gradient( 90deg, #FBA165 0%, #F11C0C 100%);
transition: all 0.3s ease;
}
.active-btn:active {
opacity: 0.8;
}
.active-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.downloading-animation {
transition: width 0.3s ease;
}
}
.text-with-newline {
/* 关键样式:让\n生效 */
white-space: pre-line;
line-height: 1.6;
}
</style>

View File

@ -115,6 +115,11 @@ export function useDiy(params: any = {}) {
let sources = JSON.parse(requestData.value); // todo diy的结构应该后台处理好前端就不需要再转换了
diyData.global = sources.global;
diyData.global.id = requestData.id;
diyData.global.site_id = requestData.site_id;
diyData.global.type = requestData.type;
// 用于区分微页面之间弹窗的id
if (diyData.global.popWindow && diyData.global.popWindow.show) {
diyData.global.popWindow.id = requestData.id;
@ -147,7 +152,6 @@ export function useDiy(params: any = {}) {
uni.setNavigationBarTitle({
title: diyData.title
});
}
loading.value = false;
@ -190,9 +194,9 @@ export function useDiy(params: any = {}) {
}
// 监听页面卸载
const onUnloadLifeCycle = () => {
const onUnloadLifeCycle = (callback: any = null) => {
onUnload(() => {
if (callback) callback()
})
}

View File

@ -42,15 +42,18 @@ export function useLocation(isOpenLocation: any) {
}
// #endif
// #ifdef MP
// #ifndef H5
if (systemStore.mapConfig.is_open && !uni.getStorageSync('location_address')) {
uni.getLocation({
// #ifndef APP-PLUS
type: 'gcj02',
// #endif
success: res => {
let latlng = res.latitude + ',' + res.longitude; // 纬度浮点数范围为90 ~ -90经度浮点数范围为180 ~ -180
getAddressByLatlngFn(latlng);
},
fail: (res) => {
uni.showToast({ title: res.errMsg, icon: 'none', duration: 15000 })
systemStore.defaultPositionAddress = '定位失败';
if (res.errno) {
if (res.errno == 104) {
@ -145,7 +148,7 @@ export function useLocation(isOpenLocation: any) {
let latitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.latitude : '';
let longitude = systemStore.diyAddressInfo ? systemStore.diyAddressInfo.longitude : '';
// #ifdef MP
// #ifndef H5
uni.chooseLocation({
latitude,
longitude,

View File

@ -4,7 +4,9 @@ import {
updateWeappOpenid,
updateWechatOpenid,
wechatUser,
wechatUserLogin, updateWechatOpenidByH5
wechatUserLogin,
updateWechatOpenidByH5,
wxappLogin
} from '@/app/api/auth'
import { getWechatAuthCode } from '@/app/api/system'
import useMemberStore from '@/stores/member'
@ -21,8 +23,7 @@ export function useLogin() {
const config = useConfigStore()
const systemStore = useSystemStore()
// #ifdef MP-WEIXIN
// #ifdef MP
if (!uni.getStorageSync('autoLoginLock') && config.login.is_bind_mobile) {
uni.setStorageSync('isBindMobile', true) // 强制绑定手机号标识
}
@ -36,7 +37,7 @@ export function useLogin() {
// 如果只开启了账号密码登录,就不需要跳转到登录中间页了,直接进入普通账号密码登录页面
// #ifdef MP-WEIXIN
// #ifdef MP
if (config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile && !config.login.is_auth_register) {
@ -67,6 +68,16 @@ export function useLogin() {
}
}
// #endif
// #ifdef APP-PLUS
if (config.login.is_username && !config.login.is_mobile) {
redirect({ url: '/app/pages/auth/login', param: { type: 'username' } })
} else if (systemStore.initStatus == 'finish' && !config.login.is_username && !config.login.is_mobile) {
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
} else {
redirect({ url: '/app/pages/auth/index' })
}
// #endif
})
}
@ -103,9 +114,9 @@ export function useLogin() {
mobile: params.mobile,
mobile_code: params.mobile_code
};
uni.getStorageSync('pid') && (Object.assign(obj, { pid: uni.getStorageSync('pid') }))
// #ifdef MP-WEIXIN
uni.getStorageSync('pid') && (Object.assign(obj, { pid: uni.getStorageSync('pid') }))
weappLogin(obj).then((res: any) => {
if (res.data.token) {
useMemberStore().setToken(res.data.token, () => {
@ -152,7 +163,6 @@ export function useLogin() {
// #endif
// #ifdef H5
uni.getStorageSync('pid') && (Object.assign(obj, { pid: uni.getStorageSync('pid') }))
wechatUser(obj).then((user_res: any) => {
if (user_res.data) {
wechatUserLogin(user_res.data).then((res: any) => {
@ -221,7 +231,27 @@ export function useLogin() {
}
})
// #endif
// #ifdef APP-PLUS
wxappLogin(obj).then((res) => {
if (res.data.token) {
useMemberStore().setToken(res.data.token, () => {
handleLoginBack();
})
} else {
uni.setStorageSync('openid', res.data.openid)
uni.setStorageSync('unionid', res.data.unionid)
if(res.data.nickname) uni.setStorageSync('nickname', res.data.nickname)
if(res.data.avatar) uni.setStorageSync('avatar', res.data.avatar)
}
if (params.successCallback) params.successCallback(res.data)
}).catch((err) => {
uni.showToast({ title: err.msg, icon: 'none' })
})
// #endif
}
/**
* openid
* @param code
@ -337,6 +367,20 @@ export function useLogin() {
})
// #endif
// #ifdef APP-PLUS
uni.login({
"provider": "weixin",
"onlyAuthorize": true, // 微信登录仅请求授权认证
success: function(event){
const { code } = event
authLogin({ code })
},
fail: function (err) {
uni.showToast({ title: err.msg, icon: 'none' })
}
})
// #endif
}
const updateWechatOpenidForH5 = (wx_openid: any) => {

View File

@ -10,6 +10,8 @@ import wechat from '@/utils/wechat'
export const useShare = () => {
let wechatOptions: any = {};
let weappOptions: any = {};
const systemStore = useSystemStore()
const wechatInit = () => {
if (!isWeixinBrowser()) return;
@ -40,53 +42,51 @@ export const useShare = () => {
}
const setShare = (options: any = {}) => {
console.log('setShare options',options)
if (currRoute() == '' || currRoute().indexOf('app/pages/index/close') != -1 || currRoute().indexOf('app/pages/index/nosite') != -1) return;
let queryStr = getQuery();
// #ifdef H5
let h5Link = location.origin + location.pathname + (queryStr.length > 0 ? '?' + queryStr.join('&') : '');
wechatOptions = {
link: h5Link
}
// #endif
// #ifdef APP-PLUS
let h5Link = systemStore.site.wap_url + currShareRoute().path + (queryStr.length > 0 ? '?' + queryStr.join('&') : '');
wechatOptions = {
link: h5Link
}
// #endif
// #ifdef MP-WEIXIN
weappOptions = {
path: '/' + currRoute() + (queryStr.length > 0 ? '?' + queryStr.join('&') : ''),
query: queryStr.join('&'),
}
// #endif
if (options && Object.keys(options).length) {
if (options.wechat) {
// #ifdef H5
wechatOptions.title = options.wechat.title || ''
wechatOptions.link = options.wechat.link || h5Link
wechatOptions.desc = options.wechat.desc || ''
wechatOptions.imgUrl = options.wechat.url ? img(options.wechat.url) : ''
// wechatOptions.success = options.wechat.callback || null;
// useSystemStore().shareCallback = options.wechat.callback || null;
// #ifdef H5
wechatShare()
// #endif
}
if (options.weapp) {
// #ifdef MP-WEIXIN
weappOptions.title = options.weapp.title || ''
if (options.weapp.path) weappOptions.path = options.weapp.path
weappOptions.imageUrl = options.weapp.url ? img(options.weapp.url) : ''
useSystemStore().shareCallback = options.weapp.callback || null;
// #ifdef MP-WEIXIN
uni.setStorageSync('weappOptions', weappOptions)
// #endif
}
}
getShareInfo({
route: '/' + currRoute(),
@ -94,7 +94,6 @@ export const useShare = () => {
}).then((res: any) => {
let data = res.data;
// #ifdef H5
let wechat = data.wechat;
if (wechat) {
wechatOptions.title = wechat.title
@ -104,50 +103,120 @@ export const useShare = () => {
wechatOptions.title = document.title;
wechatOptions.desc = ''
}
// #ifdef H5
wechatShare()
// #endif
// #ifdef MP-WEIXIN
let weapp = data.weapp;
if (weapp) {
weappOptions.title = weapp.title
weappOptions.imageUrl = weapp.url ? img(weapp.url) : ''
}
// #endif
// #ifdef MP
if(!weappOptions.title && !weappOptions.imageUrl){
return;
uni.setStorageSync('weappOptions', {})
return;
}
uni.setStorageSync('weappOptions', weappOptions)
// #endif
useSystemStore().$patch((state) => {
state.shareOptions = {
wechatOptions,
weappOptions
}
})
})
useSystemStore().$patch((state) => {
state.shareOptions = {
wechatOptions,
weappOptions
}
})
}
// 小程序分享,分享给好友
const shareApp = (options = {}) => {
// #ifdef MP
return onShareAppMessage(() => {
let config: any = uni.getStorageSync('weappOptions')
if (!config) config = {}
if (useSystemStore().shareCallback) useSystemStore().shareCallback();
if (systemStore.shareCallback) systemStore.shareCallback();
return {
...config,
...options
}
})
// #endif
// #ifdef APP-PLUS
const weappOptions = systemStore.shareOptions.weappOptions
const wechatOptions = systemStore.shareOptions.wechatOptions
if (!weappOptions.title && !wechatOptions.title) return
const shareOptions: any = {}
if (weappOptions.title && systemStore.appConfig.weapp_original) {
shareOptions.type = 5
shareOptions.title = weappOptions.title
shareOptions.imageUrl = weappOptions.imageUrl
shareOptions.miniProgram = {
id: systemStore.appConfig.weapp_original,
path: weappOptions.path,
type: 0,
webUrl: wechatOptions.link
}
} else {
shareOptions.type = 0
shareOptions.href = wechatOptions.link
shareOptions.title = wechatOptions.title
shareOptions.summary = wechatOptions.desc
shareOptions.imageUrl = wechatOptions.imgUrl
}
uni.share({
provider: "weixin",
scene: "WXSceneSession",
success: () => {
if (systemStore.shareCallback) systemStore.shareCallback();
},
...shareOptions
});
// #endif
}
// 小程序分享,分享到朋友圈
const shareTime = (options = {}) => {
// #ifdef MP
return onShareTimeline(() => {
let config: any = uni.getStorageSync('weappOptions')
if (!config) config = {}
if (useSystemStore().shareCallback) useSystemStore().shareCallback();
if (systemStore.shareCallback) systemStore.shareCallback();
return {
...config,
...options
}
})
// #endif
// #ifdef APP-PLUS
const wechatOptions = systemStore.shareOptions.wechatOptions
if (wechatOptions.title) {
uni.share({
provider: "weixin",
scene: "WXSceneTimeline",
type: 0,
href: wechatOptions.link,
title: wechatOptions.title,
summary: wechatOptions.desc,
imageUrl: wechatOptions.imgUrl,
success: () => {
if (systemStore.shareCallback) systemStore.shareCallback();
}
});
}
// #endif
}
// 禁用当前页面的分享功能(同时支持小程序和公众号)

View File

@ -0,0 +1,25 @@
<template>
<slot />
<!-- #ifdef APP-PLUS -->
<update-version ref="updateVersionRef"/>
<!-- #endif -->
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import useSystemStore from '@/stores/system'
import updateVersion from '@/components/update-version/update-version.vue'
const systemStore = useSystemStore()
// #ifdef APP-PLUS
const updateVersionRef = ref(null)
watch(() => systemStore.updateVersionPopup, () => {
if (systemStore.updateVersionPopup) updateVersionRef.value?.open()
else updateVersionRef.value?.close()
})
// #endif
</script>
<style lang="scss">
</style>

View File

@ -10,6 +10,7 @@ const t = (message: string) => {
// #ifdef MP
route = '/' + (getCurrentInstance()?.root.ctx.$scope.__route__ || useSystemStore().currRoute)
// #endif
const file = language.getFileKey(route)
const key = `${ file.fileKey }.${ message }`
if (i18n.global.t(message) != message) return i18n.global.t(message)

View File

@ -14,7 +14,6 @@ class Language {
/**
*
* @param locale
* @param path
*/
public setI18nLanguage(locale: string, path: string = '') {
if (this.i18n.global.locale == locale) return
@ -45,43 +44,54 @@ class Language {
this.setI18nLanguage(locale, file)
return nextTick()
}
this.loadLocale.push(`${fileKey}.${locale}`)
// 引入语言包文件
// #ifdef APP-PLUS
const appLang = import.meta.glob('../app/locale/**/*.json', { eager: true })
const addonLang = import.meta.glob('../addon/**/locale/**/*.json', { eager: true })
const messages = route == 'app' ? appLang[`../app/locale/${locale}/${file}.json`] : addonLang[`../addon/${route}/locale/${locale}/${file}.json`]
// #endif
// #ifndef APP-PLUS
const messages = await import(route == 'app' ? `../${route}/locale/${locale}/${file}.json` : `../addon/${route}/locale/${locale}/${file}.json`)
// #endif
let data: Record<string, string> = {}
Object.keys(messages.default).forEach(key => {
data[`${fileKey}.${key}`] = messages.default[key]
})
this.i18n.global.mergeLocaleMessage(locale, data)
this.setI18nLanguage(locale, file)
this.loadLocale.push(`${fileKey}.${locale}`)
return nextTick()
} catch (e) {
// console.log(e)
this.setI18nLanguage(locale)
return nextTick()
}
}
public getFileKey = (path: string) => {
const pathArr = path.split('/')
let route = pathArr[1] == 'app' ? pathArr[1] : pathArr[2];
try {
const pathArr = path.split('/')
let route = pathArr[1] == 'app' ? pathArr[1] : pathArr[2];
let file = path == '/' ? 'pages.index.index' : path.replace('/', '').replaceAll('/', '.')
let file = path == '/' ? 'pages.index.index' : path.replace('/', '').replace(/\//g, ".")
// 如果是系统页面则移除“app.”
let fileKey = ''
if (route == 'app') {
fileKey = file.replace('app.', '')
file = file.replace('app.', '')
} else {
fileKey = file.replace(`addon.`, '')
file = file.replace(`addon.${route}.`, '')
// 如果是系统页面则移除“app.”
let fileKey = ''
if (route == 'app') {
fileKey = file.replace('app.', '')
file = file.replace('app.', '')
} else {
fileKey = file.replace(`addon.`, '')
file = file.replace(`addon.${route}.`, '')
}
return { file, fileKey, route }
} catch (e) {
return { file: 'pages.index.index', fileKey: 'pages.index.index', route: 'app' }
}
return { file, fileKey, route }
}
}

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { getInitInfo, getSiteInfo, getMemberMobileExist } from '@/app/api/system'
import { getInitInfo, getSiteInfo, getMemberMobileExist, getNewVersion } from '@/app/api/system'
import useConfigStore from '@/stores/config'
import useMemberStore from '@/stores/member'
import { isWeixinBrowser } from '@/utils/common'
@ -24,7 +24,17 @@ interface System {
topTabbarInfo: {
height: number
fullHeight: string
}
},
appConfig: {
wechat_app_id: string
weapp_original: string
},
shareOptions: {
wechatOptions: any
weappOptions: any
},
versionInfo: any,
updateVersionPopup: boolean
}
const useSystemStore = defineStore('system', {
@ -56,7 +66,17 @@ const useSystemStore = defineStore('system', {
topTabbarInfo: {
height: 0,
fullHeight: ''
}
},
appConfig: {
wechat_app_id: '',
weapp_original: ''
},
shareOptions: {
wechatOptions: {},
weappOptions: {}
},
versionInfo: null,
updateVersionPopup: false
}
},
actions: {
@ -115,6 +135,7 @@ const useSystemStore = defineStore('system', {
// 如果会员已存在则小程序端快捷登录时不再弹出授权弹框
uni.setStorageSync('member_exist', data.member_exist)
data.app_config && (this.appConfig = data.app_config)
this.initStatus = 'finish'; // 初始化完成
@ -127,6 +148,24 @@ const useSystemStore = defineStore('system', {
this.getMenuButtonInfoFn();
},
// 获取版本数据信息
getVersionInfoFn(callback: any = null) {
// #ifdef APP-PLUS
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
const platform = systemInfo.osName;
const version_code = systemInfo.appVersionCode
getNewVersion({
version_code,
platform
}).then((res: any) => {
if (res.data) {
this.versionInfo = res.data
if (callback) callback(res.data)
}
})
// #endif
},
// 如果已有手机号,就不弹出绑定手机号弹框
getMemberMobileExistFn() {
getMemberMobileExist({
@ -175,6 +214,9 @@ const useSystemStore = defineStore('system', {
this.topTabbarInfo = Object.assign({}, this.topTabbarInfo, data);
this.topTabbarInfo.height = this.topTabbarInfo.height || 0
this.topTabbarInfo.fullHeight = this.topTabbarInfo.height ? this.topTabbarInfo.height+'px' : 0
},
showUpdateVersion() {
this.updateVersionPopup = true
}
}
})

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3952239 */
src: url('//at.alicdn.com/t/c/font_3952239_5hb6cojxgdi.woff2?t=1755829954368') format('woff2'),
url('//at.alicdn.com/t/c/font_3952239_5hb6cojxgdi.woff?t=1755829954368') format('woff'),
url('//at.alicdn.com/t/c/font_3952239_5hb6cojxgdi.ttf?t=1755829954368') format('truetype');
src: url('https://at.alicdn.com/t/c/font_3952239_yxckpayhvp.woff2?t=1755857288182') format('woff2'),
url('https://at.alicdn.com/t/c/font_3952239_yxckpayhvp.woff?t=1755857288182') format('woff'),
url('https://at.alicdn.com/t/c/font_3952239_yxckpayhvp.ttf?t=1755857288182') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.iconjiahaoV6xx1:before {
content: "\e7cc";
}
.icona-xiangxiaV6xx1:before {
content: "\e79e";
}

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "nc-iconfont"; /* Project id 4567203 */
src: url('//at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.woff2?t=1750327830437') format('woff2'),
url('//at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.woff?t=1750327830437') format('woff'),
url('//at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.ttf?t=1750327830437') format('truetype');
src: url('https://at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.woff2?t=1750327830437') format('woff2'),
url('https://at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.woff?t=1750327830437') format('woff'),
url('https://at.alicdn.com/t/c/font_4567203_r3nkqy7l1w.ttf?t=1750327830437') format('truetype');
}
.nc-iconfont {

View File

@ -0,0 +1,97 @@
## 1.5.22023-09-08
优化文档
## 1.5.12023-05-26
优化文档
## 1.5.02023-05-23
优化文档
## 1.4.92023-05-23
文档新增后台版本管理示例图
## 1.4.82023-05-23
优化当前版本显示
## 1.4.72023-05-23
新增当前运行版本名称和新版本名称显示
## 1.4.62023-05-22
新增显示安装包大小
## 1.4.52023-04-27
优化页面不透明
## 1.4.42023-04-25
新增pages_init.json自动注册页面
## 1.4.32023-04-25
修改app下载链接
## 1.4.22023-04-25
优化
## 1.4.12023-04-15
优化bug
## 1.4.02023-04-14
删除无用代码
## 1.3.92023-04-14
优化
## 1.3.82023-04-03
优化文档
## 1.3.72023-03-23
优化文档
## 1.3.62023-03-23
优化文档
## 1.3.52023-03-08
新增常见问题
## 1.3.42023-03-07
解决应用切换到后台再次打开更新弹窗叠加多个的问题
## 1.3.32023-03-02
优化提示文档
## 1.3.22023-02-02
优化部分wgt包无法安装的提示
## 1.3.12023-01-12
修改示例下载文件地址
## 1.3.02022-11-17
兼容低版本安卓手机,用户拒绝安装后,去掉自动重启,优化体验
## 1.2.92022-11-14
优化插件
## 1.2.82022-11-14
优化整包更新用户体验
## 1.2.72022-11-14
修复apk整包更新时点击拒绝安装更新进度还在的bug
## 1.2.62022-10-17
优化问题汇总
## 1.2.52022-10-17
常见问题优化
## 1.2.42022-09-21
文档新增常见问题汇总,方便更快的解决问题
## 1.2.32022-09-21
文档新增常见问题汇总,方便更快的解决问题
## 1.2.22022-09-21
文档新增常见问题汇总,方便更快的解决问题
## 1.2.12022-09-21
文档新增常见问题汇总,方便更快的解决问题
## 1.2.02022-08-03
优化插件wgt升级重启整包升级不重启
## 1.1.92022-08-01
新增弹出一个合并页面路由的pages.json修改界面。插件使用者点击确认按钮即可完成插件页面向项目pages.json的注册。HBuilderX 3.5.0+支持
## 1.1.82022-07-25
1、静默更新后提示用户重启应用以解决样式错乱的问题
2、跳转应用市场下载后解决更新提示弹窗一直叠加的问题
## 1.1.72022-07-22
优化示例代码
## 1.1.62022-07-22
优化文档
## 1.1.52022-07-19
优化文档
## 1.1.42022-07-19
优化文档
## 1.1.32022-07-19
优化文档
## 1.1.22022-07-18
优化wgt更新文档
## 1.1.12022-07-17
新增wgt包静默更新
## 1.1.02022-05-17
优化readme文档
## 1.0.92022-05-14
优化
## 1.0.82022-05-05
修复图片不显示的bug
## 1.0.72022-01-19
1.0.7 优化readme文档
## 1.0.62022-01-19
正式支持uni_modules
## 1.0.52022-01-19
测试支持uni_models

View File

@ -0,0 +1,303 @@
<template>
<view class="update-mask flex-center">
<view class="content botton-radius">
<view class="content-top">
<view class="content-top-text">
<text class="">发现新版本 v{{data.edition_name}}</text>
<text class="version">当前版本{{version}}</text>
</view>
<image class="content-top" style="top: 0;" width="100%" height="100%" src="/uni_modules/rt-uni-update/static/bg_top.png"></image>
</view>
<view class="content-header"></view>
<view class="content-body">
<view class="title"><text>更新内容</text></view>
<view class="body">
<scroll-view class="box-des-scroll" scroll-y="true"><rich-text :nodes="data.describe"></rich-text></scroll-view>
</view>
<view class="footer flex-center">
<view class="progress-box flex-column" v-if="!updateBtn">
<progress class="progress" border-radius="35" :percent="percent" activeColor="#3DA7FF" show-info stroke-width="10" />
<!-- <u-line-progress :striped="true" :percent="percent" :striped-active="true"></u-line-progress> -->
<view><text class="fs24">正在下载请稍后 ({{downloadedSize}}/{{packageFileSize}}M)</text></view>
</view>
<button class="content-button" style="border: none;color: #fff;" plain @click="confirm" v-if="updateBtn">立即升级</button>
</view>
</view>
<image v-if="cancleBtn" class="close-img" src="/uni_modules/rt-uni-update/static/app_update_close.png" @click.stop="cancel"></image>
</view>
</view>
</template>
<script>
export default {
data() {
return {
version:'1.0.0',//(manifest)
percent: 0, //
updateBtn: true, //
cancleBtn: false, //
downloadedSize:0,//
packageFileSize:0,//
data: {
describe: '1. 修复已知问题<br>2. 优化用户体验',
edition_url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-6bef1fe3-e3e3-4909-9f0c-6ed9bd11c93b/aae2360a-6628-4c93-b873-ce1600b9a852.apk', //
edition_force: 1, // 0 1
package_type: 0 ,//0 1wgt
edition_name:'1.0.1' //
}
};
},
onHide() { //
// console.log('');
this.data.edition_force = 0;
uni.navigateBack({
delta: 1
})
},
onLoad({obj}) {
this.data = JSON.parse(obj);
if (this.data.edition_force == 0) {
this.cancleBtn = true;
}
plus.runtime.getProperty(plus.runtime.appid,(inf) => {
this.version = inf.version;
})
},
onBackPress() {
//
if (this.data.edition_force == 1) {
return true;
}
},
methods: {
cancel() {
//
uni.navigateBack({
delta: 1
});
},
confirm() {
if (this.data.package_type == 0) {
//apk .apk
if (this.data.edition_url.includes('.apk')) {
this.updateBtn = false;
this.cancleBtn = false;
this.download();
} else {
// h5
this.data.edition_force = 0; //
plus.runtime.openURL(this.data.edition_url);
uni.navigateBack({
delta: 1
});
}
} else {
this.updateBtn = false;
this.cancleBtn = false;
//wgt .wgt
this.download();
}
},
download() {
let package_type = this.data.package_type;
let that = this;
const downloadTask = uni.downloadFile({
url: this.data.edition_url,
success: res => {
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath,
{
force: true //truefalse
},
function() {
// console.log('success', success);
if (package_type == 1) {
plus.runtime.restart();
}
},
function(e) {
//wgt
that.data.edition_force = 0;
uni.showToast({
title:e.message,
icon:'none',
duration:2500
})
setTimeout(()=>{
uni.navigateBack()
},2000)
}
);
if (package_type == 0) {
// app
this.data.edition_force = 0;
uni.navigateBack();
}
}
}
});
//
downloadTask.onProgressUpdate(res => {
this.percent = res.progress;
this.downloadedSize = (res.totalBytesWritten / Math.pow(1024, 2)).toFixed(2);
this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);
});
}
}
};
</script>
<style>
page {
background: transparent;
}
.flex-center {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
}
.update-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.65);
}
.botton-radius {
border-bottom-left-radius: 30rpx;
border-bottom-right-radius: 30rpx;
}
.content {
position: relative;
top: 0;
width: 600rpx;
background-color: #fff;
box-sizing: border-box;
padding: 0 50rpx;
font-family: Source Han Sans CN;
}
.text {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
line-height: 200px;
text-align: center;
color: #ffffff;
}
.content-top {
position: absolute;
top: -195rpx;
left: 0;
width: 600rpx;
height: 270rpx;
}
.content-top-text {
font-size: 40rpx;
font-weight: bold;
color: #f8f8fa;
position: absolute;
top: 120rpx;
left: 50rpx;
z-index: 1;
display: flex;
flex-direction: column;
}
.content-header {
height: 70rpx;
}
.title {
font-size: 33rpx;
font-weight: bold;
color: #3da7ff;
line-height: 38px;
}
.footer {
height: 150rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.box-des-scroll {
box-sizing: border-box;
padding: 0 40rpx;
text-align: left;
}
.box-des {
font-size: 26rpx;
color: #000000;
line-height: 50rpx;
}
.progress-box {
width: 100%;
}
.progress {
width: 83%;
height: 40rpx;
border-radius: 35px;
}
.close-img {
width: 70rpx;
height: 70rpx;
z-index: 1000;
position: absolute;
bottom: -120rpx;
left: calc(50% - 70rpx / 2);
}
.content-button {
text-align: center;
flex: 1;
font-size: 30rpx;
font-weight: 400;
color: #ffffff;
border-radius: 40rpx;
margin: 0 18rpx;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(to right, #1785ff, #3da7ff);
}
.flex-column {
display: flex;
flex-direction: column;
align-items: center;
}
.fs24{
font-size: 24rpx;
}
.version{
font-size: 24rpx;
margin-top: 10rpx;
color: #eeeeee;
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,31 @@
export default function silenceUpdate(url) {
uni.downloadFile({
url,
success: res => {
if (res.statusCode === 200) {
plus.runtime.install(
res.tempFilePath, {
force: true //true表示强制安装不进行版本号的校验false则需要版本号校验
},
function() {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,请重启应用',
showCancel: false,
success: function(res) {
if (res.confirm) {
// console.log('用户点击确定');
plus.runtime.restart()
}
}
});
// console.log('install success...');
},
function(e) {
console.error('install fail...');
}
);
}
}
});
}

View File

@ -0,0 +1,80 @@
{
"id": "rt-uni-update",
"displayName": "app升级整包更新和热更新支持vue3 支持打开安卓、苹果市场wgt静默更新",
"version": "1.5.2",
"description": "app升级、整包更新和热更新组件 支持vue3 支持打开安卓、苹果应用市场支持wgt静默更新无感知支持覆盖原生tabar原生导航栏",
"keywords": [
"整包更新",
"热更新",
"vue3",
"静默更新",
"app更新升级"
],
"repository": "",
"engines": {
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,192 @@
## 整包更新和热更新组件 支持vue3 支持打开安卓、苹果应用市场支持wgt静默更新
- ui图是采用uniapp官方更新组件的ui如不满足需要可自行替换
- 一键式检查更新同时支持整包升级与wgt资源包更新 支持打开安卓自带的应用市场和苹果appstore
- 好看、实用、可自定义的客户端提示框
- 支持强制更新,无法退出
- 支持静默更新,下次启动后更新的内容自动生效
- 支持覆盖原生tabar原生导航栏
## 安装指引
1. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`选择要导入的项目点击确定建议使用uni_modules版本 非uni_modules版本不在维护有需要自行修改
2. 在`pages.json`中添加页面路径。注意一定不要设置为pages.json中第一项在1.1.9版本新增弹出一个合并页面路由的pages.json修改界面。点击确认按钮即可完成插件页面向项目pages.json的注册。HBuilderX 3.5.0+支持,无需手动添加)
```
"pages": [
// ……其他页面配置
{
"path": "uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
]
```
3. 查看显示效果 (注意:这里只是查看显示效果,具体代码需要按照下面的项目使用说明编写)
```
// App.vue的onShow中查看效果 如果无法跳转 请在`pages.json`中添加页面路径,参照第二步
uni.navigateTo({
url: '/uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update'
});
```
## 前言一般来说后台都需要有一个app的版本管理系统可参考下图
![app的版本管理系统](https://img-cdn-aliyun.dcloud.net.cn/stream/plugin_screens/d7898110-7905-11ec-a3c8-0f6ace22f6cc_3.png?image_process=quality,q_70/format,webp&v=1684809490)
![app的版本管理系统](https://img-cdn-aliyun.dcloud.net.cn/stream/plugin_screens/d7898110-7905-11ec-a3c8-0f6ace22f6cc_4.png?image_process=quality,q_70/format,webp&v=1684809494)
## 项目使用说明 最重要!!!
- 注意!!!后端返回数据要求 字段如下 (如果后端字段不一样,请在跳转更新页时手动赋值,示例见下面代码)
```
data:{
// 版本更新内容 支持<br>自动换行
describe: '1. 修复已知问题<br>
2. 优化用户体验',
edition_url: '', //apk、wgt包下载地址或者应用市场地址 安卓应用市场 market://details?id=xxxx 苹果store itms-apps://itunes.apple.com/cn/app/xxxxxx
edition_force: 0, //是否强制更新 0代表否 1代表是
package_type: 1, //0是整包升级apk或者appstore或者安卓应用市场 1是wgt升级
edition_issue:1, //是否发行 0否 1是 为了控制上架应用市场审核时不能弹出热更新框
edition_number:100, //版本号 最重要的manifest里的版本号 检查更新主要以服务器返回的edition_number版本号是否大于当前app的版本号来实现是否更新
edition_name:'1.0.0',// 版本名称 manifest里的版本名称
edition_silence:0, // 是否静默更新 0代表否 1代表是
}
// 如果后端返回的字段和上面不一致,请在前端手动赋值(示例)
data.edition_url = res.data.editionUrl
data.edition_force = res.data.editionForce
data.package_type = res.data.packageType
data.xxx = res.data.xxx
```
## 后端注意!!!
edition_number传这个参数是为了解决部分用户app长期不使用第一次打开服务器查到的版本是最新的是wgt包但是之前app有过整包更新如果直接更新最新wgt的话会出现以前的整包添加的原生模块或者安卓权限无法使用所以后端查询版本必须返回大于当前edition_number版本的最新的整包apk地址或者是应用市场地址如果没有大于edition_number的整包就返回最新的wgt包地址就行。
- 前端示例代码 或者根据实际业务修改 如果需要自动检测新版本建议写在App.vue的onShow中
```
import silenceUpdate from '@/uni_modules/rt-uni-update/js_sdk/silence-update.js' //引入静默更新
//#ifdef APP-PLUS
// 获取本地应用资源版本号
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
//获取服务器的版本号
uni.request({
url: 'http://127.0.0.1:8088/edition_manage/get_edition', //示例接口
data: {
edition_type: plus.runtime.appid,
version_type: uni.getSystemInfoSync().platform, //android或者ios
edition_number: inf.versionCode // 打包时manifest设置的版本号
},
success: (res) => {
//res.data.xxx根据后台返回的数据决定我这里后端返回的是data所以是res.data.data
//判断后台返回版本号是否大于当前应用版本号 && 是否发行 (上架应用市场时一定不能弹出更新提示)
if (Number(res.data.data.edition_number) > Number(inf.versionCode) && res
.data.data.edition_issue == 1) {
//如果是wgt升级并且是静默更新 (注意!!! 如果是手动检查新版本,就不用判断静默更新,请直接跳转更新页,不然点击检查新版本后会没反应)
if (res.data.data.package_type == 1 && res.data.data.edition_silence == 1) {
//调用静默更新方法 传入下载地址
silenceUpdate(res.data.data.edition_url)
} else {
//跳转更新页面 注意如果pages.json第一页的代码里有一打开就跳转其他页面的操作下面这行代码最好写在setTimeout里面设置延时3到5秒再执行
uni.navigateTo({
url: '/uni_modules/rt-uni-update/components/rt-uni-update/rt-uni-update?obj=' +
JSON.stringify(res.data.data)
});
}
} else {
// 如果是手动检查新版本 需开启以下注释
/* uni.showModal({
title: '提示',
content: '已是最新版本',
showCancel: false
}) */
}
}
})
});
//#endif
```
# 常见问题汇总!!!
# 热更新制作wgt包的方法1、修改manifest.json版本名称和版本号必须大于当前版本。2、点击菜单的发行——原生App-制作应用wgt包
# app上传地址个人建议开通unicloud的阿里云按量付费方便、便宜apk或者wgt包直接上传到云存储就行。
## 1、调试请打包自定义基座测试否则uni.getSystemInfoSync().platform获取到的可能不是android或者ios会导致无法跳转更新页
## 2、进度条不显示但可以正常安装原因99%的情况是因为下载链接为内网链接,内网链接无法监听下载进度,请更换为外网链接
## 3、进度条显示下载apk完成后安卓不会自动弹出安装页面原因可能是离线打包未添加安卓安装权限请添加以下权限或者使用云打包
```
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
```
```
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
```
## 4、在app.vue中无法跳转到更新页原因第一、在pages.json中忘记注册页面第二、如果已经注册过页面一般在app.vue或者首页中会有默认跳转所以影响到了跳转更新页解决办法修改跳转逻辑或者在跳转更新页时加setTimeout延时几秒在跳转
## 5、app内下载apk时会跳转外部下载原因安卓apk下载链接必须为.apk结尾如果不是.apk结尾就会跳转外部下载比如应用市场链接
## 6、热更新时wgt包可以下载但是无法安装控制台提示wgt/wgtu文件格式错误。解决方法下载地址必须为http://xxxxxx.wgt的格式就是链接必须以.wgt结尾。2、如果地址是http://xxxxxx.wgt格式请在浏览器打开这个下载地址如果无法自动下载一般可能都是后端下载权限的问题导致的
## 7、整包更新/热更新成功后还是一直弹更新弹窗原因是打wgt包时未修改manifest.json的版本号请修改版本号后上传服务器后重试。
## 8、苹果支持appstore链接和wgt更新不支持整包ipa更新。
## 9、wgt更新进度条100%苹果无法安装原因1、wgt包名不要设置为中文2、增加原生模块必须上传appstore不能热更新
## 10、不能热更新的有1、如果原项目没有nvue页面新增nvue后也必须整包更新2、增加推送、第三方登录、地图、视频播放、支付等模块或者其他安卓权限。3、修改启动图或者app图标
## 11、更新弹窗后面的页面一半儿白屏[官方的bug](https://ask.dcloud.net.cn/question/164141)
## 12、跳转更新页后无法获取参数可能是使用了uni-simple-router等第三方路由插件解决办法通过eventChannel.$emit等方式传参在插件里接收赋值
有鼓励,更有动力,如果您认为这个插件帮到了您的开发工作,麻烦给个五星好评鼓励一下,有能力的也可以小小赞赏一下,感谢支持。
<img src="https://mp-bed742be-5cd0-413d-b7a5-c1bdcda83cd2.cdn.bspapp.com/rookie-ui/34afc1f2862e7579c3cbdd33d23d0de.jpg" width="220" >
<img style="margin-left: 100px;" src="https://mp-bed742be-5cd0-413d-b7a5-c1bdcda83cd2.cdn.bspapp.com/rookie-ui/e14de964c6d89008035f651be6fa2c8.jpg" width="220" >
## 如有问题请加qq 965969604

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -206,7 +206,7 @@ export const currShareRoute = () => {
}
let currentRoute = pages[pages.length - 1].route //获取当前页面路由
// #ifdef H5
// #ifndef MP
let currentParam: any = pages[pages.length - 1].$page.options; //获取路由参数
// #endif
@ -296,6 +296,26 @@ export function img(path: string): string {
return isUrl(path) ? path : `${imgDomain}/${path}`
}
/**
*
* @param path
* @returns
*/
export function getUrl(path: string): string {
// #ifdef H5
let urlDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin
// #endif
// #ifndef H5
let urlDomain = import.meta.env.VITE_IMG_DOMAIN
// #endif
if (typeof path == 'string' && path.startsWith('/')) path = path.replace(/^\//, '')
if (typeof urlDomain == 'string' && urlDomain.endsWith('/')) urlDomain = urlDomain.slice(0, -1)
return isUrl(path) ? path : `${urlDomain}/${path}`
}
/**
*
*/
@ -407,7 +427,7 @@ export function timeTurnTimeStamp(dateStr: string) {
}
const trimmedDateStr = dateStr.trim();
// 定义支持的日期格式转换规则
const formatRules = [
// 'YYYY年M月D日' -> 'YYYY-MM-DD'
@ -448,7 +468,7 @@ export function timeTurnTimeStamp(dateStr: string) {
// 创建日期对象并验证
const date = new Date(normalizedDateStr);
// 检查日期是否有效
if (isNaN(date.getTime())) {
return null;
@ -788,3 +808,12 @@ export function rgbaToHex (r, g, b, a) {
let hex = "#" + componentToHex(rBlend) + componentToHex(gBlend) + componentToHex(bBlend)
return hex.toUpperCase()
}
// 获取 topFixedStatus 缓存名称
export function getTopFixedStatusName(data: any = {}) {
let name = 'topFixedStatus'
if (data.id) name += '_' + data.id
if (data.site_id) name += '_' + data.site_id
if (data.type) name += '_' + data.type
return name
}

View File

@ -14,7 +14,7 @@ export const redirectInterceptor = (route: { path: string, query: object }) => {
setThemeColor(route.path)
// 开发模式下如果未配置站点ID则跳转到开发环境配置页面
// #ifdef H5
// #ifndef MP
if (process.env.NODE_ENV == 'development') {
if ((getSiteId(import.meta.env.VITE_SITE_ID || uni.getStorageSync('wap_site_id')) === '') && route.path != '/app/pages/index/develop') {
redirect({ url: '/app/pages/index/develop', mode: 'reLaunch' })
@ -72,7 +72,7 @@ export const launchInterceptor = () => {
// 加载语言包
language.loadAllLocaleMessages('app', uni.getLocale())
// #ifdef H5
// #ifndef MP
language.loadAllLocaleMessages('addon', uni.getLocale())
// #endif

View File

@ -1,3 +1,4 @@
import { watch } from 'vue'
import { currRoute } from './common'
import { redirectInterceptor } from './interceptor'
import useConfigStore from "@/stores/config";
@ -49,16 +50,32 @@ export default {
},
onShareAppMessage() {
// #ifdef MP
return useShare().onShareAppMessage()
// #endif
},
onShareTimeline() {
// #ifdef MP
return useShare().onShareTimeline()
// #endif
},
methods: {
themeColor() {
const configStore = useConfigStore()
return configStore.getThemeColor();
}
},
onReady(){
const systemStore = useSystemStore()
// #ifdef APP-PLUS
watch(() => systemStore.versionInfo, () => {
const newVersion = useSystemStore().versionInfo
if (newVersion) {
const lock = uni.getStorageSync('update_version_close_lock') || {}
if (!lock[ newVersion.version_code ] || newVersion.is_forced_upgrade) useSystemStore().showUpdateVersion()
}
}, { immediate: true })
// #endif
}
});
},