h5相关
This commit is contained in:
parent
721638c5bc
commit
6cb7132e22
292
backups/admin-center-ui-20260709/GeneralHeader.vue
Normal file
292
backups/admin-center-ui-20260709/GeneralHeader.vue
Normal file
@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 状态栏占位区域(仅在APP中显示) -->
|
||||
<div v-if="isInAppEnvironment" style="height: 30px"></div>
|
||||
|
||||
<!-- header内容 -->
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
"
|
||||
>
|
||||
<!-- 返回键 -->
|
||||
<div style="width: 1.5em; display: flex; align-items: center">
|
||||
<button v-if="showBack" class="back-btn" @click="handleBack">
|
||||
<img
|
||||
v-smart-img
|
||||
class="flipImg"
|
||||
:src="backImg"
|
||||
alt=""
|
||||
style="width: 100%; aspect-ratio: 1/1; display: block"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h1
|
||||
style="
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
letter-spacing: 0.3px;
|
||||
"
|
||||
:style="{ color }"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<!-- 帮助按钮 -->
|
||||
<button v-if="showHelp" class="help-btn" style="width: 1.5em" @click="$emit('help')">
|
||||
<img v-smart-img :src="helpImg" alt="" style="width: 100%; display: block" />
|
||||
</button>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<div
|
||||
v-if="showLanguageList"
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
color: rgba(187, 146, 255, 1) !important;
|
||||
"
|
||||
@click="showLang"
|
||||
>
|
||||
<div style="font-weight: bold">{{ currentLangName }}</div>
|
||||
<transition name="slide-fade">
|
||||
<div v-show="visibleList" class="extraList">
|
||||
<div
|
||||
style="padding: 8px; color: black !important; font-weight: 590"
|
||||
v-for="lang in langStore.langList"
|
||||
@click.stop="handleLanguageChange(lang)"
|
||||
>
|
||||
{{ lang.value }}
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- 插槽 -->
|
||||
<slot name="extraFunction"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, watch, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useLangStore } from '@/stores/lang'
|
||||
import { setLocale } from '@/locales/i18n'
|
||||
import { isInApp, closePage } from '@/utils/appBridge.js'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// 是否显示返回按钮
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// 是否显示帮助按钮
|
||||
showHelp: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
// 是否为首页
|
||||
isHomePage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
// 是否显示语言列表
|
||||
showLanguageList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
// 标题颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#fff',
|
||||
},
|
||||
|
||||
// 返回图标
|
||||
backImg: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return new URL('../assets/icon/Azizi/arrowBackBlack.png', import.meta.url).href
|
||||
},
|
||||
},
|
||||
|
||||
// 帮助图标
|
||||
helpImg: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return new URL('../assets/icon/Azizi/helpWhite.png', import.meta.url).href
|
||||
},
|
||||
},
|
||||
|
||||
// 携带参数返回
|
||||
backQuery: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
defineEmits(['help'])
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const langStore = useLangStore()
|
||||
|
||||
const visibleList = ref(false) //语言列表显示
|
||||
|
||||
// 当前语言类型
|
||||
const currentLangName = computed(() => {
|
||||
return langStore.selectedLang?.value || 'EN'
|
||||
})
|
||||
|
||||
// 监听语言变化,确保UI更新
|
||||
watch(
|
||||
() => langStore.selectedLang,
|
||||
(newLang) => {
|
||||
console.log('Language changed in header:', newLang)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const handleLanguageChange = (item) => {
|
||||
visibleList.value = false
|
||||
console.log('visibleList.value', visibleList.value)
|
||||
|
||||
console.log('Changing language to:', item)
|
||||
setLocale(langStore, item.type)
|
||||
// 立即更新visibleList状态
|
||||
}
|
||||
|
||||
// 检测是否在APP环境中
|
||||
const isInAppEnvironment = ref(false)
|
||||
|
||||
// 计算是否为首页
|
||||
const isCurrentlyHomePage = computed(() => {
|
||||
return !!props.isHomePage
|
||||
})
|
||||
|
||||
const handleBack = () => {
|
||||
if (isCurrentlyHomePage.value && isInApp()) {
|
||||
// 首页且在APP中:关闭页面
|
||||
console.log('home back')
|
||||
closePage()
|
||||
} else {
|
||||
// 非首页或浏览器环境:正常返回
|
||||
router.go(-1)
|
||||
if (props.backQuery != null) {
|
||||
console.log('props.backQuery', props.backQuery)
|
||||
|
||||
// 使用 nextTick 确保上一步完成
|
||||
nextTick(() => {
|
||||
// 使用 replace 替换为带参数的版本
|
||||
router.replace({
|
||||
path: props.backQuery.from,
|
||||
query: {
|
||||
activeAction: props.backQuery.activeAction,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//语言列表显示
|
||||
const showLang = () => {
|
||||
visibleList.value = !visibleList.value
|
||||
|
||||
console.log('visibleList.value', visibleList.value)
|
||||
}
|
||||
|
||||
// 组件挂载时检测环境
|
||||
onMounted(() => {
|
||||
isInAppEnvironment.value = isInApp()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.back-btn,
|
||||
.help-btn {
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.back-btn:active,
|
||||
.help-btn:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.extraList {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
background-color: rgba(255, 255, 255);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 5px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.extraList > div:not(:last-child) {
|
||||
border-bottom: 0.1px solid black;
|
||||
}
|
||||
|
||||
[dir='rtl'] .flipImg {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
[dir='rtl'] .extraList {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
* {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
* {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
* {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
392
backups/admin-center-ui-20260709/ItemDistribution.vue
Normal file
392
backups/admin-center-ui-20260709/ItemDistribution.vue
Normal file
@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<div class="fullPage">
|
||||
<div class="bg">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:showLanguageList="true"
|
||||
:title="t('item_distribution')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 标签 -->
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
style="font-size: 1.3em; padding: 10px 0; flex: 1; text-align: center"
|
||||
class="tab-item"
|
||||
:class="activeIndex == index ? 'tabName-active' : 'tabName'"
|
||||
ref="tabItems"
|
||||
@click="setActiveTab(index)"
|
||||
>
|
||||
{{ t(item.name) }}
|
||||
</div>
|
||||
<!-- 下划线元素 -->
|
||||
<div
|
||||
style="width: 15px; height: 3px; background-color: #bb92ff"
|
||||
:style="underlineStyle"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
padding: 15px;
|
||||
"
|
||||
>
|
||||
<div v-for="(product, index) in productList" :key="index" @click="showSendDialog(product)">
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<img :src="product.cover" alt="" style="width: 60%; margin: 10px 0" />
|
||||
<button
|
||||
style="
|
||||
background-color: #bb92ff;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
padding: 5px 10px;
|
||||
"
|
||||
>
|
||||
{{ t('send') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<!-- 遮罩层 -->
|
||||
<maskLayer :maskLayerShow="dialogshow" @click="closedPopup">
|
||||
<div
|
||||
style="
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
margin: 20% 5%;
|
||||
"
|
||||
@click.stop
|
||||
>
|
||||
<img :src="selectedGoods.cover" alt="" style="width: 60%; margin: 5px 0" />
|
||||
<div style="font-weight: 600; margin: 10px 0">
|
||||
<span style="color: #00000080; margin-right: 10px">{{ t('validity') }}:</span>
|
||||
<span style="font-weight: 700; color: #000">{{ validityPeriod }}{{ t('day') }}</span>
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
width: 90%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="t('enter_user_id_placeholder')"
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background-color: #f1f1f1b2;
|
||||
padding: 8px;
|
||||
margin-right: 4px;
|
||||
font-size: 1em;
|
||||
font-weight: 590;
|
||||
"
|
||||
v-model="targetUserId"
|
||||
/>
|
||||
<button
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
padding: 8px;
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
font-weight: 590;
|
||||
"
|
||||
:style="{ backgroundColor: confirmClick ? '#bb92ff' : '#00000080' }"
|
||||
@click="sendGoods"
|
||||
>
|
||||
{{ t('confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</maskLayer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { showWarning, showSuccess } from '@/utils/toast.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { getAdminCenterList, giveProps, hasSendGiftsPermission } from '@/api/itemDistribution'
|
||||
import { searchUser } from '@/api/userInfo'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
import maskLayer from '@/components/MaskLayer.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const tabList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'frames',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'vehicles',
|
||||
},
|
||||
// {
|
||||
// id: 3,
|
||||
// name: 'chat_box',
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// name: 'theme',
|
||||
// },
|
||||
])
|
||||
|
||||
const activeIndex = ref(0)
|
||||
const currentTab = ref('frames') //选中名字
|
||||
const tabItems = ref([]) // 存储标签DOM引用
|
||||
|
||||
const confirmClick = ref(true)
|
||||
|
||||
// 选择标签
|
||||
const setActiveTab = (index) => {
|
||||
if (activeIndex.value != index) {
|
||||
console.log('切换标签')
|
||||
productList.value = [] //置空商品列表
|
||||
activeIndex.value = index
|
||||
currentTab.value = tabList.value[index].name
|
||||
|
||||
loadProps()
|
||||
}
|
||||
}
|
||||
|
||||
// 计算下划线样式
|
||||
const underlineStyle = computed(() => {
|
||||
console.log('计算属性')
|
||||
|
||||
// 访问 locale.value 使其成为依赖项
|
||||
const currentLocale = locale.value
|
||||
console.log('当前语言:', currentLocale)
|
||||
|
||||
if (tabItems.value.length === 0) return {} //没有这个元素
|
||||
const activeTab = tabItems.value[activeIndex.value]
|
||||
console.log('选中元素的左边距', activeTab.offsetLeft)
|
||||
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${activeTab.offsetLeft + activeTab.offsetWidth / 2 - 15 / 2}px`,
|
||||
bottom: '0',
|
||||
transition: 'left 0.3s ease', // 平滑过渡
|
||||
}
|
||||
})
|
||||
|
||||
const targetUserId = ref('')
|
||||
|
||||
// 检查是否有赠送某个栏目商品的资格
|
||||
const getHasSendVIPPermission = async (propsType) => {
|
||||
const response = await hasSendGiftsPermission(propsType)
|
||||
console.log('response:', response)
|
||||
return response.body
|
||||
}
|
||||
|
||||
const sendGoods = async () => {
|
||||
if (confirmClick.value) {
|
||||
confirmClick.value = false
|
||||
|
||||
console.log('赠送用户id:', targetUserId.value)
|
||||
console.log('赠送商品:', selectedGoods.value)
|
||||
|
||||
try {
|
||||
const targetUserInfo = await searchUser(targetUserId.value)
|
||||
console.log('targetUserInfo:', targetUserInfo)
|
||||
if (targetUserInfo.status && targetUserInfo.body) {
|
||||
const data = {
|
||||
propsId: selectedGoods.value.id,
|
||||
acceptUserId: targetUserInfo.body.id,
|
||||
}
|
||||
const res = await giveProps(data)
|
||||
if (res.errorCode == 0 && res.status) {
|
||||
closedPopup()
|
||||
showSuccess(t('gift_delivery_successful'))
|
||||
} else {
|
||||
confirmClick.value = true
|
||||
}
|
||||
} else {
|
||||
confirmClick.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error:', error)
|
||||
// 信息提示id有误
|
||||
showWarning(error.response.errorMsg)
|
||||
confirmClick.value = true
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选中的标签
|
||||
const getPropsType = () => {
|
||||
const typeMap = {
|
||||
frames: 'AVATAR_FRAME',
|
||||
vehicles: 'RIDE',
|
||||
chat_box: 'CHAT_BUBBLE',
|
||||
theme: 'THEME',
|
||||
vip: 'NOBLE_VIP',
|
||||
}
|
||||
return typeMap[currentTab.value]
|
||||
}
|
||||
|
||||
// 获取商品列表
|
||||
const loadProps = async () => {
|
||||
try {
|
||||
const params = {
|
||||
propsType: getPropsType(),
|
||||
currencyType: 'GOLD',
|
||||
}
|
||||
const response = await getAdminCenterList(params)
|
||||
console.log('response:', response)
|
||||
productList.value = response.body
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 虚拟商品数据
|
||||
const productList = ref([])
|
||||
|
||||
// 选中商品
|
||||
const selectedGoods = ref({})
|
||||
|
||||
const validityPeriod = computed(() => {
|
||||
return currentTab.value == 'VIP' ? '3' : '7'
|
||||
})
|
||||
|
||||
// 获取本地图片
|
||||
const getImageUrl = (imgUrl) => {
|
||||
return new URL(imgUrl, import.meta.url).href
|
||||
}
|
||||
|
||||
const dialogshow = ref(false)
|
||||
// 展示弹窗
|
||||
const showSendDialog = (product) => {
|
||||
console.log('product:', product)
|
||||
selectedGoods.value = product
|
||||
dialogshow.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closedPopup = () => {
|
||||
confirmClick.value = true
|
||||
selectedGoods.value = {}
|
||||
targetUserId.value = ''
|
||||
dialogshow.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('onMounted')
|
||||
|
||||
// 检查是否有赠送VIP的资格
|
||||
if (getHasSendVIPPermission('NOBLE_VIP')) {
|
||||
tabList.value.push({
|
||||
id: 3,
|
||||
name: 'vip',
|
||||
})
|
||||
}
|
||||
|
||||
if (tabItems.value.length > 0) {
|
||||
underlineStyle.value // 触发计算属性更新
|
||||
}
|
||||
loadProps()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bg {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.tabName {
|
||||
color: #00000066;
|
||||
}
|
||||
|
||||
.tabName-active {
|
||||
color: black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
backups/admin-center-ui-20260709/MaskLayer.vue
Normal file
145
backups/admin-center-ui-20260709/MaskLayer.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="maskLayerShow"
|
||||
class="mask-layer"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@pointerdown.stop
|
||||
@pointerup.stop
|
||||
@touchstart.stop
|
||||
@touchend.stop
|
||||
@touchmove.prevent
|
||||
@wheel.prevent
|
||||
>
|
||||
<div class="mask-layer__content" @touchmove.stop>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onUnmounted, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
maskLayerShow: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
lockScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
let lockCount = 0
|
||||
let lockedScrollY = 0
|
||||
let previousBodyStyle = {}
|
||||
let previousHtmlStyle = {}
|
||||
|
||||
const getStyleSnapshot = (el) => ({
|
||||
overflow: el.style.overflow,
|
||||
position: el.style.position,
|
||||
top: el.style.top,
|
||||
left: el.style.left,
|
||||
right: el.style.right,
|
||||
width: el.style.width,
|
||||
touchAction: el.style.touchAction,
|
||||
overscrollBehavior: el.style.overscrollBehavior,
|
||||
})
|
||||
|
||||
const restoreStyleSnapshot = (el, style) => {
|
||||
Object.keys(style).forEach((key) => {
|
||||
el.style[key] = style[key]
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗展示时锁住背景页滚动,iOS 需要固定 body 并在关闭后恢复滚动位置。
|
||||
const lockPageScroll = () => {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
||||
|
||||
if (lockCount === 0) {
|
||||
const body = document.body
|
||||
const html = document.documentElement
|
||||
|
||||
lockedScrollY = window.scrollY || html.scrollTop || body.scrollTop || 0
|
||||
previousBodyStyle = getStyleSnapshot(body)
|
||||
previousHtmlStyle = getStyleSnapshot(html)
|
||||
|
||||
html.style.overflow = 'hidden'
|
||||
html.style.touchAction = 'none'
|
||||
html.style.overscrollBehavior = 'none'
|
||||
|
||||
body.style.overflow = 'hidden'
|
||||
body.style.position = 'fixed'
|
||||
body.style.top = `-${lockedScrollY}px`
|
||||
body.style.left = '0'
|
||||
body.style.right = '0'
|
||||
body.style.width = '100%'
|
||||
body.style.touchAction = 'none'
|
||||
body.style.overscrollBehavior = 'none'
|
||||
}
|
||||
|
||||
lockCount += 1
|
||||
}
|
||||
|
||||
const unlockPageScroll = () => {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
||||
if (lockCount <= 0) return
|
||||
|
||||
lockCount -= 1
|
||||
|
||||
if (lockCount === 0) {
|
||||
restoreStyleSnapshot(document.body, previousBodyStyle)
|
||||
restoreStyleSnapshot(document.documentElement, previousHtmlStyle)
|
||||
window.scrollTo(0, lockedScrollY)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.maskLayerShow,
|
||||
(newVal) => {
|
||||
if (!props.lockScroll) return
|
||||
|
||||
if (newVal) {
|
||||
lockPageScroll()
|
||||
} else {
|
||||
unlockPageScroll()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
if (props.lockScroll && props.maskLayerShow) {
|
||||
unlockPageScroll()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
* {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.mask-layer {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #00000080;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000 !important;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
overscroll-behavior: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mask-layer__content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
</style>
|
||||
522
backups/admin-center-ui-20260709/MyAgencyTeams.vue
Normal file
522
backups/admin-center-ui-20260709/MyAgencyTeams.vue
Normal file
@ -0,0 +1,522 @@
|
||||
<template>
|
||||
<div class="fullPage gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:showLanguageList="true"
|
||||
:title="t('my_agency_teams')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div
|
||||
style="
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
"
|
||||
>
|
||||
<!-- 总收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('total_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.totalIncome || 0 }}</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('previous_period_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.previousPeriodIncome || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Agency团队收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<!-- 团队信息 -->
|
||||
<div style="font-weight: 700">
|
||||
{{ t('agencies_label') }} {{ TeamOverview?.memberCount || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('team_total_income_label') }} ${{ TeamOverview?.teamTotalIncome || 0 }}
|
||||
</div>
|
||||
|
||||
<!-- Agency成员列表 -->
|
||||
<div
|
||||
v-for="AgencyInfo in TeamOverview?.members"
|
||||
style="
|
||||
position: relative;
|
||||
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="AgencyInfo.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ AgencyInfo.userName || '-' }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }}{{ AgencyInfo.account || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('number_of_hosts_personnel_label') }} {{ AgencyInfo?.subMemberCount || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('total_income') }} ${{ AgencyInfo?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更多按钮 -->
|
||||
<div class="moreBt" @click="incomeShow(AgencyInfo)">
|
||||
{{ t('more') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
|
||||
<div style="position: fixed; bottom: 0; width: 100%">
|
||||
<!-- 某个Agency的收益 -->
|
||||
<div
|
||||
v-if="showIncome"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="display: flex; justify-content: center; align-items: center">
|
||||
<div style="margin: 0; font-weight: 600; color: #333">
|
||||
{{ t('agency_id_label') }} {{ moreUserAccount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 总体信息 -->
|
||||
<div
|
||||
style="
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: 0 4px;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div style="display: grid; gap: 4px">
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('total_income') }} ${{ moreDetail?.totalIncome || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('previous_period_income') }}: ${{ moreDetail?.previousPeriodIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: 600">{{ t('income_details_label') }}</div>
|
||||
|
||||
<!-- 历史薪资列表 -->
|
||||
<div style="overflow-y: auto" class="salaryList">
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 8px"
|
||||
v-for="incomeInfo in moreDetail?.incomeDetails"
|
||||
>
|
||||
<!-- 进行中 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'In Progress'"
|
||||
type="In Progress"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px">
|
||||
<div>{{ t('host_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>{{ t('team_total_income_text') }} ${{ incomeInfo.teamTotalIncome }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
|
||||
<!-- 已完成 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'Completed'"
|
||||
type="Completed"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
<div>{{ t('host_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>{{ t('team_total_income_text') }} ${{ incomeInfo.teamTotalIncome }}</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 某个时期的Host收益详情 -->
|
||||
<div
|
||||
v-if="showHistory"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('host_label') }}:{{ incomeDetailsItem.memberCount }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('team_total_income_text_capitalized') }} ${{ incomeDetailsItem.teamTotalIncome }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Host列表 -->
|
||||
<div
|
||||
v-for="member in incomeDetailsItem.members"
|
||||
style="
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="member.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ member.userName }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }}{{ member.account }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500">{{ t('host_income_label') }}</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500">${{ member.income }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</maskLayer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setDocumentDirection } from '@/locales/i18n'
|
||||
import { handleAvatarImageError } from '@/utils/image/imageHandler.js'
|
||||
|
||||
import {
|
||||
apiGetTeamOverview, //查询BD团队概览
|
||||
apiGetTeamMemberDetail, // 查询BD团队成员详情(More弹框)
|
||||
} from '@/api/admin.js'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
import maskLayer from '@/components/MaskLayer.vue'
|
||||
import historySalary from './components/historySalary.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 监听语言变化并设置文档方向
|
||||
locale.value && setDocumentDirection(locale.value)
|
||||
|
||||
const TeamOverview = ref(null) //团队概览
|
||||
const moreUserAccount = ref('') //选中的成员id
|
||||
const moreDetail = ref(null) //某个成员的详情
|
||||
const incomeDetailsItem = ref(null) //某个时期的收益详情
|
||||
|
||||
const showIncome = ref(false)
|
||||
const showHistory = ref(false)
|
||||
// 遮罩层显示状态
|
||||
const maskLayerShow = computed(() => showIncome.value || showHistory.value)
|
||||
|
||||
const incomeShow = (AgencyInfo) => {
|
||||
console.log('点击了收入按钮')
|
||||
console.log('AgencyInfo.userId:', AgencyInfo.userId)
|
||||
moreUserAccount.value = AgencyInfo.account
|
||||
getTeamMemberDetail(AgencyInfo.userId)
|
||||
|
||||
showIncome.value = true
|
||||
}
|
||||
|
||||
const historyShow = (incomeInfo) => {
|
||||
console.log('点击了历史薪资按钮')
|
||||
console.log('incomeInfo:', incomeInfo)
|
||||
incomeDetailsItem.value = incomeInfo
|
||||
|
||||
showIncome.value = false
|
||||
showHistory.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closedPopup = () => {
|
||||
showIncome.value = false
|
||||
showHistory.value = false
|
||||
moreUserAccount.value = ''
|
||||
moreDetail.value = null
|
||||
incomeDetailsItem.value = null
|
||||
}
|
||||
|
||||
// 获取任务列表
|
||||
const getTeamOverview = async () => {
|
||||
let data = {
|
||||
type: 'AGENCY',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamOverview(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
TeamOverview.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 查询AGENCY团队成员详情(More弹框)
|
||||
const getTeamMemberDetail = async (userId) => {
|
||||
let data = {
|
||||
targetId: userId,
|
||||
type: 'AGENCY',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamMemberDetail(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
moreDetail.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
getTeamOverview()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
* {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
}
|
||||
|
||||
.fullPage::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.overflow-y-auto::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.salaryList::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.moreBt {
|
||||
font-size: 0.8em;
|
||||
|
||||
position: absolute;
|
||||
bottom: 4%;
|
||||
right: 4%;
|
||||
color: #bb92ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
[dir='rtl'] .moreBt {
|
||||
left: 4%;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
524
backups/admin-center-ui-20260709/MyBDLeaderTeams.vue
Normal file
524
backups/admin-center-ui-20260709/MyBDLeaderTeams.vue
Normal file
@ -0,0 +1,524 @@
|
||||
<template>
|
||||
<div class="fullPage gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:showLanguageList="true"
|
||||
:title="t('my_bd_leader_teams')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div
|
||||
style="
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
"
|
||||
>
|
||||
<!-- 总收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('total_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.totalIncome || 0 }}</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('previous_period_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.previousPeriodIncome || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BDLeader团队收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<!-- 团队信息 -->
|
||||
<div style="font-weight: 700">
|
||||
{{ t('bd_leaders_label') }} {{ TeamOverview?.memberCount || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('team_total_income_label') }} ${{ TeamOverview?.teamTotalIncome || 0 }}
|
||||
</div>
|
||||
|
||||
<!-- BDLeader成员列表 -->
|
||||
<div
|
||||
v-for="BDLeaderInfo in TeamOverview?.members"
|
||||
style="
|
||||
position: relative;
|
||||
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="BDLeaderInfo.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ BDLeaderInfo.userName || '-' }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }} {{ BDLeaderInfo.account || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('number_of_agencies_personnel_label') }} {{ BDLeaderInfo?.subMemberCount || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('total_income') }} ${{ BDLeaderInfo?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更多按钮 -->
|
||||
<div class="moreBt" @click="incomeShow(BDLeaderInfo)">
|
||||
{{ t('more') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
|
||||
<div style="position: fixed; bottom: 0; width: 100%">
|
||||
<!-- 某个BDLeader的收益 -->
|
||||
<div
|
||||
v-if="showIncome"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="display: flex; justify-content: center; align-items: center">
|
||||
<div style="margin: 0; font-weight: 600; color: #333">
|
||||
{{ t('bd_leader_id_label') }} {{ moreUserAccount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 总体信息 -->
|
||||
<div
|
||||
style="
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: 0 4px;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div style="display: grid; gap: 4px">
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('total_income') }} ${{ moreDetail?.totalIncome || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('previous_period_income') }}: ${{ moreDetail?.previousPeriodIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: 600">{{ t('income_details_label') }}</div>
|
||||
|
||||
<!-- 历史薪资列表 -->
|
||||
<div style="overflow-y: auto" class="salaryList">
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 8px"
|
||||
v-for="incomeInfo in moreDetail?.incomeDetails"
|
||||
>
|
||||
<!-- 进行中 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'In Progress'"
|
||||
type="In Progress"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px">
|
||||
<div>{{ t('agency_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>{{ t('team_total_income_text') }} ${{ incomeInfo.teamTotalIncome }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
|
||||
<!-- 已完成 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'Completed'"
|
||||
type="Completed"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
<div>{{ t('agency_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>
|
||||
{{ t('team_total_income_text_capitalized') }}${{ incomeInfo.teamTotalIncome }}
|
||||
</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 某个时期的BD收益详情 -->
|
||||
<div
|
||||
v-if="showHistory"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('agency_label') }} {{ incomeDetailsItem.memberCount }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('team_total_income_text_capitalized') }} ${{ incomeDetailsItem.teamTotalIncome }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BD列表 -->
|
||||
<div
|
||||
v-for="member in incomeDetailsItem.members"
|
||||
style="
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="member.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ member.userName }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }} {{ member.account }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500">{{ t('agency_team_income') }}</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500">${{ member.income }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</maskLayer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setDocumentDirection } from '@/locales/i18n'
|
||||
import { handleAvatarImageError } from '@/utils/image/imageHandler.js'
|
||||
|
||||
import {
|
||||
apiGetTeamOverview, //查询BD团队概览
|
||||
apiGetTeamMemberDetail, // 查询BD团队成员详情(More弹框)
|
||||
} from '@/api/admin.js'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
import maskLayer from '@/components/MaskLayer.vue'
|
||||
import historySalary from './components/historySalary.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 监听语言变化并设置文档方向
|
||||
locale.value && setDocumentDirection(locale.value)
|
||||
|
||||
const TeamOverview = ref(null) //团队概览
|
||||
const moreUserAccount = ref('') //选中的成员id
|
||||
const moreDetail = ref(null) //某个成员的详情
|
||||
const incomeDetailsItem = ref(null) //某个时期的收益详情
|
||||
|
||||
const showIncome = ref(false)
|
||||
const showHistory = ref(false)
|
||||
// 遮罩层显示状态
|
||||
const maskLayerShow = computed(() => showIncome.value || showHistory.value)
|
||||
|
||||
const incomeShow = (BDLeaderInfo) => {
|
||||
console.log('点击了收入按钮')
|
||||
console.log('BDLeaderInfo.userId:', BDLeaderInfo.userId)
|
||||
moreUserAccount.value = BDLeaderInfo.account
|
||||
getTeamMemberDetail(BDLeaderInfo.userId)
|
||||
|
||||
showIncome.value = true
|
||||
}
|
||||
|
||||
const historyShow = (incomeInfo) => {
|
||||
console.log('点击了历史薪资按钮')
|
||||
console.log('incomeInfo:', incomeInfo)
|
||||
incomeDetailsItem.value = incomeInfo
|
||||
|
||||
showIncome.value = false
|
||||
showHistory.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closedPopup = () => {
|
||||
showIncome.value = false
|
||||
showHistory.value = false
|
||||
moreUserAccount.value = ''
|
||||
moreDetail.value = null
|
||||
incomeDetailsItem.value = null
|
||||
}
|
||||
|
||||
// 获取任务列表
|
||||
const getTeamOverview = async () => {
|
||||
let data = {
|
||||
type: 'BD_LEADER',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamOverview(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
TeamOverview.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 查询BD团队成员详情(More弹框)
|
||||
const getTeamMemberDetail = async (userId) => {
|
||||
let data = {
|
||||
targetId: userId,
|
||||
type: 'BD',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamMemberDetail(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
moreDetail.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
getTeamOverview()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
* {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
}
|
||||
|
||||
.fullPage::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.overflow-y-auto::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.salaryList::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.moreBt {
|
||||
font-size: 0.8em;
|
||||
|
||||
position: absolute;
|
||||
bottom: 4%;
|
||||
right: 4%;
|
||||
color: #bb92ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
[dir='rtl'] .moreBt {
|
||||
left: 4%;
|
||||
right: auto;
|
||||
}
|
||||
</style>
|
||||
519
backups/admin-center-ui-20260709/MyBDTeams.vue
Normal file
519
backups/admin-center-ui-20260709/MyBDTeams.vue
Normal file
@ -0,0 +1,519 @@
|
||||
<template>
|
||||
<div class="fullPage gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:showLanguageList="true"
|
||||
:title="t('my_bd_teams')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div
|
||||
style="
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
"
|
||||
>
|
||||
<!-- 总收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('total_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.totalIncome || 0 }}</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<div style="font-weight: 600; font-size: 0.9em">{{ t('previous_period_income') }}</div>
|
||||
<div style="font-weight: 700">${{ TeamOverview?.previousPeriodIncome || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BD团队收入信息 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<!-- 团队信息 -->
|
||||
<div style="font-weight: 700">{{ t('bds_label') }}{{ TeamOverview?.memberCount || 0 }}</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('team_total_income_label') }} ${{ TeamOverview?.teamTotalIncome || 0 }}
|
||||
</div>
|
||||
|
||||
<!-- BD成员列表 -->
|
||||
<div
|
||||
v-for="BDInfo in TeamOverview?.members"
|
||||
style="
|
||||
position: relative;
|
||||
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="BDInfo.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ BDInfo.userName || '-' }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }} {{ BDInfo.account || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('number_of_agencies_personnel_label') }} {{ BDInfo?.subMemberCount || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500; text-align: end">
|
||||
{{ t('total_income') }} ${{ BDInfo?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更多按钮 -->
|
||||
<div class="moreBt" @click="incomeShow(BDInfo)">
|
||||
{{ t('more') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
|
||||
<div style="position: fixed; bottom: 0; width: 100%">
|
||||
<!-- 某个BD的收益 -->
|
||||
<div
|
||||
v-if="showIncome"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="display: flex; justify-content: center; align-items: center">
|
||||
<div style="margin: 0; font-weight: 600; color: #333">
|
||||
{{ t('bd_id_label') }} {{ moreUserAccount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 总体信息 -->
|
||||
<div
|
||||
style="
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: 0 4px;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div style="display: grid; gap: 4px">
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('total_income') }} ${{ moreDetail?.totalIncome || 0 }}
|
||||
</div>
|
||||
<div style="font-size: 0.9em; font-weight: 600">
|
||||
{{ t('previous_period_income') }}: ${{ moreDetail?.previousPeriodIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: 600">{{ t('income_details_label') }}</div>
|
||||
|
||||
<!-- 历史薪资列表 -->
|
||||
<div style="overflow-y: auto" class="salaryList">
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 8px"
|
||||
v-for="incomeInfo in moreDetail?.incomeDetails"
|
||||
>
|
||||
<!-- 进行中 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'In Progress'"
|
||||
type="In Progress"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px">
|
||||
<div>{{ t('agency_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>{{ t('team_total_income_text') }} ${{ incomeInfo.teamTotalIncome }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
|
||||
<!-- 已完成 -->
|
||||
<historySalary
|
||||
v-if="incomeInfo.status === 'Completed'"
|
||||
type="Completed"
|
||||
@showMore="historyShow(incomeInfo)"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<div>{{ incomeInfo.settlementPeriod }}</div>
|
||||
<div>{{ t('agency_label') }} {{ incomeInfo.memberCount }}</div>
|
||||
<!-- 团队收入 -->
|
||||
<div>{{ t('team_total_income_text') }} ${{ incomeInfo.teamTotalIncome }}</div>
|
||||
</template>
|
||||
</historySalary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 某个时期的Agency收益详情 -->
|
||||
<div
|
||||
v-if="showHistory"
|
||||
style="
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 30px;
|
||||
"
|
||||
class="overflow-y-auto"
|
||||
@click.stop
|
||||
>
|
||||
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('agency_label') }} {{ incomeDetailsItem.memberCount }}
|
||||
</div>
|
||||
<div style="font-size: 0.8em; font-weight: 600">
|
||||
{{ t('team_total_income_text_capitalized') }} ${{ incomeDetailsItem.teamTotalIncome }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BD列表 -->
|
||||
<div
|
||||
v-for="member in incomeDetailsItem.members"
|
||||
style="
|
||||
background-color: white;
|
||||
padding: 14px 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 15%;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="member.avatar || ''"
|
||||
alt=""
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ member.userName }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }} {{ member.account }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收入信息 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
"
|
||||
>
|
||||
<div style="font-size: 0.8em; font-weight: 500">{{ t('agency_team_income') }}</div>
|
||||
<div style="font-size: 0.8em; font-weight: 500">${{ member.income }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</maskLayer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setDocumentDirection } from '@/locales/i18n'
|
||||
import { handleAvatarImageError } from '@/utils/image/imageHandler.js'
|
||||
|
||||
import {
|
||||
apiGetTeamOverview, //查询BD团队概览
|
||||
apiGetTeamMemberDetail, // 查询BD团队成员详情(More弹框)
|
||||
} from '@/api/admin.js'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
import maskLayer from '@/components/MaskLayer.vue'
|
||||
import historySalary from './components/historySalary.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 监听语言变化并设置文档方向
|
||||
locale.value && setDocumentDirection(locale.value)
|
||||
|
||||
const TeamOverview = ref(null) //团队概览
|
||||
const moreUserAccount = ref('') //选中的成员id
|
||||
const moreDetail = ref(null) //某个成员的详情
|
||||
const incomeDetailsItem = ref(null) //某个时期的收益详情
|
||||
|
||||
const showIncome = ref(false)
|
||||
const showHistory = ref(false)
|
||||
// 遮罩层显示状态
|
||||
const maskLayerShow = computed(() => showIncome.value || showHistory.value)
|
||||
|
||||
const incomeShow = (BDInfo) => {
|
||||
console.log('点击了收入按钮')
|
||||
console.log('BDInfo.userId:', BDInfo.userId)
|
||||
moreUserAccount.value = BDInfo.account
|
||||
getTeamMemberDetail(BDInfo.userId)
|
||||
|
||||
showIncome.value = true
|
||||
}
|
||||
|
||||
const historyShow = (incomeInfo) => {
|
||||
console.log('点击了历史薪资按钮')
|
||||
console.log('incomeInfo:', incomeInfo)
|
||||
incomeDetailsItem.value = incomeInfo
|
||||
|
||||
showIncome.value = false
|
||||
showHistory.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closedPopup = () => {
|
||||
showIncome.value = false
|
||||
showHistory.value = false
|
||||
moreUserAccount.value = ''
|
||||
moreDetail.value = null
|
||||
incomeDetailsItem.value = null
|
||||
}
|
||||
|
||||
// 获取任务列表
|
||||
const getTeamOverview = async () => {
|
||||
let data = {
|
||||
type: 'BD',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamOverview(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
TeamOverview.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 查询BD团队成员详情(More弹框)
|
||||
const getTeamMemberDetail = async (userId) => {
|
||||
let data = {
|
||||
targetId: userId,
|
||||
type: 'BD',
|
||||
}
|
||||
const resInvitedList = await apiGetTeamMemberDetail(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
moreDetail.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
getTeamOverview()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
* {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
}
|
||||
|
||||
.fullPage::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.overflow-y-auto::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.salaryList::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.moreBt {
|
||||
font-size: 0.8em;
|
||||
|
||||
position: absolute;
|
||||
bottom: 4%;
|
||||
right: 4%;
|
||||
color: #bb92ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
[dir='rtl'] .moreBt {
|
||||
left: 4%;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
320
backups/admin-center-ui-20260709/MyRechargeAgency.vue
Normal file
320
backups/admin-center-ui-20260709/MyRechargeAgency.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="fullPage">
|
||||
<div class="bg">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:showLanguageList="true"
|
||||
:title="t('my_linked_recharge_agent')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div
|
||||
style="
|
||||
padding: 12px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
"
|
||||
>
|
||||
<!-- 这个月总充值 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
"
|
||||
>
|
||||
<div style="font: 0.8em; font-weight: 500; color: rgba(0, 0, 0, 0.4)">
|
||||
{{ t('my_total_income') }}
|
||||
</div>
|
||||
<div style="font: 0.9em; font-weight: 600; color: rgba(0, 0, 0, 0.8)">
|
||||
${{ totalRechargeDetail?.totalIncome || 0 }}
|
||||
</div>
|
||||
<div style="font: 0.8em; font-weight: 500; color: rgba(0, 0, 0, 0.4)">
|
||||
{{ t('this_period_recharge') }}
|
||||
</div>
|
||||
<div style="font: 0.9em; font-weight: 600; color: rgba(0, 0, 0, 0.8)">
|
||||
${{ totalRechargeDetail?.currentMonthRecharge || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: 700">{{ t('recharge_agency_list') }}</div>
|
||||
|
||||
<div
|
||||
v-for="(agency, aIndex) in agencyList"
|
||||
:key="aIndex"
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s ease-out;
|
||||
"
|
||||
@click="showAgencyInfo(aIndex)"
|
||||
>
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="agency.userAvatar"
|
||||
alt=""
|
||||
style="display: block; object-fit: cover; width: 3em; border-radius: 50%"
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ agency.userNickname }}
|
||||
</div>
|
||||
<div style="font-weight: 700; color: rgba(0, 0, 0, 0.4)">
|
||||
{{ t('user_id_prefix') }}{{ agency.account }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展开按钮 -->
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/arrow.png"
|
||||
alt=""
|
||||
style="
|
||||
width: 24px;
|
||||
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
"
|
||||
:class="{ rotated: aIndex == selectedAgencyIndex }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展示信息 -->
|
||||
<transition name="info-fade">
|
||||
<div style="display: flex" v-show="aIndex == selectedAgencyIndex">
|
||||
<div style="flex: 1">
|
||||
<div style="font-weight: 500; color: rgba(0, 0, 0, 0.4)">
|
||||
{{ t('this_month') }}:
|
||||
</div>
|
||||
<div style="font-weight: 600; color: rgba(0, 0, 0, 0.8)">
|
||||
${{ formatPrice(agency.thisMonth) || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<div style="font-weight: 500; color: rgba(0, 0, 0, 0.4)">
|
||||
{{ t('last_month') }}:
|
||||
</div>
|
||||
<div style="font-weight: 600; color: rgba(0, 0, 0, 0.8)">
|
||||
${{ formatPrice(agency.lastMonth) || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<!-- 触发加载功能 -->
|
||||
<div ref="Agloadmore" v-if="showAgLoading"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useThrottle } from '@/utils/useDebounce'
|
||||
|
||||
import { getAgencyList, getAgencyTotalRecharge } from '@/api/userInfo'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const selectedAgencyIndex = ref(-1) //选中的agency用户下标
|
||||
const showAgencyInfo = (index) => {
|
||||
selectedAgencyIndex.value = selectedAgencyIndex.value != index ? index : -1
|
||||
} //选中的agency用户
|
||||
|
||||
const send = async () => {
|
||||
router.push({ path: '/item-distribution' })
|
||||
}
|
||||
|
||||
const totalRechargeDetail = ref({}) //代理消费金额
|
||||
const agencyList = ref([]) // 代理列表
|
||||
const agencyCurrent = ref(1) //代理列表第几页
|
||||
const showAgLoading = ref(true) //触底加载功能
|
||||
|
||||
onMounted(async () => {
|
||||
getAgencise() // 获取代理列表
|
||||
getAgencyMonthsRecharge() //获取代理总支付
|
||||
|
||||
// 开始监听底部元素
|
||||
if (Agloadmore.value) {
|
||||
observer.observe(Agloadmore.value)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取代理列表
|
||||
const getAgencyMonthsRecharge = async () => {
|
||||
const resTotalRecharge = await getAgencyTotalRecharge()
|
||||
console.log('resTotalRecharge:', resTotalRecharge)
|
||||
if (resTotalRecharge.status && resTotalRecharge.body) {
|
||||
totalRechargeDetail.value = resTotalRecharge.body
|
||||
}
|
||||
}
|
||||
|
||||
// 获取代理列表
|
||||
const getAgencise = async () => {
|
||||
let data = {
|
||||
cursor: agencyCurrent.value,
|
||||
limit: 20,
|
||||
}
|
||||
const resagencyList = await getAgencyList(data)
|
||||
console.log('resagencyList:', resagencyList)
|
||||
if (resagencyList.status && resagencyList.body) {
|
||||
agencyList.value.push(...resagencyList.body.records)
|
||||
if (resagencyList.body.total <= resagencyList.body.current * resagencyList.body.size) {
|
||||
showAgLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Agloadmore = ref(null)
|
||||
|
||||
const debouceGetAgList = useThrottle(getAgencise, 1000)
|
||||
|
||||
// IntersectionObserver配置
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.intersectionRatio > 0) {
|
||||
console.log('监控')
|
||||
if (entry.target == Agloadmore.value && agencyList.value.length != 0) {
|
||||
agencyCurrent.value++
|
||||
debouceGetAgList()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const formatPrice = (value) => {
|
||||
if (value && value != 0) {
|
||||
return Number(value).toFixed(2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bg {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.tabName {
|
||||
color: #00000066;
|
||||
}
|
||||
|
||||
.tabName-active {
|
||||
color: black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-fade-enter-from,
|
||||
.info-fade-leave-to {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.info-fade-enter-active {
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
|
||||
.info-fade-leave-active {
|
||||
transition: all 0.5s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
.rotated {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
* {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
172
backups/admin-center-ui-20260709/components/historySalary.vue
Normal file
172
backups/admin-center-ui-20260709/components/historySalary.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div style="position: relative; width: 100%">
|
||||
<img :src="bgUrl" alt="" width="100%" style="display: block" class="flipImg" />
|
||||
<div class="status">
|
||||
<div
|
||||
style="width: 50%; height: 50%; display: flex; justify-content: center; align-items: center"
|
||||
>
|
||||
<div style="color: #fff; font-weight: 600" :style="{ fontSize }">
|
||||
{{ getTypeText(type) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; inset: 0; padding: 3% 4%">
|
||||
<div
|
||||
style="height: 100%; display: flex; flex-direction: column; justify-content: space-around"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<div class="moreBt" @click="btMore">{{ t('more') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setDocumentDirection } from '@/locales/i18n'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 监听语言变化并设置文档方向
|
||||
locale.value && setDocumentDirection(locale.value)
|
||||
|
||||
const progress = new URL('@/assets/images/Azizi/BDCenter/progress.png', import.meta.url).href
|
||||
const completed = new URL('@/assets/images/Azizi/BDCenter/completed.png', import.meta.url).href
|
||||
const progressLong = new URL('@/assets/images/Azizi/BDCenter/progressLong.png', import.meta.url)
|
||||
.href
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
long: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
date: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const bgUrl = computed(() => {
|
||||
if (props.type == 'In Progress' && props.long) {
|
||||
return progressLong
|
||||
} else if (props.type == 'In Progress') {
|
||||
return progress
|
||||
} else if (props.type == 'Completed') {
|
||||
return completed
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['showMore'])
|
||||
const btMore = () => {
|
||||
emit('showMore')
|
||||
}
|
||||
|
||||
const backgroundImage = ref('')
|
||||
const fontSize = ref('')
|
||||
|
||||
watch(
|
||||
() => props.type,
|
||||
(newType) => {
|
||||
if (newType == 'In Progress') {
|
||||
fontSize.value = '0.9em'
|
||||
}
|
||||
if (newType == 'Completed') {
|
||||
fontSize.value = '1em'
|
||||
}
|
||||
return
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 根据状态文本返回翻译后的文本
|
||||
const getTypeText = (type) => {
|
||||
console.log('type:', type)
|
||||
|
||||
switch (type) {
|
||||
case 'In Progress':
|
||||
return t('in_progress')
|
||||
case 'Completed':
|
||||
return t('completed')
|
||||
case 'Out of account':
|
||||
return t('out_of_account')
|
||||
case 'Pending':
|
||||
return t('pending')
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
* {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.contentTime {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.contentText {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.moreBt {
|
||||
font-size: 0.8em;
|
||||
position: absolute;
|
||||
bottom: 4%;
|
||||
right: 4%;
|
||||
color: #bb92ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 48%;
|
||||
height: 40%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 3%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[dir='rtl'] .status {
|
||||
right: auto;
|
||||
left: 3%;
|
||||
}
|
||||
|
||||
[dir='rtl'] .flipImg {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
[dir='rtl'] .moreBt {
|
||||
right: auto;
|
||||
left: 4%;
|
||||
}
|
||||
|
||||
@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>
|
||||
609
backups/admin-center-ui-20260709/index.vue
Normal file
609
backups/admin-center-ui-20260709/index.vue
Normal file
@ -0,0 +1,609 @@
|
||||
<template>
|
||||
<div class="fullPage">
|
||||
<div class="bg gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:isHomePage="true"
|
||||
:showLanguageList="true"
|
||||
:title="t('admin_center')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div
|
||||
style="
|
||||
padding: 16px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
"
|
||||
>
|
||||
<!-- 用户信息卡片 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div style="font-weight: 600">{{ t('my_leader') }}</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div style="display: flex; align-items: center; gap: 4px">
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
style="
|
||||
width: 3.5em;
|
||||
align-self: stretch;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="userInfo.userAvatar || ''"
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
"
|
||||
@error="handleAvatarImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
"
|
||||
>
|
||||
{{ userInfo.userNickname || '-' }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('user_id_prefix') }} {{ userInfo.account || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色模块 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div style="font-weight: 600; color: #1f2937">{{ t('my_role') }}</div>
|
||||
|
||||
<!-- 角色身份 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<div style="font-size: 1.1em; font-weight: 600">{{ t('admin') }}</div>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/detail.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.5em"
|
||||
class="flipImg"
|
||||
@click="gotoPolicy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
|
||||
<!-- 已完成任务 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<div style="font-weight: 600">{{ t('task_completion_status') }}</div>
|
||||
<div style="font-weight: 600">({{ taskData.completedTaskCount || 0 }})</div>
|
||||
</div>
|
||||
|
||||
<div v-if="taskData.tasks" style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 500">
|
||||
{{ taskData.cycleStartDate }}-{{ taskData.cycleEndDate }}
|
||||
</div>
|
||||
<!-- 任务列表与完成状态 -->
|
||||
<div
|
||||
v-for="task in taskData.tasks"
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ taskName(task) }}
|
||||
</div>
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ taskCompletedText(task) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本期团队收入 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<!-- 总收入 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<!-- 基本信息 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 600">
|
||||
{{ t('team_earnings_this_period') }} ${{ teamSummary.teamEarningsThisPeriod || 0 }}
|
||||
</div>
|
||||
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('available_salaries') }} ${{ availableIncome?.availableBalance || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 跳转按钮 -->
|
||||
<img
|
||||
src="../../assets/icon/Azizi/arrowBlack.png"
|
||||
alt=""
|
||||
style="display: block; width: 2em"
|
||||
class="flipImg"
|
||||
@click="gotoIncome"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
|
||||
<!-- 我的BD Leader团队 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<!-- 基本信息 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 600">
|
||||
{{ t('my_bd_leader_teams') }}({{ teamSummary.bdLeaderTeams?.count || 0 }})
|
||||
</div>
|
||||
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('total_income') }} ${{ teamSummary.bdLeaderTeams?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 跳转按钮 -->
|
||||
<div style="align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<img
|
||||
src="../../assets/icon/Azizi/addUser.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/invite-bd-leader')"
|
||||
/>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/list.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/my-BDLeader-teams')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
|
||||
<!-- 我的BD团队 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<!-- 基本信息 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 600">
|
||||
{{ t('my_bd_teams') }}({{ teamSummary.bdTeams?.count || 0 }})
|
||||
</div>
|
||||
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('total_income') }} ${{ teamSummary.bdTeams?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 跳转按钮 -->
|
||||
<div style="align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<img
|
||||
src="../../assets/icon/Azizi/addUser.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/invite-bd')"
|
||||
/>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/list.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/my-BD-teams')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
|
||||
<!-- 我的Agency团队 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<!-- 基本信息 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 600">
|
||||
{{ t('my_agency_teams') }}({{ teamSummary.agencyTeams?.count || 0 }})
|
||||
</div>
|
||||
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('total_income') }} ${{ teamSummary.agencyTeams?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 跳转按钮 -->
|
||||
<div style="align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<img
|
||||
src="../../assets/icon/Azizi/addUser.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/invite-agency')"
|
||||
/>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/list.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/my-agency-teams')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
|
||||
<!-- 我的Recharge Agency团队 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<!-- 基本信息 -->
|
||||
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||
<div style="font-weight: 600">
|
||||
{{ t('number_of_recharge_agents_invited') }}({{
|
||||
teamSummary.rechargeTeams?.count || 0
|
||||
}})
|
||||
</div>
|
||||
|
||||
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
|
||||
{{ t('total_recharge') }}: ${{ teamSummary.rechargeTeams?.totalIncome || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 跳转按钮 -->
|
||||
<div style="align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<img
|
||||
src="../../assets/icon/Azizi/addUser.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/invite-recharge-agency')"
|
||||
/>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/list.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.6em"
|
||||
class="flipImg"
|
||||
@click="gotoPage('/my-recharge-agency')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div style="border-bottom: 1px solid #e6e6e6"></div>
|
||||
</div>
|
||||
|
||||
<!-- 赠送礼物 -->
|
||||
<div
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
@click="send"
|
||||
>
|
||||
<div style="font-weight: 600">{{ t('send_welcome_gift') }}</div>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/arrowBlack.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.5em"
|
||||
class="flipImg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 前往用户编辑 -->
|
||||
<div
|
||||
v-if="teamPermissionCheck.hasUserEditingPermission"
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
@click="gotoAppPage('editingUser')"
|
||||
>
|
||||
<div style="font-weight: 600">{{ t('user_editing') }}</div>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/arrowBlack.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.5em"
|
||||
class="flipImg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 前往房间编辑 -->
|
||||
<div
|
||||
v-if="teamPermissionCheck.hasRoomEditingPermission"
|
||||
style="
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
"
|
||||
@click="gotoAppPage('editingRoom')"
|
||||
>
|
||||
<div style="font-weight: 600">{{ t('room_editing') }}</div>
|
||||
<img
|
||||
src="../../assets/icon/Azizi/arrowBlack.png"
|
||||
alt=""
|
||||
style="display: block; width: 1.5em"
|
||||
class="flipImg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setDocumentDirection } from '@/locales/i18n'
|
||||
import { gotoAppPage } from '@/utils/appBridge.js'
|
||||
import { getUserId } from '@/utils/userStore.js'
|
||||
import { usePageInitializationWithConfig } from '@/utils/pageConfig.js'
|
||||
import { handleAvatarImageError } from '@/utils/image/imageHandler.js'
|
||||
import { useIdentityStore } from '@/stores/identity.js'
|
||||
import { showError } from '@/utils/toast.js'
|
||||
|
||||
import {
|
||||
apiTaskList, //获取任务列表
|
||||
apiGetTeamSummary, // 查询BD团队总览
|
||||
apiGetTeamPermissionCheck, // 检查权限
|
||||
} from '@/api/admin.js'
|
||||
import {
|
||||
apiGetBankBalance, //获取当前余额
|
||||
} from '@/api/wallet.js'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const router = useRouter()
|
||||
const identityStore = useIdentityStore()
|
||||
|
||||
// 监听语言变化并设置文档方向
|
||||
locale.value && setDocumentDirection(locale.value)
|
||||
|
||||
const taskData = ref({}) //任务项目数据
|
||||
const availableIncome = ref({})
|
||||
const teamSummary = ref({}) //团队总览
|
||||
const teamPermissionCheck = ref({}) //团队权限检查
|
||||
|
||||
// 任务名称
|
||||
const taskName = (task) => {
|
||||
if (task.taskType == 'MIC_USAGE') {
|
||||
return t('mic_usage_time')
|
||||
} else if (task.taskType == 'WORKING_DAYS') {
|
||||
return t('working_days_desc')
|
||||
} else if (task.taskType == 'NEW_BD_LEADERS') {
|
||||
return t('new_bd_leaders_bound')
|
||||
} else if (task.taskType == 'NEW_BDS') {
|
||||
return t('new_bds_bound')
|
||||
} else if (task.taskType == 'NEW_AGENCIES') {
|
||||
return t('new_agencies_bound')
|
||||
}
|
||||
}
|
||||
|
||||
// 任务完成情况
|
||||
const taskCompletedText = (task) => {
|
||||
if (task.taskType == 'MIC_USAGE') {
|
||||
return `(${task.completedCount}/${task.targetCount}h)`
|
||||
} else {
|
||||
return `(${task.completedCount}/${task.targetCount})`
|
||||
}
|
||||
}
|
||||
|
||||
const send = () => {
|
||||
router.push({ path: '/item-distribution' })
|
||||
}
|
||||
|
||||
// 前往收益处理页面
|
||||
const gotoIncome = () => {
|
||||
router.push({ path: '/available-income' })
|
||||
}
|
||||
|
||||
// 前往政策页面
|
||||
const gotoPolicy = () => {
|
||||
router.push({ path: '/admin-policy' })
|
||||
}
|
||||
|
||||
// 前往页面
|
||||
const gotoPage = (path) => {
|
||||
router.push({ path: path })
|
||||
}
|
||||
|
||||
// 获取任务列表
|
||||
const getTaskList = async () => {
|
||||
let data = {
|
||||
userId: getUserId(),
|
||||
}
|
||||
const resInvitedList = await apiTaskList(data)
|
||||
console.log('任务列表:', resInvitedList)
|
||||
if (resInvitedList.status && resInvitedList.body) {
|
||||
taskData.value = resInvitedList.body
|
||||
}
|
||||
}
|
||||
|
||||
// 获取银行余额
|
||||
const fetchBankBalance = async () => {
|
||||
try {
|
||||
const response = await apiGetBankBalance('ADMIN_SALARY')
|
||||
|
||||
if (response.status && response.body) {
|
||||
availableIncome.value = response.body || {}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch bank balance:', error)
|
||||
showError(t('failed_to_load_balance'))
|
||||
}
|
||||
}
|
||||
|
||||
// 查询团队总览
|
||||
const getTeamSummary = async () => {
|
||||
const resTeamSummary = await apiGetTeamSummary()
|
||||
console.log('团队总览:', resTeamSummary)
|
||||
if (resTeamSummary.status && resTeamSummary.body) {
|
||||
teamSummary.value = resTeamSummary.body
|
||||
}
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
const getTeamPermissionCheck = async () => {
|
||||
const resTeamPermissionCheck = await apiGetTeamPermissionCheck()
|
||||
console.log('权限列表:', resTeamPermissionCheck)
|
||||
if (resTeamPermissionCheck.status && resTeamPermissionCheck.body) {
|
||||
teamPermissionCheck.value = resTeamPermissionCheck.body
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
getTaskList() // 获取任务列表
|
||||
fetchBankBalance() // 获取银行余额
|
||||
getTeamSummary() // 查询BD团队总览
|
||||
getTeamPermissionCheck() // 检查权限
|
||||
}
|
||||
|
||||
const { userInfo } = usePageInitializationWithConfig('ADMIN_CENTER', {
|
||||
forceRefresh: true, // 强制刷新
|
||||
needsTeamInfo: true,
|
||||
needsBankBalance: false,
|
||||
onDataLoaded: () => {},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
identityStore.setIdentity('ADMIN')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.fullPage {
|
||||
position: relative;
|
||||
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.fullPage::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bg {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
[dir='rtl'] .flipImg {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
</style>
|
||||
94
backups/admin-center-ui-20260709/policy.vue
Normal file
94
backups/admin-center-ui-20260709/policy.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="policy-container">
|
||||
<!-- 顶部导航 -->
|
||||
<GeneralHeader
|
||||
:title="t('admin_policy')"
|
||||
color="black"
|
||||
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
|
||||
/>
|
||||
<div v-if="userRegion">
|
||||
<div v-if="userRegion.regionCode != 'AR'">
|
||||
<img v-smart-img :src="imageUrl('NOT_AR_Policy')" alt="Policy Image" class="policy-image" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<img v-smart-img :src="imageUrl(getImgName('policy0'))" alt="" class="policy-image" />
|
||||
<img v-smart-img :src="imageUrl(getImgName('policy1'))" alt="" class="policy-image" />
|
||||
<img v-smart-img :src="imageUrl(getImgName('policy2'))" alt="" class="policy-image" />
|
||||
<img v-smart-img :src="imageUrl(getImgName('policy3'))" alt="" class="policy-image" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useLangStore } from '@/stores/lang'
|
||||
import { getPngUrl } from '@/config/imagePaths.js'
|
||||
import { preloadImages } from '@/utils/image/imagePreloader.js'
|
||||
import { getUserId } from '@/utils/userStore.js'
|
||||
|
||||
import { getRegionByUserId } from '@/api/wallet.js'
|
||||
|
||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||
|
||||
// 获取OSS图片URL的函数
|
||||
const imageUrl = (filename) => getPngUrl('AdminCenter/Policy/', filename)
|
||||
// 根据语言获取图片名
|
||||
const getImgName = (filename) => {
|
||||
return currentLangType.value === 'ar' ? `${filename}_ar` : filename
|
||||
}
|
||||
|
||||
const langStore = useLangStore()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 当前语言类型
|
||||
const currentLangType = computed(() => {
|
||||
return langStore.selectedLang?.type || 'en'
|
||||
})
|
||||
|
||||
const userRegion = ref(null) // 区域代码
|
||||
|
||||
// 预加载关键图片
|
||||
const preloadCriticalImages = () => {
|
||||
const criticalImages = [
|
||||
imageUrl(getImgName('policy0')),
|
||||
imageUrl(getImgName('policy1')),
|
||||
imageUrl(getImgName('policy2')),
|
||||
imageUrl(getImgName('policy3')),
|
||||
]
|
||||
|
||||
preloadImages(criticalImages)
|
||||
}
|
||||
|
||||
// 获取用户所在的区域信息
|
||||
const apiGetRegionByUserId = async () => {
|
||||
const response = await getRegionByUserId(getUserId())
|
||||
if (response.status) {
|
||||
userRegion.value = response.body
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
apiGetRegionByUserId()
|
||||
preloadCriticalImages() //预加载关键图片
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.policy-container {
|
||||
height: 100vh;
|
||||
background-image: url(../../assets/images/Azizi/secondBg.png);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.policy-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.policy-image {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
@ -20,7 +20,8 @@
|
||||
"build:azizi-prod": "vite build --mode production-azizi && node scripts/generate-version.js",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --fix",
|
||||
"docs:sync:security": "node scripts/sync-security-docs.js"
|
||||
"docs:sync:security": "node scripts/sync-security-docs.js",
|
||||
"aslan:webview": "node scripts/aslan-webview-sim.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^13.9.0",
|
||||
|
||||
176
scripts/aslan-webview-sim.mjs
Normal file
176
scripts/aslan-webview-sim.mjs
Normal file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const LOGIN_URL =
|
||||
'https://api.atuchat.com/e64f27b9ba6b37881120f4584a5444a5c684d8491b703d0af953e5cc6a5f4cec'
|
||||
|
||||
const account = process.env.ASLAN_ACCOUNT || '6666'
|
||||
const password = process.env.ASLAN_PASSWORD || '123456'
|
||||
const targetUrl =
|
||||
process.env.ASLAN_URL || 'https://h5.atuchat.com/#/admin-center?lang=en'
|
||||
const chromePath =
|
||||
process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
||||
const headless = process.env.ASLAN_HEADLESS === '1'
|
||||
const exitAfterLoad = process.env.ASLAN_EXIT_AFTER_LOAD === '1'
|
||||
|
||||
async function loadPlaywright() {
|
||||
try {
|
||||
return await import('playwright')
|
||||
} catch {
|
||||
return await import(
|
||||
'/Users/hy/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright/index.mjs'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function appHeaders(imei = 'aslan-webview-sim') {
|
||||
return {
|
||||
'req-lang': 'en',
|
||||
'X-Forwarded-Proto': 'http',
|
||||
'User-Agent': 'Dart/3.7.2 (dart:io)',
|
||||
'req-imei': imei,
|
||||
'req-app-intel':
|
||||
'version=1.0.0;build=1;model=CodexWebView;sysVersion=1;channel=Google',
|
||||
'req-client': 'Android',
|
||||
'req-sys-origin': 'origin=ATYOU;originChild=ATYOU',
|
||||
'Req-Atyou': 'true',
|
||||
}
|
||||
}
|
||||
|
||||
function publicUserProfile(profile = {}) {
|
||||
return {
|
||||
id: profile.id || profile.userId,
|
||||
account: profile.account,
|
||||
specialAccount: profile.ownSpecialId?.account,
|
||||
userNickname: profile.userNickname,
|
||||
countryName: profile.countryName,
|
||||
originSys: profile.originSys || profile.sysOrigin,
|
||||
sysOriginChild: profile.sysOriginChild,
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const response = await fetch(LOGIN_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...appHeaders('aslan-webview-sim-login'),
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ account, pwd: password }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (!response.ok || !data.status || !data.body?.token) {
|
||||
const error = {
|
||||
httpStatus: response.status,
|
||||
status: data.status,
|
||||
errorCode: data.errorCode,
|
||||
errorMsg: data.errorMsg,
|
||||
}
|
||||
throw new Error(`Aslan login failed: ${JSON.stringify(error)}`)
|
||||
}
|
||||
|
||||
return data.body
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const auth = await login()
|
||||
const profile = auth.userProfile || {}
|
||||
const { chromium } = await loadPlaywright()
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless,
|
||||
executablePath: chromePath,
|
||||
args: ['--no-sandbox'],
|
||||
})
|
||||
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 390, height: 844 },
|
||||
isMobile: true,
|
||||
hasTouch: true,
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Linux; Android 12; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
|
||||
})
|
||||
|
||||
await context.addInitScript(
|
||||
({ token, profile }) => {
|
||||
window.__aslanWebViewMessages = []
|
||||
|
||||
const postBridgeMessage = (message) => {
|
||||
const text = String(message)
|
||||
window.__aslanWebViewMessages.push(text)
|
||||
console.info(`[AslanWebViewSim] FlutterPageControl.postMessage: ${text}`)
|
||||
}
|
||||
|
||||
window.FlutterPageControl = {
|
||||
postMessage: postBridgeMessage,
|
||||
}
|
||||
|
||||
window.app = {
|
||||
getAccessOrigin: () =>
|
||||
JSON.stringify({
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Req-Lang': 'en',
|
||||
'Req-App-Intel':
|
||||
'build=1;version=1.0.0;model=CodexWebView;channel=Google;Req-Imei=aslan-webview-sim',
|
||||
'Req-Sys-Origin': 'origin=ATYOU;originChild=ATYOU',
|
||||
}),
|
||||
closePage: () => postBridgeMessage('close_page'),
|
||||
back: () => history.back(),
|
||||
gotoRoom: (roomId) => postBridgeMessage(`go_to_room:${roomId}`),
|
||||
gotoPrivateChat: (userId) => postBridgeMessage(`private_chat:${userId}`),
|
||||
openUserInfo: (userId) => postBridgeMessage(`view_user_info:${userId}`),
|
||||
gotoAppPage: (pageName) => postBridgeMessage(pageName),
|
||||
goToFlutterPage: (pageName) => postBridgeMessage(pageName),
|
||||
uploadImgFile: () => postBridgeMessage('uploadImgFile'),
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem('userProfile', JSON.stringify(profile))
|
||||
} catch {}
|
||||
},
|
||||
{ token: auth.token, profile },
|
||||
)
|
||||
|
||||
const page = await context.newPage()
|
||||
page.on('console', (msg) => {
|
||||
const text = msg.text()
|
||||
if (
|
||||
text.includes('[AslanWebViewSim]') ||
|
||||
/error|warn|Route|Permission|APP|Failed|identity/i.test(text)
|
||||
) {
|
||||
console.log(`[browser:${msg.type()}] ${text}`)
|
||||
}
|
||||
})
|
||||
page.on('pageerror', (error) => {
|
||||
console.log(`[browser:pageerror] ${error.message}`)
|
||||
})
|
||||
|
||||
console.log('Aslan WebView simulator started')
|
||||
console.log(`account=${account}`)
|
||||
console.log(`profile=${JSON.stringify(publicUserProfile(profile))}`)
|
||||
console.log(`url=${targetUrl}`)
|
||||
console.log('Close the Chrome window to stop the simulator.')
|
||||
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 })
|
||||
|
||||
if (exitAfterLoad) {
|
||||
await page.waitForTimeout(6_000)
|
||||
const state = await page.evaluate(() => ({
|
||||
href: location.href,
|
||||
text: (document.body?.innerText || '').replace(/\s+/g, ' ').slice(0, 800),
|
||||
bridgeMessages: window.__aslanWebViewMessages || [],
|
||||
}))
|
||||
console.log(`loaded=${JSON.stringify(state)}`)
|
||||
await browser.close()
|
||||
return
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
browser.on('disconnected', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message || error)
|
||||
process.exit(1)
|
||||
})
|
||||
@ -138,18 +138,10 @@ const fetchTeamPolicy = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化目标值显示
|
||||
// Target 必须展示接口返回的完整数值,不能使用 K/M 等单位缩写。
|
||||
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()
|
||||
if (target === null || target === undefined || target === '') return '0'
|
||||
return String(target)
|
||||
}
|
||||
|
||||
// 格式化奖励类型
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user