fix: 新增支付页面

This commit is contained in:
hzj 2025-09-05 11:33:32 +08:00
parent 4e6943a8d8
commit bd7d271dea
7 changed files with 1097 additions and 418 deletions

39
src/api/pay.js Normal file
View File

@ -0,0 +1,39 @@
import { get, post } from "../utils/http.js";
// 获取支付应用信息
export const getPayAplication = async (applicationId) => {
try {
const response = await get(`/order/web/pay/application/${applicationId}`);
return response;
} catch (error) {
console.error("Failed to fetch get pay application:", error);
onsole.error("error:" + error.response.errorMsg);
throw error;
}
};
// 获取商品列表
export const getAppCommodityCard = async (params) => {
try {
const response = await get(
`/order/web/pay/commodity?applicationId=${params.applicationId}&payCountryId=${params.payCountryId}&regionId=${params.regionId}&type=GOLD`
);
return response;
} catch (error) {
console.error("Failed to fetch get app commodity card:", error);
onsole.error("error:" + error.response.errorMsg);
throw error;
}
};
// 下单充值
export const payPlaceAnOrderRecharge = async (data) => {
try {
const response = await post("/order/web/pay/recharge", data);
return response;
} catch (error) {
console.error("Failed to fetch get pay application:", error);
onsole.error("error:" + error.response.errorMsg);
throw error;
}
};

View File

