素材云添加视频图片占位符,对接授权应用接口

This commit is contained in:
Lrd
2026-07-03 11:20:41 +08:00
parent 5a98d7c1f2
commit b1e328d92d
4 changed files with 96 additions and 37 deletions
+17 -17
View File
@@ -137,16 +137,16 @@ const AdminOauthAppList: React.FC = () => {
useEffect(() => { load(); }, []);
const columns = [
{ title: 'ID', dataIndex: 'id',
{ title: 'ID', dataIndex: 'id', width: 120,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '应用ID', dataIndex: 'appId',
{ title: '应用ID', dataIndex: 'appId', width: 120,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '应用密钥', dataIndex: 'secret',
{ title: '应用密钥', dataIndex: 'secret', width: 120,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '开户方式', dataIndex: 'openType',
{ title: '开户方式', dataIndex: 'openType', width: 90,
render: (v: number) => {
const typeMap: Record<number, string> = {
1: '千川', 2: '广告', 3: '本地推', 4: '星图', 5: '快手代理商',
@@ -155,29 +155,29 @@ const AdminOauthAppList: React.FC = () => {
return <Typography.Text>{typeMap[v] || v}</Typography.Text>;
},
},
{ title: '归属公司', dataIndex: 'company',
{ title: '归属公司', dataIndex: 'company', width: 120,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '授权次数', dataIndex: 'count',
{ title: '授权次数', dataIndex: 'maxCount', width: 90,
render: (v: number) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '状态', dataIndex: 'status',
{ title: '状态', dataIndex: 'status', width: 80,
render: (v: number) => <Tag color={v === 1 ? 'green' : 'red'}>{v === 1 ? '正常' : '禁用'}</Tag>,
},
{ title: '授权URL', dataIndex: 'authUrl',
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '创建人', dataIndex: 'createBy',
{ title: '授权URL', dataIndex: 'authUrl', width: 180,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
{ title: '创建时间', dataIndex: 'createdAt',
{ title: '创建', dataIndex: 'createBy', width: 120,
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
},
// { title: '创建时间', dataIndex: 'createdAt', width: 160,
// render: (v: string) => <Typography.Text>{formatDate(v)}</Typography.Text>,
// },
{ title: '更新时间', dataIndex: 'updatedAt', width: 160,
render: (v: string) => <Typography.Text>{formatDate(v)}</Typography.Text>,
},
{ title: '更新时间', dataIndex: 'updatedAt',
render: (v: string) => <Typography.Text>{formatDate(v)}</Typography.Text>,
},
{ title: '操作',
{ title: '操作', width: 120, fixed: 'right' as const,
render: (_: any, record: OAuthApp) => (
<Space>
<Button
@@ -226,7 +226,7 @@ const AdminOauthAppList: React.FC = () => {
showTotal: (t) => `${t} 条记录`,
onChange: (p) => { setPage(p); load(p); },
}}
scroll={{ x: 800 }}
scroll={{ x: 'max-content' }}
/>
</Card>
+9
View File
@@ -259,6 +259,15 @@ body {
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.animate-fadeInUp {
animation: fadeInUp 0.45s ease-out both;
}
+11 -11
View File
@@ -172,7 +172,7 @@ const AuthorizationPage: React.FC = () => {
key: 'id',
},
{
title: '授权账户ID',
title: '授权绑定账户ID',
dataIndex: 'accountId',
key: 'accountId',
},
@@ -210,7 +210,7 @@ const AuthorizationPage: React.FC = () => {
},
},
{
title: '授权账户用户ID',
title: '授权登录账户用户ID',
dataIndex: 'accountUserid',
key: 'accountUserid',
render: (text: string) => <span style={{ color: text ? '#1e293b' : '#94a3b8' }}>{text || '-'}</span>,
@@ -293,8 +293,16 @@ const AuthorizationPage: React.FC = () => {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 16 }}>
<div style={{ display: 'flex', gap: 12 }}>
<Input
placeholder="授权绑定账户ID"
value={searchParams.account_id}
onChange={(e) => setSearchParams(prev => ({ ...prev, account_id: e.target.value }))}
style={{ width: 180 }}
allowClear
onPressEnter={() => { setCurrentPage(1); loadOAuthList(1, pageSize); }}
/>
<Input
placeholder="账号用户ID"
placeholder="授权登录账户用户ID"
value={searchParams.account_userid}
onChange={(e) => setSearchParams(prev => ({ ...prev, account_userid: e.target.value }))}
style={{ width: 180 }}
@@ -309,14 +317,6 @@ const AuthorizationPage: React.FC = () => {
allowClear
options={openTypeOptions}
/>
<Input
placeholder="账号ID"
value={searchParams.account_id}
onChange={(e) => setSearchParams(prev => ({ ...prev, account_id: e.target.value }))}
style={{ width: 180 }}
allowClear
onPressEnter={() => { setCurrentPage(1); loadOAuthList(1, pageSize); }}
/>
<Button
type="primary"
size="medium"
+59 -9
View File
@@ -77,6 +77,7 @@ const GeneratedRecord: React.FC = () => {
const [selectedOauthItems, setSelectedOauthItems] = useState<({ value: string; label: string } | undefined)[]>([undefined]);
const [materialFileNames, setMaterialFileNames] = useState<Map<string, string>>(new Map());
const [unifiedFileName, setUnifiedFileName] = useState('');
const [mediaLoadStatus, setMediaLoadStatus] = useState<Map<string, 'loading' | 'loaded' | 'error'>>(new Map());
const updateFilenameDebounceRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
useEffect(() => {
@@ -1247,15 +1248,63 @@ const GeneratedRecord: React.FC = () => {
}}
>
{showImage ? (
<img
src={displayUrl}
alt="预览"
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
<img
src={displayUrl}
alt="预览"
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: mediaLoadStatus.get(getItemResourceId(item)) === 'loaded' ? 1 : 0,
transition: 'opacity 0.3s',
}}
onLoad={() => {
setMediaLoadStatus(prev => {
const newMap = new Map(prev);
newMap.set(getItemResourceId(item), 'loaded');
return newMap;
});
}}
onError={() => {
setMediaLoadStatus(prev => {
const newMap = new Map(prev);
newMap.set(getItemResourceId(item), 'error');
return newMap;
});
}}
/>
{mediaLoadStatus.get(getItemResourceId(item)) !== 'loaded' && (
<div style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)',
}}>
<div style={{
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: '#cbd5e1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
animation: mediaLoadStatus.get(getItemResourceId(item)) === 'loading' ? 'pulse 1.5s infinite' : 'none',
}}>
{item.mediaType === 'video' ? (
<VideoCameraOutlined style={{ color: '#64748b', fontSize: 16 }} />
) : (
<PictureOutlined style={{ color: '#64748b', fontSize: 16 }} />
)}
</div>
</div>
)}
</div>
) : (
<div style={{
width: '100%',
@@ -1377,6 +1426,7 @@ const GeneratedRecord: React.FC = () => {
</>
)}
{isSelectionMode && isSelectedItem && (
<div style={{
position: 'absolute',