更新版
This commit is contained in:
parent
819ad5a45b
commit
a04b27b0c3
322
src/api/teamBill.js
Normal file
322
src/api/teamBill.js
Normal file
@ -0,0 +1,322 @@
|
||||
import { get, post } from '../utils/http.js'
|
||||
|
||||
/**
|
||||
* 获取团队成员列表
|
||||
* @param {string} teamId - 团队ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const getTeamMembers = async (teamId) => {
|
||||
try {
|
||||
const response = await get(`/team/members?id=${teamId}`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team members:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除团队成员
|
||||
* @param {string} memberId - 成员ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const deleteMember = async (memberId) => {
|
||||
try {
|
||||
const response = await post('/team/member/remove', {
|
||||
memberId: memberId
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to delete member:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取团队发布的政策
|
||||
* @param {string} teamId - 团队ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const getTeamReleasePolicy = async (teamId) => {
|
||||
try {
|
||||
const response = await get(`/team/release/policy?id=${teamId}`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team release policy:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户等待申请的团队信息
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const getUserSendJoinWaitApplyTeamProfile = async () => {
|
||||
try {
|
||||
const response = await get('/team/wait-apply/profile')
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to get user wait apply team profile:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索团队信息
|
||||
* @param {string} account - 团队账户
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const searchTeamByAccount = async (account) => {
|
||||
try {
|
||||
const response = await get(`/team/search-account?account=${account}`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to search team by account:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交团队申请
|
||||
* @param {string} teamId - 团队ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const sendTeamApplyJoin = async (teamId) => {
|
||||
try {
|
||||
const response = await post('/team/user/apply', {
|
||||
teamId,
|
||||
reason: 'JOIN'
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to send team apply join:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消加入申请
|
||||
* @param {string} messageId - 申请消息ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const cancelJoinApplyById = async (messageId) => {
|
||||
try {
|
||||
const response = await post('/team/user/join-apply-cancel', {
|
||||
id: messageId
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel join apply:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请消息列表
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const getInviteMessageList = async () => {
|
||||
try {
|
||||
const response = await get('/anchor-agent/invite/message')
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to get invite message list:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理邀请消息
|
||||
* @param {object} data - 处理数据 {id, status, contact}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const processInviteMessage = async (data) => {
|
||||
try {
|
||||
const response = await post('/anchor-agent/invite/process', data)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to process invite message:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取团队成员工作报告
|
||||
* @param {string} userId - 用户ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const getTeamMemberWork = async (userId) => {
|
||||
try {
|
||||
const response = await get(`/team/member-work?userId=${userId}`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team member work:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化显示状态
|
||||
* @param {string} status - 状态值
|
||||
* @returns {object} 包含显示文本和样式类的对象
|
||||
*/
|
||||
export const formatBillStatus = (status) => {
|
||||
const statusMap = {
|
||||
'UNPAID': {
|
||||
text: 'In Progress',
|
||||
class: 'in-progress',
|
||||
color: '#3B82F6'
|
||||
},
|
||||
'HANG_UP': {
|
||||
text: 'Pending',
|
||||
class: 'pending',
|
||||
color: '#EF4444'
|
||||
},
|
||||
'PAID': {
|
||||
text: 'Completed',
|
||||
class: 'completed',
|
||||
color: '#10B981'
|
||||
},
|
||||
'SETTLED': {
|
||||
text: 'Settled',
|
||||
class: 'settled',
|
||||
color: '#059669'
|
||||
}
|
||||
}
|
||||
|
||||
return statusMap[status] || {
|
||||
text: status,
|
||||
class: 'unknown',
|
||||
color: '#666'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间显示
|
||||
* @param {number|string} minutes - 分钟数
|
||||
* @returns {string} 格式化的时间字符串
|
||||
*/
|
||||
export const formatOnlineTime = (minutes) => {
|
||||
if (!minutes || minutes === '0') return '0h'
|
||||
const numMinutes = parseInt(minutes)
|
||||
const hours = Math.floor(numMinutes / 60)
|
||||
const mins = numMinutes % 60
|
||||
if (hours > 0) {
|
||||
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
|
||||
}
|
||||
return `${mins}m`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化礼物价值显示
|
||||
* @param {string|number} value - 礼物价值
|
||||
* @returns {string} 格式化的价值字符串
|
||||
*/
|
||||
export const formatGiftValue = (value) => {
|
||||
if (!value || value === '0') return '$0'
|
||||
|
||||
// 如果已经是格式化的字符串,直接返回
|
||||
if (typeof value === 'string' && value.includes('K')) {
|
||||
return `$${value}`
|
||||
}
|
||||
|
||||
const numValue = parseFloat(value)
|
||||
if (isNaN(numValue)) return '$0'
|
||||
|
||||
if (numValue >= 1000000) {
|
||||
return `$${(numValue / 1000000).toFixed(1)}M`
|
||||
} else if (numValue >= 1000) {
|
||||
return `$${(numValue / 1000).toFixed(1)}K`
|
||||
}
|
||||
return `$${numValue.toFixed(0)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化钻石价值显示
|
||||
* @param {string|number} value - 钻石价值
|
||||
* @returns {string} 格式化的钻石价值字符串
|
||||
*/
|
||||
export const formatDiamondValue = (value) => {
|
||||
if (!value || value === '0') return '0'
|
||||
const numValue = parseInt(value)
|
||||
if (isNaN(numValue)) return '0'
|
||||
|
||||
if (numValue >= 1000000) {
|
||||
return `${(numValue / 1000000).toFixed(1)}M`
|
||||
} else if (numValue >= 1000) {
|
||||
return `${(numValue / 1000).toFixed(1)}K`
|
||||
}
|
||||
return numValue.toLocaleString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期显示
|
||||
* @param {number} dateNumber - 日期数字格式 (如: 20250808)
|
||||
* @returns {string} 格式化的日期字符串
|
||||
*/
|
||||
export const formatDateDisplay = (dateNumber) => {
|
||||
if (!dateNumber) return ''
|
||||
const dateStr = dateNumber.toString()
|
||||
if (dateStr.length !== 8) return dateStr
|
||||
|
||||
const year = dateStr.substring(0, 4)
|
||||
const month = dateStr.substring(4, 6)
|
||||
const day = dateStr.substring(6, 8)
|
||||
return `${month}/${day}/${year}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化账户显示
|
||||
* @param {object} memberProfile - 成员资料对象
|
||||
* @returns {string} 格式化的账户字符串
|
||||
*/
|
||||
export const formatAccount = (memberProfile) => {
|
||||
if (!memberProfile) return ''
|
||||
|
||||
// 如果有特殊ID,优先显示特殊ID
|
||||
if (memberProfile.ownSpecialId && memberProfile.ownSpecialId.account) {
|
||||
return memberProfile.ownSpecialId.account
|
||||
}
|
||||
|
||||
// 否则显示普通账户
|
||||
return memberProfile.account || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以领取道具奖励
|
||||
* @param {object} item - 账单项目
|
||||
* @returns {boolean} 是否可以领取奖励
|
||||
*/
|
||||
export const canReceivePropsReward = (item) => {
|
||||
if (item.status !== 'SETTLED') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!item.target || !item.target.settlementResult) {
|
||||
return false
|
||||
}
|
||||
|
||||
const propsRewards = item.target.settlementResult.propsRewards
|
||||
if (!propsRewards || propsRewards.length <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取政策道具奖励
|
||||
* @param {string} targetId - 目标ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const receivePolicyPropsReward = async (targetId) => {
|
||||
try {
|
||||
const response = await post('/team/policy-props-reward', {
|
||||
id: targetId
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to receive policy props reward:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { get } from '../utils/http.js'
|
||||
import {get, post} from '../utils/http.js'
|
||||
|
||||
/**
|
||||
* 获取钱包流水详情
|
||||
* @returns {Promise} 返回流水详情数据
|
||||
*/
|
||||
export function getWalletDetails() {
|
||||
return get('/wallet/salary-diamond/balance/water/details')
|
||||
return get('/wallet/bank/balance/water/details')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,6 +24,25 @@ export function userBankCheckTransfer() {
|
||||
return get('/wallet/bank/check/transfer')
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证兑换金币限制
|
||||
* @returns {Promise} 返回用户角色和权限信息
|
||||
*/
|
||||
export function userSalaryCheckExchange() {
|
||||
return get('/wallet/salary-diamond/check/exchange-gold')
|
||||
}
|
||||
|
||||
// 获取用户银行卡余额-兑换金币
|
||||
export function userBankExchangeGold(data) {
|
||||
return post('/wallet/bank/exchange/gold', data)
|
||||
}
|
||||
|
||||
// 用户银行卡余额-转账
|
||||
export function userBankTransfer(data) {
|
||||
return post('/wallet/bank/transfer', data)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 搜索用户资料
|
||||
* @param {string} type - 用户类型 (AGENT/BD/ANCHOR)
|
||||
@ -46,7 +65,7 @@ export function formatDateTime(timestamp) {
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
|
||||
532
src/components/team/TeamBillMore.vue
Normal file
532
src/components/team/TeamBillMore.vue
Normal file
@ -0,0 +1,532 @@
|
||||
<template>
|
||||
<div class="team-bill-more">
|
||||
<div class="bill-header">
|
||||
<h2>{{ billData?.billTitle }}</h2>
|
||||
<span :class="['bill-status', getStatusClass(billData?.status)]">
|
||||
{{ getStatusText(billData?.status) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 账单汇总信息 -->
|
||||
<div class="bill-summary-card">
|
||||
<h3>Summary</h3>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Gift Value:</span>
|
||||
<span class="summary-value">{{ formatGiftValue(billData?.target?.acceptGiftValue) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Online Time:</span>
|
||||
<span class="summary-value">{{ formatOnlineTime(billData?.target?.onlineTime) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Diamond:</span>
|
||||
<span class="summary-value diamond-value">{{ formatDiamondValue(billData?.target?.acceptDiamondValue) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Effective Days:</span>
|
||||
<span class="summary-value">{{ billData?.target?.effectiveDay || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结算结果(如果已结算) -->
|
||||
<div v-if="billData?.status === 'SETTLED' && billData?.target?.settlementResult" class="settlement-info">
|
||||
<div class="settlement-row">
|
||||
<span class="settlement-label">Target Level:</span>
|
||||
<span class="settlement-value">{{ billData.target.settlementResult.level || '' }}</span>
|
||||
</div>
|
||||
<div class="settlement-row">
|
||||
<span class="settlement-label">Member Salary:</span>
|
||||
<span class="settlement-value salary-value">${{ billData.target.settlementResult.memberSalary || 0 }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 道具奖励 -->
|
||||
<div v-if="canReceivePropsReward(billData)" class="props-reward-section">
|
||||
<div class="props-reward-header">
|
||||
<span class="props-label">
|
||||
{{ billData.target.settlementResult.propsReceived ? 'Props Received:' : 'Props Reward:' }}
|
||||
</span>
|
||||
<button
|
||||
v-if="!billData.target.settlementResult.propsReceived"
|
||||
class="receive-props-btn"
|
||||
@click="receivePropsReward"
|
||||
:disabled="receivingReward"
|
||||
>
|
||||
{{ receivingReward ? 'Receiving...' : 'Receive Props' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="props-list">
|
||||
<div
|
||||
v-for="(prop, index) in billData.target.settlementResult.propsRewards"
|
||||
:key="index"
|
||||
class="prop-item"
|
||||
>
|
||||
<div class="prop-info">
|
||||
<span class="prop-name">{{ prop.name }}</span>
|
||||
<span class="prop-type">{{ prop.type }}</span>
|
||||
</div>
|
||||
<div class="prop-value">${{ prop.amount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 每日详情 -->
|
||||
<div class="daily-details-card">
|
||||
<h3>Daily Details</h3>
|
||||
<div v-if="billData?.target?.dailyTargets?.length > 0" class="daily-list">
|
||||
<div
|
||||
v-for="(daily, index) in billData.target.dailyTargets"
|
||||
:key="index"
|
||||
class="daily-item"
|
||||
>
|
||||
<div class="daily-header">
|
||||
<span class="daily-date">{{ formatDate(daily.dateNumber) }}</span>
|
||||
<span class="daily-time">{{ formatCreateTime(daily.createTime) }}</span>
|
||||
</div>
|
||||
<div class="daily-stats">
|
||||
<div class="daily-stat">
|
||||
<span class="stat-label">Gift:</span>
|
||||
<span class="stat-value">{{ formatGiftValue(daily.acceptGiftValue) }}</span>
|
||||
</div>
|
||||
<div class="daily-stat">
|
||||
<span class="stat-label">Time:</span>
|
||||
<span class="stat-value">{{ formatOnlineTime(daily.onlineTime) }}</span>
|
||||
</div>
|
||||
<div class="daily-stat">
|
||||
<span class="stat-label">Diamond:</span>
|
||||
<span class="stat-value diamond-value">{{ formatDiamondValue(daily.acceptDiamondValue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-daily-data">
|
||||
<div class="no-data-icon">📅</div>
|
||||
<p>No daily data available</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<div class="close-button-container">
|
||||
<button class="close-btn" @click="$emit('close')">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
formatGiftValue,
|
||||
formatOnlineTime,
|
||||
formatDiamondValue,
|
||||
formatDateDisplay,
|
||||
canReceivePropsReward,
|
||||
receivePolicyPropsReward
|
||||
} from '../../api/teamBill.js'
|
||||
|
||||
const props = defineProps({
|
||||
billData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'propsReceived'])
|
||||
|
||||
const receivingReward = ref(false)
|
||||
|
||||
// 格式化状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'UNPAID': 'In Progress',
|
||||
'HANG_UP': 'Pending',
|
||||
'PAID': 'Completed',
|
||||
'SETTLED': 'Settled'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
const statusClassMap = {
|
||||
'UNPAID': 'in-progress',
|
||||
'HANG_UP': 'pending',
|
||||
'PAID': 'completed',
|
||||
'SETTLED': 'settled'
|
||||
}
|
||||
return statusClassMap[status] || 'unknown'
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateNumber) => {
|
||||
return formatDateDisplay(dateNumber)
|
||||
}
|
||||
|
||||
// 格式化创建时间
|
||||
const formatCreateTime = (timestamp) => {
|
||||
if (!timestamp) return ''
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 领取道具奖励
|
||||
const receivePropsReward = async () => {
|
||||
if (!props.billData?.target?.id || receivingReward.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
receivingReward.value = true
|
||||
await receivePolicyPropsReward(props.billData.target.id)
|
||||
|
||||
// 更新状态
|
||||
if (props.billData.target.settlementResult) {
|
||||
props.billData.target.settlementResult.propsReceived = true
|
||||
}
|
||||
|
||||
emit('propsReceived')
|
||||
} catch (error) {
|
||||
console.error('Failed to receive props reward:', error)
|
||||
alert('Failed to receive props reward. Please try again.')
|
||||
} finally {
|
||||
receivingReward.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.team-bill-more {
|
||||
background-color: #f1f2f3;
|
||||
padding: 20px;
|
||||
min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
/* 账单头部 */
|
||||
.bill-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.bill-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bill-status {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-status.in-progress {
|
||||
background-color: #DBEAFE;
|
||||
color: #1D4ED8;
|
||||
}
|
||||
|
||||
.bill-status.pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.bill-status.completed {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.bill-status.settled {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.bill-summary-card,
|
||||
.daily-details-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.bill-summary-card h3,
|
||||
.daily-details-card h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 汇总网格 */
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.diamond-value {
|
||||
color: #1E40AF !important;
|
||||
}
|
||||
|
||||
/* 结算信息 */
|
||||
.settlement-info {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.settlement-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settlement-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.settlement-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.salary-value {
|
||||
color: #059669 !important;
|
||||
}
|
||||
|
||||
/* 道具奖励区域 */
|
||||
.props-reward-section {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.props-reward-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.props-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.receive-props-btn {
|
||||
background-color: #3B82F6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.receive-props-btn:hover:not(:disabled) {
|
||||
background-color: #2563EB;
|
||||
}
|
||||
|
||||
.receive-props-btn:disabled {
|
||||
background-color: #9CA3AF;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.props-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.prop-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.prop-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.prop-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.prop-type {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.prop-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
/* 每日列表 */
|
||||
.daily-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.daily-item {
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.daily-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.daily-date {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.daily-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.daily-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.daily-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 无数据状态 */
|
||||
.no-daily-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-data-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.no-daily-data p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 关闭按钮 */
|
||||
.close-button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background-color: #6B7280;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background-color: #4B5563;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.daily-stats {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.daily-stat {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.props-reward-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -29,11 +29,7 @@ const router = createRouter({
|
||||
name: 'host-setting',
|
||||
component: () => import('../views/HostSettingView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/history-salary',
|
||||
name: 'history-salary',
|
||||
component: () => import('../views/HistorySalaryView.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/transfer',
|
||||
name: 'transfer',
|
||||
|
||||
@ -3,7 +3,7 @@ const API_BASE_URL = 'https://api.likeichat.com'
|
||||
|
||||
// 公共请求头配置
|
||||
const COMMON_HEADERS = {
|
||||
'authorization': 'Bearer 1850459451220E4F1FABF65F36A43E76.djIlM0ExOTU0MDU5NzkzOTYzMzkzMDI1JTNBTElLRUklM0ExNzU3NjQ4ODMwMjM3JTNBMTc1NTA1NjgzMDIzNw==',
|
||||
'authorization': 'Bearer F77126B08E13987C3AD88B6015C5B777.djIlM0ExOTU0MDU5NzkzOTYzMzkzMDI1JTNBTElLRUklM0ExNzU3NzYzODU5NzE1JTNBMTc1NTE3MTg1OTcxNQ==',
|
||||
'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google',
|
||||
'req-client': 'Android',
|
||||
'req-imei': '8fa54d728ab449e04f9292329ed44bda',
|
||||
@ -32,6 +32,7 @@ export async function request(url, options = {}) {
|
||||
const config = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...COMMON_HEADERS,
|
||||
...headers
|
||||
},
|
||||
|
||||
@ -139,7 +139,11 @@
|
||||
<div class="team-member-section">
|
||||
<div class="team-member-card" @click="goToTeamMember">
|
||||
<div class="card-content">
|
||||
<span class="member-title">Team Member ({{ teamMemberCount }})</span>
|
||||
<span class="member-title">
|
||||
Team Member
|
||||
<span v-if="loadingMemberCount">(Loading...)</span>
|
||||
<span v-else>({{ teamMemberCount }})</span>
|
||||
</span>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -159,9 +163,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { get } from '../utils/http.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@ -172,7 +177,8 @@ const currentIdentity = ref('agency')
|
||||
const taskTab = ref('agency')
|
||||
|
||||
// 团队成员数量
|
||||
const teamMemberCount = ref(87)
|
||||
const teamMemberCount = ref(0)
|
||||
const loadingMemberCount = ref(false)
|
||||
|
||||
// 薪资信息
|
||||
const salaryInfo = reactive({
|
||||
@ -259,6 +265,33 @@ const transfer = () => {
|
||||
const goToPersonalSalary = () => {
|
||||
router.push('/platform-policy')
|
||||
}
|
||||
|
||||
// 获取团队成员数量
|
||||
const fetchTeamMemberCount = async () => {
|
||||
try {
|
||||
loadingMemberCount.value = true
|
||||
// 使用传入的teamId,这里使用示例中的ID
|
||||
const teamId = '1927993836921233409'
|
||||
|
||||
const response = await get(`/team/members/count?id=${teamId}`)
|
||||
|
||||
if (response.status && typeof response.body === 'number') {
|
||||
teamMemberCount.value = response.body
|
||||
} else {
|
||||
teamMemberCount.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team member count:', error)
|
||||
teamMemberCount.value = 0
|
||||
} finally {
|
||||
loadingMemberCount.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchTeamMemberCount()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="apply-page gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<MobileHeader
|
||||
title="Apply to join the team"
|
||||
:show-help="true"
|
||||
@help="showHelp"
|
||||
<MobileHeader
|
||||
title="Apply to join the team"
|
||||
:show-help="true"
|
||||
@help="showHelp"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
@ -20,9 +20,9 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">Apply ID:</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="agentId"
|
||||
type="text"
|
||||
<input
|
||||
v-model="agentId"
|
||||
type="text"
|
||||
placeholder="Please enter the agent ID"
|
||||
class="form-input"
|
||||
/>
|
||||
@ -45,50 +45,283 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { get, post } from '../utils/http.js'
|
||||
|
||||
const router = useRouter()
|
||||
const agentId = ref('')
|
||||
const showHelpModal = ref(false)
|
||||
|
||||
// 显示帮助
|
||||
// 响应式数据
|
||||
const searchAccount = ref('')
|
||||
const searching = ref(false)
|
||||
const applying = ref(false)
|
||||
const cancelling = ref(false)
|
||||
const showHelpModal = ref(false)
|
||||
const showInviteModal = ref(false)
|
||||
const showContactModal = ref(false)
|
||||
|
||||
const teamProfile = reactive({})
|
||||
const teamOwnUserProfile = reactive({})
|
||||
const applyMessageId = ref('')
|
||||
|
||||
const inviteMessage = ref([])
|
||||
const inviteMessageLoading = ref(false)
|
||||
const processingItem = reactive({})
|
||||
|
||||
const contactForm = reactive({
|
||||
id: '',
|
||||
status: '',
|
||||
contact: ''
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const hasUnreadInvites = computed(() => {
|
||||
return inviteMessage.value.some(msg => msg.status === 0)
|
||||
})
|
||||
|
||||
// 获取用户等待申请的团队信息
|
||||
const loadUserSendJoinWaitApplyTeamProfile = async () => {
|
||||
try {
|
||||
const response = await get('/team/wait-apply/profile')
|
||||
|
||||
if (response.status && response.body) {
|
||||
const result = response.body
|
||||
applyMessageId.value = result.messageId || ''
|
||||
Object.assign(teamProfile, result.teamProfile || {})
|
||||
Object.assign(teamOwnUserProfile, result.ownUserProfile || {})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load wait apply profile:', error)
|
||||
if (error.errorCode === 6001) {
|
||||
// 用户已经在团队中,跳转到团队页面
|
||||
router.replace('/host-center')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索团队
|
||||
const searchTeam = async () => {
|
||||
if (!searchAccount.value.trim()) {
|
||||
alert('Please enter agent ID')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
searching.value = true
|
||||
const response = await get(`/team/search-account?account=${searchAccount.value.trim()}`)
|
||||
|
||||
if (response.status && response.body) {
|
||||
const result = response.body
|
||||
Object.assign(teamProfile, result.teamProfile || {})
|
||||
Object.assign(teamOwnUserProfile, result.ownUserProfile || {})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to search team:', error)
|
||||
if (error.errorCode === 6010) {
|
||||
alert('Team not found!')
|
||||
} else if (error.errorMsg) {
|
||||
alert(error.errorMsg)
|
||||
}
|
||||
} finally {
|
||||
searching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 提交申请加入团队
|
||||
const submitApplyJoin = async () => {
|
||||
if (!teamProfile.id) {
|
||||
alert('Invalid team information')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
applying.value = true
|
||||
const response = await post('/team/user/apply', {
|
||||
teamId: teamProfile.id,
|
||||
reason: 'JOIN'
|
||||
})
|
||||
|
||||
if (response.status && response.body) {
|
||||
applyMessageId.value = response.body
|
||||
alert('Application submitted successfully!')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to apply:', error)
|
||||
if (error.errorMsg) {
|
||||
alert(error.errorMsg)
|
||||
}
|
||||
} finally {
|
||||
applying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消申请
|
||||
const cancelApplication = async () => {
|
||||
if (!applyMessageId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
cancelling.value = true
|
||||
await post('/team/user/join-apply-cancel', {
|
||||
id: applyMessageId.value
|
||||
})
|
||||
|
||||
// 清空团队信息
|
||||
Object.assign(teamProfile, {})
|
||||
Object.assign(teamOwnUserProfile, {})
|
||||
applyMessageId.value = ''
|
||||
|
||||
alert('Application cancelled successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel application:', error)
|
||||
} finally {
|
||||
cancelling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载邀请消息
|
||||
const loadInviteMessage = async () => {
|
||||
try {
|
||||
inviteMessageLoading.value = true
|
||||
const response = await get('/anchor-agent/invite/message')
|
||||
|
||||
if (response.status && response.body) {
|
||||
inviteMessage.value = response.body || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load invite messages:', error)
|
||||
inviteMessage.value = []
|
||||
} finally {
|
||||
inviteMessageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理邀请
|
||||
const processInvite = async (type, item) => {
|
||||
Object.assign(processingItem, item)
|
||||
contactForm.id = item.id
|
||||
|
||||
if (type === 'PASS') {
|
||||
contactForm.status = 1
|
||||
showContactModal.value = true
|
||||
return
|
||||
}
|
||||
|
||||
contactForm.status = 2
|
||||
await submitInviteProcess()
|
||||
}
|
||||
|
||||
// 提交邀请处理
|
||||
const submitInviteProcess = async () => {
|
||||
try {
|
||||
const response = await post('/anchor-agent/invite/process', contactForm)
|
||||
|
||||
if (response.status) {
|
||||
processingItem.status = contactForm.status
|
||||
closeContactModal()
|
||||
|
||||
if (contactForm.status === 1) {
|
||||
// 同意后跳转到主页
|
||||
router.replace('/host-center')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to process invite:', error)
|
||||
if (error.errorMsg) {
|
||||
alert(error.errorMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const getAvatarColor = (name) => {
|
||||
if (!name) return '#8B5CF6'
|
||||
const colors = ['#8B5CF6', '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
|
||||
const index = name.charCodeAt(0) % colors.length
|
||||
return colors[index]
|
||||
}
|
||||
|
||||
const formatAccount = (userProfile) => {
|
||||
if (!userProfile) return ''
|
||||
if (userProfile.ownSpecialId && userProfile.ownSpecialId.account) {
|
||||
return userProfile.ownSpecialId.account
|
||||
}
|
||||
return userProfile.account || ''
|
||||
}
|
||||
|
||||
const truncateName = (name) => {
|
||||
if (!name) return ''
|
||||
return name.length > 12 ? name.substring(0, 12) + '...' : name
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
const showHelp = () => {
|
||||
showHelpModal.value = true
|
||||
}
|
||||
|
||||
// 提交申请
|
||||
const submitApplication = () => {
|
||||
if (!agentId.value.trim()) {
|
||||
alert('Please enter agent ID')
|
||||
const goBackToSearch = () => {
|
||||
Object.assign(teamProfile, {})
|
||||
Object.assign(teamOwnUserProfile, {})
|
||||
applyMessageId.value = ''
|
||||
}
|
||||
|
||||
const showInviteMessages = () => {
|
||||
showInviteModal.value = true
|
||||
loadInviteMessage()
|
||||
}
|
||||
|
||||
const closeInviteModal = () => {
|
||||
showInviteModal.value = false
|
||||
}
|
||||
|
||||
const closeContactModal = () => {
|
||||
showContactModal.value = false
|
||||
contactForm.contact = ''
|
||||
}
|
||||
|
||||
const submitContactInfo = () => {
|
||||
if (!contactForm.contact.trim()) {
|
||||
alert('Please enter WhatsApp number')
|
||||
return
|
||||
}
|
||||
|
||||
// 这里可以添加实际的提交逻辑
|
||||
alert(`Application submitted with Agent ID: ${agentId.value}`)
|
||||
// 提交成功后可以跳转到其他页面
|
||||
// router.push('/host-center')
|
||||
submitInviteProcess()
|
||||
}
|
||||
|
||||
// 组件挂载时执行
|
||||
onMounted(() => {
|
||||
loadUserSendJoinWaitApplyTeamProfile()
|
||||
loadInviteMessage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apply-page {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background-color: #f1f2f3;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
padding: 16px 16px 100px 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 搜索区域 */
|
||||
.search-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.reminder {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@ -142,13 +375,13 @@ const submitApplication = () => {
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #007AFF;
|
||||
border-color: #8B5CF6;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
padding: 12px 24px;
|
||||
background-color: #8B5CF6;
|
||||
background-color: #10B981;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
@ -156,18 +389,202 @@ const submitApplication = () => {
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.apply-btn:hover:not(:disabled) {
|
||||
background-color: #7C3AED;
|
||||
background-color: #059669;
|
||||
}
|
||||
|
||||
.apply-btn:disabled {
|
||||
background-color: #ccc;
|
||||
background-color: #9CA3AF;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.apply-tips {
|
||||
background-color: #F0F9FF;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3B82F6;
|
||||
}
|
||||
|
||||
.apply-tips p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
/* 团队信息区域 */
|
||||
.team-profile-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.team-profile-card {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-owner-info {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
margin: 0 auto 12px auto;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.owner-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.name-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.owner-details {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.owner-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.owner-id {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm,
|
||||
.btn-cancel-apply {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #F3F4F6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #E5E7EB;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background-color: #10B981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-confirm:hover:not(:disabled) {
|
||||
background-color: #059669;
|
||||
}
|
||||
|
||||
.btn-cancel-apply {
|
||||
background-color: #EF4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-cancel-apply:hover:not(:disabled) {
|
||||
background-color: #DC2626;
|
||||
}
|
||||
|
||||
.btn-cancel:disabled,
|
||||
.btn-confirm:disabled,
|
||||
.btn-cancel-apply:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 底部消息栏 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border-top: 1px solid #E5E7EB;
|
||||
padding: 12px 16px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.message-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.message-btn:hover {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
.message-btn.has-unread {
|
||||
background-color: #EF4444;
|
||||
}
|
||||
|
||||
.message-btn.has-unread:hover {
|
||||
background-color: #DC2626;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
background-color: #FBBF24;
|
||||
color: #92400E;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -182,43 +599,332 @@ const submitApplication = () => {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
.help-modal,
|
||||
.invite-modal,
|
||||
.contact-modal {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal h3 {
|
||||
.help-modal {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.help-modal h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.modal p {
|
||||
.help-modal p {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
.help-modal button {
|
||||
padding: 8px 20px;
|
||||
background-color: #007AFF;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 手机适配 */
|
||||
.input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
/* 邀请和联系模态框 */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 邀请列表 */
|
||||
.invite-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.invite-item {
|
||||
background-color: #F6FAFA;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.invite-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.invite-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.invite-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.invite-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.invite-id,
|
||||
.invite-country {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.invite-status {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-passed {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-refused {
|
||||
background-color: #FEE2E2;
|
||||
color: #DC2626;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.invite-message-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.invite-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-refuse,
|
||||
.btn-accept {
|
||||
flex: 1;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-refuse {
|
||||
background-color: #FEE2E2;
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.btn-refuse:hover {
|
||||
background-color: #FECACA;
|
||||
}
|
||||
|
||||
.btn-accept {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.btn-accept:hover {
|
||||
background-color: #A7F3D0;
|
||||
}
|
||||
|
||||
/* 联系方式表单 */
|
||||
.contact-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.contact-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.contact-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contact-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.contact-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.contact-form .form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.contact-form label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.contact-input {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.contact-input:focus {
|
||||
outline: none;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
flex: 1;
|
||||
padding: 8px 16px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
background-color: #9CA3AF;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 加载和空状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-invites {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-invites p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -20,11 +20,11 @@
|
||||
<!-- 兑换部分 -->
|
||||
<div class="exchange-section">
|
||||
<h3>Exchange Gold Coins</h3>
|
||||
|
||||
|
||||
<!-- 金币选择 -->
|
||||
<div class="coins-grid">
|
||||
<div
|
||||
v-for="coin in coinOptions"
|
||||
<div
|
||||
v-for="coin in coinOptions"
|
||||
:key="coin.id"
|
||||
@click="selectCoin(coin)"
|
||||
:class="['coin-item', { selected: selectedCoin === coin.id }]"
|
||||
@ -36,8 +36,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 兑换按钮 -->
|
||||
<button
|
||||
class="exchange-btn"
|
||||
<button
|
||||
class="exchange-btn"
|
||||
@click="exchangeCoins"
|
||||
:disabled="!selectedCoin"
|
||||
>
|
||||
@ -52,31 +52,34 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getBankBalance } from '../api/wallet.js'
|
||||
import { getBankBalance, userSalaryCheckExchange, userBankExchangeGold } from '../api/wallet.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const availableSalary = ref('0')
|
||||
const selectedCoin = ref(null)
|
||||
const loading = ref(false)
|
||||
const isLoadingRole = ref(false)
|
||||
const userRole = ref('')
|
||||
const selectedUserType = ref('')
|
||||
|
||||
// 金币选项
|
||||
const coinOptions = reactive([
|
||||
{ id: 1, amount: 10000, price: 1 },
|
||||
{ id: 2, amount: 10000, price: 1 },
|
||||
{ id: 3, amount: 10000, price: 1 },
|
||||
{ id: 4, amount: 10000, price: 1 },
|
||||
{ id: 5, amount: 10000, price: 1 },
|
||||
{ id: 6, amount: 10000, price: 1 }
|
||||
{ id: 2, amount: 50000, price: 5 },
|
||||
{ id: 3, amount: 100000, price: 10 },
|
||||
{ id: 4, amount: 500000, price: 50 },
|
||||
{ id: 5, amount: 1000000, price: 100 },
|
||||
{ id: 6, amount: 2000000, price: 200 }
|
||||
])
|
||||
|
||||
// 获取银行余额
|
||||
const fetchBankBalance = async () => {
|
||||
loading.value = true
|
||||
|
||||
|
||||
try {
|
||||
const response = await getBankBalance()
|
||||
|
||||
|
||||
if (response.status && typeof response.body === 'number') {
|
||||
// 格式化余额显示,保留2位小数
|
||||
availableSalary.value = response.body.toFixed(2)
|
||||
@ -93,6 +96,106 @@ const fetchBankBalance = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户exchange角色
|
||||
// 检查用户exchange角色
|
||||
const checkUserRole = async () => {
|
||||
isLoadingRole.value = true
|
||||
|
||||
try {
|
||||
const response = await userSalaryCheckExchange()
|
||||
console.log('sssss', response)
|
||||
if (response.status && response.body) {
|
||||
// 根据接口返回的role设置用户角色
|
||||
userRole.value = response.body.role || ''
|
||||
console.log('用户角色:', userRole.value)
|
||||
|
||||
// 设置默认的用户类型
|
||||
if (userRole.value === 'TEAM_OWN') {
|
||||
selectedUserType.value = 'ANCHOR' // TEAM_OWN 默认选择 ANCHOR
|
||||
} else {
|
||||
selectedUserType.value = 'AGENT' // 其他角色默认选择 AGENT
|
||||
}
|
||||
} else {
|
||||
// 接口返回成功但没有有效数据的情况
|
||||
userRole.value = ''
|
||||
selectedUserType.value = 'USER'
|
||||
router.back() // 对于其他严重错误,也建议退出页面
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
// 处理特定错误情况
|
||||
if (error.errorCode === 1083) {
|
||||
// NOT_OPEN_ERROR
|
||||
alert('Exchange function is not available')
|
||||
} else if (error.message && error.message.includes('HTTP Error: 406')) {
|
||||
// 处理 406 Not Acceptable 状态码
|
||||
alert('The server cannot produce a response matching the list of acceptable values defined in the request headers')
|
||||
} else {
|
||||
// 其他错误情况
|
||||
alert('Failed to load user information')
|
||||
}
|
||||
router.back() // 对于其他严重错误,也建议退出页面
|
||||
|
||||
// 清理状态
|
||||
userRole.value = ''
|
||||
selectedUserType.value = 'USER'
|
||||
} finally {
|
||||
isLoadingRole.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const exchangeCoins = async () => {
|
||||
if (!selectedCoin.value) {
|
||||
alert('Please select a coin amount first')
|
||||
return
|
||||
}
|
||||
|
||||
const selectedCoinData = coinOptions.find(coin => coin.id === selectedCoin.value)
|
||||
|
||||
// 检查余额是否足够
|
||||
const coinPrice = selectedCoinData.price
|
||||
const availableBalance = parseFloat(availableSalary.value)
|
||||
|
||||
if (isNaN(availableBalance) || availableBalance < coinPrice) {
|
||||
alert(`Insufficient balance. Your current balance is $${availableBalance.toFixed(2)}, but you need $${coinPrice} to exchange.`)
|
||||
return
|
||||
}
|
||||
|
||||
// 显示确认对话框
|
||||
const confirmExchange = confirm(`Do you want to exchange ${selectedCoinData.amount} coins for $${selectedCoinData.price}?`)
|
||||
|
||||
if (confirmExchange) {
|
||||
try {
|
||||
// 调用接口进行兑换
|
||||
const response = await userBankExchangeGold({
|
||||
coinId: selectedCoinData.id,
|
||||
amount: selectedCoinData.price,
|
||||
price: selectedCoinData.price
|
||||
})
|
||||
console.log('res', response)
|
||||
|
||||
if (response.status === true || response.errorCode === 0) {
|
||||
alert(`Successfully exchanged ${selectedCoinData.amount} coins for $${selectedCoinData.price}`)
|
||||
|
||||
// 刷新银行余额
|
||||
await fetchBankBalance()
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
|
||||
} else {
|
||||
// 接口调用失败
|
||||
const errorMessage = response.message || 'Exchange failed, please try again later.'
|
||||
alert(errorMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Exchange error:', error)
|
||||
alert('An error occurred during the exchange process. Please try again later.')
|
||||
}
|
||||
}
|
||||
// 如果用户取消,则什么都不做
|
||||
}
|
||||
|
||||
// 方法
|
||||
const showDetails = () => {
|
||||
router.push('/information-details')
|
||||
@ -107,28 +210,10 @@ const selectCoin = (coin) => {
|
||||
}
|
||||
}
|
||||
|
||||
const exchangeCoins = () => {
|
||||
if (!selectedCoin.value) {
|
||||
alert('Please select a coin amount first')
|
||||
return
|
||||
}
|
||||
|
||||
const selectedCoinData = coinOptions.find(coin => coin.id === selectedCoin.value)
|
||||
|
||||
// 显示确认对话框
|
||||
const confirmExchange = confirm(`Do you want to exchange ${selectedCoinData.amount} coins for $${selectedCoinData.price}?`)
|
||||
|
||||
if (confirmExchange) {
|
||||
alert(`Successfully exchanged ${selectedCoinData.amount} coins for $${selectedCoinData.price}`)
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取银行余额
|
||||
onMounted(() => {
|
||||
fetchBankBalance()
|
||||
checkUserRole()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -126,14 +126,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getBankBalance } from '../api/wallet.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 当前身份
|
||||
const currentIdentity = ref('host')
|
||||
const loading = ref(false)
|
||||
|
||||
// 身份选项
|
||||
const identities = [
|
||||
@ -142,6 +144,29 @@ const identities = [
|
||||
{ label: 'Coin Seller', value: 'coin-seller' }
|
||||
]
|
||||
|
||||
// 获取银行余额
|
||||
const fetchBankBalance = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await getBankBalance()
|
||||
|
||||
if (response.status && typeof response.body === 'number') {
|
||||
// 格式化余额显示,保留2位小数
|
||||
salaryInfo.currentSalary = response.body.toFixed(2)
|
||||
} else {
|
||||
salaryInfo.currentSalary = 0.00
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch bank balance:', error)
|
||||
salaryInfo.currentSalary = 0.00
|
||||
// 可以显示错误提示
|
||||
alert('Failed to load balance. Please try again.')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
const userInfo = reactive({
|
||||
name: 'User1',
|
||||
@ -150,8 +175,8 @@ const userInfo = reactive({
|
||||
|
||||
// 收入信息
|
||||
const salaryInfo = reactive({
|
||||
hostSalary: 12345,
|
||||
currentSalary: 111
|
||||
hostSalary: 120,
|
||||
currentSalary: 0.00
|
||||
})
|
||||
|
||||
// 任务信息
|
||||
@ -207,6 +232,10 @@ const transfer = () => {
|
||||
router.push('/transfer')
|
||||
}
|
||||
|
||||
// 页面加载时获取银行余额
|
||||
onMounted(() => {
|
||||
fetchBankBalance()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -3,89 +3,158 @@
|
||||
<MobileHeader title="Platform Policy" />
|
||||
|
||||
<div class="content">
|
||||
<div class="policy-list">
|
||||
<div
|
||||
v-for="policy in policies"
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading policy...</p>
|
||||
</div>
|
||||
|
||||
<!-- 政策列表 -->
|
||||
<div v-else-if="policies.length > 0" class="policy-list">
|
||||
<div
|
||||
v-for="policy in policies"
|
||||
:key="policy.level"
|
||||
class="policy-item"
|
||||
>
|
||||
<div class="policy-header">
|
||||
<h3>{{ policy.level }}</h3>
|
||||
<h3>Lv.{{ policy.level }}</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="policy-details">
|
||||
<div class="policy-grid">
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Time (hours)</span>
|
||||
<span class="cell-value">{{ policy.timeHours }}</span>
|
||||
<span class="cell-value">{{ policy.onlineTime || 0 }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Target</span>
|
||||
<span class="cell-value">{{ policy.target }}</span>
|
||||
<span class="cell-value">{{ formatTarget(policy.target) }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Host Salary</span>
|
||||
<span class="cell-value">${{ policy.hostSalary }}</span>
|
||||
<span class="cell-label">Member Salary</span>
|
||||
<span class="cell-value salary">${{ policy.memberSalary || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="policy-grid bottom-row">
|
||||
|
||||
<!-- 只有角色是 OWN 时才显示额外的薪资信息 -->
|
||||
<div v-if="role === 'OWN'" class="policy-grid bottom-row">
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Agent Salary</span>
|
||||
<span class="cell-value">${{ policy.agentSalary }}</span>
|
||||
<span class="cell-label">Owner Salary</span>
|
||||
<span class="cell-value salary">${{ policy.ownSalary || 0 }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Total Salary</span>
|
||||
<span class="cell-value">${{ policy.totalSalary }}</span>
|
||||
<span class="cell-value salary">${{ policy.totalSalary || 0 }}</span>
|
||||
</div>
|
||||
<div class="policy-cell empty"></div>
|
||||
</div>
|
||||
|
||||
<!-- 道具奖励区域 -->
|
||||
<div v-if="policy.propsRewards && policy.propsRewards.length > 0" class="rewards-section">
|
||||
<div class="rewards-divider"></div>
|
||||
<div class="rewards-header">
|
||||
<span class="rewards-title">Rewards:</span>
|
||||
</div>
|
||||
<div class="rewards-list">
|
||||
<div
|
||||
v-for="(reward, index) in policy.propsRewards"
|
||||
:key="index"
|
||||
class="reward-item"
|
||||
>
|
||||
<div class="reward-info">
|
||||
<span class="reward-name">{{ reward.name }}</span>
|
||||
<span class="reward-type">{{ formatRewardType(reward.type) }}</span>
|
||||
</div>
|
||||
<div class="reward-amount">${{ reward.amount || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-icon">📋</div>
|
||||
<p>No policy data available</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { get } from '../utils/http.js'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const policies = ref([])
|
||||
const role = ref('')
|
||||
|
||||
// 政策数据
|
||||
const policies = ref([
|
||||
{
|
||||
level: 'Lv.1',
|
||||
timeHours: 20,
|
||||
target: 32500,
|
||||
hostSalary: 3,
|
||||
agentSalary: 1,
|
||||
totalSalary: 4
|
||||
},
|
||||
{
|
||||
level: 'Lv.2',
|
||||
timeHours: 20,
|
||||
target: 65000,
|
||||
hostSalary: 6,
|
||||
agentSalary: 2,
|
||||
totalSalary: 8
|
||||
},
|
||||
{
|
||||
level: 'Lv.3',
|
||||
timeHours: 15,
|
||||
target: 108000,
|
||||
hostSalary: 10,
|
||||
agentSalary: 3,
|
||||
totalSalary: 13
|
||||
// 获取团队政策数据
|
||||
const fetchTeamPolicy = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 从路由参数获取 teamId 和 role,如果没有则使用默认值
|
||||
const teamId = router.currentRoute.value.query.teamId || '1927993836921233409'
|
||||
const userRole = router.currentRoute.value.query.role || 'OWN'
|
||||
role.value = userRole
|
||||
|
||||
const response = await get(`/team/release/policy?id=${teamId}`)
|
||||
|
||||
if (response.status && response.body && response.body.policy) {
|
||||
policies.value = response.body.policy || []
|
||||
} else {
|
||||
policies.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team policy:', error)
|
||||
policies.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// 格式化目标值显示
|
||||
const formatTarget = (target) => {
|
||||
if (!target) return '0'
|
||||
const numTarget = parseFloat(target)
|
||||
if (isNaN(numTarget)) return target
|
||||
|
||||
if (numTarget >= 1000000) {
|
||||
return `${(numTarget / 1000000).toFixed(1)}M`
|
||||
} else if (numTarget >= 1000) {
|
||||
return `${(numTarget / 1000).toFixed(1)}K`
|
||||
}
|
||||
return numTarget.toString()
|
||||
}
|
||||
|
||||
// 格式化奖励类型
|
||||
const formatRewardType = (type) => {
|
||||
const typeMap = {
|
||||
'AVATAR_FRAME': 'Avatar Frame',
|
||||
'RIDE': 'Ride Effect',
|
||||
'THEME': 'Theme',
|
||||
'BADGE': 'Badge',
|
||||
'GIFT': 'Gift'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchTeamPolicy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.platform-policy {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background-color: #f1f2f3;
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -94,6 +163,31 @@ const policies = ref([
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 政策列表 */
|
||||
.policy-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -109,7 +203,8 @@ const policies = ref([
|
||||
|
||||
.policy-header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.policy-header h3 {
|
||||
@ -139,6 +234,9 @@ const policies = ref([
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
background-color: #f1f2f3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.policy-cell.empty {
|
||||
@ -153,23 +251,131 @@ const policies = ref([
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cell-value.salary {
|
||||
color: #FF6600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 奖励区域 */
|
||||
.rewards-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.rewards-divider {
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, #c2d9f0 0%, #03dbb8 100%);
|
||||
margin-bottom: 12px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.rewards-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.rewards-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.rewards-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.reward-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: linear-gradient(135deg, #c2d9f0 0%, #03dbb8 100%);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reward-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.reward-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.reward-type {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.reward-amount {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 480px) {
|
||||
.policy-grid {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
.cell-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
|
||||
.cell-value {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.policy-cell {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.reward-item {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.reward-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.reward-type {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.reward-amount {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,50 +3,67 @@
|
||||
<MobileHeader title="Team Bill" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 历史团队收入 -->
|
||||
<div class="history-income-card">
|
||||
<div class="income-header">
|
||||
<span class="income-label">History Team Income:</span>
|
||||
<button class="help-btn" @click="showHelpInfo">?</button>
|
||||
<!-- 成员资料信息 -->
|
||||
<div v-if="memberProfile" class="member-profile-card">
|
||||
<div class="member-info">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="memberProfile.userAvatar" class="member-avatar" alt="Avatar" />
|
||||
<span class="country-flag">{{ memberProfile.countryCode }}</span>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<div class="member-name">{{ memberProfile.userNickname }}</div>
|
||||
<div class="member-id">ID: {{ memberProfile.account }}</div>
|
||||
<div class="member-status">{{ memberProfile.accountStatusName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="income-amount">${{ historyIncome }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作报告标题 -->
|
||||
<div class="work-report-title">
|
||||
<span>Work report:</span>
|
||||
<button class="help-btn" @click="showHelpInfo">?</button>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
|
||||
<!-- 账单列表 -->
|
||||
<div class="bill-list">
|
||||
<div v-else-if="billList.length > 0" class="bill-list">
|
||||
<div
|
||||
v-for="bill in bills"
|
||||
v-for="bill in billList"
|
||||
:key="bill.id"
|
||||
class="bill-item"
|
||||
@click="showBillDetail(bill)"
|
||||
>
|
||||
<div class="bill-header">
|
||||
<span class="bill-period">{{ bill.period }}</span>
|
||||
<span :class="['bill-status', bill.status.toLowerCase()]">
|
||||
{{ bill.statusText }}
|
||||
<span class="bill-period">{{ bill.billTitle }}</span>
|
||||
<span :class="['bill-status', getStatusClass(bill.status)]">
|
||||
{{ getStatusText(bill.status) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="bill-details">
|
||||
<div class="salary-row">
|
||||
<div class="salary-item">
|
||||
<span class="salary-label">Host Salary:</span>
|
||||
<span class="salary-value">{{ bill.hostSalary }}</span>
|
||||
</div>
|
||||
<div class="salary-item">
|
||||
<span class="salary-label">Agent Salary:</span>
|
||||
<span class="salary-value">{{ bill.agentSalary }}</span>
|
||||
</div>
|
||||
<div class="bill-row">
|
||||
<span class="bill-label">Gift Value:</span>
|
||||
<span class="bill-value">{{ formatGiftValue(bill.target?.acceptGiftValue) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="total-row">
|
||||
<span class="total-label">Total:</span>
|
||||
<span class="total-value">{{ bill.total }}</span>
|
||||
<div class="bill-row">
|
||||
<span class="bill-label">Online Time:</span>
|
||||
<span class="bill-value">{{ formatOnlineTime(bill.target?.onlineTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="bill-row">
|
||||
<span class="bill-label">Diamond:</span>
|
||||
<span class="bill-value diamond-value">{{ formatDiamondValue(bill.target?.acceptDiamondValue) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="bill-row">
|
||||
<span class="bill-label">Effective Days:</span>
|
||||
<span class="bill-value">{{ bill.target?.effectiveDay || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -56,20 +73,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空数据状态 -->
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-icon">📋</div>
|
||||
<p>No data available</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 帮助模态框 -->
|
||||
<div v-if="showHelpModal" class="modal-overlay" @click="closeHelpModal">
|
||||
<div class="help-modal-content" @click.stop>
|
||||
<div class="help-modal-header">
|
||||
<h3>Help Information</h3>
|
||||
<h3>Work Report Help</h3>
|
||||
<button class="close-btn" @click="closeHelpModal">×</button>
|
||||
</div>
|
||||
|
||||
<div class="help-modal-body">
|
||||
<div class="help-section">
|
||||
<p class="help-description">
|
||||
Shown below is the work report of the members "More" can view more work detailed.
|
||||
Below is the work report of the team members. Click "More" to view detailed daily work records.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -78,23 +101,18 @@
|
||||
<div class="status-list">
|
||||
<div class="status-item">
|
||||
<span class="status-number">(1)</span>
|
||||
<span class="status-name">Completed:</span>
|
||||
<span class="status-desc">The system has completed the settlement report.</span>
|
||||
<span class="status-name">UNPAID:</span>
|
||||
<span class="status-desc">Work is currently in progress.</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(2)</span>
|
||||
<span class="status-name">Pending:</span>
|
||||
<span class="status-desc">There is currently a dispute, and the system cannot perform a settlement.</span>
|
||||
<span class="status-name">HANG_UP:</span>
|
||||
<span class="status-desc">Work has been suspended or is pending review.</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(3)</span>
|
||||
<span class="status-name">Out of account:</span>
|
||||
<span class="status-desc">waiting for system verification</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(4)</span>
|
||||
<span class="status-name">In progress:</span>
|
||||
<span class="status-desc">work now in progress.</span>
|
||||
<span class="status-name">PAID:</span>
|
||||
<span class="status-desc">Work has been completed and settled.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,177 +120,100 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成员收入弹窗 -->
|
||||
<div v-if="showMemberModal" class="modal-overlay" @click="closeMemberModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>Member Income</h3>
|
||||
<button class="close-btn" @click="closeMemberModal">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="modal-period">
|
||||
<span>{{ selectedBill?.period }}</span>
|
||||
<span :class="['modal-status', selectedBill?.status.toLowerCase()]">
|
||||
{{ selectedBill?.statusText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="search-section">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Please enter the host ID"
|
||||
v-model="searchQuery"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="member-list">
|
||||
<div
|
||||
v-for="member in filteredMembers"
|
||||
:key="member.id"
|
||||
class="member-item"
|
||||
>
|
||||
<div class="member-info">
|
||||
<div class="member-avatar">
|
||||
<span>{{ member.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<div class="member-name">{{ member.name }}</div>
|
||||
<div class="member-id">ID: {{ member.id }}</div>
|
||||
<div class="member-target">Target: {{ member.target }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-stats">
|
||||
<div class="member-stat">
|
||||
<span class="stat-icon">💰</span>
|
||||
<span>{{ member.id }}</span>
|
||||
</div>
|
||||
<div class="member-time">Time (Days): {{ member.days }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 账单详情模态框 (使用 TeamBillMore 组件) -->
|
||||
<div v-if="showDetailModal" class="modal-overlay">
|
||||
<div class="detail-modal-wrapper">
|
||||
<TeamBillMore
|
||||
:billData="selectedBill"
|
||||
@close="closeDetailModal"
|
||||
@propsReceived="handlePropsReceived"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import TeamBillMore from '../components/team/TeamBillMore.vue'
|
||||
import { getTeamMemberWork, formatGiftValue, formatOnlineTime, formatDateDisplay } from '../api/teamBill.js'
|
||||
|
||||
const router = useRouter()
|
||||
const historyIncome = ref('12345')
|
||||
const showMemberModal = ref(false)
|
||||
const loading = ref(false)
|
||||
const showHelpModal = ref(false)
|
||||
const showDetailModal = ref(false)
|
||||
const selectedBill = ref(null)
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 账单数据
|
||||
const bills = ref([
|
||||
{
|
||||
id: 1,
|
||||
period: '2025.08',
|
||||
status: 'pending',
|
||||
statusText: '进行中 (Pending)',
|
||||
hostSalary: '–',
|
||||
agentSalary: '–',
|
||||
total: '–'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: '2025.07',
|
||||
status: 'pending',
|
||||
statusText: '待处理 (Pending)',
|
||||
hostSalary: '$100',
|
||||
agentSalary: '$10',
|
||||
total: '110'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: '2025.06',
|
||||
status: 'completed',
|
||||
statusText: '已完成 (Completed)',
|
||||
hostSalary: '$200',
|
||||
agentSalary: '$30',
|
||||
total: '$230'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: '2025.05',
|
||||
status: 'out',
|
||||
statusText: '挂起 (Out of account)',
|
||||
hostSalary: '$500',
|
||||
agentSalary: '$100',
|
||||
total: '$600'
|
||||
// 数据
|
||||
const memberProfile = ref(null)
|
||||
const billList = ref([])
|
||||
|
||||
// 获取团队成员工作数据
|
||||
const fetchTeamMemberWork = async (userId) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await getTeamMemberWork(userId)
|
||||
|
||||
if (response.status && response.body) {
|
||||
memberProfile.value = response.body.memberProfile
|
||||
billList.value = response.body.targets || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team member work:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// 成员数据
|
||||
const members = ref([
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
// 格式化状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'UNPAID': 'In Progress',
|
||||
'HANG_UP': 'Pending',
|
||||
'PAID': 'Completed'
|
||||
}
|
||||
])
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 过滤后的成员列表
|
||||
const filteredMembers = computed(() => {
|
||||
if (!searchQuery.value) return members.value
|
||||
return members.value.filter(member =>
|
||||
member.id.includes(searchQuery.value) ||
|
||||
member.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
const statusClassMap = {
|
||||
'UNPAID': 'in-progress',
|
||||
'HANG_UP': 'pending',
|
||||
'PAID': 'completed'
|
||||
}
|
||||
return statusClassMap[status] || 'unknown'
|
||||
}
|
||||
|
||||
// 格式化钻石价值
|
||||
const formatDiamondValue = (value) => {
|
||||
if (!value || value === '0') return '0'
|
||||
const numValue = parseInt(value)
|
||||
if (numValue >= 1000000) {
|
||||
return `${(numValue / 1000000).toFixed(1)}M`
|
||||
} else if (numValue >= 1000) {
|
||||
return `${(numValue / 1000).toFixed(1)}K`
|
||||
}
|
||||
return numValue.toString()
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateNumber) => {
|
||||
return formatDateDisplay(dateNumber)
|
||||
}
|
||||
|
||||
// 显示账单详情
|
||||
const showBillDetail = (bill) => {
|
||||
selectedBill.value = bill
|
||||
showMemberModal.value = true
|
||||
showDetailModal.value = true
|
||||
}
|
||||
|
||||
// 关闭成员模态框
|
||||
const closeMemberModal = () => {
|
||||
showMemberModal.value = false
|
||||
// 关闭详情模态框
|
||||
const closeDetailModal = () => {
|
||||
showDetailModal.value = false
|
||||
selectedBill.value = null
|
||||
searchQuery.value = ''
|
||||
}
|
||||
|
||||
// 显示帮助模态框
|
||||
@ -284,11 +225,26 @@ const showHelpInfo = () => {
|
||||
const closeHelpModal = () => {
|
||||
showHelpModal.value = false
|
||||
}
|
||||
|
||||
// 处理道具领取成功
|
||||
const handlePropsReceived = () => {
|
||||
// 可以在这里添加提示或者刷新数据
|
||||
console.log('Props received successfully')
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
// 从路由参数或者默认值获取用户ID
|
||||
const userId = router.currentRoute.value.query.userId || '1911661502909562881'
|
||||
fetchTeamMemberWork(userId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.team-bill {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background-color: #f1f2f3;
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -297,8 +253,8 @@ const closeHelpModal = () => {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 历史收入卡片 */
|
||||
.history-income-card {
|
||||
/* 成员资料卡片 */
|
||||
.member-profile-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
@ -306,16 +262,65 @@ const closeHelpModal = () => {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.income-header {
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.country-flag {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 2px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.member-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-status {
|
||||
font-size: 12px;
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
/* 工作报告标题 */
|
||||
.work-report-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.income-label {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
@ -334,23 +339,30 @@ const closeHelpModal = () => {
|
||||
background-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.help-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.income-amount {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 工作报告标题 */
|
||||
.work-report-title {
|
||||
font-size: 16px;
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 账单列表 */
|
||||
.bill-list {
|
||||
display: flex;
|
||||
@ -391,6 +403,11 @@ const closeHelpModal = () => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-status.in-progress {
|
||||
background-color: #DBEAFE;
|
||||
color: #1D4ED8;
|
||||
}
|
||||
|
||||
.bill-status.pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
@ -401,54 +418,34 @@ const closeHelpModal = () => {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.bill-status.out {
|
||||
background-color: #FEE2E2;
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.bill-details {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.salary-row {
|
||||
.bill-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.salary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.bill-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.salary-label {
|
||||
font-size: 12px;
|
||||
.bill-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.salary-value {
|
||||
.bill-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
.diamond-value {
|
||||
color: #1E40AF !important;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
@ -458,13 +455,28 @@ const closeHelpModal = () => {
|
||||
gap: 4px;
|
||||
color: #C084FC;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
/* 空数据状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* 模态框公共样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -479,7 +491,8 @@ const closeHelpModal = () => {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.help-modal-content,
|
||||
.detail-modal-content {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
@ -524,149 +537,7 @@ const closeHelpModal = () => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-period {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.modal-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-status.pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.member-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-target {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.member-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.member-time {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 帮助模态框 */
|
||||
.help-modal-content {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 帮助模态框样式 */
|
||||
.help-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -739,7 +610,7 @@ const closeHelpModal = () => {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
min-width: 100px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
@ -747,4 +618,27 @@ const closeHelpModal = () => {
|
||||
line-height: 1.4;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 详情模态框新样式 */
|
||||
.detail-modal-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
background-color: white;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 在大屏幕上显示为模态框 */
|
||||
@media (min-width: 768px) {
|
||||
.detail-modal-wrapper {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,212 +1,178 @@
|
||||
<template>
|
||||
<div class="team-member gradient-background-circles">
|
||||
<MobileHeader title="Team Member (87)" />
|
||||
<MobileHeader :title="`Team Member (${members.length})`" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by ID or name"
|
||||
v-model="searchQuery"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<div class="filter-tabs">
|
||||
<button
|
||||
v-for="tab in filterTabs"
|
||||
:key="tab.value"
|
||||
@click="activeFilter = tab.value"
|
||||
:class="['filter-tab', { active: activeFilter === tab.value }]"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading members...</p>
|
||||
</div>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<div class="member-list">
|
||||
<div
|
||||
v-for="member in filteredMembers"
|
||||
<div v-else-if="members.length > 0" class="member-list">
|
||||
<div
|
||||
v-for="member in members"
|
||||
:key="member.id"
|
||||
class="member-item"
|
||||
@click="viewMemberDetail(member)"
|
||||
>
|
||||
<div class="member-info">
|
||||
<div class="member-avatar">
|
||||
<img v-if="member.avatar" :src="member.avatar" :alt="member.name" />
|
||||
<span v-else>{{ member.name.charAt(0) }}</span>
|
||||
<img
|
||||
v-if="member.userProfile.userAvatar"
|
||||
:src="member.userProfile.userAvatar"
|
||||
:alt="member.userProfile.userNickname"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<span v-else>{{ member.userProfile.userNickname.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<h4>{{ member.name }}</h4>
|
||||
<p>ID: {{ member.id }}</p>
|
||||
<div class="member-tags">
|
||||
<span v-if="member.isHost" class="tag host-tag">👑 Host</span>
|
||||
<span v-if="member.isAgent" class="tag agent-tag">👤 Agent</span>
|
||||
<span v-if="member.isActive" class="tag active-tag">🟢 Active</span>
|
||||
<span v-else class="tag inactive-tag">🔴 Inactive</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Target:</span>
|
||||
<span class="stat-value">{{ member.target }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Days:</span>
|
||||
<span class="stat-value">{{ member.activeDays }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Salary:</span>
|
||||
<span class="stat-value">${{ member.salary }}</span>
|
||||
<h4>{{ member.userProfile.userNickname }}</h4>
|
||||
<p>ID: {{ formatAccount(member.userProfile) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="delete-btn"
|
||||
@click="handleDeleteMember(member)"
|
||||
:disabled="deletingMemberId === member.id"
|
||||
>
|
||||
<span v-if="deletingMemberId === member.id">⏳</span>
|
||||
<span v-else>🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredMembers.length === 0" class="empty-state">
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-icon">👥</div>
|
||||
<p>No members found</p>
|
||||
<p>No team members found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除确认模态框 -->
|
||||
<div v-if="showDeleteModal" class="modal-overlay" @click="closeDeleteModal">
|
||||
<div class="delete-modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>Confirm Delete</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to remove <strong>{{ memberToDelete?.userProfile?.userNickname }}</strong> from the team?</p>
|
||||
<p class="warning-text">This action cannot be undone.</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="cancel-btn" @click="closeDeleteModal">Cancel</button>
|
||||
<button class="confirm-btn" @click="confirmDelete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getTeamMembers, deleteMember as deleteMemberApi } from '../api/teamBill.js'
|
||||
|
||||
const router = useRouter()
|
||||
const searchQuery = ref('')
|
||||
const activeFilter = ref('all')
|
||||
const loading = ref(false)
|
||||
const members = ref([])
|
||||
const deletingMemberId = ref(null)
|
||||
const showDeleteModal = ref(false)
|
||||
const memberToDelete = ref(null)
|
||||
|
||||
// 筛选标签
|
||||
const filterTabs = [
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Host', value: 'host' },
|
||||
{ label: 'Agent', value: 'agent' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' }
|
||||
]
|
||||
// 获取团队成员列表
|
||||
const fetchTeamMembers = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
// 这里需要传入实际的 teamId,可以从路由参数或者用户状态获取
|
||||
const teamId = router.currentRoute.value.query.teamId || '1927993836921233409'
|
||||
|
||||
// 成员数据
|
||||
const members = ref([
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Alice Johnson',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: true,
|
||||
target: 'A',
|
||||
activeDays: 25,
|
||||
salary: 450
|
||||
},
|
||||
{
|
||||
id: '0987654321',
|
||||
name: 'Bob Smith',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: true,
|
||||
target: 'B',
|
||||
activeDays: 18,
|
||||
salary: 320
|
||||
},
|
||||
{
|
||||
id: '1122334455',
|
||||
name: 'Carol Davis',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: false,
|
||||
target: 'A',
|
||||
activeDays: 8,
|
||||
salary: 120
|
||||
},
|
||||
{
|
||||
id: '5566778899',
|
||||
name: 'David Wilson',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: true,
|
||||
target: 'C',
|
||||
activeDays: 30,
|
||||
salary: 580
|
||||
},
|
||||
{
|
||||
id: '9988776655',
|
||||
name: 'Eva Brown',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: false,
|
||||
target: 'B',
|
||||
activeDays: 5,
|
||||
salary: 80
|
||||
},
|
||||
{
|
||||
id: '4433221100',
|
||||
name: 'Frank Miller',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: true,
|
||||
target: 'A',
|
||||
activeDays: 22,
|
||||
salary: 390
|
||||
const response = await getTeamMembers(teamId)
|
||||
|
||||
if (response.status && response.body) {
|
||||
members.value = response.body || []
|
||||
} else {
|
||||
members.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team members:', error)
|
||||
members.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
])
|
||||
|
||||
// 过滤后的成员列表
|
||||
const filteredMembers = computed(() => {
|
||||
let filtered = members.value
|
||||
|
||||
// 搜索过滤
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(member =>
|
||||
member.name.toLowerCase().includes(query) ||
|
||||
member.id.includes(query)
|
||||
)
|
||||
}
|
||||
|
||||
// 标签过滤
|
||||
switch (activeFilter.value) {
|
||||
case 'host':
|
||||
filtered = filtered.filter(member => member.isHost)
|
||||
break
|
||||
case 'agent':
|
||||
filtered = filtered.filter(member => member.isAgent)
|
||||
break
|
||||
case 'active':
|
||||
filtered = filtered.filter(member => member.isActive)
|
||||
break
|
||||
case 'inactive':
|
||||
filtered = filtered.filter(member => !member.isActive)
|
||||
break
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
// 查看成员详情
|
||||
const viewMemberDetail = (member) => {
|
||||
alert(`View details for ${member.name}`)
|
||||
}
|
||||
|
||||
// 格式化账户显示
|
||||
const formatAccount = (userProfile) => {
|
||||
if (!userProfile) return ''
|
||||
|
||||
// 如果有特殊ID,优先显示特殊ID
|
||||
if (userProfile.ownSpecialId && userProfile.ownSpecialId.account) {
|
||||
return userProfile.ownSpecialId.account
|
||||
}
|
||||
|
||||
// 否则显示普通账户
|
||||
return userProfile.account || ''
|
||||
}
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = (event) => {
|
||||
event.target.style.display = 'none'
|
||||
event.target.nextElementSibling.style.display = 'flex'
|
||||
}
|
||||
|
||||
// 删除成员(重命名避免冲突)
|
||||
const handleDeleteMember = (member) => {
|
||||
memberToDelete.value = member
|
||||
showDeleteModal.value = true
|
||||
}
|
||||
|
||||
// 关闭删除模态框
|
||||
const closeDeleteModal = () => {
|
||||
showDeleteModal.value = false
|
||||
memberToDelete.value = null
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
const confirmDelete = async () => {
|
||||
if (!memberToDelete.value) return
|
||||
|
||||
try {
|
||||
deletingMemberId.value = memberToDelete.value.id
|
||||
|
||||
// 调用删除成员的API接口
|
||||
await deleteMemberApi(memberToDelete.value.id)
|
||||
|
||||
// 从列表中移除成员
|
||||
const index = members.value.findIndex(m => m.id === memberToDelete.value.id)
|
||||
if (index > -1) {
|
||||
members.value.splice(index, 1)
|
||||
}
|
||||
|
||||
closeDeleteModal()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete member:', error)
|
||||
alert('Failed to delete member. Please try again.')
|
||||
} finally {
|
||||
deletingMemberId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchTeamMembers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.team-member {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
min-height: 100vh;
|
||||
background-color: #f1f2f3;
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -215,70 +181,28 @@ const viewMemberDetail = (member) => {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 搜索部分 */
|
||||
.search-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border-color: #8B5CF6;
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.filter-tab:not(.active):hover {
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 成员列表 */
|
||||
@ -293,18 +217,20 @@ const viewMemberDetail = (member) => {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.member-item:active {
|
||||
transform: scale(0.98);
|
||||
.member-item:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
@ -320,6 +246,7 @@ const viewMemberDetail = (member) => {
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.member-avatar img {
|
||||
@ -328,6 +255,14 @@ const viewMemberDetail = (member) => {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.member-avatar span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
flex: 1;
|
||||
}
|
||||
@ -337,70 +272,47 @@ const viewMemberDetail = (member) => {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member-details p {
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.member-tags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-tag {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
.agent-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.active-tag {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.inactive-tag {
|
||||
/* 删除按钮 */
|
||||
.delete-btn {
|
||||
background-color: #FEE2E2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
.delete-btn:hover:not(:disabled) {
|
||||
background-color: #FECACA;
|
||||
border-color: #FCA5A5;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
.delete-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
.delete-btn:active:not(:disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
@ -423,4 +335,110 @@ const viewMemberDetail = (member) => {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 删除确认模态框 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.delete-modal-content {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px 20px 0 20px;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #DC2626 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #F3F4F6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: #E5E7EB;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #DC2626;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn:hover {
|
||||
background-color: #B91C1C;
|
||||
}
|
||||
|
||||
.cancel-btn:active,
|
||||
.confirm-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.member-details h4 {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getBankBalance } from '../api/wallet.js'
|
||||
import {getBankBalance, userBankTransfer} from '../api/wallet.js'
|
||||
import { getSelectedPayee, clearSelectedPayee } from '../utils/payeeStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
@ -176,22 +176,59 @@ const selectCoin = (coin) => {
|
||||
}
|
||||
}
|
||||
|
||||
const transfer = () => {
|
||||
const transfer = async () => {
|
||||
if (!selectedCoin.value) {
|
||||
alert('Please select an amount first')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!selectedPayee.id) {
|
||||
alert('Please select a payee first')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const selectedCoinData = coinOptions.find(coin => coin.id === selectedCoin.value)
|
||||
alert(`Transfer ${selectedCoinData.amount} (${selectedCoinData.price} USD) to ${selectedPayee.name}`)
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
|
||||
// 获取支付密码
|
||||
// const password = prompt('Please enter your payment password:')
|
||||
// if (!password) {
|
||||
// alert('Payment password is required')
|
||||
// return
|
||||
// }
|
||||
|
||||
// 构造请求参数
|
||||
const transferData = {
|
||||
amount: selectedCoinData.price,
|
||||
acceptUserId: selectedPayee.id,
|
||||
acceptMethod: 'BANK_TRANSFER',
|
||||
// password: password
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用转账接口
|
||||
const response = await userBankTransfer(transferData)
|
||||
|
||||
// 转账成功
|
||||
alert(`Successfully transferred $${selectedCoinData.price} to ${selectedPayee.name}`)
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
|
||||
// 刷新银行余额
|
||||
await fetchBankBalance()
|
||||
|
||||
} catch (error) {
|
||||
// 错误处理
|
||||
console.error('Transfer failed:', error)
|
||||
|
||||
if (error.errorCode === 5000) {
|
||||
alert('Insufficient balance')
|
||||
} else if (error.errorCode === 5010 || error.errorCode === 5009) {
|
||||
alert('Payment password error')
|
||||
} else {
|
||||
alert('Transfer failed, please try again later')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取银行余额和初始化收款人
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user