aslan-h5/src/views/HistorySalaryView.vue
2025-09-05 21:06:00 +08:00

965 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="history-salary gradient-background-circles">
<MobileHeader title="History Salary" />
<div class="content">
<!-- Host Salary 卡片 -->
<div class="host-salary-card">
<div class="salary-header">
<h3>Host Salary</h3>
<button class="info-btn" @click="showHelpInfo">
<span>?</span>
</button>
</div>
<div class="salary-amount">${{ totalSalary }}</div>
</div>
<!-- Work Report 部分 -->
<div class="work-report-section">
<h3>Work report:</h3>
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner"></div>
<p>Loading...</p>
</div>
<!-- 报告列表 -->
<div v-else-if="workReports.length > 0" class="report-list">
<div
v-for="report in workReports"
:key="report.id"
class="report-item"
>
<div class="report-header">
<span class="report-date">{{ report.date }}</span>
<span :class="['status-badge', report.status.toLowerCase().replace(' ', '-')]">
{{ report.status }}
</span>
</div>
<div class="report-details">
<div class="detail-row">
<div class="detail-item">
<span class="detail-label">Level:</span>
<span class="detail-value">{{ report.target }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Salary:</span>
<span class="detail-value">{{ report.salary }}</span>
</div>
</div>
<div class="detail-row">
<div class="detail-item">
<span class="detail-label">Time(Days):</span>
<span class="detail-value">{{ report.timeDays }}</span>
</div>
<div class="detail-item">
<!-- More 按钮 -->
<button v-if="report.hasDetails" class="more-btn" @click="showReportDetail(report)">
<span>More</span>
<span class="arrow">></span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 空数据状态 -->
<div v-else class="empty-state">
<div class="empty-icon">📋</div>
<p>No data available</p>
</div>
</div>
</div>
<!-- 详情模态框 -->
<div v-if="showDetailModal" class="modal-overlay" @click="closeDetailModal">
<div class="detail-modal" @click.stop>
<div class="detail-header">
<h3>{{ selectedReport?.date || 'Work Details' }}</h3>
<button class="close-btn" @click="closeDetailModal">×</button>
</div>
<div class="detail-content">
<!-- 等级卡片 -->
<!-- <div v-if="levelData" class="level-card">
<div class="level-header">
<h4>{{ levelData.level }}</h4>
</div>
<div class="level-stats">
<div class="stat-row">
<div class="stat-item">
<span class="stat-label">Time (hours)</span>
<span class="stat-value">{{ levelData.timeHours }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Target</span>
<span class="stat-value">{{ levelData.target }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Host Salary</span>
<span class="stat-value">${{ levelData.hostSalary }}</span>
</div>
</div>
<div class="stat-row">
<div class="stat-item">
<span class="stat-label">Agent Salary</span>
<span class="stat-value">${{ levelData.agentSalary }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Total Salary</span>
<span class="stat-value">${{ levelData.totalSalary }}</span>
</div>
<div class="stat-item">
&lt;!&ndash; 空占位 &ndash;&gt;
</div>
</div>
</div>
</div>-->
<!-- 每日薪资列表 -->
<div v-if="dailyTargets.length > 0" class="daily-salary">
<h4>Daily salary:</h4>
<div class="daily-list">
<div v-for="daily in dailyTargets" :key="daily.dateNumber" class="daily-item">
<div class="daily-date">{{ formatDateNumber(daily.dateNumber) }}</div>
<div class="daily-info">
<div class="daily-target">
<span>Level: {{ getLevelFromPolicy(daily.acceptGiftValue) }}</span>
</div>
<div class="daily-time">
<span>Time: </span>
<span class="time-value">{{ Math.min(formatOnlineTime(daily.onlineTime), 120) }}/120</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 帮助模态框 -->
<div v-if="showHelpModal" class="modal-overlay" @click="closeHelpModal">
<div class="help-modal-content" @click.stop>
<div class="help-modal-header">
<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.
</p>
</div>
<div class="help-section">
<h4>Status Description:</h4>
<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>
</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>
</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>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 薪资信息弹窗 -->
<div v-if="showSalaryModal" class="modal-overlay" @click="showSalaryModal = false">
<div class="modal" @click.stop>
<h3>Salary Information</h3>
<div class="salary-breakdown">
<div class="breakdown-item">
<span>Base Salary:</span>
<span>$8,000</span>
</div>
<div class="breakdown-item">
<span>Performance Bonus:</span>
<span>$3,000</span>
</div>
<div class="breakdown-item">
<span>Commission:</span>
<span>$1,345</span>
</div>
<div class="breakdown-total">
<span>Total:</span>
<span>$12,345</span>
</div>
</div>
<button @click="showSalaryModal = false">Close</button>
</div>
</div>
</div>
</template>
<script setup>
import {onMounted, ref} from 'vue'
import MobileHeader from '../components/MobileHeader.vue'
import {getTeamMemberWork, getTeamReleasePolicy} from '../api/teamBill.js'
import {getTeamId, getUserId, getUserProfile} from "@/utils/userStore.js";
import {getUserIdentity} from "@/api/wallet.js";
const totalSalary = ref('0.00')
const showSalaryModal = ref(false)
const showHelpModal = ref(false)
const loading = ref(false)
const showDetailModal = ref(false)
const selectedReport = ref(null)
const originalTargetsData = ref([]) // 保存原始数据
const policyData = ref(null) // 添加政策数据
const loadingPolicy = ref(false) // 政策加载状态
// 工作报告数据
const workReports = ref([])
const dailyTargets = ref([])
// 获取政策数据
const fetchPolicyData = async () => {
try {
loadingPolicy.value = true
const teamId = getTeamId()
if (!teamId) {
console.warn('No team ID available')
return
}
const response = await getTeamReleasePolicy(teamId)
if (response && response.status && response.body) {
policyData.value = response.body
console.debug('Policy data loaded:', policyData.value)
}
} catch (error) {
console.error('Failed to fetch policy data:', error)
} finally {
loadingPolicy.value = false
}
}
// 根据政策数据和礼物价值计算等级
const getLevelFromPolicy = (giftValue) => {
if (!policyData.value?.policy || !giftValue) {
return 1 // 默认等级
}
const numValue = parseFloat(giftValue)
if (isNaN(numValue)) {
return 1
}
// 按target从大到小排序找到第一个小于等于giftValue的level
const sortedPolicy = [...policyData.value.policy].sort((a, b) => {
return parseFloat(b.target) - parseFloat(a.target)
})
for (const policy of sortedPolicy) {
if (numValue >= parseFloat(policy.target)) {
return policy.level
}
}
// 如果都不匹配,返回最低等级
return Math.min(...policyData.value.policy.map(p => p.level))
}
// 获取工作报告数据
const fetchWorkReports = async (userId) => {
try {
loading.value = true
const response = await getTeamMemberWork(userId)
if (response.status && response.body) {
// 将 targets 数据转换为 workReports 格式
const targets = response.body.targets || []
originalTargetsData.value = targets // 保存原始数据
const userIdentity = await getUserIdentity()
workReports.value = targets.map(target => ({
id: target.id,
date: target.billTitle,
status: getStatusText(target.status),
target: getLevelFromPolicy(target.target?.acceptGiftValue), // 使用政策数据计算等级
salary: formatSalaryValue(
userIdentity.body?.agent
? (target.target?.settlementResult?.ownSalary || 0) + (target.target?.settlementResult?.memberSalary || 0)
: userIdentity.body?.anchor
? target.target?.settlementResult?.memberSalary
: null
) || '-',
totalSalary: formatSalaryValue(target.target?.settlementResult?.totalSalary) || '-',
timeDays: target.target?.effectiveDay || '-',
hasDetails: target.target && target.target.dailyTargets && target.target.dailyTargets.length > 0,
originalData: target // 保存原始数据用于详情显示
}))
// 计算总薪资 - 根据用户身份选择对应的薪资字段
const totalAmount = targets.reduce((sum, target) => {
let salary = 0
// 根据用户身份决定使用哪个薪资字段
if (userIdentity.body?.agent) {
// agent 为 true 时,使用 ownSalary
salary = parseFloat((target.target?.settlementResult?.ownSalary || 0) + (target.target?.settlementResult?.memberSalary || 0))
} else if (userIdentity.body?.anchor) {
// anchor 为 true 时,使用 memberSalary
salary = parseFloat(target.target?.settlementResult?.memberSalary || 0)
}
return sum + salary
}, 0)
totalSalary.value = totalAmount.toFixed(2)
}
} catch (error) {
console.error('Failed to fetch work reports:', error)
// 保持默认数据作为fallback
workReports.value = [
{
id: 1,
date: '2025.07',
status: 'In Progress',
target: '-',
salary: '-',
timeDays: '-',
diamond: '-'
}
]
} finally {
loading.value = false
}
}
// 格式化薪资数值(与 TeamBillView 保持一致)
const formatSalaryValue = (value) => {
if (!value || value === '0' || value === 0) return '0.00'
const numValue = parseFloat(value)
return numValue.toFixed(2)
}
// 格式化状态文本(与 TeamBillView 保持一致)
const getStatusText = (status) => {
const statusMap = {
'UNPAID': 'In Progress',
'HANG_UP': 'Pending',
'PAID': 'Completed'
}
return statusMap[status] || status
}
// 显示报告详情
const showReportDetail = (report) => {
selectedReport.value = report
if (report.originalData && report.originalData.target) {
const targetData = report.originalData.target
// 直接设置每日目标数据
dailyTargets.value = targetData.dailyTargets || []
}
showDetailModal.value = true
}
// 关闭详情模态框
const closeDetailModal = () => {
showDetailModal.value = false
selectedReport.value = null
dailyTargets.value = []
}
// 格式化在线时间(为 Daily Salary 显示分钟数)
const formatOnlineTime = (minutes) => {
const mins = parseInt(minutes) || 0
return mins.toString()
}
// 格式化日期数字
const formatDateNumber = (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}`
}
// 显示帮助信息
const showHelpInfo = () => {
showHelpModal.value = true
}
// 关闭帮助模态框
const closeHelpModal = () => {
showHelpModal.value = false
}
// 组件挂载时获取数据
onMounted(async () => {
// 先获取政策数据,再获取工作报告
await fetchPolicyData()
await fetchWorkReports(getUserId())
})
</script>
<style scoped>
.history-salary {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.content {
padding: 16px;
position: relative;
z-index: 2;
}
/* Host Salary 卡片 */
.host-salary-card {
background-color: white;
padding: 20px;
border-radius: 16px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.salary-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.salary-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #666;
}
.info-btn {
width: 20px;
height: 20px;
border-radius: 10px;
border: 1px solid #8B5CF6;
background-color: #8B5CF6;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: white;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.info-btn:hover {
background-color: #7C3AED;
border-color: #7C3AED;
}
.salary-amount {
font-size: 32px;
font-weight: 700;
color: #333;
margin: 0;
}
/* Work Report 部分 */
.work-report-section {
margin-bottom: 20px;
}
.work-report-section h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 500;
color: #666;
}
/* 加载状态 */
.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-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color: #666;
}
.empty-icon {
font-size: 48px;
margin-bottom: 12px;
}
.report-list {
display: flex;
flex-direction: column;
gap: 12px;
}
/* 报告项目 */
.report-item {
background-color: white;
padding: 16px;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.report-date {
font-size: 18px;
font-weight: 600;
color: #333;
}
.status-badge {
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
color: white;
}
.status-badge.in-progress {
background-color: #3B82F6;
}
.status-badge.pending {
background-color: #EF4444;
}
.status-badge.completed {
background-color: #10B981;
}
.status-badge.out-of-account {
background-color: #9CA3AF;
}
/* 报告详情 */
.report-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-row {
display: flex;
justify-content: space-between;
gap: 20px;
}
.detail-item {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.detail-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.detail-value {
font-size: 14px;
color: #333;
font-weight: 600;
}
.total-salary {
color: #10B981 !important;
font-weight: 700;
}
/* More 按钮 */
.more-btn {
display: flex;
align-items: center;
gap: 4px;
background: none;
border: none;
color: #8B5CF6;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: color 0.2s;
}
.more-btn:hover {
color: #7C3AED;
}
.arrow {
font-weight: bold;
}
/* 详情模态框样式 */
.detail-modal {
background: white;
border-radius: 16px;
width: 90%;
max-width: 480px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e5e7eb;
background-color: #f8f9fa;
}
.detail-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.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;
}
.detail-content {
padding: 20px;
overflow-y: auto;
}
/* 等级卡片样式 */
.level-card {
background-color: #f8f9fa;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.level-header {
margin-bottom: 12px;
}
.level-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.level-stats {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 4px;
}
.stat-label {
font-size: 12px;
color: #666;
font-weight: 500;
}
.stat-value {
font-size: 14px;
font-weight: 600;
color: #333;
}
/* 每日薪资样式 */
.daily-salary {
margin-top: 20px;
}
.daily-salary h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.daily-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.daily-item {
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
}
.daily-date {
font-size: 12px;
color: #666;
margin-bottom: 8px;
}
.daily-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.daily-target {
font-size: 14px;
color: #333;
font-weight: 500;
}
.daily-time {
font-size: 14px;
color: #333;
}
.time-value {
color: #10B981;
font-weight: 600;
}
/* 帮助模态框样式 */
.help-modal-content {
background-color: white;
border-radius: 12px;
width: 90%;
max-width: 400px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.help-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #e5e7eb;
background-color: #f8f9fa;
}
.help-modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.help-modal-body {
padding: 20px;
overflow-y: auto;
}
.help-section {
margin-bottom: 20px;
}
.help-section:last-child {
margin-bottom: 0;
}
.help-description {
font-size: 14px;
line-height: 1.5;
color: #555;
margin: 0;
padding: 12px;
background-color: #f0f9ff;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.help-section h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.status-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.status-item {
display: flex;
gap: 8px;
padding: 12px;
background-color: #f9fafb;
border-radius: 8px;
border: 1px solid #e5e7eb;
}
.status-number {
font-weight: 600;
color: #8B5CF6;
flex-shrink: 0;
}
.status-name {
font-weight: 600;
color: #333;
flex-shrink: 0;
min-width: 100px;
}
.status-desc {
color: #666;
line-height: 1.4;
flex: 1;
}
/* 弹窗样式 */
.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;
}
.modal {
background: white;
padding: 24px;
border-radius: 16px;
width: 100%;
max-width: 320px;
}
.modal h3 {
margin: 0 0 20px 0;
font-size: 18px;
text-align: center;
color: #333;
}
.salary-breakdown {
margin-bottom: 20px;
}
.breakdown-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
font-size: 14px;
color: #666;
border-bottom: 1px solid #f0f0f0;
}
.breakdown-total {
display: flex;
justify-content: space-between;
padding: 12px 0 8px 0;
font-size: 16px;
font-weight: 600;
color: #333;
border-top: 2px solid #e0e0e0;
margin-top: 8px;
}
.modal button {
width: 100%;
padding: 12px;
background-color: #3B82F6;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
}
.modal button:active {
background-color: #2563EB;
}
</style>