fix: 认证策略是先充值后实名, 首页入口
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 1m11s
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 1m11s
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
export { authApi } from './modules/auth.js';
|
export { authApi } from './modules/auth.js';
|
||||||
export { assetApi, isAssetRealNameCompleted } from './modules/asset.js';
|
export { assetApi, hasActiveOrPendingPackage, isAssetRealNameCompleted } from './modules/asset.js';
|
||||||
export { deviceApi } from './modules/device.js';
|
export { deviceApi } from './modules/device.js';
|
||||||
export { exchangeApi } from './modules/exchange.js';
|
export { exchangeApi } from './modules/exchange.js';
|
||||||
export { orderApi } from './modules/order.js';
|
export { orderApi } from './modules/order.js';
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ export const isAssetRealNameCompleted = (assetInfo = {}) => {
|
|||||||
return Number(assetInfo.real_name_status) === 1;
|
return Number(assetInfo.real_name_status) === 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasActiveOrPendingPackage = async (identifier) => {
|
||||||
|
const [pendingData, activeData] = await Promise.all([
|
||||||
|
assetApi.getPackageHistory(identifier, 1, 1, { status: 0 }),
|
||||||
|
assetApi.getPackageHistory(identifier, 1, 1, { status: 1 })
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [pendingData, activeData].some(data => Array.isArray(data?.items) && data.items.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
export const assetApi = {
|
export const assetApi = {
|
||||||
getInfo(identifier) {
|
getInfo(identifier) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -18,10 +18,17 @@ The H5/C client SHALL use `GET /api/c/v1/asset/info?identifier=...` as the sourc
|
|||||||
|
|
||||||
#### Scenario: Real name is required after ordering
|
#### Scenario: Real name is required after ordering
|
||||||
|
|
||||||
- **WHEN** asset information returns `effective_realname_policy = after_order`
|
- **WHEN** the effective real-name policy is `after_order`
|
||||||
- **THEN** the client SHALL allow the policy-defined post-order flow to proceed without applying a before-order block
|
- **AND** `/api/c/v1/device/cards` returns no card with completed real-name authentication
|
||||||
- **AND** the client SHALL display the returned real-name status
|
- **AND** package history contains neither a pending package (`status = 0`) nor an active package (`status = 1`)
|
||||||
- **AND** selecting the homepage real-name entry SHALL show a confirmation prompt and allow the user to navigate to package ordering instead of entering real-name authentication directly
|
- **THEN** selecting a real-name entry on the homepage or operator-switch page SHALL block direct real-name authentication
|
||||||
|
- **AND** the client SHALL show a confirmation prompt that can navigate to package ordering
|
||||||
|
|
||||||
|
#### Scenario: Post-order real-name requirement is satisfied
|
||||||
|
|
||||||
|
- **WHEN** the effective real-name policy is `after_order`
|
||||||
|
- **AND** `/api/c/v1/device/cards` returns at least one authenticated card, or package history contains a pending or active package
|
||||||
|
- **THEN** the homepage and operator-switch real-name entries SHALL retain the existing real-name authentication flow
|
||||||
|
|
||||||
#### Scenario: Device status is resolved from server output
|
#### Scenario: Device status is resolved from server output
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
- [x] 2.2 Drive real-name prompts, status labels, and order gates from `effective_realname_policy`, `realname_required`, and `real_name_status`
|
- [x] 2.2 Drive real-name prompts, status labels, and order gates from `effective_realname_policy`, `realname_required`, and `real_name_status`
|
||||||
- [x] 2.3 Remove local card/device real-name policy inference while preserving the existing real-name link flow
|
- [x] 2.3 Remove local card/device real-name policy inference while preserving the existing real-name link flow
|
||||||
- [x] 2.4 Replace current-package-only expiry display with `estimated_final_expires_at` and apply backend expiry status/highlight fields
|
- [x] 2.4 Replace current-package-only expiry display with `estimated_final_expires_at` and apply backend expiry status/highlight fields
|
||||||
- [x] 2.5 Redirect the homepage real-name entry to package ordering after confirmation when the effective policy is `after_order`
|
- [x] 2.5 Gate homepage and operator-switch real-name entries for `after_order` using device card real-name states and pending/active package history
|
||||||
|
|
||||||
## 3. Package purchase and renewal
|
## 3. Package purchase and renewal
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@
|
|||||||
import {
|
import {
|
||||||
assetApi,
|
assetApi,
|
||||||
deviceApi,
|
deviceApi,
|
||||||
|
hasActiveOrPendingPackage,
|
||||||
notificationApi
|
notificationApi
|
||||||
} from '@/api/index.js';
|
} from '@/api/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -197,6 +198,7 @@
|
|||||||
let boundPhone = ref('');
|
let boundPhone = ref('');
|
||||||
let realNameStatus = ref('');
|
let realNameStatus = ref('');
|
||||||
let effectiveRealnamePolicy = ref('none');
|
let effectiveRealnamePolicy = ref('none');
|
||||||
|
let realnameEntryChecking = ref(false);
|
||||||
let wifi_info = reactive({
|
let wifi_info = reactive({
|
||||||
ssid: '',
|
ssid: '',
|
||||||
pwd: ''
|
pwd: ''
|
||||||
@@ -715,6 +717,51 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldRedirectRealnameToPackageOrder = async () => {
|
||||||
|
if (effectiveRealnamePolicy.value !== 'after_order') return false;
|
||||||
|
|
||||||
|
const identifier = userStore.state.identifier;
|
||||||
|
let cards = [{ real_name_status: isRealName.value ? 1 : 0 }];
|
||||||
|
if (userInfo.isDevice) {
|
||||||
|
const data = await deviceApi.getCards(identifier);
|
||||||
|
cards = Array.isArray(data?.cards) ? data.cards : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cards.some(card => Number(card?.real_name_status) === 1)) return false;
|
||||||
|
return !(await hasActiveOrPendingPackage(identifier));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRealnameEntry = async () => {
|
||||||
|
if (realnameEntryChecking.value) return;
|
||||||
|
realnameEntryChecking.value = true;
|
||||||
|
uni.showLoading({ title: '校验中...', mask: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const shouldRedirect = await shouldRedirectRealnameToPackageOrder();
|
||||||
|
uni.hideLoading();
|
||||||
|
if (!shouldRedirect) {
|
||||||
|
uni.navigateTo({ url: '/pages/auth/auth' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '当前设备所有卡均未实名,且暂无生效中或待生效套餐。请先订购套餐,再进行实名认证。',
|
||||||
|
confirmText: '去订购',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: ({ confirm }) => {
|
||||||
|
if (confirm) uni.navigateTo({ url: '/pages/package-order/package-order' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('校验实名入口条件失败', error);
|
||||||
|
uni.showToast({ title: '实名条件校验失败,请稍后重试', icon: 'none' });
|
||||||
|
} finally {
|
||||||
|
realnameEntryChecking.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const enterDetail = (name) => {
|
const enterDetail = (name) => {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'notifications':
|
case 'notifications':
|
||||||
@@ -774,23 +821,7 @@
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'authentication':
|
case 'authentication':
|
||||||
if (effectiveRealnamePolicy.value === 'after_order') {
|
openRealnameEntry();
|
||||||
uni.showModal({
|
|
||||||
title: '提示',
|
|
||||||
content: '当前认证策略为先充值后实名,请先订购套餐后再进行实名认证。是否前往套餐订购页面?',
|
|
||||||
confirmText: '去订购',
|
|
||||||
cancelText: '取消',
|
|
||||||
success: ({ confirm }) => {
|
|
||||||
if (confirm) {
|
|
||||||
uni.navigateTo({ url: '/pages/package-order/package-order' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/auth/auth'
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'recover':
|
case 'recover':
|
||||||
recoverShow.value = true;
|
recoverShow.value = true;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, onMounted } from 'vue';
|
import { reactive, ref, onMounted } from 'vue';
|
||||||
import { assetApi, deviceApi, realnameApi } from '@/api/index.js';
|
import { assetApi, deviceApi, hasActiveOrPendingPackage, realnameApi } from '@/api/index.js';
|
||||||
import { useUserStore } from '@/store/index.js';
|
import { useUserStore } from '@/store/index.js';
|
||||||
import slot1Icon from '@/static/卡槽1.png';
|
import slot1Icon from '@/static/卡槽1.png';
|
||||||
import slot2Icon from '@/static/卡槽2.png';
|
import slot2Icon from '@/static/卡槽2.png';
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
let switching = ref(false);
|
let switching = ref(false);
|
||||||
let showIccidModal = ref(false);
|
let showIccidModal = ref(false);
|
||||||
let currentModalIccid = ref('');
|
let currentModalIccid = ref('');
|
||||||
|
let realnameEntryChecking = ref(false);
|
||||||
|
|
||||||
const carrierMap = {
|
const carrierMap = {
|
||||||
CMCC: { name: '中国移动', logo: cmccLogo },
|
CMCC: { name: '中国移动', logo: cmccLogo },
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
carrier_name: card.carrier_name,
|
carrier_name: card.carrier_name,
|
||||||
is_current: card.is_active,
|
is_current: card.is_active,
|
||||||
real_name_status: card.real_name_status,
|
real_name_status: card.real_name_status,
|
||||||
|
realname_policy: card.realname_policy,
|
||||||
network_status: card.network_status,
|
network_status: card.network_status,
|
||||||
slot_position: card.slot_position
|
slot_position: card.slot_position
|
||||||
}));
|
}));
|
||||||
@@ -176,11 +178,52 @@
|
|||||||
switching.value = false;
|
switching.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toReal = (card) => {
|
const openRealnameModal = (iccid) => {
|
||||||
currentModalIccid.value = card.iccid;
|
currentModalIccid.value = iccid;
|
||||||
showIccidModal.value = true;
|
showIccidModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toReal = async (card) => {
|
||||||
|
if (realnameEntryChecking.value) return;
|
||||||
|
if (card.realname_policy !== 'after_order') {
|
||||||
|
openRealnameModal(card.iccid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
realnameEntryChecking.value = true;
|
||||||
|
uni.showLoading({ title: '校验中...', mask: true });
|
||||||
|
try {
|
||||||
|
const data = await deviceApi.getCards(userStore.state.identifier);
|
||||||
|
const cards = Array.isArray(data?.cards) ? data.cards : [];
|
||||||
|
const currentCard = cards.find(item => item.iccid === card.iccid) || card;
|
||||||
|
const hasRealNamedCard = cards.some(item => Number(item?.real_name_status) === 1);
|
||||||
|
|
||||||
|
if (currentCard.realname_policy !== 'after_order' || hasRealNamedCard ||
|
||||||
|
await hasActiveOrPendingPackage(userStore.state.identifier)) {
|
||||||
|
uni.hideLoading();
|
||||||
|
openRealnameModal(card.iccid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '当前设备所有卡均未实名,且暂无生效中或待生效套餐。请先订购套餐,再进行实名认证。',
|
||||||
|
confirmText: '去订购',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: ({ confirm }) => {
|
||||||
|
if (confirm) uni.navigateTo({ url: '/pages/package-order/package-order' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('校验切换运营商实名条件失败', error);
|
||||||
|
uni.showToast({ title: '实名条件校验失败,请稍后重试', icon: 'none' });
|
||||||
|
} finally {
|
||||||
|
realnameEntryChecking.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
showIccidModal.value = false;
|
showIccidModal.value = false;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user