2.3.0
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
<p class="mask-description">{{ description }}</p>
|
||||
<div class="mask-hint">
|
||||
<span class="material-icons-round hint-icon">info</span>
|
||||
<span>请联系管理员开通权限</span>
|
||||
<span>请联系下方客服开通权限</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,10 +36,7 @@
|
||||
<!-- 下方:名片区域 -->
|
||||
<div v-if="contacts && contacts.length" class="cards-area">
|
||||
<div class="cards-header">
|
||||
<button class="refresh-btn" @click="shuffleContacts">
|
||||
<span class="material-icons-round" style="font-size:18px;">refresh</span>
|
||||
换一批
|
||||
</button>
|
||||
<img :src="exchangeIcon" class="refresh-btn" @click="shuffleContacts" alt="换一批" />
|
||||
</div>
|
||||
<div class="cards-row">
|
||||
<div
|
||||
@@ -50,12 +47,13 @@
|
||||
<div class="card-avatar-wrapper">
|
||||
<img :src="contact.avatar" class="card-avatar" alt="" />
|
||||
</div>
|
||||
<img :src="cardBg" class="card-bg" alt="" />
|
||||
<div class="card-body">
|
||||
<div class="card-name">{{ contact.name }}</div>
|
||||
<div class="card-desc">{{ contact.desc }}</div>
|
||||
<img :src="contact.qrcode" class="card-qrcode" alt="二维码" />
|
||||
<div class="card-phone">
|
||||
<span class="material-icons-round phone-icon">phone</span>
|
||||
<img :src="phoneIcon" class="phone-icon" alt="" />
|
||||
{{ contact.phone }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,6 +68,9 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { getPermissions } from '@/utils/storage'
|
||||
import cardBg from '@/assets/nav/card.png'
|
||||
import phoneIcon from '@/assets/nav/phone.png'
|
||||
import exchangeIcon from '@/assets/nav/exchange.png'
|
||||
|
||||
const props = defineProps({
|
||||
permissionKey: {
|
||||
@@ -98,7 +99,8 @@ const props = defineProps({
|
||||
|
||||
const wrapperRef = ref(null)
|
||||
const maskRef = ref(null)
|
||||
const contactOffset = ref(0)
|
||||
const visibleIndices = ref([])
|
||||
const visibleContacts = ref([])
|
||||
|
||||
const guardKey = computed(() => `guard_${props.permissionKey}_${Date.now()}`)
|
||||
|
||||
@@ -108,16 +110,48 @@ const hasAccess = computed(() => {
|
||||
return permissionsData.value[props.permissionKey] === 1
|
||||
})
|
||||
|
||||
// 每次显示3张,换一批向后轮转
|
||||
const visibleContacts = computed(() => {
|
||||
if (!props.contacts.length) return []
|
||||
const pickRandomIndices = (sourceIndices, count) => {
|
||||
const arr = [...sourceIndices]
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
;[arr[i], arr[j]] = [arr[j], arr[i]]
|
||||
}
|
||||
return arr.slice(0, count)
|
||||
}
|
||||
|
||||
const updateVisibleContacts = (excludeCurrent = false) => {
|
||||
const total = props.contacts.length
|
||||
return [0, 1, 2].map(i => props.contacts[(contactOffset.value + i) % total])
|
||||
})
|
||||
|
||||
if (total === 0) {
|
||||
visibleIndices.value = []
|
||||
visibleContacts.value = []
|
||||
return
|
||||
}
|
||||
|
||||
if (total <= 3) {
|
||||
const all = [...Array(total).keys()]
|
||||
visibleIndices.value = pickRandomIndices(all, total)
|
||||
visibleContacts.value = visibleIndices.value.map(i => props.contacts[i])
|
||||
return
|
||||
}
|
||||
|
||||
const all = [...Array(total).keys()]
|
||||
let candidates = all
|
||||
|
||||
if (excludeCurrent && visibleIndices.value.length) {
|
||||
const current = new Set(visibleIndices.value)
|
||||
candidates = all.filter(i => !current.has(i))
|
||||
if (candidates.length < 3) {
|
||||
candidates = all
|
||||
}
|
||||
}
|
||||
|
||||
visibleIndices.value = pickRandomIndices(candidates, 3)
|
||||
visibleContacts.value = visibleIndices.value.map(i => props.contacts[i])
|
||||
}
|
||||
|
||||
const shuffleContacts = () => {
|
||||
if (props.contacts.length <= 3) return
|
||||
contactOffset.value = (contactOffset.value + 3) % props.contacts.length
|
||||
updateVisibleContacts(true)
|
||||
}
|
||||
|
||||
let permissionCheckInterval = null
|
||||
@@ -160,8 +194,7 @@ const setupDOMProtection = () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log("获取名片",props.contacts)
|
||||
|
||||
updateVisibleContacts(false)
|
||||
permissionCheckInterval = setInterval(refreshPermissions, 2000)
|
||||
setTimeout(setupDOMProtection, 100)
|
||||
})
|
||||
@@ -171,6 +204,14 @@ onUnmounted(() => {
|
||||
if (observer) observer.disconnect()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.contacts,
|
||||
() => {
|
||||
updateVisibleContacts(false)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(hasAccess, (newVal) => {
|
||||
if (observer) observer.disconnect()
|
||||
if (!newVal) setTimeout(setupDOMProtection, 100)
|
||||
@@ -305,7 +346,7 @@ watch(hasAccess, (newVal) => {
|
||||
/* 名片区域 */
|
||||
.cards-area {
|
||||
width: 100%;
|
||||
padding: 3vh 4rem 2rem;
|
||||
padding: 3vh 0 2rem;
|
||||
animation: slideUp 0.4s ease-out 0.1s both;
|
||||
}
|
||||
|
||||
@@ -313,25 +354,18 @@ watch(hasAccess, (newVal) => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 10%;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0.5rem 1.25rem;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
height: 2.2vw;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
display: block;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
background: #1d4ed8;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cards-row {
|
||||
@@ -341,30 +375,47 @@ watch(hasAccess, (newVal) => {
|
||||
|
||||
/* 单张名片 */
|
||||
.contact-card {
|
||||
flex: 1;
|
||||
max-width: 280px;
|
||||
background: #fff;
|
||||
width: 17%;
|
||||
flex: none;
|
||||
max-width: unset;
|
||||
aspect-ratio: 2 / 3;
|
||||
background: transparent;
|
||||
border-radius: 1.25rem;
|
||||
padding-top: 52px;
|
||||
padding-top: 0;
|
||||
position: relative;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.contact-card::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
border-radius: 1.25rem;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-avatar-wrapper {
|
||||
position: absolute;
|
||||
top: -44px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
width: 40%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
border: 4px solid #fff;
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
overflow: hidden;
|
||||
background: #e2e8f0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.card-avatar {
|
||||
@@ -377,43 +428,50 @@ watch(hasAccess, (newVal) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1.5rem 1.5rem;
|
||||
padding: 50% 8% 6%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
justify-content: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.1vw;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.3vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.8vw;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0.5vw;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-qrcode {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
width: 60%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.5vw;
|
||||
}
|
||||
|
||||
.card-phone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 1rem;
|
||||
gap: 0.3vw;
|
||||
font-size: 0.85vw;
|
||||
color: #2563eb;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.phone-icon {
|
||||
font-size: 1.1rem;
|
||||
color: #2563eb;
|
||||
width: 0.9vw;
|
||||
height: 0.9vw;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user