feat(admin): 后台新增「抖音码速达」配置面板
- 新增 Tab:/admin/platform-shops?tab=douyinMashida - 面板字段:enabled / baseUrl / uid / secret / certLink / afterVerifyLink / afterVerifyMessage / messageType / timeoutMs / handledTopics(tags) - 密钥脱敏:出参 secret 永远为空,输入框未编辑时对外 value 恒为 '', 掩码不会污染后端「空串=保留原值」语义 - 顶部展示 ready 状态,未就绪时用 Alert 列出缺哪几项; 始终展示平台侧要配的推送地址(可复制) - baseUrl 命中本地地址时提示「当前指向本地网关桩,不会请求真实码速达」 - 把 AdminPlatformShopsPage 内联的 SecretFieldControl 抽成共享组件 (函数体与原实现逐字节一致,8 处既有调用点参数不变)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { EditOutlined, UndoOutlined } from '@ant-design/icons'
|
||||
import { Button, Input, Space, Tag, Typography } from 'antd'
|
||||
import { useState } from 'react'
|
||||
|
||||
/**
|
||||
* 脱敏密钥输入控件。
|
||||
*
|
||||
* 已配置时只显示掩码 + 「修改」按钮,点开后才出现密码输入框;
|
||||
* 未编辑时对外 value 恒为 '',避免把掩码值当成新密钥回填提交。
|
||||
*/
|
||||
export function SecretFieldControl({
|
||||
label,
|
||||
field,
|
||||
masked,
|
||||
hasSecret,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
}: {
|
||||
label: string
|
||||
field: string
|
||||
masked?: string | undefined
|
||||
hasSecret?: boolean | undefined
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
placeholder?: string | undefined
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
||||
const isStaged = Boolean(value && value.trim())
|
||||
const configured = Boolean(hasSecret || (masked && masked.trim()))
|
||||
|
||||
function handleStartEdit() {
|
||||
setDraft('')
|
||||
setIsEditing(true)
|
||||
}
|
||||
|
||||
function handleApply() {
|
||||
const trimmed = draft.trim()
|
||||
if (trimmed) {
|
||||
onChange(trimmed)
|
||||
setIsEditing(false)
|
||||
setDraft('')
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setDraft('')
|
||||
setIsEditing(false)
|
||||
}
|
||||
|
||||
function handleRevert() {
|
||||
onChange('')
|
||||
setDraft('')
|
||||
setIsEditing(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
minHeight: 22,
|
||||
}}
|
||||
>
|
||||
<Typography.Text type="secondary">{label}</Typography.Text>
|
||||
{isStaged ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
style={{ padding: 0, height: 'auto', fontSize: 12 }}
|
||||
onClick={handleRevert}
|
||||
>
|
||||
撤销修改
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input.Password
|
||||
autoFocus
|
||||
autoComplete="new-password"
|
||||
data-1p-ignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
data-form-type="other"
|
||||
name={`secure_field_${field}`}
|
||||
id={`secure_field_${field}`}
|
||||
placeholder={placeholder || `输入新 ${label}(留空不修改)`}
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
onPressEnter={handleApply}
|
||||
/>
|
||||
<Button type="primary" onClick={handleApply} disabled={!draft.trim()}>
|
||||
应用
|
||||
</Button>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
</Space.Compact>
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
height: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 11px',
|
||||
background: '#fafafa',
|
||||
border: isStaged ? '1px dashed #faad14' : '1px solid #d9d9d9',
|
||||
borderRadius: 6,
|
||||
fontFamily:
|
||||
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
fontSize: 12,
|
||||
color: isStaged ? '#d46b08' : configured ? '#262626' : '#8c8c8c',
|
||||
userSelect: 'none',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
marginRight: 8,
|
||||
}}
|
||||
>
|
||||
{isStaged
|
||||
? '已输入新密钥(待保存)'
|
||||
: masked || (configured ? '••••••••••••••••' : '未配置')}
|
||||
</span>
|
||||
{isStaged ? (
|
||||
<Tag color="warning" style={{ margin: 0, flexShrink: 0 }}>
|
||||
待保存
|
||||
</Tag>
|
||||
) : configured ? (
|
||||
<Tag color="success" style={{ margin: 0, flexShrink: 0 }}>
|
||||
已配置
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag style={{ margin: 0, flexShrink: 0 }}>未配置</Tag>
|
||||
)}
|
||||
</div>
|
||||
<Button icon={<EditOutlined />} onClick={handleStartEdit} style={{ flexShrink: 0 }}>
|
||||
{configured ? '修改' : '设置'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user