Compare commits

...

2 Commits

Author SHA1 Message Date
前端小啊白 7d89536404 Merge branch 'master' of github.com:tanzhicc/fucai-claim 2025-11-18 09:42:26 +08:00
前端小啊白 9492585fc2 feat: 集成vant组件库并替换自定义message组件
- 添加vant依赖及unplugin-vue-components插件
- 移除自定义message组件及相关逻辑
- 使用vant的Dialog组件替代原有message功能
- 更新相关页面调用方式
2025-11-18 09:42:19 +08:00
7 changed files with 265 additions and 410 deletions

22
components.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ImageViewer: typeof import('./src/components/ImageViewer.vue')['default']
Loading: typeof import('./src/components/Loading.vue')['default']
Message: typeof import('./src/components/Message.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
VanButton: typeof import('vant/es')['Button']
VanDialog: typeof import('vant/es')['Dialog']
}
}

View File

@ -14,6 +14,7 @@
"dayjs": "^1.11.19",
"qrcode.vue": "^3.6.0",
"tailwindcss": "^4.1.17",
"vant": "^4.9.21",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
},
@ -22,6 +23,7 @@
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.2.2",
"vue-tsc": "^3.1.3"
},

View File

@ -23,6 +23,9 @@ importers:
tailwindcss:
specifier: ^4.1.17
version: 4.1.17
vant:
specifier: ^4.9.21
version: 4.9.21(vue@3.5.24(typescript@5.9.3))
vue:
specifier: ^3.5.24
version: 3.5.24(typescript@5.9.3)
@ -42,6 +45,9 @@ importers:
typescript:
specifier: ~5.9.3
version: 5.9.3
unplugin-vue-components:
specifier: ^30.0.0
version: 30.0.0(@babel/parser@7.28.5)(vue@3.5.24(typescript@5.9.3))
vite:
specifier: ^7.2.2
version: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)
@ -449,6 +455,14 @@ packages:
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
'@vant/popperjs@1.3.0':
resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
'@vant/use@1.6.0':
resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
peerDependencies:
vue: ^3.0.0
'@vitejs/plugin-vue@6.0.1':
resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -516,6 +530,11 @@ packages:
vue:
optional: true
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
alien-signals@3.1.0:
resolution: {integrity: sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg==}
@ -529,16 +548,35 @@ packages:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dayjs@1.11.19:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@ -583,6 +621,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@ -714,6 +755,10 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@ -729,6 +774,12 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
@ -740,6 +791,9 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -747,6 +801,12 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@ -759,6 +819,13 @@ packages:
peerDependencies:
vue: ^3.0.0
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
rollup@4.53.2:
resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -784,9 +851,38 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unplugin-utils@0.3.1:
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
engines: {node: '>=20.19.0'}
unplugin-vue-components@30.0.0:
resolution: {integrity: sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==}
engines: {node: '>=14'}
peerDependencies:
'@babel/parser': ^7.15.8
'@nuxt/kit': ^3.2.2 || ^4.0.0
vue: 2 || 3
peerDependenciesMeta:
'@babel/parser':
optional: true
'@nuxt/kit':
optional: true
unplugin@2.3.10:
resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
engines: {node: '>=18.12.0'}
vant@4.9.21:
resolution: {integrity: sha512-hXUoZMrLLjykimFRLDlGNd+K2iYSRh9YwLMKnsVdVZ+9inUKxpqnjhOqlZwocbnYkvJlS+febf9u9aJpDol4Pw==}
peerDependencies:
vue: ^3.0.0
vite@7.2.2:
resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -849,6 +945,9 @@ packages:
typescript:
optional: true
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
snapshots:
'@babel/helper-string-parser@7.27.1': {}
@ -1103,6 +1202,12 @@ snapshots:
dependencies:
undici-types: 7.16.0
'@vant/popperjs@1.3.0': {}
'@vant/use@1.6.0(vue@3.5.24(typescript@5.9.3))':
dependencies:
vue: 3.5.24(typescript@5.9.3)
'@vitejs/plugin-vue@6.0.1(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.29
@ -1194,6 +1299,8 @@ snapshots:
typescript: 5.9.3
vue: 3.5.24(typescript@5.9.3)
acorn@8.15.0: {}
alien-signals@3.1.0: {}
asynckit@0.4.0: {}
@ -1211,14 +1318,26 @@ snapshots:
es-errors: 1.3.0
function-bind: 1.1.2
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
confbox@0.1.8: {}
confbox@0.2.2: {}
csstype@3.1.3: {}
dayjs@1.11.19: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
delayed-stream@1.0.0: {}
detect-libc@2.1.2: {}
@ -1282,6 +1401,8 @@ snapshots:
estree-walker@2.0.2: {}
exsolve@1.0.8: {}
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@ -1384,6 +1505,12 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.2
lightningcss-win32-x64-msvc: 1.30.2
local-pkg@1.1.2:
dependencies:
mlly: 1.8.0
pkg-types: 2.3.0
quansync: 0.2.11
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@ -1396,16 +1523,39 @@ snapshots:
dependencies:
mime-db: 1.52.0
mlly@1.8.0:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
ms@2.1.3: {}
muggle-string@0.4.1: {}
nanoid@3.3.11: {}
path-browserify@1.0.1: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@4.0.3: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.0
pathe: 2.0.3
pkg-types@2.3.0:
dependencies:
confbox: 0.2.2
exsolve: 1.0.8
pathe: 2.0.3
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@ -1418,6 +1568,10 @@ snapshots:
dependencies:
vue: 3.5.24(typescript@5.9.3)
quansync@0.2.11: {}
readdirp@4.1.2: {}
rollup@4.53.2:
dependencies:
'@types/estree': 1.0.8
@ -1459,8 +1613,45 @@ snapshots:
typescript@5.9.3: {}
ufo@1.6.1: {}
undici-types@7.16.0: {}
unplugin-utils@0.3.1:
dependencies:
pathe: 2.0.3
picomatch: 4.0.3
unplugin-vue-components@30.0.0(@babel/parser@7.28.5)(vue@3.5.24(typescript@5.9.3)):
dependencies:
chokidar: 4.0.3
debug: 4.4.3
local-pkg: 1.1.2
magic-string: 0.30.21
mlly: 1.8.0
tinyglobby: 0.2.15
unplugin: 2.3.10
unplugin-utils: 0.3.1
vue: 3.5.24(typescript@5.9.3)
optionalDependencies:
'@babel/parser': 7.28.5
transitivePeerDependencies:
- supports-color
unplugin@2.3.10:
dependencies:
'@jridgewell/remapping': 2.3.5
acorn: 8.15.0
picomatch: 4.0.3
webpack-virtual-modules: 0.6.2
vant@4.9.21(vue@3.5.24(typescript@5.9.3)):
dependencies:
'@vant/popperjs': 1.3.0
'@vant/use': 1.6.0(vue@3.5.24(typescript@5.9.3))
'@vue/shared': 3.5.24
vue: 3.5.24(typescript@5.9.3)
vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2):
dependencies:
esbuild: 0.25.12
@ -1497,3 +1688,5 @@ snapshots:
'@vue/shared': 3.5.24
optionalDependencies:
typescript: 5.9.3
webpack-virtual-modules@0.6.2: {}

