This commit is contained in:
parent
9d8b85987b
commit
d61d8e67a1
23
api/auth.ts
23
api/auth.ts
|
|
@ -1,6 +1,6 @@
|
||||||
import { APP_CONFIG } from '@/config';
|
import { APP_CONFIG } from '@/config';
|
||||||
import { HttpStatusError, httpRequest, toFormUrlEncoded } from '@/utils/http';
|
import { HttpStatusError, httpRequest, toFormUrlEncoded } from '@/utils/http';
|
||||||
import { postForm } from '@/utils/request';
|
import { get, postForm } from '@/utils/request';
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
code: number | string;
|
code: number | string;
|
||||||
|
|
@ -58,6 +58,8 @@ export interface BindMobileResponse {
|
||||||
code: number | string;
|
code: number | string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
data?: {
|
data?: {
|
||||||
|
token?: string;
|
||||||
|
expires_in?: number;
|
||||||
code?: string;
|
code?: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
mobile?: string;
|
mobile?: string;
|
||||||
|
|
@ -76,3 +78,22 @@ export const bindMobile = (encryptedData: string) => {
|
||||||
platform: resolveLoginPlatform()
|
platform: resolveLoginPlatform()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface OpenInfoResult {
|
||||||
|
code: number | string;
|
||||||
|
msg?: string;
|
||||||
|
data?: {
|
||||||
|
user?: {
|
||||||
|
user_id?: number | string;
|
||||||
|
user_name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
};
|
||||||
|
open?: {
|
||||||
|
user_id?: number | string;
|
||||||
|
open_id?: number | string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前登录用户信息;有 user_id 视为已绑定过手机号/账号资料 */
|
||||||
|
export const fetchOpenInfo = () => get<OpenInfoResult>('user/v1/open.info');
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
// #ifdef MP-ALIPAY
|
// #ifdef MP-ALIPAY
|
||||||
const APP_ID = '123019';
|
// const APP_ID = '123019';
|
||||||
|
const APP_ID = '12317';
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
const APP_ID = '123018';
|
// const APP_ID = '123018';
|
||||||
|
const APP_ID = '12316';
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
// #ifndef MP-ALIPAY
|
// #ifndef MP-ALIPAY
|
||||||
const APP_ID = '123018';
|
// const APP_ID = '123018';
|
||||||
|
const APP_ID = '12316';
|
||||||
// #endif
|
// #endif
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
export const APP_CONFIG = {
|
export const APP_CONFIG = {
|
||||||
API_BASE_URL: 'https://dev.api.leapy.cn',
|
API_BASE_URL: 'https://api.leapy.cn',
|
||||||
APP_ID,
|
APP_ID,
|
||||||
RESOURCE_URL: 'https://resource.leapy.cn/staff/',
|
RESOURCE_URL: 'https://resource.leapy.cn/staff/',
|
||||||
PROPERTY_IMG_URL: 'https://resource.leapy.cn/',
|
PROPERTY_IMG_URL: 'https://resource.leapy.cn/',
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,14 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="records.length && (loading || finished)" class="records-footer">
|
<view v-if="records.length && (loading || finished)" class="records-footer">
|
||||||
<wd-loadmore :state="loadMoreState" />
|
<wd-loadmore v-if="loading" state="loading" />
|
||||||
|
<text v-else class="records-finished">没有更多了</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||||
import { platformScoreRecord } from '@/api/cashier';
|
import { platformScoreRecord } from '@/api/cashier';
|
||||||
import type { ScoreRecordItem } from '@/api/cashier';
|
import type { ScoreRecordItem } from '@/api/cashier';
|
||||||
|
|
@ -42,7 +43,6 @@ const loading = ref(false);
|
||||||
const finished = ref(false);
|
const finished = ref(false);
|
||||||
|
|
||||||
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
||||||
const loadMoreState = computed(() => (loading.value ? 'loading' : 'finished'));
|
|
||||||
|
|
||||||
const formatMoney = (value?: string | number) => {
|
const formatMoney = (value?: string | number) => {
|
||||||
const amount = Number(value || 0);
|
const amount = Number(value || 0);
|
||||||
|
|
@ -198,4 +198,12 @@ onReachBottom(() => {
|
||||||
.records-footer {
|
.records-footer {
|
||||||
padding: 12rpx 0 24rpx;
|
padding: 12rpx 0 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.records-finished {
|
||||||
|
display: block;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 24rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<view class="merchant-label">
|
<view class="merchant-label">
|
||||||
<view class="merchant-label-inner">
|
<view class="merchant-label-inner">
|
||||||
<text class="muted">向</text>
|
<text class="muted">向</text>
|
||||||
<text class="merchant-name">{{ merchant.name }}</text>
|
<text class="merchant-name" :style="{ fontSize: merchantNameFontSize }">{{ merchant.name }}</text>
|
||||||
<text class="muted">付款</text>
|
<text class="muted">付款</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -88,7 +88,6 @@
|
||||||
<view
|
<view
|
||||||
v-else
|
v-else
|
||||||
class="property-row"
|
class="property-row"
|
||||||
:class="{ 'property-row--disabled': alipayPropertyUnbound }"
|
|
||||||
hover-class="property-row-hover"
|
hover-class="property-row-hover"
|
||||||
@tap.stop="handlePropertyRow"
|
@tap.stop="handlePropertyRow"
|
||||||
>
|
>
|
||||||
|
|
@ -189,7 +188,7 @@ import { computed, ref } from 'vue';
|
||||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||||
import { bindMerchantByUrl, cancelTrade, changeMerchant, merchantListByUrl, supplierByUrl } from '@/api/cashier';
|
import { bindMerchantByUrl, cancelTrade, changeMerchant, merchantListByUrl, supplierByUrl } from '@/api/cashier';
|
||||||
import type { MerchantListItem } from '@/api/cashier';
|
import type { MerchantListItem } from '@/api/cashier';
|
||||||
import { bindMobile, resolveLoginPlatform } from '@/api/auth';
|
import { bindMobile, fetchOpenInfo, resolveLoginPlatform } from '@/api/auth';
|
||||||
import { postForm } from '@/utils/request';
|
import { postForm } from '@/utils/request';
|
||||||
import { DEFAULT_MERCHANT, APP_CONFIG, PROPERTY_APP } from '@/config';
|
import { DEFAULT_MERCHANT, APP_CONFIG, PROPERTY_APP } from '@/config';
|
||||||
import {
|
import {
|
||||||
|
|
@ -203,7 +202,7 @@ import {
|
||||||
resolveMerchantUrlFromScanResult,
|
resolveMerchantUrlFromScanResult,
|
||||||
syncActiveMerchantContext
|
syncActiveMerchantContext
|
||||||
} from '@/utils/merchant-context';
|
} from '@/utils/merchant-context';
|
||||||
import { encryptedStorage } from '@/utils/storage';
|
import { authStorage, encryptedStorage } from '@/utils/storage';
|
||||||
import { formatPaymentError, isPaymentCancelled, requestPlatformPayment } from '@/utils/wxpay';
|
import { formatPaymentError, isPaymentCancelled, requestPlatformPayment } from '@/utils/wxpay';
|
||||||
|
|
||||||
interface MerchantInfo {
|
interface MerchantInfo {
|
||||||
|
|
@ -247,11 +246,22 @@ const accumulationAmount = computed(() => {
|
||||||
});
|
});
|
||||||
const showBenefitTip = computed(() => Number(merchant.value.accumulationRate) > 0);
|
const showBenefitTip = computed(() => Number(merchant.value.accumulationRate) > 0);
|
||||||
const benefitTitle = computed(() => `预计获得${scoreName.value || '物业公积金'}`);
|
const benefitTitle = computed(() => `预计获得${scoreName.value || '物业公积金'}`);
|
||||||
|
/** 商户名越长字号越小,尽量单行展示「向 xxx 付款」 */
|
||||||
|
const merchantNameFontSize = computed(() => {
|
||||||
|
const len = String(merchant.value.name || '').trim().length;
|
||||||
|
if (len <= 6) return '34rpx';
|
||||||
|
if (len <= 10) return '30rpx';
|
||||||
|
if (len <= 14) return '26rpx';
|
||||||
|
if (len <= 18) return '24rpx';
|
||||||
|
return '22rpx';
|
||||||
|
});
|
||||||
const propertyRowText = computed(() => {
|
const propertyRowText = computed(() => {
|
||||||
if (propertyName.value) return propertyName.value;
|
if (propertyName.value) return propertyName.value;
|
||||||
if (propertyList.value.length) return '选择物业';
|
if (propertyList.value.length) return '选择物业';
|
||||||
if (IS_ALIPAY) {
|
if (IS_ALIPAY) {
|
||||||
if (alipayPropertyUnbound.value) return '暂未绑定,请去微信小程序绑定';
|
if (alipayPropertyUnbound.value) {
|
||||||
|
return `未绑定,微信搜「${PROPERTY_APP.WECHAT_NAME}」绑定房源`;
|
||||||
|
}
|
||||||
return '绑定手机号查看房源';
|
return '绑定手机号查看房源';
|
||||||
}
|
}
|
||||||
return '业主去绑定';
|
return '业主去绑定';
|
||||||
|
|
@ -260,7 +270,7 @@ const isPropertyActionText = computed(() => !propertyName.value);
|
||||||
const showAlipayPhoneAuthBtn = computed(
|
const showAlipayPhoneAuthBtn = computed(
|
||||||
() => IS_ALIPAY && !propertyName.value && !propertyList.value.length && !alipayPropertyUnbound.value
|
() => IS_ALIPAY && !propertyName.value && !propertyList.value.length && !alipayPropertyUnbound.value
|
||||||
);
|
);
|
||||||
const showPropertyArrow = computed(() => !IS_ALIPAY || !alipayPropertyUnbound.value);
|
const showPropertyArrow = computed(() => true);
|
||||||
const propertySheetTitle = computed(() => (propertyName.value ? '切换物业' : '选择物业'));
|
const propertySheetTitle = computed(() => (propertyName.value ? '切换物业' : '选择物业'));
|
||||||
|
|
||||||
interface PropertyBindingCache {
|
interface PropertyBindingCache {
|
||||||
|
|
@ -582,6 +592,32 @@ const resolvePropertyListFallback = async (merchantUrl: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝:无物业名/列表时,用 open.info 的 user_id 判断是否已绑过手机号。
|
||||||
|
* 有 user_id → 显示「未绑定,微信搜「乐享云…」绑定房源」;否则 → 「绑定手机号查看房源」。
|
||||||
|
*/
|
||||||
|
const resolveAlipayEmptyPropertyState = async (options: { assumeBoundIfEmpty?: boolean } = {}) => {
|
||||||
|
if (!IS_ALIPAY) return;
|
||||||
|
|
||||||
|
if (propertyName.value || propertyList.value.length) {
|
||||||
|
alipayPropertyUnbound.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetchOpenInfo();
|
||||||
|
const userId = response?.data?.user?.user_id ?? response?.data?.open?.user_id;
|
||||||
|
if (userId != null && String(userId).trim() !== '') {
|
||||||
|
alipayPropertyUnbound.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('open.info empty property state', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
alipayPropertyUnbound.value = !!options.assumeBoundIfEmpty;
|
||||||
|
};
|
||||||
|
|
||||||
const loadPropertyBinding = async (merchantUrl: string, options: { skipFallback?: boolean } = {}) => {
|
const loadPropertyBinding = async (merchantUrl: string, options: { skipFallback?: boolean } = {}) => {
|
||||||
const cacheKey = syncBindingCacheForUrl(merchantUrl);
|
const cacheKey = syncBindingCacheForUrl(merchantUrl);
|
||||||
const cached = encryptedStorage.get<PropertyBindingCache>(cacheKey);
|
const cached = encryptedStorage.get<PropertyBindingCache>(cacheKey);
|
||||||
|
|
@ -595,17 +631,20 @@ const loadPropertyBinding = async (merchantUrl: string, options: { skipFallback?
|
||||||
if (!options.skipFallback) {
|
if (!options.skipFallback) {
|
||||||
await resolvePropertyListFallback(merchantUrl);
|
await resolvePropertyListFallback(merchantUrl);
|
||||||
}
|
}
|
||||||
|
await resolveAlipayEmptyPropertyState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestBinding = buildBindingCache(response.data || {});
|
const latestBinding = buildBindingCache(response.data || {});
|
||||||
applyBindingInfo(latestBinding);
|
applyBindingInfo(latestBinding);
|
||||||
encryptedStorage.set(cacheKey, latestBinding, PROPERTY_CACHE_TTL_SECONDS);
|
encryptedStorage.set(cacheKey, latestBinding, PROPERTY_CACHE_TTL_SECONDS);
|
||||||
|
await resolveAlipayEmptyPropertyState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('bind merchant fallback cache', error);
|
console.log('bind merchant fallback cache', error);
|
||||||
if (!options.skipFallback) {
|
if (!options.skipFallback) {
|
||||||
await resolvePropertyListFallback(merchantUrl);
|
await resolvePropertyListFallback(merchantUrl);
|
||||||
}
|
}
|
||||||
|
await resolveAlipayEmptyPropertyState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -654,7 +693,7 @@ const deleteKey = () => {
|
||||||
const showAlipayUnboundTip = () => {
|
const showAlipayUnboundTip = () => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '暂未绑定房源',
|
title: '暂未绑定房源',
|
||||||
content: '您在微信小程序中暂无绑定房源,请前往微信小程序完成绑定后再使用。',
|
content: `请打开微信,搜索并进入「${PROPERTY_APP.WECHAT_NAME}」小程序,完成房源绑定后消费即可获得物业基金。`,
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: '知道了'
|
confirmText: '知道了'
|
||||||
});
|
});
|
||||||
|
|
@ -668,16 +707,17 @@ const refreshPropertyAfterPhoneBind = async () => {
|
||||||
propertyList.value = [];
|
propertyList.value = [];
|
||||||
encryptedStorage.remove(getPropertyBindingCacheKey(activeMerchantUrl.value));
|
encryptedStorage.remove(getPropertyBindingCacheKey(activeMerchantUrl.value));
|
||||||
await loadPropertyBinding(activeMerchantUrl.value);
|
await loadPropertyBinding(activeMerchantUrl.value);
|
||||||
|
// 刚绑完手机号仍无房源时,兜底按已绑定处理(open.info 偶发未带上 user_id)
|
||||||
|
if (!propertyName.value && !propertyList.value.length && !alipayPropertyUnbound.value) {
|
||||||
|
await resolveAlipayEmptyPropertyState({ assumeBoundIfEmpty: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (propertyName.value || propertyList.value.length) {
|
if (propertyName.value || propertyList.value.length) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已匹配到物业信息',
|
title: '已匹配到物业信息',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alipayPropertyUnbound.value = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryParseJson = (value: any) => {
|
const tryParseJson = (value: any) => {
|
||||||
|
|
@ -775,6 +815,13 @@ const handleAlipayPhoneAuthorize = async (event?: any) => {
|
||||||
throw new Error(response?.msg || '手机号绑定失败');
|
throw new Error(response?.msg || '手机号绑定失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextToken = String(response?.data?.token || '').trim();
|
||||||
|
if (!nextToken) {
|
||||||
|
throw new Error('绑定成功但未返回 token');
|
||||||
|
}
|
||||||
|
authStorage.setToken(nextToken, Number(response?.data?.expires_in || 0));
|
||||||
|
logPhoneAuth('已替换登录 token', { token: nextToken });
|
||||||
|
|
||||||
await refreshPropertyAfterPhoneBind();
|
await refreshPropertyAfterPhoneBind();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logPhoneAuth('授权失败', error);
|
logPhoneAuth('授权失败', error);
|
||||||
|
|
@ -1232,6 +1279,7 @@ onShow(() => {
|
||||||
.merchant-label-inner {
|
.merchant-label-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
flex-wrap: nowrap;
|
||||||
max-width: 88%;
|
max-width: 88%;
|
||||||
padding: 14rpx 32rpx;
|
padding: 14rpx 32rpx;
|
||||||
border-radius: 999rpx;
|
border-radius: 999rpx;
|
||||||
|
|
@ -1239,16 +1287,22 @@ onShow(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
|
flex-shrink: 0;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.merchant-name {
|
.merchant-name {
|
||||||
|
min-width: 0;
|
||||||
margin: 0 8rpx;
|
margin: 0 8rpx;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 34rpx;
|
font-size: 34rpx;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
text-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.35);
|
text-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.35);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount-zone {
|
.amount-zone {
|
||||||
|
|
@ -1388,6 +1442,8 @@ onShow(() => {
|
||||||
.property-name-tip {
|
.property-name-tip {
|
||||||
color: #8B929E;
|
color: #8B929E;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-arrow {
|
.property-arrow {
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,14 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="records.length && (loading || finished)" class="records-footer">
|
<view v-if="records.length && (loading || finished)" class="records-footer">
|
||||||
<wd-loadmore :state="loadMoreState" />
|
<wd-loadmore v-if="loading" state="loading" />
|
||||||
|
<text v-else class="records-finished">没有更多了</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||||
import { tradeList } from '@/api/cashier';
|
import { tradeList } from '@/api/cashier';
|
||||||
import type { TradeListItem } from '@/api/cashier';
|
import type { TradeListItem } from '@/api/cashier';
|
||||||
|
|
@ -54,8 +55,6 @@ const finished = ref(false);
|
||||||
|
|
||||||
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
||||||
|
|
||||||
const loadMoreState = computed(() => (loading.value ? 'loading' : 'finished'));
|
|
||||||
|
|
||||||
const formatMoney = (value?: string | number) => {
|
const formatMoney = (value?: string | number) => {
|
||||||
const amount = Number(value || 0);
|
const amount = Number(value || 0);
|
||||||
if (!Number.isFinite(amount)) return '0.00';
|
if (!Number.isFinite(amount)) return '0.00';
|
||||||
|
|
@ -262,4 +261,12 @@ onReachBottom(() => {
|
||||||
.records-footer {
|
.records-footer {
|
||||||
padding: 12rpx 0 24rpx;
|
padding: 12rpx 0 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.records-finished {
|
||||||
|
display: block;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 24rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue