fix: improve admin center mobile taps

This commit is contained in:
zhx 2026-07-09 23:29:07 +08:00
parent 08ae81d5a8
commit ef5af2ac27
11 changed files with 1075 additions and 695 deletions

View File

@ -15,7 +15,7 @@
>
<!-- 返回键 -->
<div style="width: 1.5em; display: flex; align-items: center">
<button v-if="showBack" class="back-btn" @click="handleBack">
<button v-if="showBack" class="back-btn" v-touch-tap="handleBack">
<img
v-smart-img
class="flipImg"
@ -43,7 +43,7 @@
<!-- 右侧按钮 -->
<!-- 帮助按钮 -->
<button v-if="showHelp" class="help-btn" style="width: 1.5em" @click="$emit('help')">
<button v-if="showHelp" class="help-btn" style="width: 1.5em" v-touch-tap="emitHelp">
<img v-smart-img :src="helpImg" alt="" style="width: 100%; display: block" />
</button>
@ -56,7 +56,7 @@
position: relative;
color: rgba(187, 146, 255, 1) !important;
"
@click="showLang"
v-touch-tap="showLang"
>
<div style="font-weight: bold">{{ currentLangName }}</div>
<transition name="slide-fade">
@ -64,7 +64,7 @@
<div
style="padding: 8px; color: black !important; font-weight: 590"
v-for="lang in langStore.langList"
@click.stop="handleLanguageChange(lang)"
v-touch-tap.stop="() => handleLanguageChange(lang)"
>
{{ lang.value }}
</div>
@ -147,7 +147,7 @@ const props = defineProps({
})
// emits
defineEmits(['help'])
const emit = defineEmits(['help'])
const router = useRouter()
const route = useRoute()
@ -178,6 +178,10 @@ const handleLanguageChange = (item) => {
// visibleList
}
const emitHelp = () => {
emit('help')
}
// APP
const isInAppEnvironment = ref(false)

View File

@ -2,7 +2,6 @@
<div
v-show="maskLayerShow"
class="mask-layer"
@click.stop
@mousedown.stop
@mouseup.stop
@pointerdown.stop
@ -12,7 +11,7 @@
@touchmove.prevent
@wheel.prevent
>
<div class="mask-layer__content" @touchmove.stop>
<div class="mask-layer__content" @touchmove.stop v-touch-tap.self.stop="emitClose">
<slot></slot>
</div>
</div>
@ -31,6 +30,11 @@ const props = defineProps({
default: true,
},
})
const emit = defineEmits(['close'])
const emitClose = () => {
emit('close')
}
let lockCount = 0
let lockedScrollY = 0

View File

@ -8,6 +8,7 @@ import { installRuntimeSecurity } from './utils/runtimeSecurity.js'
import { applyPerformanceModeClass } from './utils/performanceMode.js'
import { versionChecker } from './utils/versionChecker.js'
import { smartImage } from './directives/smartImage.js'
import { vTouchTap } from './utils/touchTap.js'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import { useLangStore } from '@/stores/lang'
@ -28,6 +29,7 @@ pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.directive('smart-img', smartImage)
app.directive('touch-tap', vTouchTap)
app.component('LoadingContent', LoadingContent)
app.use(router)

167
src/utils/touchTap.js Normal file
View File

@ -0,0 +1,167 @@
const TAP_MOVE_LIMIT = 10
const DUPLICATE_EVENT_LIMIT = 700
const FOCUSABLE_SELECTOR =
'a,button,input,textarea,select,[tabindex],[role="button"],[contenteditable="true"]'
const getEventPoint = (event) => {
if (event.changedTouches?.length) return event.changedTouches[0]
if (event.touches?.length) return event.touches[0]
return event
}
const triggerBinding = (el, event) => {
const binding = el.__touchTapBinding
if (!binding) return
if (binding.modifiers.self && event.target !== el) {
return
}
if (binding.modifiers.stop) {
event.stopPropagation()
}
if (binding.modifiers.prevent) {
event.preventDefault()
}
if (typeof binding.value === 'function') {
binding.value(event)
}
}
export const vTouchTap = {
mounted(el, binding) {
el.__touchTapBinding = binding
let startX = 0
let startY = 0
let moved = false
let active = false
let lastTouchTime = 0
let lastActivationTime = 0
if (!el.matches(FOCUSABLE_SELECTOR)) {
el.setAttribute('role', 'button')
el.setAttribute('tabindex', '0')
el.__touchTapAddedA11y = true
}
const activate = (event) => {
lastActivationTime = Date.now()
triggerBinding(el, event)
}
// Pointer Events 同时覆盖移动端触摸和桌面鼠标;移动阈值用于避免滚动列表时误触。
const onStart = (event) => {
if (el.__touchTapBinding?.modifiers.self && event.target !== el) {
active = false
return
}
if (event.button !== undefined && event.button !== 0) return
const point = getEventPoint(event)
startX = point.clientX || 0
startY = point.clientY || 0
moved = false
active = true
}
const onMove = (event) => {
if (!active) return
const point = getEventPoint(event)
const deltaX = Math.abs((point.clientX || 0) - startX)
const deltaY = Math.abs((point.clientY || 0) - startY)
if (deltaX > TAP_MOVE_LIMIT || deltaY > TAP_MOVE_LIMIT) {
moved = true
}
}
const onEnd = (event) => {
if (!active) return
const point = getEventPoint(event)
const deltaX = Math.abs((point.clientX || 0) - startX)
const deltaY = Math.abs((point.clientY || 0) - startY)
active = false
if (moved || deltaX > TAP_MOVE_LIMIT || deltaY > TAP_MOVE_LIMIT) return
activate(event)
}
const onTouchEnd = (event) => {
lastTouchTime = Date.now()
onEnd(event)
}
const onMouseEnd = (event) => {
if (Date.now() - lastTouchTime < DUPLICATE_EVENT_LIMIT) return
onEnd(event)
}
const onClick = (event) => {
if (event.detail !== 0) return
if (Date.now() - lastActivationTime < DUPLICATE_EVENT_LIMIT) return
activate(event)
}
const onKeydown = (event) => {
if (event.key !== 'Enter' && event.key !== ' ') return
if (event.key === ' ') {
event.preventDefault()
}
activate(event)
}
const onCancel = () => {
active = false
moved = false
}
const hasPointerEvent = typeof window !== 'undefined' && typeof window.PointerEvent === 'function'
const pointerOrTouchEvents = hasPointerEvent
? [
['pointerdown', onStart, { passive: true }],
['pointermove', onMove, { passive: true }],
['pointerup', onEnd, { passive: false }],
['pointercancel', onCancel, { passive: true }],
]
: [
['touchstart', onStart, { passive: true }],
['touchmove', onMove, { passive: true }],
['touchend', onTouchEnd, { passive: false }],
['touchcancel', onCancel, { passive: true }],
['mousedown', onStart, { passive: true }],
['mousemove', onMove, { passive: true }],
['mouseup', onMouseEnd, { passive: false }],
]
const events = [
...pointerOrTouchEvents,
['click', onClick, { passive: false }],
['keydown', onKeydown, { passive: false }],
]
events.forEach(([name, handler, options]) => {
el.addEventListener(name, handler, options)
})
el.__touchTapCleanup = () => {
events.forEach(([name, handler, options]) => {
el.removeEventListener(name, handler, options)
})
}
},
updated(el, binding) {
el.__touchTapBinding = binding
},
beforeUnmount(el) {
el.__touchTapCleanup?.()
delete el.__touchTapBinding
delete el.__touchTapCleanup
if (el.__touchTapAddedA11y) {
el.removeAttribute('role')
el.removeAttribute('tabindex')
delete el.__touchTapAddedA11y
}
},
}

View File

@ -1,7 +1,6 @@
<template>
<div class="fullPage">
<div class="distribution-page">
<div class="bg">
<!-- 顶部导航 -->
<GeneralHeader
:showLanguageList="true"
:title="t('item_distribution')"
@ -9,141 +8,61 @@
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)">
<main class="distribution-content">
<div class="tabs">
<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;
"
v-for="(item, index) in tabList"
:key="index"
class="tab-item"
:class="activeIndex == index ? 'tabName-active' : 'tabName'"
ref="tabItems"
v-touch-tap="() => setActiveTab(index)"
>
<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(item.name) }}
</div>
<div class="tab-underline" :style="underlineStyle"></div>
</div>
<div class="product-grid">
<div
v-for="(product, index) in productList"
:key="index"
class="product-card"
v-touch-tap="() => showSendDialog(product)"
>
<img :src="product.cover" alt="" class="product-cover" />
<button class="send-pill" type="button">
{{ 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>
<maskLayer :maskLayerShow="dialogshow" @close="closedPopup">
<div class="send-dialog" @touchend.stop @pointerup.stop>
<img :src="selectedGoods.cover" alt="" class="dialog-cover" />
<div class="validity-row">
<span>{{ t('validity') }}:</span>
<strong>{{ validityPeriod }}{{ t('day') }}</strong>
</div>
<div class="send-form">
<input
type="text"
:placeholder="t('enter_user_id_placeholder')"
class="user-input"
v-model="targetUserId"
/>
<button
class="confirm-btn"
type="button"
:style="{ backgroundColor: confirmClick ? '#bb92ff' : '#00000080' }"
v-touch-tap="sendGoods"
>
{{ t('confirm') }}
</button>
</div>
</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>
</maskLayer>
</main>
</div>
</div>
</template>
@ -208,11 +127,12 @@ const underlineStyle = computed(() => {
if (tabItems.value.length === 0) return {} //
const activeTab = tabItems.value[activeIndex.value]
const underlineWidth = 24
console.log('选中元素的左边距', activeTab.offsetLeft)
return {
position: 'absolute',
left: `${activeTab.offsetLeft + activeTab.offsetWidth / 2 - 15 / 2}px`,
left: `${activeTab.offsetLeft + activeTab.offsetWidth / 2 - underlineWidth / 2}px`,
bottom: '0',
transition: 'left 0.3s ease', //
}
@ -341,12 +261,18 @@ onMounted(() => {
<style scoped>
* {
color: black;
box-sizing: border-box;
}
.fullPage {
background-color: #ffffff;
min-height: 100vh;
.distribution-page {
background-color: #f5f7fb;
height: 100vh;
position: relative;
overflow-y: auto;
}
.distribution-page::-webkit-scrollbar {
display: none;
}
.bg {
@ -357,24 +283,176 @@ onMounted(() => {
background-repeat: no-repeat;
}
.distribution-content {
display: flex;
flex-direction: column;
gap: 16px;
padding: 14px 14px 28px;
}
.tabs {
display: flex;
justify-content: space-around;
position: relative;
padding: 6px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.86);
box-shadow: 0 10px 26px rgba(37, 45, 65, 0.07);
}
.tab-item {
flex: 1;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 8px;
font-size: 1.1em;
font-weight: 800;
text-align: center;
border-radius: 14px;
touch-action: manipulation;
}
.tabName {
color: #00000066;
color: rgba(31, 41, 55, 0.48);
}
.tabName-active {
color: black;
font-weight: 500;
color: #111827;
background: #f4f2ff;
}
.tab-underline {
width: 24px;
height: 4px;
border-radius: 999px;
background-color: #bb92ff;
}
.product-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.product-card {
min-width: 0;
min-height: 170px;
padding: 14px 10px 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
border: 1px solid rgba(212, 218, 232, 0.9);
border-radius: 18px;
background-color: rgba(255, 255, 255, 0.96);
box-shadow: 0 10px 26px rgba(37, 45, 65, 0.08);
touch-action: manipulation;
}
.product-cover {
display: block;
width: 68%;
max-height: 112px;
object-fit: contain;
}
.send-pill,
.confirm-btn {
border: 0;
color: white;
background-color: #bb92ff;
font-weight: 800;
touch-action: manipulation;
}
.send-pill {
min-width: 76px;
min-height: 36px;
padding: 8px 14px;
border-radius: 999px;
font-size: 1em;
}
.send-dialog {
margin: 18vh 5% 0;
padding: 22px 18px;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 22px;
box-shadow: 0 18px 45px rgba(0, 0, 0, 0.18);
}
.dialog-cover {
display: block;
width: 62%;
max-height: 190px;
object-fit: contain;
}
.validity-row {
display: flex;
align-items: center;
gap: 10px;
margin: 14px 0;
font-size: 1.08em;
font-weight: 700;
}
.validity-row span {
color: rgba(0, 0, 0, 0.5);
}
.validity-row strong {
color: #000;
}
.send-form {
width: 100%;
display: flex;
align-items: stretch;
gap: 10px;
}
.user-input {
flex: 1;
min-width: 0;
border-radius: 12px;
border: 0;
outline: none;
background-color: #f1f1f1b2;
padding: 12px;
font-size: 1.05em;
font-weight: 700;
}
.confirm-btn {
min-width: 86px;
border-radius: 12px;
padding: 0 14px;
font-size: 1.05em;
}
.product-card:active,
.send-pill:active,
.confirm-btn:active,
.tab-item:active {
transform: scale(0.98);
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -155,7 +155,7 @@
</div>
<!-- 更多按钮 -->
<div class="moreBt" @click="incomeShow(AgencyInfo)">
<div class="moreBt" v-touch-tap="() => incomeShow(AgencyInfo)">
{{ t('more') }}
</div>
</div>
@ -163,7 +163,7 @@
</div>
<!-- 遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<maskLayer :maskLayerShow="maskLayerShow" @close="closedPopup">
<div style="position: fixed; bottom: 0; width: 100%">
<!-- 某个Agency的收益 -->
<div
@ -178,7 +178,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="display: flex; justify-content: center; align-items: center">
<div style="margin: 0; font-weight: 600; color: #333">
@ -262,7 +263,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
<div style="display: flex; justify-content: space-between">
@ -463,6 +465,8 @@ onMounted(() => {
overflow-y: auto;
background-color: #ffffff;
background-image: url(../../assets/images/Azizi/secondBg.png);
background-size: 100% auto;
background-repeat: no-repeat;
}
.fullPage::-webkit-scrollbar {
@ -482,13 +486,19 @@ onMounted(() => {
}
.moreBt {
font-size: 0.8em;
min-width: 56px;
min-height: 30px;
padding: 6px 10px;
border-radius: 999px;
background: #f4f0ff;
position: absolute;
bottom: 4%;
right: 4%;
color: #bb92ff;
font-weight: 500;
font-size: 0.95em;
font-weight: 700;
text-align: center;
touch-action: manipulation;
}
[dir='rtl'] .moreBt {
@ -498,13 +508,13 @@ onMounted(() => {
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -155,7 +155,7 @@
</div>
<!-- 更多按钮 -->
<div class="moreBt" @click="incomeShow(BDLeaderInfo)">
<div class="moreBt" v-touch-tap="() => incomeShow(BDLeaderInfo)">
{{ t('more') }}
</div>
</div>
@ -163,7 +163,7 @@
</div>
<!-- 遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<maskLayer :maskLayerShow="maskLayerShow" @close="closedPopup">
<div style="position: fixed; bottom: 0; width: 100%">
<!-- 某个BDLeader的收益 -->
<div
@ -178,7 +178,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="display: flex; justify-content: center; align-items: center">
<div style="margin: 0; font-weight: 600; color: #333">
@ -264,7 +265,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
<div style="display: flex; justify-content: space-between">
@ -465,6 +467,8 @@ onMounted(() => {
overflow-y: auto;
background-color: #ffffff;
background-image: url(../../assets/images/Azizi/secondBg.png);
background-size: 100% auto;
background-repeat: no-repeat;
}
.fullPage::-webkit-scrollbar {
@ -484,24 +488,30 @@ onMounted(() => {
}
.moreBt {
font-size: 0.8em;
min-width: 56px;
min-height: 30px;
padding: 6px 10px;
border-radius: 999px;
background: #f4f0ff;
position: absolute;
bottom: 4%;
right: 4%;
color: #bb92ff;
font-weight: 500;
font-size: 0.95em;
font-weight: 700;
text-align: center;
touch-action: manipulation;
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -153,7 +153,7 @@
</div>
<!-- 更多按钮 -->
<div class="moreBt" @click="incomeShow(BDInfo)">
<div class="moreBt" v-touch-tap="() => incomeShow(BDInfo)">
{{ t('more') }}
</div>
</div>
@ -161,7 +161,7 @@
</div>
<!-- 遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<maskLayer :maskLayerShow="maskLayerShow" @close="closedPopup">
<div style="position: fixed; bottom: 0; width: 100%">
<!-- 某个BD的收益 -->
<div
@ -176,7 +176,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="display: flex; justify-content: center; align-items: center">
<div style="margin: 0; font-weight: 600; color: #333">
@ -259,7 +260,8 @@
padding: 10px 12px 30px;
"
class="overflow-y-auto"
@click.stop
@touchend.stop
@pointerup.stop
>
<div style="font-weight: 600">{{ incomeDetailsItem.settlementPeriod }}</div>
<div style="display: flex; justify-content: space-between">
@ -460,6 +462,8 @@ onMounted(() => {
overflow-y: auto;
background-color: #ffffff;
background-image: url(../../assets/images/Azizi/secondBg.png);
background-size: 100% auto;
background-repeat: no-repeat;
}
.fullPage::-webkit-scrollbar {
@ -479,13 +483,19 @@ onMounted(() => {
}
.moreBt {
font-size: 0.8em;
min-width: 56px;
min-height: 30px;
padding: 6px 10px;
border-radius: 999px;
background: #f4f0ff;
position: absolute;
bottom: 4%;
right: 4%;
color: #bb92ff;
font-weight: 500;
font-size: 0.95em;
font-weight: 700;
text-align: center;
touch-action: manipulation;
}
[dir='rtl'] .moreBt {
@ -495,13 +505,13 @@ onMounted(() => {
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -1,7 +1,6 @@
<template>
<div class="fullPage">
<div class="recharge-page">
<div class="bg">
<!-- 顶部导航 -->
<GeneralHeader
:showLanguageList="true"
:title="t('my_linked_recharge_agent')"
@ -9,154 +8,59 @@
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') }}
<main class="recharge-content">
<section class="summary-card">
<div class="metric-block">
<div class="metric-label">{{ t('my_total_income') }}</div>
<div class="metric-value">${{ totalRechargeDetail?.totalIncome || 0 }}</div>
</div>
<div style="font: 0.9em; font-weight: 600; color: rgba(0, 0, 0, 0.8)">
${{ totalRechargeDetail?.totalIncome || 0 }}
<div class="metric-block">
<div class="metric-label">{{ t('this_period_recharge') }}</div>
<div class="metric-value">${{ totalRechargeDetail?.currentMonthRecharge || 0 }}</div>
</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>
</section>
<div style="font-weight: 700">{{ t('recharge_agency_list') }}</div>
<div class="list-title">{{ t('recharge_agency_list') }}</div>
<div
<section
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)"
class="agency-card"
:class="{ expanded: aIndex == selectedAgencyIndex }"
v-touch-tap="() => 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 class="agency-main">
<div class="agency-profile">
<img :src="agency.userAvatar" alt="" class="agency-avatar" />
<div class="agency-meta">
<div class="agency-name">{{ agency.userNickname }}</div>
<div class="agency-id">{{ t('user_id_prefix') }}{{ agency.account }}</div>
</div>
<!-- 展开按钮 -->
<div
style="
width: auto;
min-width: 0;
align-self: stretch;
display: flex;
align-items: center;
"
>
</div>
<div class="expand-icon">
<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 class="agency-detail" v-show="aIndex == selectedAgencyIndex">
<div class="detail-metric">
<div class="metric-label">{{ t('this_month') }}:</div>
<div class="detail-value">${{ 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 class="detail-metric">
<div class="metric-label">{{ t('last_month') }}:</div>
<div class="detail-value">${{ formatPrice(agency.lastMonth) || 0 }}</div>
</div>
</div>
</transition>
</div>
<!-- 触发加载功能 -->
</section>
<div ref="Agloadmore" v-if="showAgLoading"></div>
</div>
</main>
</div>
</div>
</template>
@ -251,12 +155,18 @@ const formatPrice = (value) => {
<style scoped>
* {
color: black;
box-sizing: border-box;
}
.fullPage {
background-color: #ffffff;
min-height: 100vh;
.recharge-page {
background-color: #f5f7fb;
height: 100vh;
position: relative;
overflow-y: auto;
}
.recharge-page::-webkit-scrollbar {
display: none;
}
.bg {
@ -267,13 +177,148 @@ const formatPrice = (value) => {
background-repeat: no-repeat;
}
.tabName {
color: #00000066;
.recharge-content {
display: flex;
flex-direction: column;
gap: 14px;
padding: 16px 14px 28px;
}
.tabName-active {
color: black;
font-weight: 500;
.summary-card,
.agency-card {
background: rgba(255, 255, 255, 0.96);
border: 1px solid rgba(212, 218, 232, 0.9);
border-radius: 18px;
box-shadow: 0 10px 26px rgba(37, 45, 65, 0.08);
}
.summary-card {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 16px;
}
.metric-block {
min-width: 0;
}
.metric-label {
color: rgba(31, 41, 55, 0.55);
font-size: 1em;
font-weight: 700;
line-height: 1.35;
}
.metric-value {
margin-top: 5px;
color: #111827;
font-size: 1.35em;
font-weight: 800;
line-height: 1.2;
overflow-wrap: anywhere;
}
.list-title {
color: #111827;
font-size: 1.2em;
font-weight: 800;
}
.agency-card {
padding: 14px;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
touch-action: manipulation;
}
.agency-card.expanded {
box-shadow: 0 14px 32px rgba(37, 45, 65, 0.12);
}
.agency-main,
.agency-profile,
.expand-icon {
display: flex;
align-items: center;
}
.agency-main {
justify-content: space-between;
gap: 12px;
}
.agency-profile {
min-width: 0;
flex: 1;
gap: 12px;
}
.agency-avatar {
display: block;
width: 54px;
height: 54px;
flex: 0 0 54px;
border-radius: 50%;
object-fit: cover;
background: #eef2f7;
}
.agency-meta {
min-width: 0;
flex: 1;
}
.agency-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #111827;
font-size: 1.12em;
font-weight: 800;
}
.agency-id {
margin-top: 4px;
color: rgba(31, 41, 55, 0.5);
font-size: 0.98em;
font-weight: 700;
}
.expand-icon {
width: 38px;
height: 38px;
justify-content: center;
border-radius: 12px;
background: #f4f2ff;
}
.expand-icon img {
display: block;
width: 22px;
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.agency-detail {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid rgba(229, 231, 235, 0.9);
}
.detail-metric {
min-width: 0;
}
.detail-value {
margin-top: 4px;
color: rgba(31, 41, 55, 0.9);
font-size: 1.08em;
font-weight: 800;
overflow-wrap: anywhere;
}
.info-fade-enter-from,
@ -296,13 +341,13 @@ const formatPrice = (value) => {
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -16,7 +16,7 @@
>
<slot name="content"></slot>
</div>
<div class="moreBt" @click="btMore">{{ t('more') }}</div>
<div class="moreBt" v-touch-tap="btMore">{{ t('more') }}</div>
</div>
</div>
</template>
@ -119,12 +119,19 @@ const getTypeText = (type) => {
}
.moreBt {
font-size: 0.8em;
min-width: 56px;
min-height: 30px;
padding: 6px 10px;
border-radius: 999px;
background: #f4f0ff;
font-size: 0.95em;
position: absolute;
bottom: 4%;
right: 4%;
color: #bb92ff;
font-weight: 500;
font-weight: 700;
text-align: center;
touch-action: manipulation;
}
.status {
@ -154,13 +161,13 @@ const getTypeText = (type) => {
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}

View File

@ -1,7 +1,6 @@
<template>
<div class="fullPage">
<div class="bg gradient-background-circles">
<!-- 顶部导航 -->
<div class="admin-page">
<div class="admin-shell gradient-background-circles">
<GeneralHeader
:isHomePage="true"
:showLanguageList="true"
@ -10,415 +9,215 @@
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;
"
>
<main class="admin-content">
<section class="leader-panel">
<div class="section-kicker">{{ t('my_leader') }}</div>
<div class="leader-card">
<div class="leader-avatar">
<img
:src="userInfo.userAvatar || ''"
style="
display: block;
width: 100%;
aspect-ratio: 1/1;
object-fit: cover;
border-radius: 50%;
"
class="avatar-image"
alt=""
@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">
<div class="leader-meta">
<div class="leader-name">{{ userInfo.userNickname || '-' }}</div>
<div class="muted-text">
{{ t('user_id_prefix') }} {{ userInfo.account || '-' }}
</div>
</div>
</div>
</div>
</section>
<!-- 角色模块 -->
<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"
/>
<section class="admin-card role-card">
<div class="section-title-row">
<div>
<div class="section-kicker">{{ t('my_role') }}</div>
<div class="section-title">{{ t('admin') }}</div>
</div>
<button class="icon-action" type="button" v-touch-tap="gotoPolicy">
<img src="../../assets/icon/Azizi/detail.png" alt="" class="icon-image flipImg" />
</button>
</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 class="task-summary">
<div>{{ t('task_completion_status') }}</div>
<strong>({{ taskData.completedTaskCount || 0 }})</strong>
</div>
<div v-if="taskData.tasks" style="display: flex; flex-direction: column; gap: 4px">
<div style="font-weight: 500">
<div v-if="taskData.tasks" class="task-list">
<div class="task-period">
{{ 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 v-for="task in taskData.tasks" :key="task.taskType" class="task-row">
<span>{{ taskName(task) }}</span>
<strong>{{ taskCompletedText(task) }}</strong>
</div>
</div>
</div>
</section>
<!-- 本期团队收入 -->
<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">
<section class="admin-card income-card">
<div class="income-summary">
<div>
<div class="income-title">
{{ 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">
<div class="muted-text">
{{ 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"
/>
<button class="round-action" type="button" v-touch-tap="gotoIncome">
<img src="../../assets/icon/Azizi/arrowBlack.png" alt="" class="flipImg" />
</button>
</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 class="team-list">
<div class="team-row">
<div class="team-copy">
<div class="team-title">
{{ t('my_bd_leader_teams') }}({{ teamSummary.bdLeaderTeams?.count || 0 }})
</div>
<div class="muted-text">
{{ t('total_income') }} ${{ teamSummary.bdLeaderTeams?.totalIncome || 0 }}
</div>
</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 class="team-actions">
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/invite-bd-leader')"
>
<img src="../../assets/icon/Azizi/addUser.png" alt="" class="flipImg" />
</button>
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/my-BDLeader-teams')"
>
<img src="../../assets/icon/Azizi/list.png" alt="" class="flipImg" />
</button>
</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 class="team-row">
<div class="team-copy">
<div class="team-title">
{{ t('my_bd_teams') }}({{ teamSummary.bdTeams?.count || 0 }})
</div>
<div class="muted-text">
{{ t('total_income') }} ${{ teamSummary.bdTeams?.totalIncome || 0 }}
</div>
</div>
<div class="team-actions">
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/invite-bd')"
>
<img src="../../assets/icon/Azizi/addUser.png" alt="" class="flipImg" />
</button>
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/my-BD-teams')"
>
<img src="../../assets/icon/Azizi/list.png" alt="" class="flipImg" />
</button>
</div>
</div>
<div class="team-row">
<div class="team-copy">
<div class="team-title">
{{ t('my_agency_teams') }}({{ teamSummary.agencyTeams?.count || 0 }})
</div>
<div class="muted-text">
{{ t('total_income') }} ${{ teamSummary.agencyTeams?.totalIncome || 0 }}
</div>
</div>
<div class="team-actions">
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/invite-agency')"
>
<img src="../../assets/icon/Azizi/addUser.png" alt="" class="flipImg" />
</button>
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/my-agency-teams')"
>
<img src="../../assets/icon/Azizi/list.png" alt="" class="flipImg" />
</button>
</div>
</div>
<div class="team-row">
<div class="team-copy">
<div class="team-title">
{{ t('number_of_recharge_agents_invited') }}({{
teamSummary.rechargeTeams?.count || 0
}})
</div>
<div class="muted-text">
{{ t('total_recharge') }}: ${{ teamSummary.rechargeTeams?.totalIncome || 0 }}
</div>
</div>
<div class="team-actions">
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/invite-recharge-agency')"
>
<img src="../../assets/icon/Azizi/addUser.png" alt="" class="flipImg" />
</button>
<button
class="square-action"
type="button"
v-touch-tap="() => gotoPage('/my-recharge-agency')"
>
<img src="../../assets/icon/Azizi/list.png" alt="" class="flipImg" />
</button>
</div>
</div>
</div>
</section>
<!-- 分割线 -->
<div style="border-bottom: 1px solid #e6e6e6"></div>
<section class="quick-actions">
<button class="quick-action" type="button" v-touch-tap="send">
<span>{{ t('send_welcome_gift') }}</span>
<img src="../../assets/icon/Azizi/arrowBlack.png" alt="" class="flipImg" />
</button>
<!-- 我的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>
<button
v-if="teamPermissionCheck.hasUserEditingPermission"
class="quick-action"
type="button"
v-touch-tap="() => gotoAppPage('editingUser')"
>
<span>{{ t('user_editing') }}</span>
<img src="../../assets/icon/Azizi/arrowBlack.png" alt="" class="flipImg" />
</button>
<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>
<button
v-if="teamPermissionCheck.hasRoomEditingPermission"
class="quick-action"
type="button"
v-touch-tap="() => gotoAppPage('editingRoom')"
>
<span>{{ t('room_editing') }}</span>
<img src="../../assets/icon/Azizi/arrowBlack.png" alt="" class="flipImg" />
</button>
</section>
</main>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { setDocumentDirection } from '@/locales/i18n'
@ -563,21 +362,21 @@ onMounted(() => {
<style scoped>
* {
color: rgba(0, 0, 0, 0.8);
box-sizing: border-box;
}
.fullPage {
.admin-page {
position: relative;
max-height: 100vh;
height: 100vh;
overflow-y: auto;
background-color: #ffffff;
background: #f5f7fb;
}
.fullPage::-webkit-scrollbar {
.admin-page::-webkit-scrollbar {
display: none;
}
.bg {
.admin-shell {
width: 100vw;
min-height: 100vh;
background-image: url(../../assets/images/Azizi/secondBg.png);
@ -585,15 +384,259 @@ onMounted(() => {
background-repeat: no-repeat;
}
.admin-content {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
gap: 14px;
padding: 18px 16px 26px;
}
.admin-card,
.leader-panel,
.quick-action {
background: rgba(255, 255, 255, 0.96);
border: 1px solid rgba(212, 218, 232, 0.9);
border-radius: 18px;
box-shadow: 0 10px 26px rgba(37, 45, 65, 0.08);
}
.leader-panel {
padding: 16px;
}
.leader-card {
display: flex;
align-items: center;
gap: 14px;
margin-top: 10px;
}
.leader-avatar {
width: 68px;
height: 68px;
flex: 0 0 68px;
padding: 3px;
border-radius: 50%;
background: linear-gradient(135deg, #bb92ff, #6fb1ff);
}
.avatar-image {
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
background: #eef2f7;
}
.leader-meta {
min-width: 0;
flex: 1;
}
.leader-name {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 1.35em;
font-weight: 800;
}
.section-kicker {
color: rgba(31, 41, 55, 0.58);
font-size: 0.98em;
font-weight: 700;
}
.section-title-row,
.income-summary,
.team-row,
.quick-action {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.section-title {
margin-top: 3px;
color: #111827;
font-size: 1.32em;
font-weight: 800;
}
.role-card,
.income-card {
padding: 16px;
}
.icon-action,
.round-action,
.square-action,
.quick-action {
border: 0;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.icon-action,
.round-action,
.square-action {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
background: #f4f2ff;
}
.icon-action {
width: 46px;
height: 46px;
border-radius: 14px;
}
.icon-image {
display: block;
width: 24px;
}
.task-summary {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 14px;
padding: 12px 0;
border-top: 1px solid rgba(229, 231, 235, 0.9);
border-bottom: 1px solid rgba(229, 231, 235, 0.9);
font-size: 1.08em;
font-weight: 700;
}
.task-list {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 12px;
}
.task-period {
font-size: 1.02em;
font-weight: 700;
}
.task-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
color: rgba(31, 41, 55, 0.62);
font-size: 0.98em;
font-weight: 650;
}
.income-title {
color: #111827;
font-size: 1.2em;
font-weight: 800;
line-height: 1.35;
}
.muted-text {
color: rgba(31, 41, 55, 0.56);
font-size: 1em;
font-weight: 650;
line-height: 1.35;
}
.round-action {
width: 48px;
height: 48px;
border-radius: 50%;
}
.round-action img,
.square-action img,
.quick-action img {
display: block;
width: 24px;
}
.team-list {
display: flex;
flex-direction: column;
margin-top: 14px;
border-top: 1px solid rgba(229, 231, 235, 0.9);
}
.team-row {
min-height: 72px;
padding: 12px 0;
border-bottom: 1px solid rgba(229, 231, 235, 0.78);
}
.team-row:last-child {
border-bottom: 0;
}
.team-copy {
min-width: 0;
flex: 1;
}
.team-title {
overflow-wrap: anywhere;
color: #1f2937;
font-size: 1.08em;
font-weight: 800;
line-height: 1.35;
}
.team-actions {
display: flex;
align-items: center;
gap: 8px;
}
.square-action {
width: 44px;
height: 44px;
border-radius: 13px;
}
.quick-actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.quick-action {
width: 100%;
min-height: 58px;
padding: 0 16px;
font-size: 1.08em;
font-weight: 800;
text-align: start;
}
.quick-action:active,
.icon-action:active,
.round-action:active,
.square-action:active {
transform: scale(0.97);
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
font-size: 12px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
font-size: 14px;
}
}