View File

@ -1,213 +0,0 @@
<template>
<Transition name="message-fade">
<div v-show="visible" class="message-container" :class="`message-${type}`" :style="customStyle">
<div class="message-content">{{ message }}</div>
<button v-if="closable" class="message-close" @click="close" aria-label="关闭">
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</button>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
// Props
const props = defineProps<{
message: string
type?: 'success' | 'error' | 'warning' | 'info'
duration?: number
showIcon?: boolean
closable?: boolean
customClass?: string
offset?: number
center?: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'close'): void
}>()
//
const visible = ref(false)
let timer: number | null = null
//
const customStyle = computed(() => {
const style: Record<string, string> = {}
if (props.offset) {
style.marginTop = `${props.offset}px`
}
if (props.center) {
style.textAlign = 'center'
}
return style
})
//
const close = () => {
visible.value = false
clearTimer()
emit('close')
}
const clearTimer = () => {
if (timer) {
clearTimeout(timer)
timer = null
}
}
//
onMounted(() => {
visible.value = true
if (props.duration && props.duration > 0) {
timer = window.setTimeout(() => {
close()
}, props.duration)
}
})
onUnmounted(() => {
clearTimer()
})
</script>
<style scoped>
.message-container {
position: relative;
display: flex;
align-items: center;
min-height: 42px;
padding: 8px 16px;
margin-bottom: 12px;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
max-width: 500px;
word-wrap: break-word;
}
/* 消息类型样式 */
.message-success {
border-left: 4px solid #67C23A;
background-color: #F0F9EB;
}
.message-error {
border-left: 4px solid #F56C6C;
background-color: #FEF0F0;
}
.message-warning {
border-left: 4px solid #E6A23C;
background-color: #FDF6EC;
}
.message-info {
border-left: 4px solid #909399;
background-color: #F4F4F5;
}
/* 图标样式 */
.message-icon {
margin-right: 8px;
flex-shrink: 0;
}
.message-success .message-icon {
color: #67C23A;
}
.message-error .message-icon {
color: #F56C6C;
}
.message-warning .message-icon {
color: #E6A23C;
}
.message-info .message-icon {
color: #909399;
}
.message-success .message-content {
color: #306608;
}
.message-error .message-content {
color: #A60C0C;
}
.message-warning .message-content {
color: #9A5518;
}
.message-info .message-content {
color: #606266;
}
/* 关闭按钮样式 */
.message-close {
flex-shrink: 0;
margin-left: 12px;
padding: 0;
background: transparent;
border: none;
cursor: pointer;
font-size: 16px;
line-height: 1;
opacity: 0.5;
transition: opacity 0.3s;
color: inherit;
}
.message-close:hover {
opacity: 1;
}
/* 过渡动画 */
.message-fade-enter-active,
.message-fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.message-fade-enter-from {
opacity: 0;
transform: translateY(-10px);
}
.message-fade-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-container {
max-width: 90%;
margin-left: auto;
margin-right: auto;
}
}
/* 鼠标悬停效果 */
.message-container:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* 消息容器的wrapper样式 */
:global(.message-container-wrapper) {
pointer-events: none;
}
:global(.message-container-wrapper > div) {
pointer-events: all;
}
</style>

