素材云添加视频图片占位符,对接授权应用接口
This commit is contained in:
@@ -137,16 +137,16 @@ const AdminOauthAppList: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => { load(); }, []);
|
useEffect(() => { load(); }, []);
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: 'ID', dataIndex: 'id',
|
{ title: 'ID', dataIndex: 'id', width: 120,
|
||||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
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>,
|
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
||||||
},
|
},
|
||||||
{ title: '应用密钥', dataIndex: 'secret',
|
{ title: '应用密钥', dataIndex: 'secret', width: 120,
|
||||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
||||||
},
|
},
|
||||||
{ title: '开户方式', dataIndex: 'openType',
|
{ title: '开户方式', dataIndex: 'openType', width: 90,
|
||||||
render: (v: number) => {
|
render: (v: number) => {
|
||||||
const typeMap: Record<number, string> = {
|
const typeMap: Record<number, string> = {
|
||||||
1: '千川', 2: '广告', 3: '本地推', 4: '星图', 5: '快手代理商',
|
1: '千川', 2: '广告', 3: '本地推', 4: '星图', 5: '快手代理商',
|
||||||
@@ -155,29 +155,29 @@ const AdminOauthAppList: React.FC = () => {
|
|||||||
return <Typography.Text>{typeMap[v] || v}</Typography.Text>;
|
return <Typography.Text>{typeMap[v] || v}</Typography.Text>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ title: '归属公司', dataIndex: 'company',
|
{ title: '归属公司', dataIndex: 'company', width: 120,
|
||||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{ title: '授权次数', dataIndex: 'count',
|
{ title: '授权次数', dataIndex: 'maxCount', width: 90,
|
||||||
render: (v: number) => <Typography.Text>{v}</Typography.Text>,
|
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>,
|
render: (v: number) => <Tag color={v === 1 ? 'green' : 'red'}>{v === 1 ? '正常' : '禁用'}</Tag>,
|
||||||
},
|
},
|
||||||
{ title: '授权URL', dataIndex: 'authUrl',
|
{ title: '授权URL', dataIndex: 'authUrl', width: 180,
|
||||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
|
||||||
},
|
|
||||||
{ title: '创建人', dataIndex: 'createBy',
|
|
||||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
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>,
|
render: (v: string) => <Typography.Text>{formatDate(v)}</Typography.Text>,
|
||||||
},
|
},
|
||||||
{ title: '更新时间', dataIndex: 'updatedAt',
|
{ title: '操作', width: 120, fixed: 'right' as const,
|
||||||
render: (v: string) => <Typography.Text>{formatDate(v)}</Typography.Text>,
|
|
||||||
},
|
|
||||||
{ title: '操作',
|
|
||||||
render: (_: any, record: OAuthApp) => (
|
render: (_: any, record: OAuthApp) => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
@@ -226,7 +226,7 @@ const AdminOauthAppList: React.FC = () => {
|
|||||||
showTotal: (t) => `共 ${t} 条记录`,
|
showTotal: (t) => `共 ${t} 条记录`,
|
||||||
onChange: (p) => { setPage(p); load(p); },
|
onChange: (p) => { setPage(p); load(p); },
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 800 }}
|
scroll={{ x: 'max-content' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -259,6 +259,15 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.animate-fadeInUp {
|
.animate-fadeInUp {
|
||||||
animation: fadeInUp 0.45s ease-out both;
|
animation: fadeInUp 0.45s ease-out both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const AuthorizationPage: React.FC = () => {
|
|||||||
key: 'id',
|
key: 'id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '授权账户ID',
|
title: '授权绑定账户ID',
|
||||||
dataIndex: 'accountId',
|
dataIndex: 'accountId',
|
||||||
key: 'accountId',
|
key: 'accountId',
|
||||||
},
|
},
|
||||||
@@ -210,7 +210,7 @@ const AuthorizationPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '授权账户用户ID',
|
title: '授权登录账户用户ID',
|
||||||
dataIndex: 'accountUserid',
|
dataIndex: 'accountUserid',
|
||||||
key: 'accountUserid',
|
key: 'accountUserid',
|
||||||
render: (text: string) => <span style={{ color: text ? '#1e293b' : '#94a3b8' }}>{text || '-'}</span>,
|
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', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<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
|
<Input
|
||||||
placeholder="账号用户ID"
|
placeholder="授权登录账户用户ID"
|
||||||
value={searchParams.account_userid}
|
value={searchParams.account_userid}
|
||||||
onChange={(e) => setSearchParams(prev => ({ ...prev, account_userid: e.target.value }))}
|
onChange={(e) => setSearchParams(prev => ({ ...prev, account_userid: e.target.value }))}
|
||||||
style={{ width: 180 }}
|
style={{ width: 180 }}
|
||||||
@@ -309,14 +317,6 @@ const AuthorizationPage: React.FC = () => {
|
|||||||
allowClear
|
allowClear
|
||||||
options={openTypeOptions}
|
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
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="medium"
|
size="medium"
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
const [selectedOauthItems, setSelectedOauthItems] = useState<({ value: string; label: string } | undefined)[]>([undefined]);
|
const [selectedOauthItems, setSelectedOauthItems] = useState<({ value: string; label: string } | undefined)[]>([undefined]);
|
||||||
const [materialFileNames, setMaterialFileNames] = useState<Map<string, string>>(new Map());
|
const [materialFileNames, setMaterialFileNames] = useState<Map<string, string>>(new Map());
|
||||||
const [unifiedFileName, setUnifiedFileName] = useState('');
|
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());
|
const updateFilenameDebounceRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1247,15 +1248,63 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showImage ? (
|
{showImage ? (
|
||||||
<img
|
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
|
||||||
src={displayUrl}
|
<img
|
||||||
alt="预览"
|
src={displayUrl}
|
||||||
style={{
|
alt="预览"
|
||||||
width: '100%',
|
style={{
|
||||||
height: '100%',
|
width: '100%',
|
||||||
objectFit: 'cover',
|
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={{
|
<div style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -1377,6 +1426,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{isSelectionMode && isSelectedItem && (
|
{isSelectionMode && isSelectedItem && (
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
Reference in New Issue
Block a user