Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 13 additions & 111 deletions next/web/src/App/Admin/Tickets/Ticket/components/CustomFields.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JSXElementConstructor, ReactNode, useMemo, useState } from 'react';
import { JSXElementConstructor, useMemo, useState } from 'react';
import { isEmpty } from 'lodash-es';
import Handlebars from 'handlebars';
import DOMPurify from 'dompurify';
Expand Down Expand Up @@ -74,11 +74,10 @@ export function CustomFields({
return null;
}

const displayValue = tempValues[field.id] ?? values[field.id]?.value;
const contentNode = (
<Component
options={field.options}
value={displayValue}
value={tempValues[field.id] ?? values[field.id]?.value}
files={values[field.id]?.files}
disabled={disabled}
onChange={(value) => setTempValues({ ...tempValues, [field.id]: value })}
Expand All @@ -88,41 +87,37 @@ export function CustomFields({
let previewNode;
const { previewTemplate, id } = field;
const value = values[id]?.value;
const defaultContent = renderDefaultContent(field, value, values[id]?.files);
const previewValue = getPreviewValue(field, value);
const useDefaultContent = !previewTemplate && isGameField(field) && defaultContent;
const DEFAULT_CONTENT_PLACEHOLDER = '#DEFAULT#';
if (previewTemplate) {
const showPreviewAnyway = previewTemplate.startsWith('!');
if (showPreviewAnyway || value !== undefined) {
const template = showPreviewAnyway ? previewTemplate.slice(1) : previewTemplate;
const templateWithoutDefault = template
.replace(RegExp(`^${DEFAULT_CONTENT_PLACEHOLDER}`), '')
.replace(RegExp(`${DEFAULT_CONTENT_PLACEHOLDER}$`), '');
previewNode = (
<ErrorBoundary
fallbackRender={({ error }) => (
<div className="text-red-500">Render preview template error: {error.message}</div>
)}
>
{template.startsWith(DEFAULT_CONTENT_PLACEHOLDER) && defaultContent}
{/* {template.startsWith(DEFAULT_CONTENT_PLACEHOLDER) && defaultContent} */}
<CustomFieldPreview
template={templateWithoutDefault}
value={previewValue}
template={template
.replace(RegExp(`^${DEFAULT_CONTENT_PLACEHOLDER}`), '')
.replace(RegExp(`${DEFAULT_CONTENT_PLACEHOLDER}$`), '')}
value={value}
user={user}
/>
{template.endsWith(DEFAULT_CONTENT_PLACEHOLDER) && defaultContent}
{/* {template.endsWith(DEFAULT_CONTENT_PLACEHOLDER) && defaultContent} */}
</ErrorBoundary>
);
}
}

return (
<FormField key={field.id} label={field.label}>
{previewNode || useDefaultContent ? (
{previewNode ? (
<>
{previewNode || defaultContent}
<details className="text-sm">
{previewNode}
<details className='text-sm'>
<summary>编辑</summary>
{contentNode}
</details>
Expand All @@ -143,20 +138,14 @@ export function CustomFields({
}

function InputField({ value, disabled, onChange }: CustomFieldProps<string>) {
return (
<Input
value={formatFieldValue(value)}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
/>
);
return <Input value={value} onChange={(e) => onChange(e.target.value)} disabled={disabled} />;
}

function TextareaField({ value, disabled, onChange }: CustomFieldProps<string>) {
return (
<Input.TextArea
autoSize
value={formatFieldValue(value)}
value={value}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
/>
Expand Down Expand Up @@ -203,93 +192,6 @@ function FileField({ files }: CustomFieldProps<string[]>) {
);
}

function renderDefaultContent(field: CustomField, value: any, files?: FileFieldValue[]): ReactNode {
if (field.type === 'file') {
return <FileField files={files} onChange={() => {}} />;
}
if (value === undefined) {
return null;
}
if (isGameField(field)) {
const gameId = getGameId(value);
if (gameId) {
return (
<p>
<a href={`https://www.taptap.cn/app/${gameId}`} target="_blank" rel="noreferrer">
{gameId}
</a>
</p>
);
}
}
if (field.type === 'dropdown' || field.type === 'radios') {
return <p>{field.options?.find((option) => option.value === value)?.label || value}</p>;
}
if (field.type === 'multi-select' && Array.isArray(value)) {
const labels = value.map(
(item) => field.options?.find((option) => option.value === item)?.label || item
);
return <p>{labels.join(', ')}</p>;
}
return null;
}

function isGameField(field: CustomField) {
return field.label.includes('游戏选择');
}

function getGameId(value: any): string | undefined {
if (typeof value === 'number') {
return formatGameId(value);
}
if (typeof value === 'string') {
try {
const parsedValue = JSON.parse(value);
if (
parsedValue &&
typeof parsedValue === 'object' &&
(typeof parsedValue.id === 'string' || typeof parsedValue.id === 'number')
) {
return formatGameId(parsedValue.id);
}
} catch (error) {
// ignore the error
}
return formatGameId(value);
}
if (
value &&
typeof value === 'object' &&
(typeof value.id === 'string' || typeof value.id === 'number')
) {
return formatGameId(value.id);
}
}

function formatGameId(value: string | number): string | undefined {
const id = String(value);
return /^\d+$/.test(id) ? id : undefined;
}

function getPreviewValue(field: CustomField, value: any) {
if (!isGameField(field)) {
return value;
}
return getGameId(value) ?? value;
}

function formatFieldValue(value: any) {
if (
value === undefined ||
value === null ||
typeof value === 'string' ||
typeof value === 'number'
) {
return value;
}
return JSON.stringify(value);
}

type PartialSome<T, K extends keyof T> = Omit<T, K> &
{
[key in K]?: T[K];
Expand Down
Loading