View File

@ -1,176 +0,0 @@
import { createApp, h } from 'vue'
import Message from './Message.vue'
// 消息配置接口
export interface MessageOptions {
message: string
type?: 'success' | 'error' | 'warning' | 'info'
duration?: number
showIcon?: boolean
closable?: boolean
customClass?: string
offset?: number
center?: boolean
onClose?: () => void
}
// 消息实例接口
interface MessageInstance {
close: () => void
}
// 消息容器
const messageContainer = document.createElement('div')
messageContainer.className = 'message-container-wrapper'
document.body.appendChild(messageContainer)
// 默认配置
const defaultOptions: Partial<MessageOptions> = {
duration: 3000,
showIcon: true,
closable: false,
offset: 20,
center: false
}
// 消息管理类
class MessageService {
private instances: MessageInstance[] = []
// 创建消息实例
private createMessage(options: string | MessageOptions): MessageInstance {
// 标准化配置
const finalOptions = typeof options === 'string'
? { message: options, ...defaultOptions }
: { ...defaultOptions, ...options }
// 创建容器元素
const container = document.createElement('div')
messageContainer.appendChild(container)
// 创建应用实例
// 保存对this的引用以避免上下文问题
const self = this
const app = createApp({
render: () => {
return h(Message, {
...finalOptions,
onClose: () => {
self.closeInstance(instance)
finalOptions.onClose?.()
}
})
}
})
// 挂载组件
app.mount(container)
// 计算垂直偏移
const verticalOffset = this.calculateOffset(finalOptions.offset ?? 0)
container.style.cssText = `
position: fixed;
top: ${verticalOffset}px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
`
// 创建实例对象
const instance: MessageInstance = {
close: () => {
this.closeInstance(instance)
}
}
// 保存实例
this.instances.push(instance)
return instance
}
// 计算垂直偏移
private calculateOffset(baseOffset: number): number {
if (this.instances.length === 0) {
return baseOffset
}
let offset = baseOffset
this.instances.forEach(() => {
// 假设每个消息高度约为60px间距为12px
offset += 60 + 12
})
return offset
}
// 关闭指定实例
private closeInstance(instance: MessageInstance): void {
const index = this.instances.indexOf(instance)
if (index !== -1) {
this.instances.splice(index, 1)
// 重新计算所有消息的位置
this.updateOffsets()
}
}
// 更新所有消息的位置
private updateOffsets(): void {
let offset = defaultOptions.offset || 20
const messageElements = messageContainer.querySelectorAll('div > div')
messageElements.forEach((element, index) => {
if (index < this.instances.length) {
(element.parentElement as HTMLElement).style.top = `${offset}px`
offset += 60 + 12
}
})
}
// 显示普通消息
info(options: string | MessageOptions): MessageInstance {
return this.createMessage(typeof options === 'string'
? { message: options, type: 'info' }
: { ...options, type: 'info' }
)
}
// 显示成功消息
success(options: string | MessageOptions): MessageInstance {
return this.createMessage(typeof options === 'string'
? { message: options, type: 'success' }
: { ...options, type: 'success' }
)
}
// 显示错误消息
error(options: string | MessageOptions): MessageInstance {
return this.createMessage(typeof options === 'string'
? { message: options, type: 'error' }
: { ...options, type: 'error' }
)
}
// 显示警告消息
warning(options: string | MessageOptions): MessageInstance {
return this.createMessage(typeof options === 'string'
? { message: options, type: 'warning' }
: { ...options, type: 'warning' }
)
}
// 关闭所有消息
closeAll(): void {
this.instances.forEach(instance => {
instance.close()
})
this.instances = []
}
}
// 导出消息服务实例
export const message = new MessageService()
// 提供默认导出
export default message

