diff --git a/Cunkebao/package.json b/Cunkebao/package.json index 313741a18..0277589c7 100644 --- a/Cunkebao/package.json +++ b/Cunkebao/package.json @@ -18,6 +18,7 @@ "react-router-dom": "^6.20.0", "react-window": "^1.8.11", "vconsole": "^3.15.1", + "xmldom": "^0.6.0", "zustand": "^5.0.6" }, "devDependencies": { diff --git a/Cunkebao/pnpm-lock.yaml b/Cunkebao/pnpm-lock.yaml index dda309904..818ed7d75 100644 --- a/Cunkebao/pnpm-lock.yaml +++ b/Cunkebao/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: vconsole: specifier: ^3.15.1 version: 3.15.1 + xmldom: + specifier: ^0.6.0 + version: 0.6.0 zustand: specifier: ^5.0.6 version: 5.0.7(@types/react@19.1.10)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) @@ -2355,6 +2358,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xmldom@0.6.0: + resolution: {integrity: sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==} + engines: {node: '>=10.0.0'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -4974,6 +4981,8 @@ snapshots: wrappy@1.0.2: {} + xmldom@0.6.0: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/Cunkebao/src/components/EmojiSeclection/EmojiPicker.css b/Cunkebao/src/components/EmojiSeclection/EmojiPicker.css new file mode 100644 index 000000000..def7ee82a --- /dev/null +++ b/Cunkebao/src/components/EmojiSeclection/EmojiPicker.css @@ -0,0 +1,168 @@ +/* 表情选择器容器 */ +.emoji-picker-container { + position: relative; + display: inline-block; +} + +/* 默认触发器按钮 */ +.emoji-picker-trigger { + background: none; + border: 1px solid #e0e0e0; + border-radius: 6px; + padding: 8px 12px; + font-size: 18px; + cursor: pointer; + transition: all 0.2s ease; +} + +.emoji-picker-trigger:hover { + background-color: #f5f5f5; + border-color: #d0d0d0; +} + +/* 表情选择器面板 */ +.emoji-picker-panel { + position: absolute; + bottom: 100%; + left: 0; + z-index: 1000; + background: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + width: 320px; + max-height: 400px; + overflow: hidden; + margin-bottom: 4px; +} + +/* 分类标签栏 */ +.emoji-categories { + display: flex; + background-color: #f8f9fa; + border-bottom: 1px solid #e0e0e0; + padding: 8px; + gap: 4px; +} + +.category-btn { + background: none; + border: none; + padding: 8px 12px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.2s ease; + flex: 1; + text-align: center; +} + +.category-btn:hover { + background-color: #e9ecef; +} + +.category-btn.active { + background-color: #007bff; + color: white; +} + +/* 表情网格 */ +.emoji-grid { + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 4px; + padding: 12px; + max-height: 280px; + overflow-y: auto; +} + +/* 表情项 */ +.emoji-item { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.emoji-item:hover { + background-color: #f0f0f0; +} + +.emoji-image { + width: 24px; + height: 24px; + object-fit: contain; +} + +/* 空状态 */ +.emoji-empty { + text-align: center; + padding: 40px 20px; + color: #999; + font-size: 14px; +} + +/* 滚动条样式 */ +.emoji-grid::-webkit-scrollbar { + width: 6px; +} + +.emoji-grid::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; +} + +.emoji-grid::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +.emoji-grid::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 响应式设计 */ +@media (max-width: 480px) { + .emoji-picker-panel { + width: 280px; + } + + .emoji-grid { + grid-template-columns: repeat(7, 1fr); + } +} + +/* 深色模式支持 */ +@media (prefers-color-scheme: dark) { + .emoji-picker-panel { + background: #2d3748; + border-color: #4a5568; + color: white; + } + + .emoji-categories { + background-color: #1a202c; + border-bottom-color: #4a5568; + } + + .category-btn:hover { + background-color: #4a5568; + } + + .emoji-item:hover { + background-color: #4a5568; + } + + .emoji-picker-trigger { + border-color: #4a5568; + color: white; + } + + .emoji-picker-trigger:hover { + background-color: #4a5568; + } +} \ No newline at end of file diff --git a/Cunkebao/src/components/EmojiSeclection/EmojiPicker.tsx b/Cunkebao/src/components/EmojiSeclection/EmojiPicker.tsx new file mode 100644 index 000000000..446d0d2d1 --- /dev/null +++ b/Cunkebao/src/components/EmojiSeclection/EmojiPicker.tsx @@ -0,0 +1,120 @@ +import React, { useState, useRef, useEffect } from "react"; +import { + EmojiCategory, + EmojiInfo, + getAllEmojis, + getEmojisByCategory, +} from "./wechatEmoji"; +import "./EmojiPicker.css"; + +interface EmojiPickerProps { + onEmojiSelect: (emoji: EmojiInfo) => void; + trigger?: React.ReactNode; + className?: string; +} + +const EmojiPicker: React.FC = ({ + onEmojiSelect, + trigger, + className = "", +}) => { + const [isOpen, setIsOpen] = useState(false); + const [activeCategory, setActiveCategory] = useState( + EmojiCategory.FACE, + ); + const pickerRef = useRef(null); + + // 分类配置 + const categories = [ + { key: EmojiCategory.FACE, label: "😊", title: "人脸" }, + { key: EmojiCategory.GESTURE, label: "👋", title: "手势" }, + { key: EmojiCategory.ANIMAL, label: "🐷", title: "动物" }, + { key: EmojiCategory.BLESSING, label: "🎉", title: "祝福" }, + { key: EmojiCategory.OTHER, label: "❤️", title: "其他" }, + ]; + + // 获取当前分类的表情 + const currentEmojis = getEmojisByCategory(activeCategory); + + // 点击外部关闭 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + pickerRef.current && + !pickerRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [isOpen]); + + // 处理表情选择 + const handleEmojiClick = (emoji: EmojiInfo) => { + onEmojiSelect(emoji); + setIsOpen(false); + }; + + // 默认触发器 + const defaultTrigger = ; + + return ( +
+ {/* 触发器 */} +
setIsOpen(!isOpen)}>{trigger || defaultTrigger}
+ + {/* 表情选择器面板 */} + {isOpen && ( +
+ {/* 分类标签 */} +
+ {categories.map(category => ( + + ))} +
+ + {/* 表情网格 */} +
+ {currentEmojis.map(emoji => ( +
handleEmojiClick(emoji)} + title={emoji.name} + > + {emoji.name} +
+ ))} +
+ + {/* 空状态 */} + {currentEmojis.length === 0 && ( +
暂无表情
+ )} +
+ )} +
+ ); +}; + +export default EmojiPicker; diff --git a/Cunkebao/src/components/EmojiSeclection/EmojiPickerDemo.tsx b/Cunkebao/src/components/EmojiSeclection/EmojiPickerDemo.tsx new file mode 100644 index 000000000..47311513f --- /dev/null +++ b/Cunkebao/src/components/EmojiSeclection/EmojiPickerDemo.tsx @@ -0,0 +1,127 @@ +import React, { useState } from "react"; +import EmojiPicker from "./EmojiPicker"; +import { EmojiInfo } from "./wechatEmoji"; + +const EmojiPickerDemo: React.FC = () => { + const [selectedEmojis, setSelectedEmojis] = useState([]); + const [message, setMessage] = useState(""); + + // 处理表情选择 + const handleEmojiSelect = (emoji: EmojiInfo) => { + setSelectedEmojis(prev => [...prev, emoji]); + // 模拟发送表情 + console.log("发送表情:", emoji.name); + alert(`发送了表情: ${emoji.name}`); + }; + + // 清空选择的表情 + const clearEmojis = () => { + setSelectedEmojis([]); + }; + + return ( +
+

表情选择器演示

+ + {/* 输入区域 */} +
+