This commit is contained in:
2026-07-23 12:07:34 +08:00
18 changed files with 777 additions and 1022 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -28,12 +28,10 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-CnKJ4EKg.js"></script>
<script type="module" crossorigin src="/assets/index-C4uOdbc7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+82
View File
@@ -17,6 +17,7 @@
"qrcode.react": "^4.2.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-quill-new": "^3.8.3",
"react-router-dom": "^7.15.0",
"three": "^0.184.0",
"uuid": "^14.0.0",
@@ -2826,6 +2827,12 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2833,6 +2840,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -3615,6 +3628,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3822,6 +3854,12 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parchment": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
"license": "BSD-3-Clause"
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4138,6 +4176,35 @@
],
"license": "MIT"
},
"node_modules/quill": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"license": "BSD-3-Clause",
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"parchment": "^3.0.0",
"quill-delta": "^5.1.0"
},
"engines": {
"npm": ">=8.2.3"
}
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"license": "MIT",
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/rangetouch": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/rangetouch/-/rangetouch-2.0.1.tgz",
@@ -4189,6 +4256,21 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-quill-new": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/react-quill-new/-/react-quill-new-3.8.3.tgz",
"integrity": "sha512-c96PYqFTo0pI4R3e79B3rH9LUIce1kIQbmTBu/imJQZk8305ogyLyBqKKjG2UoInDlquXqePSzmBo2aVia3ttw==",
"license": "MIT",
"dependencies": {
"lodash-es": "^4.17.21",
"quill": "~2.0.3"
},
"peerDependencies": {
"quill-delta": "^5.1.0",
"react": "^16 || ^17 || ^18 || ^19",
"react-dom": "^16 || ^17 || ^18 || ^19"
}
},
"node_modules/react-router": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz",
+1
View File
@@ -19,6 +19,7 @@
"qrcode.react": "^4.2.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-quill-new": "^3.8.3",
"react-router-dom": "^7.15.0",
"three": "^0.184.0",
"uuid": "^14.0.0",
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Tag, Button } from 'antd';
import { BellOutlined, ThunderboltOutlined, GiftOutlined, StarOutlined, CloseOutlined } from '@ant-design/icons';
import { BellOutlined, ThunderboltOutlined, GiftOutlined, StarOutlined, CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { getNotifications, markNotificationRead } from '../api';
import { useAuthStore } from '../store/useAuthStore';
@@ -105,94 +105,61 @@ const NotificationPopup: React.FC = () => {
background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(6px)',
}} onClick={handleClose}>
<div onClick={e => e.stopPropagation()} style={{
width: 420,
borderRadius: 24,
width: 520,
borderRadius: 24,
overflow: 'hidden',
boxShadow: '0 25px 80px rgba(0,0,0,0.15)',
boxShadow: '0 30px 100px rgba(0,0,0,0.2)',
animation: 'notificationPop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)',
background: '#ffffff',
}}>
{/* Header */}
<div style={{
background: tc.gradient,
padding: '24px 28px 20px',
position: 'relative',
padding: '16px 28px 14px',
position: 'relative',
overflow: 'hidden',
}}>
<div style={{
position: 'absolute',
right: -50,
top: -50,
width: 200,
height: 200,
borderRadius: '50%',
<div style={{
position: 'absolute',
right: -50,
top: -50,
width: 200,
height: 200,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 60%)',
filter: 'blur(30px)',
}} />
<div style={{
position: 'absolute',
right: 30,
top: 10,
width: 80,
height: 80,
borderRadius: '50%',
background: 'rgba(255,255,255,0.06)',
filter: 'blur(15px)',
}} />
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{
width: 44,
height: 44,
borderRadius: 14,
background: 'rgba(255,255,255,0.15)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 20,
color: '#fff',
}}>{tc.icon}</div>
<div>
<div style={{
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
marginBottom: 4,
fontWeight: 500,
}}></div>
<div style={{
color: '#fff',
fontSize: 28,
fontWeight: 700,
letterSpacing: '-1px',
}}>{current.title}</div>
</div>
</div>
<button
onClick={handleClose}
{/* Top row: type badge + close */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<Tag style={{ background: 'rgba(255,255,255,0.2)', border: 'none', color: '#fff', borderRadius: 6, padding: '2px 10px', fontSize: 11, fontWeight: 500 }}>
{tc.icon} {tc.label}
</Tag>
<button
onClick={handleClose}
style={{
width: 30,
height: 30,
borderRadius: '50%',
background: 'rgba(255,255,255,0.1)',
border: 'none',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s ease',
outline: 'none',
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'rgba(255,255,255,0.2)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'rgba(255,255,255,0.1)';
width: 28, height: 28, borderRadius: '50%',
background: 'rgba(255,255,255,0.1)',
border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'all 0.2s ease', outline: 'none',
}}
onMouseEnter={e => { e.currentTarget.style.background = 'rgba(255,255,255,0.2)'; }}
onMouseLeave={e => { e.currentTarget.style.background = 'rgba(255,255,255,0.1)'; }}
>
<CloseOutlined style={{ color: '#fff', fontSize: 14 }} />
<CloseOutlined style={{ color: '#fff', fontSize: 12 }} />
</button>
</div>
{/* Title + time row */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12 }}>
<div style={{ color: '#fff', fontSize: 22, fontWeight: 700, letterSpacing: '-0.5px' }}>
{current.title}
</div>
<div style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11, whiteSpace: 'nowrap', flexShrink: 0 }}>
{current.createdAt ? new Date(current.createdAt).toLocaleString('zh-CN') : ''}
</div>
</div>
</div>
{/* Body */}
@@ -201,48 +168,67 @@ const NotificationPopup: React.FC = () => {
padding: '24px 28px 28px',
}}>
<div style={{
padding: '18px 20px',
background: '#f8fafc',
padding: '18px 20px',
background: '#f8fafc',
borderRadius: 16,
marginBottom: 20,
marginBottom: 20,
border: '1px solid #f1f5f9',
}}>
<div style={{
fontSize: 14,
color: '#475569',
<div style={{
fontSize: 14,
color: '#475569',
lineHeight: 1.7,
}}>
{current.content}
</div>
}} dangerouslySetInnerHTML={{ __html: current.content }} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
{/* Left: navigation with left/right buttons */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Tag
color={tc.gradient.includes('#6366f1') ? '#6366f1' : tc.gradient.includes('#f59e0b') ? '#f59e0b' : '#8b5cf6'}
style={{
borderRadius: 6,
border: 'none',
padding: '3px 10px',
fontSize: 11,
fontWeight: 500,
background: '#f1f5f9',
color: '#64748b',
}}
>
{tc.label}
</Tag>
{notifications.length > 1 && (
<>
<button
onClick={(e) => { e.stopPropagation(); setCurrentIndex(currentIndex > 0 ? currentIndex - 1 : notifications.length - 1); }}
style={{
width: 30, height: 30, borderRadius: '50%',
background: '#f1f5f9', border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'all 0.2s', outline: 'none',
}}
onMouseEnter={e => { e.currentTarget.style.background = '#e2e8f0'; }}
onMouseLeave={e => { e.currentTarget.style.background = '#f1f5f9'; }}
>
<LeftOutlined style={{ color: '#64748b', fontSize: 12 }} />
</button>
<span style={{ fontSize: 12, color: '#64748b', fontWeight: 500, minWidth: 36, textAlign: 'center' }}>
{currentIndex + 1}/{notifications.length}
</span>
<button
onClick={(e) => { e.stopPropagation(); setCurrentIndex(currentIndex < notifications.length - 1 ? currentIndex + 1 : 0); }}
style={{
width: 30, height: 30, borderRadius: '50%',
background: '#f1f5f9', border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'all 0.2s', outline: 'none',
}}
onMouseEnter={e => { e.currentTarget.style.background = '#e2e8f0'; }}
onMouseLeave={e => { e.currentTarget.style.background = '#f1f5f9'; }}
>
<RightOutlined style={{ color: '#64748b', fontSize: 12 }} />
</button>
</>
)}
</div>
<Button
type="primary"
onClick={handleAcknowledge}
{/* Right: acknowledge button */}
<Button
type="primary"
onClick={handleAcknowledge}
style={{
borderRadius: 12,
fontWeight: 600,
borderRadius: 12,
fontWeight: 600,
height: 40,
background: tc.gradient,
background: tc.gradient,
border: 'none',
paddingLeft: 28,
paddingLeft: 28,
paddingRight: 28,
boxShadow: `0 6px 16px ${tc.glowColor}`,
transition: 'all 0.2s ease',
@@ -261,28 +247,6 @@ const NotificationPopup: React.FC = () => {
</Button>
</div>
{notifications.length > 1 && (
<div style={{ display: 'flex', justifyContent: 'center', gap: 6, marginTop: 20 }}>
{notifications.map((_, i) => (
<button
key={i}
onClick={() => setCurrentIndex(i)}
style={{
width: i === currentIndex ? 20 : 5,
height: 5,
borderRadius: 3,
background: i === currentIndex
? (tc.gradient.includes('#6366f1') ? '#6366f1' : tc.gradient.includes('#f59e0b') ? '#f59e0b' : '#8b5cf6')
: '#e2e8f0',
border: 'none',
cursor: 'pointer',
transition: 'all 0.3s ease',
outline: 'none',
}}
/>
))}
</div>
)}
</div>
</div>
<style>{`
+1
View File
@@ -81,6 +81,7 @@ const MessagesPage: React.FC = () => {
dataIndex: 'content',
key: 'content',
ellipsis: true,
render: (v: string) => <div style={{ maxWidth: 300 }} dangerouslySetInnerHTML={{ __html: v }} />,
},
{
title: '状态',