收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { ArrowLeft, CheckCircle2, ImagePlus, Send, X } from 'lucide-react';
|
||||
import { ArrowLeft, CheckCircle2, Send } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import type { ProfileFeedbackEvidenceItemInput } from '../../../packages/shared/src/contracts/runtime';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
|
||||
import { PlatformUploadTile } from '../common/PlatformUploadTile';
|
||||
|
||||
const MIN_FEEDBACK_DESCRIPTION_LENGTH = 10;
|
||||
const MAX_FEEDBACK_DESCRIPTION_LENGTH = 200;
|
||||
@@ -55,7 +60,9 @@ export function PlatformFeedbackView({
|
||||
const evidenceInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [description, setDescription] = useState('');
|
||||
const [contactPhone, setContactPhone] = useState('');
|
||||
const [evidencePreviews, setEvidencePreviews] = useState<EvidencePreview[]>([]);
|
||||
const [evidencePreviews, setEvidencePreviews] = useState<EvidencePreview[]>(
|
||||
[],
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -97,7 +104,8 @@ export function PlatformFeedbackView({
|
||||
setError('反馈凭证只支持图片类型');
|
||||
return;
|
||||
}
|
||||
const remainingCount = MAX_FEEDBACK_EVIDENCE_COUNT - evidencePreviews.length;
|
||||
const remainingCount =
|
||||
MAX_FEEDBACK_EVIDENCE_COUNT - evidencePreviews.length;
|
||||
if (remainingCount <= 0 || selectedFiles.length > remainingCount) {
|
||||
setError('最多上传四张凭证');
|
||||
}
|
||||
@@ -142,7 +150,9 @@ export function PlatformFeedbackView({
|
||||
setSubmitted(false);
|
||||
})
|
||||
.catch((readError: unknown) => {
|
||||
setError(readError instanceof Error ? readError.message : '图片读取失败');
|
||||
setError(
|
||||
readError instanceof Error ? readError.message : '图片读取失败',
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -199,7 +209,9 @@ export function PlatformFeedbackView({
|
||||
.then(() => setSubmitted(true))
|
||||
.catch((submitError: unknown) => {
|
||||
setSubmitted(false);
|
||||
setError(submitError instanceof Error ? submitError.message : '提交失败');
|
||||
setError(
|
||||
submitError instanceof Error ? submitError.message : '提交失败',
|
||||
);
|
||||
})
|
||||
.finally(() => setIsSubmitting(false));
|
||||
};
|
||||
@@ -208,14 +220,16 @@ export function PlatformFeedbackView({
|
||||
<div className="platform-page-stage platform-remap-surface min-h-0 flex-1 overflow-y-auto text-[var(--platform-text-strong)]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-[30rem] flex-col px-4 pb-6 pt-4">
|
||||
<header className="flex shrink-0 items-center gap-3 pb-3">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
onClick={onBack}
|
||||
aria-label="返回我的页签"
|
||||
className="platform-button platform-button--ghost h-10 w-10 shrink-0 justify-center rounded-full p-0"
|
||||
tone="ghost"
|
||||
shape="pill"
|
||||
size="xs"
|
||||
className="h-10 w-10 shrink-0 p-0"
|
||||
>
|
||||
<ArrowLeft className="h-[1.125rem] w-[1.125rem]" />
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
<div className="min-w-0 text-base font-black text-[var(--platform-text-strong)]">
|
||||
帮助与反馈
|
||||
</div>
|
||||
@@ -226,7 +240,7 @@ export function PlatformFeedbackView({
|
||||
反馈问题
|
||||
</div>
|
||||
|
||||
<section className="platform-subpanel rounded-[1.2rem] px-4 py-4">
|
||||
<PlatformSubpanel radius="md">
|
||||
<label
|
||||
htmlFor="profile-feedback-description"
|
||||
className="block text-base font-semibold text-[var(--platform-text-strong)]"
|
||||
@@ -244,43 +258,28 @@ export function PlatformFeedbackView({
|
||||
<div className="text-right text-xs text-[var(--platform-text-soft)]">
|
||||
{descriptionLength}/{MAX_FEEDBACK_DESCRIPTION_LENGTH}
|
||||
</div>
|
||||
</section>
|
||||
</PlatformSubpanel>
|
||||
|
||||
<section className="platform-subpanel rounded-[1.2rem] px-4 py-4">
|
||||
<PlatformSubpanel radius="md">
|
||||
<div className="text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
上传凭证(提供问题截图)
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-3">
|
||||
{evidencePreviews.map((preview) => (
|
||||
<div
|
||||
<PlatformUploadPreviewCard
|
||||
key={preview.id}
|
||||
className="relative h-[5.75rem] w-[5.75rem] overflow-hidden rounded-xl border border-[var(--platform-subpanel-border)] bg-[var(--platform-input-fill)]"
|
||||
>
|
||||
<img
|
||||
src={preview.dataUrl}
|
||||
alt="反馈凭证预览"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeEvidencePreview(preview.id)}
|
||||
aria-label="移除上传凭证"
|
||||
className="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/55 text-white"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
imageSrc={preview.dataUrl}
|
||||
imageAlt="反馈凭证预览"
|
||||
removeLabel="移除上传凭证"
|
||||
onRemove={() => removeEvidencePreview(preview.id)}
|
||||
/>
|
||||
))}
|
||||
{evidencePreviews.length < MAX_FEEDBACK_EVIDENCE_COUNT ? (
|
||||
<button
|
||||
type="button"
|
||||
<PlatformUploadTile
|
||||
onClick={openEvidencePicker}
|
||||
className="flex h-[5.75rem] w-[5.75rem] flex-col items-center justify-center rounded-xl border border-dashed border-[var(--platform-subpanel-border)] bg-[var(--platform-input-fill)] text-[var(--platform-text-soft)] transition hover:border-[var(--platform-surface-hover-border)] hover:text-[var(--platform-text-strong)]"
|
||||
>
|
||||
<ImagePlus className="h-6 w-6" />
|
||||
<span className="mt-2 text-xs font-medium">上传凭证</span>
|
||||
<span className="mt-0.5 text-[11px]">(最多四张)</span>
|
||||
</button>
|
||||
label="上传凭证"
|
||||
hint="(最多四张)"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<input
|
||||
@@ -291,9 +290,9 @@ export function PlatformFeedbackView({
|
||||
className="hidden"
|
||||
onChange={(event) => addEvidenceFiles(event.target.files)}
|
||||
/>
|
||||
</section>
|
||||
</PlatformSubpanel>
|
||||
|
||||
<section className="platform-subpanel rounded-[1.2rem] px-4 py-4">
|
||||
<PlatformSubpanel radius="md">
|
||||
<label
|
||||
htmlFor="profile-feedback-phone"
|
||||
className="block text-base font-semibold text-[var(--platform-text-strong)]"
|
||||
@@ -309,34 +308,35 @@ export function PlatformFeedbackView({
|
||||
placeholder="选填,如您填写则将会同步开发者与您联系"
|
||||
className="mt-3 w-full border-0 bg-transparent text-sm leading-6 text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
|
||||
/>
|
||||
</section>
|
||||
</PlatformSubpanel>
|
||||
|
||||
{error ? (
|
||||
<div className="rounded-[1.2rem] border border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)] px-4 py-3 text-sm font-medium text-[var(--platform-button-danger-text)]">
|
||||
<PlatformStatusMessage tone="error" surface="profile">
|
||||
{error}
|
||||
</div>
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
{submitted ? (
|
||||
<div className="flex items-center gap-2 rounded-[1.2rem] border border-[var(--platform-success-border)] bg-[var(--platform-success-bg)] px-4 py-3 text-sm font-medium text-[var(--platform-success-text)]">
|
||||
<PlatformStatusMessage tone="success" surface="profile">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
反馈已提交
|
||||
</div>
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
{notice ? (
|
||||
<div className="rounded-[1.2rem] border border-[var(--platform-cool-border)] bg-[var(--platform-cool-bg)] px-4 py-3 text-sm font-medium text-[var(--platform-cool-text)]">
|
||||
<PlatformStatusMessage tone="info" surface="profile">
|
||||
{notice}
|
||||
</div>
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
fullWidth
|
||||
size="md"
|
||||
className="mt-2 h-12 text-base"
|
||||
onClick={submitFeedback}
|
||||
disabled={isSubmitting}
|
||||
className="platform-button platform-button--primary mt-2 h-12 w-full justify-center text-base disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
{isSubmitting ? '提交中' : '提交'}
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user