@ -2,91 +2,111 @@
<div class="mobile-header" :class="{ 'in-app': isInAppEnvironment }"> <div class="mobile-header" :class="{ 'in-app': isInAppEnvironment }">
<!-- 状态栏占位区域仅在APP中显示 --> <!-- 状态栏占位区域仅在APP中显示 -->
<div v-if="isInAppEnvironment" class="status-bar-spacer"></div> <div v-if="isInAppEnvironment" class="status-bar-spacer"></div>
<!-- header内容 --> <!-- header内容 -->
<div class="header-content"> <div class="header-content">
<button v-if="showBack" class="back-btn" @click="handleBack"> <button v-if="showBack" class="back-btn" @click="handleBack">
<svg class="back-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="back-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path
d="M15 18L9 12L15 6"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg> </svg>
</button> </button>
<h1 class="title">{{ title }}</h1> <h1 class="title">{{ title }}</h1>
<button v-if="showHelp" class="help-btn" @click="$emit('help')"> <button v-if="showHelp" class="help-btn" @click="$emit('help')">
<svg class="help-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="help-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/> <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" />
<path d="M9.09 9A3 3 0 0 1 12 6a3 3 0 0 1 3 3c0 2-3 3-3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path
<path d="M12 17h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> d="M9.09 9A3 3 0 0 1 12 6a3 3 0 0 1 3 3c0 2-3 3-3 3"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 17h.01"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg> </svg>
</button> </button>
<div v-else class="placeholder"></div> <div v-else class="placeholder"></div>
<slot name="extraFunction"></slot>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from "vue-router";
import { computed, ref, onMounted } from 'vue' import { computed, ref, onMounted } from "vue";
import { closePage, isInApp } from '../utils/appBridge.js' import { closePage, isInApp } from "../utils/appBridge.js";
// props // props
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
required: true required: true,
}, },
showBack: { showBack: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
showHelp: { showHelp: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
isHomePage: { isHomePage: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}) });
// emits // emits
defineEmits(['help']) defineEmits(["help"]);
const router = useRouter() const router = useRouter();
const route = useRoute() const route = useRoute();
// APP // APP
const isInAppEnvironment = ref(false) const isInAppEnvironment = ref(false);
// //
const homeRoutes = ['/host-center', '/agency-center', '/coin-seller', '/'] const homeRoutes = ["/host-center", "/agency-center", "/coin-seller", "/"];
// //
const isCurrentlyHomePage = computed(() => { const isCurrentlyHomePage = computed(() => {
// 1. props // 1. props
const propsHomePage = props.isHomePage === true || props.isHomePage === 'true' const propsHomePage = props.isHomePage === true || props.isHomePage === "true";
// 2. // 2.
const routeHomePage = homeRoutes.includes(route.path) const routeHomePage = homeRoutes.includes(route.path);
// 3. true // 3. true
return propsHomePage || routeHomePage return propsHomePage || routeHomePage;
}) });
const handleBack = () => { const handleBack = () => {
if (isCurrentlyHomePage.value && isInApp()) { if (isCurrentlyHomePage.value && isInApp()) {
// APP // APP
console.log('home back') console.log("home back");
closePage() closePage();
} else { } else {
// //
router.go(-1) router.go(-1);
} }
} };
// //
onMounted(() => { onMounted(() => {
isInAppEnvironment.value = isInApp() isInAppEnvironment.value = isInApp();
}) });
</script> </script>
<style scoped> <style scoped>
@ -112,7 +132,8 @@ onMounted(() => {
background: white !important; background: white !important;
} }
.back-btn, .help-btn { .back-btn,
.help-btn {
width: 36px; width: 36px;
height: 36px; height: 36px;
border: none; border: none;
@ -127,19 +148,22 @@ onMounted(() => {
border: 1px solid #e9ecef; border: 1px solid #e9ecef;
} }
.back-btn:hover, .help-btn:hover { .back-btn:hover,
.help-btn:hover {
background: #e9ecef !important; background: #e9ecef !important;
color: #343a40 !important; color: #343a40 !important;
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.back-btn:active, .help-btn:active { .back-btn:active,
.help-btn:active {
transform: scale(0.95); transform: scale(0.95);
background: #dee2e6 !important; background: #dee2e6 !important;
} }
.back-icon, .help-icon { .back-icon,
.help-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
color: inherit; color: inherit;
@ -167,26 +191,28 @@ onMounted(() => {
background: white !important; background: white !important;
border-bottom-color: #f0f0f0 !important; border-bottom-color: #f0f0f0 !important;
} }
.status-bar-spacer { .status-bar-spacer {
background: white !important; background: white !important;
} }
.header-content { .header-content {
background: white !important; background: white !important;
} }
.back-btn, .help-btn { .back-btn,
.help-btn {
background: #f8f9fa !important; background: #f8f9fa !important;
border-color: #e9ecef !important; border-color: #e9ecef !important;
color: #495057 !important; color: #495057 !important;
} }
.back-btn:hover, .help-btn:hover { .back-btn:hover,
.help-btn:hover {
background: #e9ecef !important; background: #e9ecef !important;
color: #343a40 !important; color: #343a40 !important;
} }
.title { .title {
color: #212529 !important; color: #212529 !important;
} }
@ -198,16 +224,17 @@ onMounted(() => {
background: white !important; background: white !important;
color: #212529 !important; color: #212529 !important;
} }
.mobile-header * { .mobile-header * {
color: inherit !important; color: inherit !important;
} }
.title { .title {
color: #212529 !important; color: #212529 !important;
} }
.back-btn, .help-btn { .back-btn,
.help-btn {
background: #f8f9fa !important; background: #f8f9fa !important;
color: #495057 !important; color: #495057 !important;
border-color: #e9ecef !important; border-color: #e9ecef !important;
@ -219,12 +246,13 @@ onMounted(() => {
.mobile-header { .mobile-header {
background: white !important; background: white !important;
} }
.title { .title {
color: #212529 !important; color: #212529 !important;
} }
.back-btn, .help-btn { .back-btn,
.help-btn {
background: #f8f9fa !important; background: #f8f9fa !important;
color: #495057 !important; color: #495057 !important;
} }

View File

@ -5,162 +5,175 @@
// 用户身份类型 // 用户身份类型
export const USER_ROLES = { export const USER_ROLES = {
FREIGHT_AGENT: 'freightAgent', FREIGHT_AGENT: "freightAgent",
AGENT: 'agent', AGENT: "agent",
BD: 'bd', BD: "bd",
ANCHOR: 'anchor', ANCHOR: "anchor",
GUEST: 'guest' GUEST: "guest",
} ADMIN: "admin",
};
// 页面路径常量 // 页面路径常量
export const PAGES = { export const PAGES = {
COIN_SELLER: '/coin-seller', COIN_SELLER: "/coin-seller",
AGENCY_CENTER: '/agency-center', AGENCY_CENTER: "/agency-center",
BD_CENTER: '/bd-center', BD_CENTER: "/bd-center",
HOST_CENTER: '/host-center', HOST_CENTER: "/host-center",
APPLY: '/apply', APPLY: "/apply",
MESSAGE: '/message', MESSAGE: "/message",
TRANSFER: '/transfer', TRANSFER: "/transfer",
EXCHANGE: '/exchange-gold-coins', EXCHANGE: "/exchange-gold-coins",
PERSONAL_SALARY: '/platform-policy', PERSONAL_SALARY: "/platform-policy",
HOST_SETTING: '/host-setting', HOST_SETTING: "/host-setting",
NOT_APP: '/not_app', NOT_APP: "/not_app",
LOADING: '/loading', LOADING: "/loading",
} ADMIN_CENTER: "/admin-center",
RECHARGE_STANDARD: "/recharge-standard",
};
// 🎯 核心改变:基于身份的权限配置 // 🎯 核心改变:基于身份的权限配置
// 每个身份拥有的页面权限列表 // 每个身份拥有的页面权限列表
export const ROLE_PERMISSIONS = { export const ROLE_PERMISSIONS = {
// Freight Agent (货运代理/金币销售员) // Freight Agent (货运代理/金币销售员)
[USER_ROLES.FREIGHT_AGENT]: [ [USER_ROLES.FREIGHT_AGENT]: [
PAGES.COIN_SELLER, // 主页面 PAGES.COIN_SELLER, // 主页面
'/seller-records', "/seller-records",
'/coin-seller-search' "/coin-seller-search",
"/recharge-freight-agent",
], ],
// Agent (代理商) // Agent (代理商)
[USER_ROLES.AGENT]: [ [USER_ROLES.AGENT]: [
PAGES.AGENCY_CENTER, // 主页面 PAGES.AGENCY_CENTER, // 主页面
PAGES.TRANSFER, // 转账功能 PAGES.TRANSFER, // 转账功能
PAGES.EXCHANGE, // 兑换功能 PAGES.EXCHANGE, // 兑换功能
PAGES.MESSAGE, // 消息功能 PAGES.MESSAGE, // 消息功能
PAGES.PERSONAL_SALARY, // 个人薪资查看 PAGES.PERSONAL_SALARY, // 个人薪资查看
'/team-member', PAGES.RECHARGE_STANDARD, //普通转账功能
'/information-details', "/team-member",
'/search-payee', "/information-details",
'/history-salary', "/search-payee",
'/platform-salary', "/history-salary",
'/invite-members', "/platform-salary",
'/team-bill' "/invite-members",
"/team-bill",
], ],
// BD (商务拓展) // BD (商务拓展)
[USER_ROLES.BD]: [ [USER_ROLES.BD]: [
PAGES.BD_CENTER, // 主页面 PAGES.BD_CENTER, // 主页面
PAGES.TRANSFER, // 转账功能 PAGES.TRANSFER, // 转账功能
PAGES.EXCHANGE, // 兑换功能 PAGES.EXCHANGE, // 兑换功能
PAGES.MESSAGE, // 消息功能 PAGES.MESSAGE, // 消息功能
'/team-member', PAGES.RECHARGE_STANDARD, //普通转账功能
'/history-salary', "/team-member",
'/platform-salary' "/history-salary",
"/platform-salary",
], ],
// Anchor (主播) // Anchor (主播)
[USER_ROLES.ANCHOR]: [ [USER_ROLES.ANCHOR]: [
PAGES.HOST_CENTER, // 主页面 PAGES.HOST_CENTER, // 主页面
PAGES.TRANSFER, // 转账功能 PAGES.TRANSFER, // 转账功能
PAGES.EXCHANGE, // 兑换功能 PAGES.EXCHANGE, // 兑换功能
PAGES.MESSAGE, // 消息功能 PAGES.MESSAGE, // 消息功能
PAGES.PERSONAL_SALARY, // 个人薪资查看 PAGES.PERSONAL_SALARY, // 个人薪资查看
PAGES.HOST_SETTING, // 主播设置 PAGES.HOST_SETTING, // 主播设置
'/information-details', PAGES.RECHARGE_STANDARD, //普通转账功能
'/search-payee', "/information-details",
'/apply', "/search-payee",
'/history-salary', "/apply",
'/platform-salary', "/history-salary",
'/invite-members', "/platform-salary",
'/team-bill' "/invite-members",
"/team-bill",
],
// Admin管理员
[USER_ROLES.ADMIN]: [
PAGES.ADMIN_CENTER, //管理员的身份验证
"/item-distribution", //赠送商品页面
PAGES.RECHARGE_STANDARD, //普通转账功能
], ],
// Guest (访客/申请者) // Guest (访客/申请者)
[USER_ROLES.GUEST]: [ [USER_ROLES.GUEST]: [
PAGES.APPLY, // 申请页面 PAGES.APPLY, // 申请页面
], ],
// 公共页面(所有身份都可以访问) // 公共页面(所有身份都可以访问)
PUBLIC_PAGES: [ PUBLIC_PAGES: [
PAGES.NOT_APP, // 错误页面 PAGES.NOT_APP, // 错误页面
], ],
// 加载页面 // 加载页面
LOADING_PAGE: [ LOADING_PAGE: [PAGES.LOADING],
PAGES.LOADING };
]
}
// 身份优先级(数字越大优先级越高) // 身份优先级(数字越大优先级越高)
export const ROLE_PRIORITY = { export const ROLE_PRIORITY = {
[USER_ROLES.ADMIN]: 5,
[USER_ROLES.FREIGHT_AGENT]: 4, [USER_ROLES.FREIGHT_AGENT]: 4,
[USER_ROLES.BD]: 3, [USER_ROLES.BD]: 3,
[USER_ROLES.AGENT]: 2, [USER_ROLES.AGENT]: 2,
[USER_ROLES.ANCHOR]: 1, [USER_ROLES.ANCHOR]: 1,
[USER_ROLES.GUEST]: 0 [USER_ROLES.GUEST]: 0,
} };
// 身份对应的默认首页 // 身份对应的默认首页
export const ROLE_DEFAULT_PAGES = { export const ROLE_DEFAULT_PAGES = {
[USER_ROLES.ADMIN]: PAGES.ADMIN_CENTER,
[USER_ROLES.FREIGHT_AGENT]: PAGES.COIN_SELLER, [USER_ROLES.FREIGHT_AGENT]: PAGES.COIN_SELLER,
[USER_ROLES.AGENT]: PAGES.AGENCY_CENTER, [USER_ROLES.AGENT]: PAGES.AGENCY_CENTER,
[USER_ROLES.BD]: PAGES.BD_CENTER, [USER_ROLES.BD]: PAGES.BD_CENTER,
[USER_ROLES.ANCHOR]: PAGES.HOST_CENTER, [USER_ROLES.ANCHOR]: PAGES.HOST_CENTER,
[USER_ROLES.GUEST]: PAGES.APPLY [USER_ROLES.GUEST]: PAGES.APPLY,
} };
// 身份显示配置 // 身份显示配置
export const ROLE_DISPLAY_CONFIG = { export const ROLE_DISPLAY_CONFIG = {
[USER_ROLES.FREIGHT_AGENT]: { [USER_ROLES.FREIGHT_AGENT]: {
name: 'Freight Agent', name: "Freight Agent",
tag: '💰 Coin Seller', tag: "💰 Coin Seller",
color: '#FCD34D', color: "#FCD34D",
bgColor: '#FEF3C7' bgColor: "#FEF3C7",
}, },
[USER_ROLES.AGENT]: { [USER_ROLES.AGENT]: {
name: 'Agent', name: "Agent",
tag: '🏢 Agency', tag: "🏢 Agency",
color: '#1E40AF', color: "#1E40AF",
bgColor: '#DBEAFE' bgColor: "#DBEAFE",
}, },
[USER_ROLES.BD]: { [USER_ROLES.BD]: {
name: 'BD', name: "BD",
tag: '📈 BD', tag: "📈 BD",
color: '#059669', color: "#059669",
bgColor: '#D1FAE5' bgColor: "#D1FAE5",
}, },
[USER_ROLES.ANCHOR]: { [USER_ROLES.ANCHOR]: {
name: 'Anchor', name: "Anchor",
tag: '👑 Host', tag: "👑 Host",
color: '#5B21B6', color: "#5B21B6",
bgColor: '#E0E7FF' bgColor: "#E0E7FF",
}, },
[USER_ROLES.GUEST]: { [USER_ROLES.GUEST]: {
name: 'Guest', name: "Guest",
tag: '👤 Guest', tag: "👤 Guest",
color: '#6B7280', color: "#6B7280",
bgColor: '#F3F4F6' bgColor: "#F3F4F6",
} },
} };
/** /**
* 基于身份的权限管理类 * 基于身份的权限管理类
*/ */
class RoleBasedPermissionManager { class RoleBasedPermissionManager {
constructor() { constructor() {
this.userIdentity = null this.userIdentity = null;
this.primaryRole = null this.primaryRole = null;
this.allRoles = [] this.allRoles = [];
this.isIdentityLoaded = false this.isIdentityLoaded = false;
this.allowedPages = [] this.allowedPages = [];
} }
/** /**
@ -168,18 +181,18 @@ class RoleBasedPermissionManager {
* @param {Object} identity - 身份信息对象 * @param {Object} identity - 身份信息对象
*/ */
setUserIdentity(identity) { setUserIdentity(identity) {
this.userIdentity = identity this.userIdentity = identity;
this.allRoles = this.calculateAllRoles(identity) this.allRoles = this.calculateAllRoles(identity);
this.primaryRole = this.calculatePrimaryRole(this.allRoles) this.primaryRole = this.calculatePrimaryRole(this.allRoles);
this.allowedPages = this.calculateAllowedPages(this.allRoles) this.allowedPages = this.calculateAllowedPages(this.allRoles);
this.isIdentityLoaded = true this.isIdentityLoaded = true;
console.debug('🔐 User identity updated:', { console.debug("🔐 User identity updated:", {
identity: this.userIdentity, identity: this.userIdentity,
allRoles: this.allRoles, allRoles: this.allRoles,
primaryRole: this.primaryRole, primaryRole: this.primaryRole,
allowedPages: this.allowedPages allowedPages: this.allowedPages,
}) });
} }
/** /**
@ -188,15 +201,16 @@ class RoleBasedPermissionManager {
* @returns {Array} 所有身份列表 * @returns {Array} 所有身份列表
*/ */
calculateAllRoles(identity) { calculateAllRoles(identity) {
if (!identity) return [USER_ROLES.GUEST] if (!identity) return [USER_ROLES.GUEST];
const roles = [] const roles = [];
if (identity.freightAgent) roles.push(USER_ROLES.FREIGHT_AGENT) if (identity.freightAgent) roles.push(USER_ROLES.FREIGHT_AGENT);
if (identity.agent) roles.push(USER_ROLES.AGENT) if (identity.agent) roles.push(USER_ROLES.AGENT);
if (identity.bd) roles.push(USER_ROLES.BD) if (identity.bd) roles.push(USER_ROLES.BD);
if (identity.anchor) roles.push(USER_ROLES.ANCHOR) if (identity.anchor) roles.push(USER_ROLES.ANCHOR);
if (identity.admin) roles.push(USER_ROLES.ADMIN);
return roles.length > 0 ? roles : [USER_ROLES.GUEST] return roles.length > 0 ? roles : [USER_ROLES.GUEST];
} }
/** /**
@ -205,12 +219,12 @@ class RoleBasedPermissionManager {
* @returns {string} 主要身份 * @returns {string} 主要身份
*/ */
calculatePrimaryRole(roles) { calculatePrimaryRole(roles) {
if (!roles || roles.length === 0) return USER_ROLES.GUEST if (!roles || roles.length === 0) return USER_ROLES.GUEST;
// 按优先级排序,返回优先级最高的身份 // 按优先级排序,返回优先级最高的身份
return roles.reduce((highest, current) => { return roles.reduce((highest, current) => {
return ROLE_PRIORITY[current] > ROLE_PRIORITY[highest] ? current : highest return ROLE_PRIORITY[current] > ROLE_PRIORITY[highest] ? current : highest;
}, USER_ROLES.GUEST) }, USER_ROLES.GUEST);
} }
/** /**
@ -219,27 +233,27 @@ class RoleBasedPermissionManager {
* @returns {Array} 允许访问的页面列表 * @returns {Array} 允许访问的页面列表
*/ */
calculateAllowedPages(roles) { calculateAllowedPages(roles) {
const allowedPages = new Set() const allowedPages = new Set();
// 添加公共页面 // 添加公共页面
ROLE_PERMISSIONS.PUBLIC_PAGES.forEach(page => { ROLE_PERMISSIONS.PUBLIC_PAGES.forEach((page) => {
allowedPages.add(page) allowedPages.add(page);
}) });
// 添加loading页面 // 添加loading页面
ROLE_PERMISSIONS.LOADING_PAGE.forEach(page => { ROLE_PERMISSIONS.LOADING_PAGE.forEach((page) => {
allowedPages.add(page) allowedPages.add(page);
}) });
// 根据用户拥有的每个身份,添加对应的页面权限 // 根据用户拥有的每个身份,添加对应的页面权限
roles.forEach(role => { roles.forEach((role) => {
const rolePages = ROLE_PERMISSIONS[role] || [] const rolePages = ROLE_PERMISSIONS[role] || [];
rolePages.forEach(page => { rolePages.forEach((page) => {
allowedPages.add(page) allowedPages.add(page);
}) });
}) });
return Array.from(allowedPages) return Array.from(allowedPages);
} }
/** /**
@ -249,20 +263,20 @@ class RoleBasedPermissionManager {
*/ */
hasPagePermission(page) { hasPagePermission(page) {
if (!this.isIdentityLoaded) { if (!this.isIdentityLoaded) {
console.warn('⚠️ Identity not loaded yet') console.warn("⚠️ Identity not loaded yet");
return false return false;
} }
const hasPermission = this.allowedPages.includes(page) const hasPermission = this.allowedPages.includes(page);
console.debug('🔍 Permission check:', { console.debug("🔍 Permission check:", {
page, page,
allRoles: this.allRoles, allRoles: this.allRoles,
primaryRole: this.primaryRole, primaryRole: this.primaryRole,
hasPermission hasPermission,
}) });
return hasPermission return hasPermission;
} }
/** /**
@ -272,10 +286,10 @@ class RoleBasedPermissionManager {
* @returns {boolean} 是否有权限 * @returns {boolean} 是否有权限
*/ */
hasRolePagePermission(role, page) { hasRolePagePermission(role, page) {
const rolePages = ROLE_PERMISSIONS[role] || [] const rolePages = ROLE_PERMISSIONS[role] || [];
const publicPages = ROLE_PERMISSIONS.PUBLIC_PAGES || [] const publicPages = ROLE_PERMISSIONS.PUBLIC_PAGES || [];
return rolePages.includes(page) || publicPages.includes(page) return rolePages.includes(page) || publicPages.includes(page);
} }
/** /**
@ -284,10 +298,10 @@ class RoleBasedPermissionManager {
* @returns {Array} 页面权限列表 * @returns {Array} 页面权限列表
*/ */
getRolePermissions(role) { getRolePermissions(role) {
const rolePages = ROLE_PERMISSIONS[role] || [] const rolePages = ROLE_PERMISSIONS[role] || [];
const publicPages = ROLE_PERMISSIONS.PUBLIC_PAGES || [] const publicPages = ROLE_PERMISSIONS.PUBLIC_PAGES || [];
return [...new Set([...rolePages, ...publicPages])] return [...new Set([...rolePages, ...publicPages])];
} }
/** /**
@ -295,7 +309,7 @@ class RoleBasedPermissionManager {
* @returns {string} 默认页面路径 * @returns {string} 默认页面路径
*/ */
getDefaultPage() { getDefaultPage() {
return ROLE_DEFAULT_PAGES[this.primaryRole] || PAGES.APPLY return ROLE_DEFAULT_PAGES[this.primaryRole] || PAGES.APPLY;
} }
/** /**
@ -303,7 +317,7 @@ class RoleBasedPermissionManager {
* @returns {string} 当前主要身份 * @returns {string} 当前主要身份
*/ */
getPrimaryRole() { getPrimaryRole() {
return this.primaryRole return this.primaryRole;
} }
/** /**
@ -311,7 +325,7 @@ class RoleBasedPermissionManager {
* @returns {Array} 所有身份 * @returns {Array} 所有身份
*/ */
getAllRoles() { getAllRoles() {
return this.allRoles return this.allRoles;
} }
/** /**
@ -320,7 +334,7 @@ class RoleBasedPermissionManager {
* @returns {Object} 显示信息 * @returns {Object} 显示信息
*/ */
getRoleDisplayInfo(role = this.primaryRole) { getRoleDisplayInfo(role = this.primaryRole) {
return ROLE_DISPLAY_CONFIG[role] || ROLE_DISPLAY_CONFIG[USER_ROLES.GUEST] return ROLE_DISPLAY_CONFIG[role] || ROLE_DISPLAY_CONFIG[USER_ROLES.GUEST];
} }
/** /**
@ -328,7 +342,7 @@ class RoleBasedPermissionManager {
* @returns {boolean} 是否有多重身份 * @returns {boolean} 是否有多重身份
*/ */
hasMultipleRoles() { hasMultipleRoles() {
return this.allRoles.length > 1 return this.allRoles.length > 1;
} }
/** /**
@ -338,11 +352,11 @@ class RoleBasedPermissionManager {
*/ */
switchToRole(role) { switchToRole(role) {
if (!this.allRoles.includes(role)) { if (!this.allRoles.includes(role)) {
console.warn(`⚠️ User does not have role: ${role}`) console.warn(`⚠️ User does not have role: ${role}`);
return this.getDefaultPage() return this.getDefaultPage();
} }
return ROLE_DEFAULT_PAGES[role] || PAGES.APPLY return ROLE_DEFAULT_PAGES[role] || PAGES.APPLY;
} }
/** /**
@ -350,16 +364,16 @@ class RoleBasedPermissionManager {
* @returns {Array} 可访问的页面菜单 * @returns {Array} 可访问的页面菜单
*/ */
getNavigationMenu() { getNavigationMenu() {
const menu = [] const menu = [];
this.allowedPages.forEach(page => { this.allowedPages.forEach((page) => {
const menuItem = this.getPageMenuInfo(page) const menuItem = this.getPageMenuInfo(page);
if (menuItem) { if (menuItem) {
menu.push(menuItem) menu.push(menuItem);
} }
}) });
return menu return menu;
} }
/** /**
@ -369,31 +383,31 @@ class RoleBasedPermissionManager {
*/ */
getPageMenuInfo(page) { getPageMenuInfo(page) {
const pageMenuConfig = { const pageMenuConfig = {
[PAGES.COIN_SELLER]: { title: 'Coin Seller', icon: '💰', order: 1 }, [PAGES.COIN_SELLER]: { title: "Coin Seller", icon: "💰", order: 1 },
[PAGES.AGENCY_CENTER]: { title: 'Agency Center', icon: '🏢', order: 2 }, [PAGES.AGENCY_CENTER]: { title: "Agency Center", icon: "🏢", order: 2 },
[PAGES.BD_CENTER]: { title: 'BD Center', icon: '📈', order: 3 }, [PAGES.BD_CENTER]: { title: "BD Center", icon: "📈", order: 3 },
[PAGES.HOST_CENTER]: { title: 'Host Center', icon: '👑', order: 4 }, [PAGES.HOST_CENTER]: { title: "Host Center", icon: "👑", order: 4 },
[PAGES.TRANSFER]: { title: 'Transfer', icon: '💸', order: 5 }, [PAGES.TRANSFER]: { title: "Transfer", icon: "💸", order: 5 },
[PAGES.EXCHANGE]: { title: 'Exchange', icon: '🔄', order: 6 }, [PAGES.EXCHANGE]: { title: "Exchange", icon: "🔄", order: 6 },
[PAGES.MESSAGE]: { title: 'Messages', icon: '💬', order: 7 }, [PAGES.MESSAGE]: { title: "Messages", icon: "💬", order: 7 },
[PAGES.PERSONAL_SALARY]: { title: 'Salary', icon: '💵', order: 8 }, [PAGES.PERSONAL_SALARY]: { title: "Salary", icon: "💵", order: 8 },
[PAGES.HOST_SETTING]: { title: 'Settings', icon: '⚙️', order: 9 } [PAGES.HOST_SETTING]: { title: "Settings", icon: "⚙️", order: 9 },
} };
const config = pageMenuConfig[page] const config = pageMenuConfig[page];
return config ? { ...config, path: page } : null return config ? { ...config, path: page } : null;
} }
/** /**
* 重置权限信息 * 重置权限信息
*/ */
reset() { reset() {
this.userIdentity = null this.userIdentity = null;
this.primaryRole = null this.primaryRole = null;
this.allRoles = [] this.allRoles = [];
this.isIdentityLoaded = false this.isIdentityLoaded = false;
this.allowedPages = [] this.allowedPages = [];
console.debug('🔄 Permission manager reset') console.debug("🔄 Permission manager reset");
} }
/** /**
@ -401,64 +415,65 @@ class RoleBasedPermissionManager {
* @returns {boolean} * @returns {boolean}
*/ */
isLoaded() { isLoaded() {
return this.isIdentityLoaded return this.isIdentityLoaded;
} }
/** /**
* 调试方法打印当前权限状态 * 调试方法打印当前权限状态
*/ */
debugPermissions() { debugPermissions() {
console.group('🔐 Current Permission State') console.group("🔐 Current Permission State");
console.log('User Identity:', this.userIdentity) console.log("User Identity:", this.userIdentity);
console.log('All Roles:', this.allRoles) console.log("All Roles:", this.allRoles);
console.log('Primary Role:', this.primaryRole) console.log("Primary Role:", this.primaryRole);
console.log('Allowed Pages:', this.allowedPages) console.log("Allowed Pages:", this.allowedPages);
console.log('Default Page:', this.getDefaultPage()) console.log("Default Page:", this.getDefaultPage());
console.log('Navigation Menu:', this.getNavigationMenu()) console.log("Navigation Menu:", this.getNavigationMenu());
console.groupEnd() console.groupEnd();
} }
} }
// 创建全局权限管理实例 // 创建全局权限管理实例
export const permissionManager = new RoleBasedPermissionManager() export const permissionManager = new RoleBasedPermissionManager();
// 便捷方法 // 便捷方法
export const hasPermission = (page) => permissionManager.hasPagePermission(page) export const hasPermission = (page) => permissionManager.hasPagePermission(page);
export const hasRolePermission = (role, page) => permissionManager.hasRolePagePermission(role, page) export const hasRolePermission = (role, page) =>
export const getRolePermissions = (role) => permissionManager.getRolePermissions(role) permissionManager.hasRolePagePermission(role, page);
export const getDefaultPage = () => permissionManager.getDefaultPage() export const getRolePermissions = (role) => permissionManager.getRolePermissions(role);
export const getPrimaryRole = () => permissionManager.getPrimaryRole() export const getDefaultPage = () => permissionManager.getDefaultPage();
export const getAllRoles = () => permissionManager.getAllRoles() export const getPrimaryRole = () => permissionManager.getPrimaryRole();
export const getRoleDisplayInfo = (role) => permissionManager.getRoleDisplayInfo(role) export const getAllRoles = () => permissionManager.getAllRoles();
export const setUserIdentity = (identity) => permissionManager.setUserIdentity(identity) export const getRoleDisplayInfo = (role) => permissionManager.getRoleDisplayInfo(role);
export const resetPermissions = () => permissionManager.reset() export const setUserIdentity = (identity) => permissionManager.setUserIdentity(identity);
export const switchToRole = (role) => permissionManager.switchToRole(role) export const resetPermissions = () => permissionManager.reset();
export const getNavigationMenu = () => permissionManager.getNavigationMenu() export const switchToRole = (role) => permissionManager.switchToRole(role);
export const debugPermissions = () => permissionManager.debugPermissions() export const getNavigationMenu = () => permissionManager.getNavigationMenu();
export const debugPermissions = () => permissionManager.debugPermissions();
// 🎯 新增:快速查询某个身份的权限 // 🎯 新增:快速查询某个身份的权限
export const QUICK_ROLE_CHECK = { export const QUICK_ROLE_CHECK = {
// 检查 Freight Agent 的权限 // 检查 Freight Agent 的权限
freightAgent: { freightAgent: {
pages: getRolePermissions(USER_ROLES.FREIGHT_AGENT), pages: getRolePermissions(USER_ROLES.FREIGHT_AGENT),
canAccess: (page) => hasRolePermission(USER_ROLES.FREIGHT_AGENT, page) canAccess: (page) => hasRolePermission(USER_ROLES.FREIGHT_AGENT, page),
}, },
// 检查 Agent 的权限 // 检查 Agent 的权限
agent: { agent: {
pages: getRolePermissions(USER_ROLES.AGENT), pages: getRolePermissions(USER_ROLES.AGENT),
canAccess: (page) => hasRolePermission(USER_ROLES.AGENT, page) canAccess: (page) => hasRolePermission(USER_ROLES.AGENT, page),
}, },
// 检查 BD 的权限 // 检查 BD 的权限
bd: { bd: {
pages: getRolePermissions(USER_ROLES.BD), pages: getRolePermissions(USER_ROLES.BD),
canAccess: (page) => hasRolePermission(USER_ROLES.BD, page) canAccess: (page) => hasRolePermission(USER_ROLES.BD, page),
}, },
// 检查 Anchor 的权限 // 检查 Anchor 的权限
anchor: { anchor: {
pages: getRolePermissions(USER_ROLES.ANCHOR), pages: getRolePermissions(USER_ROLES.ANCHOR),
canAccess: (page) => hasRolePermission(USER_ROLES.ANCHOR, page) canAccess: (page) => hasRolePermission(USER_ROLES.ANCHOR, page),
} },
} };

View File

@ -3,18 +3,24 @@
* 提供APP连接检查页面访问权限控制和自动重定向功能 * 提供APP连接检查页面访问权限控制和自动重定向功能
*/ */
import { permissionManager, PAGES, USER_ROLES } from './permissionManager.js' import { permissionManager, PAGES, USER_ROLES } from "./permissionManager.js";
import {getUserIdentity, getMemberProfile} from '../api/wallet.js' import { getUserIdentity, getMemberProfile } from "../api/wallet.js";
import {getUserId, setUserInfo} from './userStore.js' import { getUserId, setUserInfo } from "./userStore.js";
import { connectApplication, parseAccessOrigin, parseHeader, setHttpHeaders, isInApp } from './appBridge.js' import {
import { appConnectionManager, isAppConnected } from './appConnectionManager.js' connectApplication,
parseAccessOrigin,
parseHeader,
setHttpHeaders,
isInApp,
} from "./appBridge.js";
import { appConnectionManager, isAppConnected } from "./appConnectionManager.js";
/** /**
* APP连接检查器 * APP连接检查器
*/ */
class AppConnectionChecker { class AppConnectionChecker {
constructor() { constructor() {
this.isConnecting = false this.isConnecting = false;
} }
/** /**
@ -22,88 +28,91 @@ class AppConnectionChecker {
* @returns {Promise<boolean>} 连接是否成功 * @returns {Promise<boolean>} 连接是否成功
*/ */
async ensureConnection() { async ensureConnection() {
console.debug('🔗 Checking APP connection...') console.debug("🔗 Checking APP connection...");
// 如果不在APP环境中直接返回成功 // 如果不在APP环境中直接返回成功
console.log("我是app环境", window.app);
console.log("我是app环境", window.webkit);
console.log("我是app环境", window.FlutterPageControl);
if (!isInApp()) { if (!isInApp()) {
console.debug('🌐 Browser environment, no connection needed') console.debug("🌐 Browser environment, no connection needed");
return true return true;
} }
// 如果已经连接且未超时,直接返回成功 // 如果已经连接且未超时,直接返回成功
if (isAppConnected()) { if (isAppConnected()) {
console.debug('✅ APP already connected') console.debug("✅ APP already connected");
return true return true;
} }
// 如果正在连接中,等待现有连接 // 如果正在连接中,等待现有连接
if (appConnectionManager.isConnecting()) { if (appConnectionManager.isConnecting()) {
console.debug('⏳ Connection already in progress, waiting...') console.debug("⏳ Connection already in progress, waiting...");
try { try {
await appConnectionManager.getConnectionPromise() await appConnectionManager.getConnectionPromise();
return isAppConnected() return isAppConnected();
} catch (error) { } catch (error) {
console.error('❌ Connection wait failed:', error) console.error("❌ Connection wait failed:", error);
return false return false;
} }
} }
// 开始新的连接 // 开始新的连接
return await this._performConnection() return await this._performConnection();
} }
/** /**
* 执行APP连接 * 执行APP连接
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async _performConnection() { async _performConnection() {
try {
console.debug('📱 Starting new APP connection...')
const connectionPromise = new Promise((resolve, reject) => {
connectApplication(async (access) => {
try { try {
const result = parseAccessOrigin(access) console.debug("📱 Starting new APP connection...");
if (result.success) { const connectionPromise = new Promise((resolve, reject) => {
// 解析头部信息 connectApplication(async (access) => {
const headerInfo = parseHeader(result.data) try {
const result = parseAccessOrigin(access);
// 设置HTTP请求头 if (result.success) {
await setHttpHeaders(headerInfo) // 解析头部信息
const headerInfo = parseHeader(result.data);
// 更新连接状态 // 设置HTTP请求头
appConnectionManager.setConnected(headerInfo) await setHttpHeaders(headerInfo);
console.debug('🎉 APP connection established successfully') // 更新连接状态
appConnectionManager.setConnected(headerInfo);
// APP连接成功后立即获取用户信息 console.debug("🎉 APP connection established successfully");
await this._fetchUserInfoAfterConnection()
resolve(true) // APP连接成功后立即获取用户信息
} else { await this._fetchUserInfoAfterConnection();
console.error('❌ Failed to parse access origin:', result.error)
appConnectionManager.reset()
reject(new Error(result.error))
}
} catch (error) {
console.error('❌ Connection process failed:', error)
appConnectionManager.reset()
reject(error)
}
})
})
// 设置连接状态 resolve(true);
appConnectionManager.setConnecting(connectionPromise) } else {
console.error("❌ Failed to parse access origin:", result.error);
appConnectionManager.reset();
reject(new Error(result.error));
}
} catch (error) {
console.error("❌ Connection process failed:", error);
appConnectionManager.reset();
reject(error);
}
});
});
// 等待连接完成 // 设置连接状态
return await connectionPromise appConnectionManager.setConnecting(connectionPromise);
} catch (error) { // 等待连接完成
console.error('❌ APP connection failed:', error) return await connectionPromise;
appConnectionManager.reset() } catch (error) {
return false console.error("❌ APP connection failed:", error);
appConnectionManager.reset();
return false;
} }
} }
@ -113,21 +122,21 @@ class AppConnectionChecker {
*/ */
async _fetchUserInfoAfterConnection() { async _fetchUserInfoAfterConnection() {
try { try {
console.debug('👤 Fetching user info after connection...') console.debug("👤 Fetching user info after connection...");
const response = await getMemberProfile() const response = await getMemberProfile();
if (response && response.status && response.body) { if (response && response.status && response.body) {
// 缓存用户信息 // 缓存用户信息
console.debug('✅ User info cached successfully') console.debug("✅ User info cached successfully");
setUserInfo(response.body, null) setUserInfo(response.body, null);
} else { } else {
console.warn('⚠️ Failed to get team entry or invalid response') console.warn("⚠️ Failed to get team entry or invalid response");
} }
} catch (error) { } catch (error) {
// 这里不抛出错误因为APP连接已经成功 // 这里不抛出错误因为APP连接已经成功
// 用户信息获取失败可以在后续的身份检查中处理 // 用户信息获取失败可以在后续的身份检查中处理
console.warn('⚠️ Failed to fetch user info after connection:', error) console.warn("⚠️ Failed to fetch user info after connection:", error);
} }
} }
@ -135,7 +144,7 @@ class AppConnectionChecker {
* 重置连接状态 * 重置连接状态
*/ */
reset() { reset() {
appConnectionManager.reset() appConnectionManager.reset();
} }
} }
@ -144,8 +153,8 @@ class AppConnectionChecker {
*/ */
class IdentityChecker { class IdentityChecker {
constructor() { constructor() {
this.isChecking = false this.isChecking = false;
this.checkPromise = null this.checkPromise = null;
} }
/** /**
@ -155,26 +164,26 @@ class IdentityChecker {
*/ */
async checkUserIdentity(userId) { async checkUserIdentity(userId) {
if (!userId) { if (!userId) {
console.warn('⚠️ No user ID provided') console.warn("⚠️ No user ID provided");
permissionManager.setUserIdentity(null) permissionManager.setUserIdentity(null);
return USER_ROLES.GUEST return USER_ROLES.GUEST;
} }
// 如果正在检查中,等待现有的检查完成 // 如果正在检查中,等待现有的检查完成
if (this.isChecking && this.checkPromise) { if (this.isChecking && this.checkPromise) {
console.debug('⏳ Identity check already in progress, waiting...') console.debug("⏳ Identity check already in progress, waiting...");
return await this.checkPromise return await this.checkPromise;
} }
this.isChecking = true this.isChecking = true;
this.checkPromise = this._performIdentityCheck(userId) this.checkPromise = this._performIdentityCheck(userId);
try { try {
const primaryRole = await this.checkPromise const primaryRole = await this.checkPromise;
return primaryRole return primaryRole;
} finally { } finally {
this.isChecking = false this.isChecking = false;
this.checkPromise = null this.checkPromise = null;
} }
} }
@ -185,27 +194,27 @@ class IdentityChecker {
*/ */
async _performIdentityCheck(userId) { async _performIdentityCheck(userId) {
try { try {
console.debug('🔍 Checking user identity for:', userId) console.debug("🔍 Checking user identity for:", userId);
const response = await getUserIdentity(userId) const response = await getUserIdentity(userId);
if (response && response.status && response.body) { if (response && response.status && response.body) {
const identity = response.body const identity = response.body;
console.debug('✅ Identity fetched:', identity) console.debug("✅ Identity fetched:", identity);
// 设置权限信息 // 设置权限信息
permissionManager.setUserIdentity(identity) permissionManager.setUserIdentity(identity);
return permissionManager.getPrimaryRole() return permissionManager.getPrimaryRole();
} else { } else {
console.warn('⚠️ Invalid identity response') console.warn("⚠️ Invalid identity response");
permissionManager.setUserIdentity(null) permissionManager.setUserIdentity(null);
return USER_ROLES.GUEST return USER_ROLES.GUEST;
} }
} catch (error) { } catch (error) {
console.error('❌ Failed to fetch user identity:', error) console.error("❌ Failed to fetch user identity:", error);
permissionManager.setUserIdentity(null) permissionManager.setUserIdentity(null);
return USER_ROLES.GUEST return USER_ROLES.GUEST;
} }
} }
@ -213,8 +222,8 @@ class IdentityChecker {
* 重置检查状态 * 重置检查状态
*/ */
reset() { reset() {
this.isChecking = false this.isChecking = false;
this.checkPromise = null this.checkPromise = null;
} }
} }
@ -230,36 +239,36 @@ class PageRedirector {
* @returns {Promise<boolean>} 是否进行了重定向 * @returns {Promise<boolean>} 是否进行了重定向
*/ */
async redirectToCorrectPage(router, targetPath, currentPath) { async redirectToCorrectPage(router, targetPath, currentPath) {
const primaryRole = permissionManager.getPrimaryRole() const primaryRole = permissionManager.getPrimaryRole();
// 检查目标页面权限 // 检查目标页面权限
const hasPermission = permissionManager.hasPagePermission(targetPath) const hasPermission = permissionManager.hasPagePermission(targetPath);
console.debug('🔄 Redirect check:', { console.debug("🔄 Redirect check:", {
targetPath, targetPath,
currentPath, currentPath,
primaryRole, primaryRole,
hasPermission hasPermission,
}) });
if (hasPermission) { if (hasPermission) {
// 有权限访问目标页面 // 有权限访问目标页面
if (targetPath !== currentPath) { if (targetPath !== currentPath) {
console.debug('✅ Access granted, navigating to:', targetPath) console.debug("✅ Access granted, navigating to:", targetPath);
return false // 不需要重定向,让路由正常进行 return false; // 不需要重定向,让路由正常进行
} }
return false return false;
} else { } else {
// 无权限访问,重定向到默认页面 // 无权限访问,重定向到默认页面
const defaultPage = permissionManager.getDefaultPage() const defaultPage = permissionManager.getDefaultPage();
console.debug('❌ Access denied, redirecting to:', defaultPage) console.debug("❌ Access denied, redirecting to:", defaultPage);
if (defaultPage !== currentPath) { if (defaultPage !== currentPath) {
await router.replace(defaultPage) await router.replace(defaultPage);
return true // 已进行重定向 return true; // 已进行重定向
} }
return false return false;
} }
} }
@ -269,17 +278,17 @@ class PageRedirector {
* @param {string} attemptedPage - 尝试访问的页面 * @param {string} attemptedPage - 尝试访问的页面
*/ */
async handleUnauthorizedAccess(router, attemptedPage) { async handleUnauthorizedAccess(router, attemptedPage) {
const primaryRole = permissionManager.getPrimaryRole() const primaryRole = permissionManager.getPrimaryRole();
const defaultPage = permissionManager.getDefaultPage() const defaultPage = permissionManager.getDefaultPage();
console.warn('🚫 Unauthorized access attempt:', { console.warn("🚫 Unauthorized access attempt:", {
attemptedPage, attemptedPage,
primaryRole, primaryRole,
redirectingTo: defaultPage redirectingTo: defaultPage,
}) });
// 重定向到用户有权限的默认页面 // 重定向到用户有权限的默认页面
await router.replace(defaultPage) await router.replace(defaultPage);
} }
} }
@ -288,10 +297,10 @@ class PageRedirector {
*/ */
class RouteGuard { class RouteGuard {
constructor() { constructor() {
this.appConnectionChecker = new AppConnectionChecker() this.appConnectionChecker = new AppConnectionChecker();
this.identityChecker = new IdentityChecker() this.identityChecker = new IdentityChecker();
this.pageRedirector = new PageRedirector() this.pageRedirector = new PageRedirector();
this.isGuardActive = false this.isGuardActive = false;
} }
/** /**
@ -300,11 +309,11 @@ class RouteGuard {
*/ */
install(router) { install(router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
await this.handleRouteChange(to, from, next) await this.handleRouteChange(to, from, next);
}) });
this.isGuardActive = true this.isGuardActive = true;
console.debug('🛡️ Route guard installed') console.debug("🛡️ Route guard installed");
} }
/** /**
@ -314,69 +323,68 @@ class RouteGuard {
* @param {Function} next - 路由继续函数 * @param {Function} next - 路由继续函数
*/ */
async handleRouteChange(to, from, next) { async handleRouteChange(to, from, next) {
console.group('🛡️ Route Guard Check') console.group("🛡️ Route Guard Check");
console.debug('📍 Route change:', from.path, '→', to.path) console.debug("📍 Route change:", from.path, "→", to.path);
try { try {
// 1. 检查是否为公共页面(无需任何验证) // 1. 检查是否为公共页面(无需任何验证)
if (this.isPublicPage(to.path)) { if (this.isPublicPage(to.path)) {
console.debug('🌐 Public page, allowing access') console.debug("🌐 Public page, allowing access");
console.groupEnd() console.groupEnd();
next() next();
return return;
} }
// 2. 确保APP连接 // 2. 确保APP连接
console.debug('🔗 Ensuring APP connection...') console.debug("🔗 Ensuring APP connection...");
const isConnected = await this.appConnectionChecker.ensureConnection() const isConnected = await this.appConnectionChecker.ensureConnection();
if (!isConnected) { if (!isConnected) {
console.error('❌ APP connection failed, redirecting to error page') console.error("❌ APP connection failed, redirecting to error page");
console.groupEnd() console.groupEnd();
next(PAGES.NOT_APP) next(PAGES.NOT_APP);
return return;
} }
// 3. 检查用户身份是否已加载 // 3. 检查用户身份是否已加载
if (!permissionManager.isLoaded()) { if (!permissionManager.isLoaded()) {
console.debug('🔍 Identity not loaded, checking...') console.debug("🔍 Identity not loaded, checking...");
const userId = getUserId() const userId = getUserId();
if (!userId) { if (!userId) {
console.debug('❌ No user ID available, redirecting to apply') console.debug("❌ No user ID available, redirecting to apply");
console.groupEnd() console.groupEnd();
next(PAGES.APPLY) next(PAGES.APPLY);
return return;
} }
// 检查用户身份 // 检查用户身份
await this.identityChecker.checkUserIdentity(userId) await this.identityChecker.checkUserIdentity(userId);
} }
// 4. 检查页面访问权限 // 4. 检查页面访问权限
const hasPermission = permissionManager.hasPagePermission(to.path) const hasPermission = permissionManager.hasPagePermission(to.path);
if (hasPermission) { if (hasPermission) {
console.debug('✅ Permission granted, allowing access') console.debug("✅ Permission granted, allowing access");
console.groupEnd() console.groupEnd();
next() next();
} else { } else {
console.debug('❌ Permission denied') console.debug("❌ Permission denied");
const defaultPage = permissionManager.getDefaultPage() const defaultPage = permissionManager.getDefaultPage();
console.debug('🔄 Redirecting to default page:', defaultPage) console.debug("🔄 Redirecting to default page:", defaultPage);
console.groupEnd() console.groupEnd();
next(defaultPage) next(defaultPage);
} }
} catch (error) { } catch (error) {
console.error('❌ Route guard error:', error) console.error("❌ Route guard error:", error);
console.groupEnd() console.groupEnd();
// 根据错误类型决定重定向目标 // 根据错误类型决定重定向目标
if (error.message && error.message.includes('connection')) { if (error.message && error.message.includes("connection")) {
next(PAGES.NOT_APP) // 连接相关错误 next(PAGES.NOT_APP); // 连接相关错误
} else { } else {
next(PAGES.APPLY) // 其他错误 next(PAGES.APPLY); // 其他错误
} }
} }
} }
@ -388,12 +396,18 @@ class RouteGuard {
*/ */
isPublicPage(path) { isPublicPage(path) {
const publicPages = [ const publicPages = [
PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查 PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查
PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面 PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面
'/404', // 404页面 "/404", // 404页面
'/error' // 通用错误页面 "/error", // 通用错误页面
]
return publicPages.includes(path) // "/admin-center", //管理员中心(临时配置)
"/item-distribution", //赠送页面(临时配置)
"/recharge", //支付前页面(临时配置)
"/recharge-standard", //标准支付页面(临时配置)
"/recharge-freight-agent", //金币代理支付页面(临时配置)
];
return publicPages.includes(path);
} }
/** /**
@ -403,26 +417,26 @@ class RouteGuard {
*/ */
async checkAndRedirect(router, userId) { async checkAndRedirect(router, userId) {
try { try {
console.debug('🔄 Manual identity check and redirect') console.debug("🔄 Manual identity check and redirect");
// 检查身份 // 检查身份
const primaryRole = await this.identityChecker.checkUserIdentity(userId) const primaryRole = await this.identityChecker.checkUserIdentity(userId);
// 获取当前页面 // 获取当前页面
const currentPath = router.currentRoute.value.path const currentPath = router.currentRoute.value.path;
// 检查当前页面权限 // 检查当前页面权限
const hasPermission = permissionManager.hasPagePermission(currentPath) const hasPermission = permissionManager.hasPagePermission(currentPath);
if (!hasPermission) { if (!hasPermission) {
// 重定向到默认页面 // 重定向到默认页面
await this.pageRedirector.redirectToCorrectPage(router, currentPath, currentPath) await this.pageRedirector.redirectToCorrectPage(router, currentPath, currentPath);
} }
return primaryRole return primaryRole;
} catch (error) { } catch (error) {
console.error('❌ Manual check and redirect failed:', error) console.error("❌ Manual check and redirect failed:", error);
throw error throw error;
} }
} }
@ -430,17 +444,17 @@ class RouteGuard {
* 重置守卫状态 * 重置守卫状态
*/ */
reset() { reset() {
this.appConnectionChecker.reset() this.appConnectionChecker.reset();
this.identityChecker.reset() this.identityChecker.reset();
permissionManager.reset() permissionManager.reset();
console.debug('🔄 Route guard reset') console.debug("🔄 Route guard reset");
} }
} }
// 创建全局路由守卫实例 // 创建全局路由守卫实例
export const routeGuard = new RouteGuard() export const routeGuard = new RouteGuard();
// 便捷方法 // 便捷方法
export const installRouteGuard = (router) => routeGuard.install(router) export const installRouteGuard = (router) => routeGuard.install(router);
export const checkAndRedirect = (router, userId) => routeGuard.checkAndRedirect(router, userId) export const checkAndRedirect = (router, userId) => routeGuard.checkAndRedirect(router, userId);
export const resetRouteGuard = () => routeGuard.reset() export const resetRouteGuard = () => routeGuard.reset();

197
src/views/Recharge.vue Normal file
View File

@ -0,0 +1,197 @@
<!-- Recharge.vue -->
<template>
<div class="fullPage">
<!-- 顶部 -->
<MobileHeader title="Recharge" showHelp="true" @help="popupHelp" />
<!-- 内容 -->
<div style="padding: 4%">
<!-- 输入框 -->
<div style="display: flex; gap: 20px; width: 100%">
<input
style="
flex: 1;
border: 0;
border-radius: 8px;
box-shadow: 0 0 2px 2px #00000020;
padding: 10px 8px;
"
type="text"
placeholder="Please enter your Likei's lD"
v-model="targetUserId"
/>
<button
style="
border: 0;
border-radius: 8px;
font-weight: 590;
color: white;
padding: 10px 8px;
background-color: #c670ff;
background: linear-gradient(to bottom, rgba(198, 112, 255, 1), rgba(119, 38, 255, 1));
box-shadow: 0 0 1px 1px #00000020;
"
@click="checkUserId"
>
Recharge
</button>
</div>
<!-- 功能介绍 -->
<div style="margin-top: 16px">
<p style="font-weight: bold">Frequently asked question:</p>
<div class="textContent">
<p>1.Question:</p>
<p>How to check whether the account recharge amounthasbeen received.</p>
<p>Ask:</p>
<p>
Firstly, open Likei, enter the 'My' page, click on theWallet' page, and select the
recharge record in theupper right corner.
</p>
</div>
</div>
</div>
<!-- 弹窗 -->
<!-- 遮罩层 -->
<div
style="
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 9999;
background-color: #00000080;
display: flex;
justify-content: center;
align-items: center;
"
v-show="dialogshow"
@click="closedPopup"
>
<div
style="
width: 80%;
padding: 8%;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 20px;
"
@click.stop
>
<!-- 标题行 -->
<div
style="
width: 100%;
display: flex;
justify-content: center;
font-weight: 800;
font-size: 16px;
position: relative;
padding: 5px 0;
"
>
Help
<div
style="
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
font-weight: bold;
"
@click="closedPopup"
>
x
</div>
</div>
<!-- 内容 -->
<div>
<p style="font-weight: bold">User recharge instructions:</p>
<div class="textContent">
<p>
1Before recharging,the user needs toprovide the correct Likei lD to complete
thisrecharge.
</p>
<p>
2After the coin seller recharges, therecharged coins will be directly sent to
thecoin seller account.
</p>
<p>
3How to obtain an lD for an account:(1)In Likei open the 'My' page.(2)You can confrm
the account lD at the topof the page.
</p>
<p>
4lf the lD account you enter is a coinseller account, the coin will be sent to
thecoin seller center of that account.
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import MobileHeader from "../components/MobileHeader.vue";
import { onMounted, ref, computed } from "vue";
import { searchUser } from "@/api/userInfo";
// import { getUserIdentity } from "@/api/wallet";
import { useRouter } from "vue-router";
const router = useRouter();
const dialogshow = ref(false);
//
const popupHelp = () => {
dialogshow.value = true;
};
//
const closedPopup = () => {
dialogshow.value = false;
};
const targetUserId = ref("");
const checkUserId = async () => {
console.log("充值目标用户id", targetUserId.value);
try {
const targetUserInfo = await searchUser(targetUserId.value);
console.log("targetUserInfo:", targetUserInfo);
if (targetUserInfo.status && targetUserInfo.body.id) {
router.push({ path: "/recharge-freight-agent", query: { targetUserId: targetUserId.value } });
}
} catch (error) {
console.log("error:", error);
// id
showWarning("User info not found");
throw error;
}
};
</script>
<style scoped>
.fullPage {
width: 100vw;
height: 100vh;
background-image: url(../assets/images/secondBg.png);
background-size: 100% 100%;
background-repeat: no-repeat;
}
.textContent > p {
font-weight: bold;
color: rgba(0, 0, 0, 0.5);
}
input::placeholder {
font-weight: bold;
color: rgba(0, 0, 0, 0.4);
}
</style>

View File

@ -0,0 +1,183 @@
<template>
<div>
<!-- 顶部 -->
<MobileHeader title="Recharge">
<template v-slot:extraFunction>
<div
style="
display: flex;
align-items: center;
position: relative;
color: rgba(187, 146, 255, 1) !important;
"
>
<div style="font-weight: bold">English</div>
<div style="margin-left: 5px">></div>
<transition name="slide-fade">
<div
style="
position: absolute;
right: 0;
top: 100%;
padding: 10px;
display: flex;
flex-direction: column;
width: max-content;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
margin-top: 5px;
backdrop-filter: blur(8px);
"
v-show="visibleList"
class="extraList"
>
<div
style="padding: 8px; opacity: 0.5"
v-for="(item, index) in switchList"
:key="index"
>
{{ item }}
</div>
</div>
</transition>
</div>
</template>
</MobileHeader>
<div style="padding: 10px">
<div>Recharge ID</div>
<!-- 个人信息 -->
<div>name:{{ userProfile.userNickname }}</div>
<div style="width: 100%; display: flex; justify-content: space-between">
<div>Select a country</div>
<div>图标{{ selectedCountry.countryName }}</div>
</div>
<!-- 支付渠道 -->
<div v-if="channels.length != 0">
<div v-for="(channelItem, index) in channels" :key="index">
<!-- 支付类型 -->
<div
style="
display: flex;
border-radius: 8px;
box-shadow: 0 0 4px 0 #00000050;
align-items: center;
"
>
<img :src="channelItem.channel.channelIcon" alt="" style="width: 10%; margin: 10px" />
<div style="font-weight: 500">{{ channelItem.channel.channelName }}</div>
</div>
<!-- 支付商品 -->
<div
style="
border-radius: 8px;
box-shadow: 0 0 4px 0 #00000050;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
padding: 10px;
margin-top: 1rem;
"
>
<div
style="
width: 100%;
border-radius: 8px;
box-shadow: 0 0 4px 0 #00000050;
display: flex;
flex-direction: column;
align-items: center;
"
v-for="(goods, index) in commodityList"
:key="index"
@click="goPay(channelItem, goods)"
>
<img src="../assets/icon/coin.png" alt="" style="width: 30%" />
<div>{{ goods.content }}</div>
<div>${{ goods.amountUsd }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import MobileHeader from "../components/MobileHeader.vue";
import { searchUser, searchPayRechargeUser } from "@/api/userInfo";
// import { getUserIdentity } from "@/api/wallet";
import { getPayAplication, getAppCommodityCard, payPlaceAnOrderRecharge } from "@/api/pay";
import { useRoute } from "vue-router";
const route = useRoute();
const applicationId = ref("1963531459019739137");
const userProfile = ref({}); //
const countryList = ref([]); //
const regionId = ref(""); //id
const selectedCountry = ref("");
const commodityList = ref([]); //
const channels = ref([]); //
const selectedCommodity = ref({}); //
const goPay = async (item, commodity) => {
selectedCommodity.value = commodity;
let payData = {
applicationId: applicationId.value,
payCountryId: applicationId.value,
goodsId: commodity.id,
userId: userProfile.id,
channelCode: item.channel.channelCode,
};
const payRes = await payPlaceAnOrderRecharge(data);
const body = payRes.body;
if (!body) {
console.log("Payment not available, please contact administrator!!!");
return;
}
if (body.factoryCode === "AIRWALLEX") {
}
};
onMounted(async () => {
const PayAplication = await getPayAplication(applicationId.value);
console.log("PayAplication:", PayAplication);
if (PayAplication.body && PayAplication.body.appCode) {
let params = {
sysOrigin: PayAplication.body.appCode,
account: route.query.targetUserId,
};
const userInfo = await searchPayRechargeUser(params);
console.log("userInfo:", userInfo);
if (userInfo.body && userInfo.body.regionId && userInfo.body.countryList.length != 0) {
userProfile.value = { ...userInfo.body.userProfile, type: "GOLD" };
countryList.value = userInfo.body.countryList;
selectedCountry.value = userInfo.body.countryList[0];
regionId.value = userInfo.body.regionId;
let appCommodityCardParams = {
applicationId: applicationId.value,
payCountryId: userInfo.body.countryList[0].id,
regionId: userInfo.body.regionId,
};
const appCommodityCard = await getAppCommodityCard(appCommodityCardParams);
console.log("appCommodityCard:", appCommodityCard);
commodityList.value = appCommodityCard.body.commodity;
channels.value = appCommodityCard.body.channels;
}
}
});
</script>
<style scoped></style>

View File

@ -0,0 +1,203 @@
<template>
<div class="fullPage">
<!-- 上区域 -->
<div
style="
width: 100vw;
height: 40vh;
background-image: url(/src/assets/images/rechargeBg.png);
background-size: 100% 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
"
>
<!-- 顶部 -->
<MobileHeader title="Recharge">
<template v-slot:extraFunction>
<div style="display: flex; align-items: center; position: relative">
<img src="../assets/icon/listBt.png" style="width: 1.2rem" alt="" @click="listBt" />
<transition name="slide-fade">
<div
style="
position: absolute;
right: 0;
top: 100%;
padding: 10px;
display: flex;
flex-direction: column;
width: max-content;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
margin-top: 5px;
backdrop-filter: blur(8px);
"
v-show="visibleList"
class="extraList"
>
<div
style="padding: 8px; opacity: 0.5"
v-for="(item, index) in switchList"
:key="index"
>
{{ item }}
</div>
</div>
</transition>
</div>
</template>
</MobileHeader>
<!-- 余额展示 -->
<div style="flex: 1; display: flex; justify-content: center; align-items: center">
<img src="/src/assets/icon/coin.png" alt="" style="width: 32px" />
<div style="margin-left: 10px; font-weight: 500">999999</div>
</div>
</div>
<!-- 圆角修饰 -->
<div
style="
background-color: white;
margin-top: -10px;
height: 11px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
"
></div>
<!-- 主要展示 -->
<div style="padding: 10px; display: flex; flex-direction: column; align-items: center">
<!-- 商品展示 -->
<div style="width: 100%; display: flex; flex-direction: column; gap: 20px">
<div
v-for="(goods, index) in coinGoodsList"
:key="index"
style="
width: 100%;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 3px 0.5px #00000050;
display: flex;
justify-content: space-between;
box-sizing: border-box;
"
@click="selectGoods(index)"
:class="[selectedIndex == index ? 'selectedGoods' : 'notSelectedGoods']"
>
<!-- 数量 -->
<div style="display: flex; align-items: center">
<img :src="getImageUrl(goods.cover)" alt="" style="width: 32px; margin-right: 8px" />
<div style="font-weight: 500">{{ goods.amount }}</div>
</div>
<!-- 价格 -->
<div style="font-weight: 500; line-height: 32px">${{ goods.price }}</div>
</div>
</div>
<!-- 充值按钮 -->
<button
style="
width: 70%;
margin: 32px 0;
padding: 10px;
border-radius: 32px;
border: 0;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
"
:class="[selectedIndex != -1 ? 'selectedBt' : 'notSelectedBt']"
>
Recharge
</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import MobileHeader from "../components/MobileHeader.vue";
const switchList = ref(["Gold List", "Recharge List"]);
const visibleList = ref(false);
const listBt = () => {
visibleList.value = !visibleList.value;
};
const coinGoodsList = ref([
{
cover: "/src/assets/icon/coin.png",
amount: 10000,
price: 0.99,
},
{
cover: "/src/assets/icon/coinMore.png",
amount: 50000,
price: 4.99,
},
]);
const getImageUrl = (imgUrl) => {
return new URL(imgUrl, import.meta.url).href;
};
const selectedIndex = ref(-1); //
const selectedGoods = ref({}); //
const selectGoods = (index) => {
if (selectedIndex.value != index) {
selectedIndex.value = index;
selectedGoods.value = coinGoodsList.value[index];
console.log("选中下标:", selectedIndex.value);
console.log("选中商品:", coinGoodsList.value[index]);
} else {
selectedIndex.value = -1;
selectedGoods.value = {};
}
};
</script>
<style scoped>
.fullPage {
}
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateY(-10px);
opacity: 0;
}
/* 未选中状态 */
.notSelectedGoods {
border: 1px solid transparent;
}
/* 选中状态 */
.selectedGoods {
background-color: rgba(187, 146, 255, 1);
/* border: 1px solid rgba(119, 38, 255, 1); */
border: 1px solid rgba(119, 38, 255, 1);
}
/* 未选中状态 */
.notSelectedBt {
background-color: white;
color: rgba(0, 0, 0, 0.4);
}
/* 选中状态 */
.selectedBt {
background: linear-gradient(to bottom, rgba(198, 112, 255, 0.7), rgba(119, 38, 255, 0.7));
color: white;
}
.extraList > div:not(:last-child) {
border-bottom: 0.1px solid black;
}
</style>