aslan-h5/src/views/TeamBillView.vue

751 lines
15 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="team-bill gradient-background-circles">
<MobileHeader title="Team Bill" />
<div class="content">
<!-- 成员资料信息 -->
<div
style="
margin: 0 1%;
background-color: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div style="display: flex; flex-direction: column; gap: 10px">
<div>History Team Income:</div>
<div style="font-weight: 600">$000</div>
</div>
<div
style="
padding: 5px;
height: 10%;
aspect-ratio: 1/1;
border-radius: 50%;
border: 1px solid black;
font-weight: 600;
color: black;
display: flex;
align-items: center;
justify-content: center;
"
@click="showHelpInfo"
>
?
</div>
</div>
<!-- 工作报告 -->
<div style="margin-bottom: 20px">
<!-- 工作报告标题 -->
<div style="margin: 10px 1%; font-weight: 600; color: rgba(0, 0, 0, 0.4)">Work report:</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner"></div>
<p>Loading...</p>
</div>
<!-- 账单列表 -->
<div
v-else-if="billList.length > 0"
style="display: flex; flex-direction: column; gap: 12px"
>
<!-- 列表项 -->
<workReportBox
v-for="bill in billList"
:key="bill.id"
:type="getStatusText(bill.status)"
:date="bill.date"
:target="bill.target"
:salary="bill.salary"
:days="bill.timeDays"
more="true"
@showMore="showBillDetail(bill)"
></workReportBox>
</div>
<!-- 空数据状态 -->
<div v-else class="empty-state">
<div class="empty-icon">📋</div>
<p>No data available</p>
</div>
</div>
</div>
<!-- 遮罩层 -->
<div v-if="showHelpModal || showDetailModal" class="modal-overlay" @click="closeModal">
<!-- 帮助模态框 -->
<div
v-if="showHelpModal"
style="
background-color: white;
border-radius: 12px;
width: 90%;
max-width: 400px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
"
@click.stop
>
<div
style="
display: flex;
justify-content: center;
align-items: center;
padding: 16px;
position: relative;
"
>
<div style="margin: 0; font-size: 18px; font-weight: 600; color: #333">Help</div>
<div
style="
position: absolute;
right: 10px;
color: black;
display: flex;
align-items: center;
justify-content: center;
"
@click="closeModal"
>
×
</div>
</div>
<div style="padding: 0 20px 20px; overflow-y: auto">
<p>Shown below is the work report of the members "More" can view more work detailed.</p>
<p>Status Description:</p>
<p>(1)Completed: The system has completed the settlement report.</p>
<p>
(2)Pending: There is currently a dispute, and the system cannot perform a settlement.
</p>
<p>(3)Out of account: waiting for system verification.</p>
<p>(4)In progress: work now in progress.</p>
</div>
</div>
<!-- 账单详情模态框 (使用 TeamBillMore 组件) -->
<div v-if="showDetailModal" style="position: fixed; bottom: 0; width: 100%">
<TeamBillMore
:billData="selectedBill"
@close="closeModal"
@propsReceived="handlePropsReceived"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import MobileHeader from '../components/MobileHeader.vue'
import TeamBillMore from '../components/team/TeamBillMore.vue'
import { getUserIdentity } from '@/api/wallet.js'
import {
getTeamBill,
getTeamBillWorkMembers,
formatBillStatus,
getTeamReleasePolicy,
} from '../api/teamBill.js'
import { getTeamId } from '@/utils/userStore.js'
const loading = ref(false)
const showHelpModal = ref(false)
const showDetailModal = ref(false)
const selectedBill = ref(null)
// 等级政策
const policyData = ref(null)
const loadingPolicy = ref(false)
// 数据
const billList = ref([])
const selectedBillMembers = ref([])
// 获取团队账单列表
const fetchTeamBill = async (teamId) => {
try {
loading.value = true
const response = await getTeamBill(teamId)
if (response.status && response.body) {
const userIdentity = await getUserIdentity()
billList.value = response.body.map((bill) => ({
...bill,
// 将时间戳转换为可读格式
date: `${bill.billBelong.toString().substring(0, 4)}.${bill.billBelong
.toString()
.substring(4, 6)}`,
target: getTargetLevel(bill.target?.acceptGiftValue), // 使用政策数据计算等级
salary:
formatSalaryValue(
userIdentity.body?.agent
? (bill.target?.settlementResult?.ownSalary || 0) +
(bill.target?.settlementResult?.memberSalary || 0)
: userIdentity.body?.anchor
? bill.target?.settlementResult?.memberSalary
: null
) || '-',
totalSalary: formatSalaryValue(bill.target?.settlementResult?.totalSalary) || '-',
timeDays: bill.target?.effectiveDay || '-',
createTimeFormatted: new Date(bill.createTime).toLocaleDateString(),
updateTimeFormatted: new Date(bill.updateTime).toLocaleDateString(),
}))
}
} catch (error) {
console.error('Failed to fetch team bill:', error)
} finally {
loading.value = false
}
}
// 获取政策数据
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 getTargetLevel = (giftValue) => {
if (!policyData.value?.policy || !giftValue) {
return 'N/A'
}
const numValue = parseFloat(giftValue)
if (isNaN(numValue)) {
return 'N/A'
}
// 按target从大到小排序找到第一个小于等于giftValue的level
const sortedPolicy = [...policyData.value.policy].sort((a, b) => {
return parseFloat(b.target) - parseFloat(a.target)
})
console.log('sortedPolicy', JSON.stringify(sortedPolicy))
console.log('numValue', JSON.stringify(numValue))
for (const policy of sortedPolicy) {
if (numValue >= parseFloat(policy.target)) {
return policy.level
}
}
// 如果都不匹配,返回最低等级
const minLevel = Math.min(...policyData.value.policy.map((p) => p.level))
return minLevel
}
// 格式化薪资数值(与 TeamBillView 保持一致)
const formatSalaryValue = (value) => {
if (!value || value === '0' || value === 0) return '0.00'
const numValue = parseFloat(value)
return numValue.toFixed(2)
}
// 获取账单的工资成员信息
const fetchBillWorkMembers = async (billId) => {
try {
const response = await getTeamBillWorkMembers(billId)
if (response.status && response.body) {
return response.body
}
return []
} catch (error) {
console.error('Failed to fetch bill work members:', error)
return []
}
}
// 格式化状态文本
const getStatusText = (status) => {
const statusInfo = formatBillStatus(status)
return statusInfo.text
}
// 获取状态样式类
const getStatusClass = (status) => {
const statusInfo = formatBillStatus(status)
return statusInfo.class
}
// 显示账单详情
const showBillDetail = async (bill) => {
// 获取该账单的成员工作信息
const membersData = await fetchBillWorkMembers(bill.id)
selectedBillMembers.value = membersData
selectedBill.value = {
...bill,
members: membersData,
}
showDetailModal.value = true
}
// 关闭遮罩层
const closeModal = () => {
showDetailModal.value = false
showHelpModal.value = false
selectedBill.value = null
}
// 显示帮助模态框
const showHelpInfo = () => {
showHelpModal.value = true
}
// 处理道具领取成功
const handlePropsReceived = () => {
// 可以在这里添加提示或者刷新数据
console.log('Props received successfully')
}
// 组件挂载时获取数据
onMounted(() => {
// 获取团队ID
const teamId = getTeamId()
if (teamId) {
fetchTeamBill(teamId)
} else {
console.warn('未找到团队ID')
}
fetchPolicyData()
})
</script>
<style scoped>
* {
color: rgba(0, 0, 0, 0.8);
font-family: 'SF Pro Text';
}
.team-bill {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
min-height: 100vh;
background-color: #f1f2f3;
}
.content {
padding: 16px;
position: relative;
z-index: 2;
}
/* 成员资料卡片 */
.member-profile-card {
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.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;
font-size: 16px;
color: #666;
margin-bottom: 12px;
}
.help-btn {
width: 20px;
height: 20px;
border-radius: 10px;
background-color: #666;
color: white;
border: none;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.help-btn:hover {
background-color: #8b5cf6;
}
/* 加载状态 */
.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-item {
background-color: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s;
}
.bill-item:active {
transform: scale(0.98);
}
.bill-period {
font-size: 16px;
font-weight: 600;
color: #333;
}
.bill-status {
padding: 4px 8px;
border-radius: 4px;
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-details {
margin-bottom: 12px;
}
.bill-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.bill-row:last-child {
margin-bottom: 0;
}
.bill-label {
font-size: 14px;
color: #666;
}
.bill-value {
font-size: 14px;
font-weight: 600;
color: #333;
}
.total-salary-value {
color: #059669 !important;
font-weight: 700;
}
.more-btn {
display: flex;
justify-content: flex-end;
align-items: center;
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;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.help-modal-content,
.detail-modal-content {
background-color: white;
border-radius: 12px;
width: 100%;
max-width: 400px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #e5e7eb;
}
.modal-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;
}
.modal-body {
padding: 16px;
overflow-y: auto;
}
/* 帮助模态框样式 */
.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: 80px;
}
.status-desc {
color: #666;
line-height: 1.4;
flex: 1;
}
/* 详情模态框新样式 */
.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);
}
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
}
</style>