View File

@ -35,7 +35,7 @@
<button @click="formData.verifyCode ? clearPhone() : sendVerificationCode()"
:disabled="countdown > 0 || formData.phone.length !== 11"
class="h-11 rounded-lg text-sm text-[#E8424D] transition-all disabled:text-[#8a8a8a]">
class="h-11 rounded-lg text-sm! text-[#E8424D]! transition-all disabled:text-[#8a8a8a]!">
{{ countdown > 0 ? `${countdown}秒后重发` : formData.verifyCode ? '清除' : '获取验证码' }}
</button>
</div>
@ -45,7 +45,7 @@
<input :disabled="formData.verifyCode" v-model="formData.smsCode" type="text" placeholder="请输入验证码"
maxlength="6" class="outline-none focus:border-[#E8424D] transition-colors flex-1 h-11 w-full" />
<button v-if="!formData.verifyCode" @click="verifyCode" :disabled="formData.smsCode.length !== 6"
class="h-8 px-4 bg-[#E8424D] text-white rounded-lg text-sm transition-all hover:opacity-90 active:scale-98 disabled:bg-[#8a8a8a]">
class="h-8 px-4 bg-[#E8424D]! text-white! rounded-lg text-sm! transition-all hover:opacity-90 active:scale-98 disabled:bg-[#8a8a8a]!">
验证
</button>
</div>
@ -87,7 +87,7 @@
<!-- 提交按钮 -->
<button @click="submitForm" :disabled="!formData.marriageNo"
class="w-full h-12 bg-gradient-to-r from-[#E8424D] to-[#FF7A7A] text-white rounded-full text-lg font-bold transition-all hover:opacity-90 active:scale-98 disabled:from-[#8a8a8a] disabled:to-[#8a8a8a]">
class="w-full h-12 bg-gradient-to-r from-[#E8424D]! to-[#FF7A7A]! text-white! rounded-full text-lg font-bold transition-all hover:opacity-90 active:scale-98 disabled:from-[#8a8a8a]! disabled:to-[#8a8a8a]!">
提交领取喜礼
</button>
</div>
@ -133,12 +133,13 @@
</template>
<script setup lang="ts">
import message from '../../components/message';
import apiService from '../../services/apiService'
import { ref, onMounted } from 'vue'
import dayjs from 'dayjs'
import QrcodeVue from 'qrcode.vue'
import { showLoading, hideLoading } from '../../components/loading'
import { showDialog } from 'vant';
import 'vant/es/dialog/style';
const ocrUploadId = ref<HTMLInputElement>();
@ -168,21 +169,25 @@ onMounted(() => {
};
if (new Date(response.data.activityEndTime) < new Date()) {
message.error('活动已结束');
showDialog({
message: '活动已结束',
});
activityInfo.value.status = 2;
} else {
const user = JSON.parse(localStorage.getItem('userAs') || '{}');
if (user.phone && user.smsCode) {
formData.value.phone = user.phone;
formData.value.smsCode = user.smsCode;
verifyCode();
verifyCode(false);
}
}
} else {
activityInfo.value.status = 3;
}
}).catch((error) => {
message.error('获取活动信息失败,请稍后重试')
showDialog({
message: '获取活动信息失败,请稍后重试',
});
activityInfo.value.status = 3;
}).finally(() => {
hideLoading();
@ -218,7 +223,9 @@ const sendVerificationCode = () => {
//
if (!/^1[3-9]\d{9}$/.test(phone)) {
message.error('请输入正确的手机号码')
showDialog({
message: '请输入正确的手机号码',
});
return
}
showLoading();
@ -249,18 +256,22 @@ const sendVerificationCode = () => {
}, 1000)
}).catch((error) => {
console.error('发送验证码失败:', error)
message.error('验证码发送失败,请稍后重试')
showDialog({
message: '验证码发送失败,请稍后重试',
});
}).finally(() => {
hideLoading();
})
}
//
const verifyCode = () => {
const verifyCode = (flag: any) => {
const { smsCode } = formData.value
if (smsCode.length !== 6 || !/^\d{6}$/.test(smsCode)) {
message.error('请输入6位数字验证码')
showDialog({
message: '请输入6位数字验证码',
});
return
}
@ -299,11 +310,11 @@ const verifyCode = () => {
}
}
}).catch((error) => {
console.log(error, 66666666666666);
localStorage.removeItem('userAs');
message.error('验证码验证失败,请重试');
flag && showDialog({
message: '验证码验证失败,请重试',
});
formData.value = {
...formData.value,
smsCode: "",
@ -339,7 +350,9 @@ const handleScanClick = () => {
const handleImageChange = () => {
const file = ocrUploadId.value?.files?.[0]
if (!file) {
message.error('请选择图片文件')
showDialog({
message: '请选择图片文件',
});
return
}
@ -360,17 +373,23 @@ const handleImageChange = () => {
formData.value.wifeName = res?.data?.parsed?.wifeName;
formData.value.registerDate = res?.data?.parsed?.registerDate;
} else {
message.error('解析OCR信息失败请稍后重试');
showDialog({
message: '解析OCR信息失败请稍后重试',
});
ocrUploadId.value && (ocrUploadId.value.value = '');
}
}).catch((error) => {
message.error('解析OCR信息失败请稍后重试')
showDialog({
message: '解析OCR信息失败请稍后重试',
});
ocrUploadId.value && (ocrUploadId.value.value = '');
}).finally(() => {
hideLoading();
})
}).catch((error) => {
message.error('图片上传失败,请稍后重试')
showDialog({
message: '图片上传失败,请稍后重试',
});
ocrUploadId.value && (ocrUploadId.value.value = '');
hideLoading();
})
@ -381,7 +400,9 @@ const handleImageChange = () => {
//
const submitForm = () => {
if (!formData.value.phone || !formData.value.smsCode) {
message.error('请完善所有信息')
showDialog({
message: '请完善所有信息',
});
return
}
@ -394,7 +415,9 @@ const submitForm = () => {
}).then((res: any) => {
formData.value.qrCode = res.data.code;
}).catch((error) => {
message.error(error?.msg || '提交失败,请稍后重试')
showDialog({
message: error?.msg || '提交失败,请稍后重试',
});
}).finally(() => {
hideLoading();
})

View File

@ -1,10 +1,14 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), tailwindcss()],
plugins: [vue(), tailwindcss(), Components({
resolvers: [VantResolver()],
})],
resolve: {
alias: {
'@': '/src',