提示文案处理

This commit is contained in:
tianfeng 2025-08-20 19:37:43 +08:00
parent 2cd97322df
commit 71c8ed68b3
14 changed files with 414 additions and 91 deletions

View File

@ -4,7 +4,7 @@ VITE_API_BASE_URL=https://api.likeichat.com
VITE_APP_TITLE=Likei H5 (Development)
# 普通用户
VITE_USER_AUTH=03FF1925725628BD5319BAF8CAC4B1C8.djIlM0ExOTU0MDU5NzkzOTYzMzkzMDI1JTNBTElLRUklM0ExNzU4MTgwNjE1ODAxJTNBMTc1NTU4ODYxNTgwMQ==
VITE_USER_AUTH=657DB83B46ED88A0384D8FA9D83C1BA7.djIlM0ExOTU0MDU5NzkzOTYzMzkzMDI1JTNBTElLRUklM0ExNzU4Mjc1MTUwMzQ2JTNBMTc1NTY4MzE1MDM0Ng==
# 代理用户 8826603
# VITE_USER_AUTH=747E27BE38B21A129CE41BB1F80EA924.djIlM0ExOTU3MzU2MTM4NTIzMDY2MzY5JTNBTElLRUklM0ExNzU4MDk3MDg4NDA0JTNBMTc1NTUwNTA4ODQwNA==

255
src/components/Toast.vue Normal file
View File

