This commit is contained in:
2026-05-05 14:40:41 +08:00
parent e847fcea6f
commit 07e777fef8
76 changed files with 4246 additions and 444 deletions

View File

@@ -57,6 +57,13 @@ describe('PublishShareModal', () => {
expect(within(dialog).getByRole('button', { name: '分享到微信' })).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '分享到QQ' })).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '分享到抖音' })).toBeTruthy();
expect(
within(dialog).getByTestId('share-channel-logo-wechat'),
).toBeTruthy();
expect(within(dialog).getByTestId('share-channel-logo-qq')).toBeTruthy();
expect(
within(dialog).getByTestId('share-channel-logo-douyin'),
).toBeTruthy();
fireEvent.click(within(dialog).getByRole('button', { name: '分享' }));

View File

@@ -1,4 +1,4 @@
import { Check, Copy, MessageCircle, Music2 } from 'lucide-react';
import { Check, Copy } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { copyTextToClipboard } from '../../services/clipboard';
@@ -15,26 +15,74 @@ type PublishShareModalProps = {
onClose: () => void;
};
type ShareChannelId = 'wechat' | 'qq' | 'douyin';
type ShareChannel = {
id: ShareChannelId;
label: string;
iconClassName: string;
};
// 中文注释:渠道图标只承载品牌轮廓,不复用社群二维码或通用聊天图标。
const SHARE_CHANNEL_ICON_PATHS: Record<ShareChannelId, string> = {
wechat:
'M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z',
qq: 'M21.395 15.035a40 40 0 0 0-.803-2.264l-1.079-2.695c.001-.032.014-.562.014-.836C19.526 4.632 17.351 0 12 0S4.474 4.632 4.474 9.241c0 .274.013.804.014.836l-1.08 2.695a39 39 0 0 0-.802 2.264c-1.021 3.283-.69 4.643-.438 4.673.54.065 2.103-2.472 2.103-2.472 0 1.469.756 3.387 2.394 4.771-.612.188-1.363.479-1.845.835-.434.32-.379.646-.301.778.343.578 5.883.369 7.482.189 1.6.18 7.14.389 7.483-.189.078-.132.132-.458-.301-.778-.483-.356-1.233-.646-1.846-.836 1.637-1.384 2.393-3.302 2.393-4.771 0 0 1.563 2.537 2.103 2.472.251-.03.581-1.39-.438-4.673',
douyin:
'M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z',
};
const SHARE_CHANNELS = [
{
id: 'wechat',
label: '微信',
icon: MessageCircle,
className: 'bg-emerald-500 text-white',
iconClassName: 'bg-[#07c160] text-white',
},
{
id: 'qq',
label: 'QQ',
icon: MessageCircle,
className: 'bg-sky-500 text-white',
iconClassName: 'bg-[#12b7f5] text-white',
},
{
id: 'douyin',
label: '抖音',
icon: Music2,
className: 'bg-slate-950 text-white',
iconClassName: 'bg-black text-white',
},
] as const;
] as const satisfies readonly ShareChannel[];
function ShareChannelLogo({ channel }: { channel: ShareChannel }) {
const iconPath = SHARE_CHANNEL_ICON_PATHS[channel.id];
if (channel.id === 'douyin') {
return (
<svg
viewBox="-1 -1 26 26"
aria-hidden="true"
focusable="false"
className="h-6 w-6 overflow-visible"
data-share-channel-logo={channel.id}
data-testid={`share-channel-logo-${channel.id}`}
>
<path d={iconPath} fill="#25f4ee" transform="translate(-0.75 0.45)" />
<path d={iconPath} fill="#fe2c55" transform="translate(0.75 -0.45)" />
<path d={iconPath} fill="currentColor" />
</svg>
);
}
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
focusable="false"
className="h-6 w-6"
data-share-channel-logo={channel.id}
data-testid={`share-channel-logo-${channel.id}`}
>
<path d={iconPath} fill="currentColor" />
</svg>
);
}
/**
* 发布完成后的分享弹窗。
@@ -98,8 +146,6 @@ export function PublishShareModal({
footer={
<div className="grid w-full grid-cols-3 gap-3">
{SHARE_CHANNELS.map((channel) => {
const Icon = channel.icon;
return (
<button
key={channel.id}
@@ -110,9 +156,9 @@ export function PublishShareModal({
title={channel.label}
>
<span
className={`inline-flex h-11 w-11 items-center justify-center rounded-full shadow-sm ${channel.className}`}
className={`inline-flex h-11 w-11 items-center justify-center rounded-full shadow-sm ${channel.iconClassName}`}
>
<Icon className="h-5 w-5" />
<ShareChannelLogo channel={channel} />
</span>
<span>{channel.label}</span>
</button>