@ -0,0 +1,255 @@
<template>
<div v-if="visible" class="error-modal-overlay" @click="handleOverlayClick">
<div class="error-modal" :class="animationClass">
<!-- 错误图标 -->
<div class="error-icon">
<div class="error-circle">
<span class="error-x"></span>
</div>
</div>
<!-- 标题 -->
<div class="error-title" v-if="title">{{ title }}</div>
<!-- 错误信息 -->
<div class="error-message">{{ message }}</div>
<!-- 按钮 -->
<div class="error-actions">
<button class="error-btn" @click="confirm">{{ confirmText }}</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps({
title: {
type: String,
default: 'Error'
},
message: {
type: String,
required: true
},
confirmText: {
type: String,
default: 'OK'
},
clickToClose: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['confirm', 'close'])
const visible = ref(false)
const animationClass = ref('')
const show = () => {
visible.value = true
animationClass.value = 'modal-enter'
}
const close = () => {
animationClass.value = 'modal-leave'
setTimeout(() => {
visible.value = false
emit('close')
}, 200)
}
const confirm = () => {
emit('confirm')
close()
}
const handleOverlayClick = () => {
if (props.clickToClose) {
close()
}
}
//
onMounted(() => {
show()
})
//
defineExpose({
show,
close
})
</script>
<style scoped>
.error-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 20px;
}
.error-modal {
background: white;
border-radius: 16px;
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
width: 100%;
max-width: 320px;
padding: 24px 20px 20px;
text-align: center;
position: relative;
}
/* 错误图标 */
.error-icon {
margin-bottom: 16px;
display: flex;
justify-content: center;
}
.error-circle {
width: 56px;
height: 56px;
border-radius: 50%;
background-color: #FEE2E2;
border: 3px solid #FECACA;
display: flex;
align-items: center;
justify-content: center;
animation: errorPulse 0.6s ease-out;
}
.error-x {
color: #DC2626;
font-size: 24px;
font-weight: bold;
line-height: 1;
}
/* 标题 */
.error-title {
font-size: 18px;
font-weight: 600;
color: #1F2937;
margin-bottom: 12px;
line-height: 1.4;
}
/* 错误信息 */
.error-message {
font-size: 14px;
color: #6B7280;
line-height: 1.5;
margin-bottom: 20px;
word-wrap: break-word;
}
/* 按钮区域 */
.error-actions {
display: flex;
justify-content: center;
}
.error-btn {
background-color: #DC2626;
color: white;
border: none;
border-radius: 8px;
padding: 10px 32px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
min-width: 80px;
}
.error-btn:hover {
background-color: #B91C1C;
}
.error-btn:active {
background-color: #991B1B;
transform: scale(0.98);
}
/* 动画效果 */
.modal-enter {
animation: modalEnter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.modal-leave {
animation: modalLeave 0.2s ease-in;
}
@keyframes modalEnter {
0% {
opacity: 0;
transform: scale(0.7) translateY(-10px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes modalLeave {
0% {
opacity: 1;
transform: scale(1) translateY(0);
}
100% {
opacity: 0;
transform: scale(0.9) translateY(-5px);
}
}
@keyframes errorPulse {
0% {
transform: scale(0.8);
opacity: 0.5;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
/* 响应式设计 */
@media (max-width: 480px) {
.error-modal {
max-width: 280px;
padding: 20px 16px 16px;
}
.error-circle {
width: 48px;
height: 48px;
}
.error-x {
font-size: 20px;
}
.error-title {
font-size: 16px;
}
.error-message {
font-size: 13px;
}
}
</style>

106
src/utils/toast.js Normal file
View File

@ -0,0 +1,106 @@
import { createApp } from 'vue'
import ErrorModal from '../components/Toast.vue'
class ErrorModalManager {
constructor() {
this.instances = []
}
// 显示错误弹框
show(message, options = {}) {
const container = document.createElement('div')
document.body.appendChild(container)
const app = createApp(ErrorModal, {
message,
...options,
onConfirm: () => {
if (options.onConfirm) {
options.onConfirm()
}
this.cleanup(app, container)
},
onClose: () => {
if (options.onClose) {
options.onClose()
}
this.cleanup(app, container)
}
})
const instance = app.mount(container)
this.instances.push(app)
return {
close: () => instance.close(),
instance
}
}
// 清理DOM和实例
cleanup(app, container) {
setTimeout(() => {
if (container && container.parentNode) {
container.parentNode.removeChild(container)
}
app.unmount()
// 从实例列表中移除
const index = this.instances.indexOf(app)
if (index > -1) {
this.instances.splice(index, 1)
}
}, 100)
}
// 关闭所有弹框
closeAll() {
this.instances.forEach(app => {
const instance = app._instance
if (instance && instance.exposed && instance.exposed.close) {
instance.exposed.close()
}
})
this.instances = []
}
}
// 创建全局实例
export const errorModal = new ErrorModalManager()
// 快捷方法 - 替代alert
export const showError = (message, title = 'Error') => {
return errorModal.show(message, { title })
}
// 错误处理的专用方法
export const handleError = (error) => {
let message = 'An unknown error occurred'
let title = 'Error'
if (error.errorCode === 1083) {
title = 'Service Unavailable'
message = 'Exchange function is not available'
} else if (error.message && error.message.includes('HTTP Error: 406')) {
title = 'Request Not Acceptable'
message = 'Something went wrong, please retry.'
} else if (error.message) {
message = error.message
}
return errorModal.show(message, { title })
}
// 确认对话框
export const showConfirm = (message, title = 'Confirm') => {
return new Promise((resolve) => {
errorModal.show(message, {
title,
confirmText: 'Confirm',
onConfirm: () => resolve(true),
onClose: () => resolve(false)
})
})
}
export default errorModal

View File

@ -157,6 +157,7 @@ import MobileHeader from '../components/MobileHeader.vue'
import { get } from '../utils/http.js'
import {getBankBalance} from "@/api/wallet.js";
import {getTeamId} from "@/utils/userStore.js";
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -266,7 +267,7 @@ const fetchBankBalance = async () => {
console.error('Failed to fetch bank balance:', error)
salaryInfo.currentSalary = 0.00
//
alert('Failed to load balance. Please try again.')
showError('Failed to load balance. Please try again.')
}
}

View File

@ -69,7 +69,7 @@
<span class="record-status status-pending">
Pending
</span>
<button
<button
class="cancel-btn"
@click="handleCancelApply(record)"
>
@ -96,6 +96,7 @@ import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { sendTeamApplyJoin, getWaitApplyRecord, cancelApply } from '../api/wallet.js'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -124,20 +125,20 @@ const handleCancelApply = async (record) => {
if (!confirm('确定要撤销这个申请吗?')) {
return
}
try {
const response = await cancelApply(record.messageId)
if (response.status) {
alert('撤销成功!')
showError('撤销成功!')
//
applyRecords.value = [] //
await fetchApplyRecords()
} else {
alert(response.message || '撤销失败,请重试')
showError(response.message || '撤销失败,请重试')
}
} catch (error) {
console.error('撤销申请失败:', error)
alert('撤销失败,请重试')
showError('撤销失败,请重试')
}
}
@ -159,7 +160,7 @@ const submitApplication = async () => {
const response = await sendTeamApplyJoin(applicationData)
if (response.status) {
alert('Application submitted successfully!')
showError('Application submitted successfully!')
await fetchApplyRecords() //
agentId.value = '' //
} else {

View File

@ -98,6 +98,7 @@ import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { getSelectedUser, clearSelectedUser } from '../utils/coinSellerStore.js'
import { checkFreightDealer, getTeamEntry, getFreightBalance, searchFreightUser, freightRecharge } from '../api/wallet.js'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -135,7 +136,7 @@ const checkDealerAccess = async () => {
if (!dealerAccess.value) {
//
alert('您没有金币销售商权限')
showError('您没有金币销售商权限')
router.go(-1)
return
}
@ -223,14 +224,14 @@ const rechargeNow = async () => {
const amount = parseFloat(rechargeAmount.value)
if (amount <= 0) {
alert('请输入正确的充值金额')
showError('请输入正确的充值金额')
return
}
// ID
const acceptUserId = selectedUser.value.userId || selectedUser.value.id
if (!acceptUserId) {
alert('无法获取用户ID请重新选择用户')
showError('无法获取用户ID请重新选择用户')
return
}
@ -250,16 +251,16 @@ const rechargeNow = async () => {
coinsAmount.value = response.body.toString()
}
alert(`成功向 ${selectedUser.value.name} 充值 ${amount} 金币`)
showError(`成功向 ${selectedUser.value.name} 充值 ${amount} 金币`)
//
rechargeAmount.value = ''
} else {
alert('充值失败,请重试')
showError('充值失败,请重试')
}
} catch (error) {
console.error('充值失败:', error)
alert('充值失败,请检查网络后重试')
showError('充值失败,请检查网络后重试')
} finally {
isRecharging.value = false
}

View File

@ -53,6 +53,7 @@ import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { getBankBalance, userSalaryCheckExchange, userBankExchangeGold } from '../api/wallet.js'
import { showError } from "@/utils/toast.js";
const router = useRouter()
@ -90,7 +91,7 @@ const fetchBankBalance = async () => {
console.error('Failed to fetch bank balance:', error)
availableSalary.value = '0.00'
//
alert('Failed to load balance. Please try again.')
showError('Failed to load balance. Please try again.')
} finally {
loading.value = false
}
@ -125,13 +126,13 @@ const checkUserRole = async () => {
//
if (error.errorCode === 1083) {
// NOT_OPEN_ERROR
alert('Exchange function is not available')
showError('Exchange function is not available')
} else if (error.message && error.message.includes('HTTP Error: 406')) {
// 406 Not Acceptable
alert('The server cannot produce a response matching the list of acceptable values defined in the request headers')
showError('The server cannot produce a response matching the list of acceptable values defined in the request headers')
} else {
//
alert('Failed to load user information')
showError('Failed to load user information')
}
router.back() // 退
@ -145,7 +146,7 @@ const checkUserRole = async () => {
const exchangeCoins = async () => {
if (!selectedCoin.value) {
alert('Please select a coin amount first')
showError('Please select a coin amount first')
return
}
@ -156,7 +157,7 @@ const exchangeCoins = async () => {
const availableBalance = parseFloat(availableSalary.value)
if (isNaN(availableBalance) || availableBalance < coinPrice) {
alert(`Insufficient balance. Your current balance is $${availableBalance.toFixed(2)}, but you need $${coinPrice} to exchange.`)
showError(`Insufficient balance. Your current balance is $${availableBalance.toFixed(2)}, but you need $${coinPrice} to exchange.`)
return
}
@ -173,7 +174,7 @@ const exchangeCoins = async () => {
})
if (response.status === true || response.errorCode === 0) {
alert(`Successfully exchanged ${selectedCoinData.amount} coins for $${selectedCoinData.price}`)
showError(`Successfully exchanged ${selectedCoinData.amount} coins for $${selectedCoinData.price}`)
//
await fetchBankBalance()
@ -184,11 +185,11 @@ const exchangeCoins = async () => {
} else {
//
const errorMessage = response.message || 'Exchange failed, please try again later.'
alert(errorMessage)
showError(errorMessage)
}
} catch (error) {
console.error('Exchange error:', error)
alert('An error occurred during the exchange process. Please try again later.')
showError('An error occurred during the exchange process. Please try again later.')
}
}
//

View File

@ -130,6 +130,7 @@ import MobileHeader from '../components/MobileHeader.vue'
import { getBankBalance, getUserIdentity, getTeamEntry, getTeamMemberWorkStatistics } from '../api/wallet.js'
import { connectApplication, parseAccessOrigin, parseHeader, setHttpHeaders, isInApp } from '../utils/appBridge.js'
import {getUserId, setUserInfo} from '../utils/userStore.js'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -223,7 +224,7 @@ const fetchBankBalance = async () => {
console.error('Failed to fetch bank balance:', error)
salaryInfo.currentSalary = 0.00
//
alert('Failed to load balance. Please try again.')
showError('Failed to load balance. Please try again.')
} finally {
loading.value = false
}

View File

@ -9,7 +9,7 @@
<h3>My Agent</h3>
<span class="agency-tag">🏢 Agency</span>
</div>
<div class="agent-card">
<div class="agent-info">
<div class="avatar">
@ -31,6 +31,7 @@
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -43,7 +44,7 @@ const userInfo = reactive({
//
const leaveAgent = () => {
if (confirm('Are you sure you want to leave this agent?')) {
alert('Leave agent request submitted')
showError('Leave agent request submitted')
//
}
}

View File

@ -28,56 +28,8 @@
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
const router = useRouter()
const teamId = ref('123456789')
// ID
const copyTeamId = async () => {
try {
await navigator.clipboard.writeText(teamId.value)
alert('Team ID copied to clipboard!')
} catch (error) {
//
const textArea = document.createElement('textarea')
textArea.value = teamId.value
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
alert('Team ID copied to clipboard!')
}
}
//
const shareToWeChat = () => {
alert('Share to WeChat functionality')
}
//
const shareLink = () => {
const shareText = `Join my team! Team ID: ${teamId.value}\nDownload Likei APP: https://likei.app`
if (navigator.share) {
navigator.share({
title: 'Join my team on Likei',
text: shareText,
url: 'https://likei.app'
})
} else {
//
copyTeamId()
alert('Link copied! Share with your friends.')
}
}
//
const shareQRCode = () => {
alert('QR Code sharing functionality')
}
</script>
<style scoped>

View File

@ -54,6 +54,7 @@ import MobileHeader from '../components/MobileHeader.vue'
import { getApplyRecord } from '../api/teamBill.js'
import { processUserApply } from '../api/wallet.js'
import {getTeamId} from "@/utils/userStore.js";
import {showError} from "@/utils/toast.js";
const router = useRouter()
const loading = ref(false)
@ -110,18 +111,18 @@ const handleAgree = async (message) => {
})
if (response.status) {
alert(`已同意 ${message.name} 的申请`)
showError(`已同意 ${message.name} 的申请`)
//
const index = messages.value.findIndex(m => m.id === message.id)
if (index > -1) {
messages.value.splice(index, 1)
}
} else {
alert('操作失败,请重试')
showError('操作失败,请重试')
}
} catch (error) {
console.error('同意申请失败:', error)
alert('操作失败,请重试')
showError('操作失败,请重试')
}
}
@ -134,18 +135,18 @@ const handleRefuse = async (message) => {
})
if (response.status) {
alert(`已拒绝 ${message.name} 的申请`)
showError(`已拒绝 ${message.name} 的申请`)
//
const index = messages.value.findIndex(m => m.id === message.id)
if (index > -1) {
messages.value.splice(index, 1)
}
} else {
alert('操作失败,请重试')
showError('操作失败,请重试')
}
} catch (error) {
console.error('拒绝申请失败:', error)
alert('操作失败,请重试')
showError('操作失败,请重试')
}
}

View File

@ -105,6 +105,7 @@ import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { userBankCheckTransfer, userBankSearchUserProfile } from '../api/wallet.js'
import { setSelectedPayee } from '../utils/payeeStore.js'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -252,7 +253,7 @@ const performSearch = async () => {
}
if (!selectedUserType.value) {
alert('Please select user type first')
showError('Please select user type first')
return
}
@ -286,7 +287,7 @@ const performSearch = async () => {
} catch (error) {
console.error('Search failed:', error)
searchResults.value = []
alert('User not found or search failed')
showError('User not found or search failed')
} finally {
isSearching.value = false
}

View File

@ -77,6 +77,7 @@ import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import { getTeamMembers, deleteMember as deleteMemberApi } from '../api/teamBill.js'
import {getTeamId} from "@/utils/userStore.js";
import {showError} from "@/utils/toast.js";
const router = useRouter()
const loading = ref(false)
@ -157,7 +158,7 @@ const confirmDelete = async () => {
closeDeleteModal()
} catch (error) {
console.error('Failed to delete member:', error)
alert('Failed to delete member. Please try again.')
showError('Failed to delete member. Please try again.')
} finally {
deletingMemberId.value = null
}

View File

@ -83,6 +83,7 @@ import { useRouter } from 'vue-router'
import MobileHeader from '../components/MobileHeader.vue'
import {getBankBalance, userBankTransfer} from '../api/wallet.js'
import { getSelectedPayee, clearSelectedPayee } from '../utils/payeeStore.js'
import {showError} from "@/utils/toast.js";
const router = useRouter()
@ -137,7 +138,7 @@ const fetchBankBalance = async () => {
console.error('Failed to fetch bank balance:', error)
availableSalary.value = '0.00'
//
alert('Failed to load balance. Please try again.')
showError('Failed to load balance. Please try again.')
} finally {
loading.value = false
}
@ -178,12 +179,12 @@ const selectCoin = (coin) => {
const transfer = async () => {
if (!selectedCoin.value) {
alert('Please select an amount first')
showError('Please select an amount first')
return
}
if (!selectedPayee.id) {
alert('Please select a payee first')
showError('Please select a payee first')
return
}
@ -192,7 +193,7 @@ const transfer = async () => {
//
// const password = prompt('Please enter your payment password:')
// if (!password) {
// alert('Payment password is required')
// showError('Payment password is required')
// return
// }
@ -209,7 +210,7 @@ const transfer = async () => {
const response = await userBankTransfer(transferData)
//
alert(`Successfully transferred $${selectedCoinData.price} to ${selectedPayee.name}`)
showError(`Successfully transferred $${selectedCoinData.price} to ${selectedPayee.name}`)
//
selectedCoin.value = null
@ -222,11 +223,11 @@ const transfer = async () => {
console.error('Transfer failed:', error)
if (error.errorCode === 5000) {
alert('Insufficient balance')
showError('Insufficient balance')
} else if (error.errorCode === 5010 || error.errorCode === 5009) {
alert('Payment password error')
showError('Payment password error')
} else {
alert('Transfer failed, please try again later')
showError('Transfer failed, please try again later')
}
}
}