Merge branch 'main' into online
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# VITE_API_BASE=http://192.168.120.17:8000
|
||||
#VITE_API_BASE=https://apiforeign.minzhong.cn
|
||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||
VITE_USE_MOCK=false
|
||||
# Encryption disabled for dev — enable in production
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-B5S0hm2T.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-tEZsB6Nw.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -387,10 +387,11 @@ export async function getPaymentStats(params?: {
|
||||
return api.get(url);
|
||||
}
|
||||
|
||||
export async function getAdminPaymentOrders(params?: { method?: string; status?: string; startDate?: string; endDate?: string; page?: number; pageSize?: number }): Promise<{ items: any[]; total: number }> {
|
||||
export async function getAdminPaymentOrders(params?: { method?: string; status?: string; phone?: string; startDate?: string; endDate?: string; page?: number; pageSize?: number }): Promise<{ items: any[]; total: number }> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.method) qs.set('payment_method', params.method);
|
||||
if (params?.status) qs.set('status', params.status);
|
||||
if (params?.phone) qs.set('phone', params.phone);
|
||||
if (params?.startDate) qs.set('start_date', params.startDate);
|
||||
if (params?.endDate) qs.set('end_date', params.endDate);
|
||||
if (params?.page) qs.set('page', String(params.page));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Card, Col, Row, Space, Table, Tag, Typography, Statistic, message, Select, DatePicker, Button, ConfigProvider, Popconfirm
|
||||
Card, Col, Input, Row, Space, Table, Tag, Typography, Statistic, message, Select, DatePicker, Button, ConfigProvider, Popconfirm
|
||||
} from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import {
|
||||
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined
|
||||
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined, SearchOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { getPaymentStats, getAdminPaymentOrders, refundPaymentOrder } from '../api';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
@@ -22,6 +22,7 @@ const AdminPaymentStats: React.FC = () => {
|
||||
const [filters, setFilters] = useState<{
|
||||
paymentMethod?: string;
|
||||
status?: string;
|
||||
phone?: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}>({
|
||||
@@ -36,6 +37,7 @@ const AdminPaymentStats: React.FC = () => {
|
||||
getPaymentStats(filters),
|
||||
getAdminPaymentOrders({
|
||||
...filters,
|
||||
phone: filters.phone,
|
||||
page: orderPage,
|
||||
pageSize: orderPageSize,
|
||||
}),
|
||||
@@ -61,6 +63,7 @@ const AdminPaymentStats: React.FC = () => {
|
||||
startDate: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
setOrderPage(1);
|
||||
};
|
||||
|
||||
const handleRefund = async (orderNo: string) => {
|
||||
@@ -101,6 +104,7 @@ const AdminPaymentStats: React.FC = () => {
|
||||
const columns = [
|
||||
{ title: '订单号', dataIndex: 'orderNo', key: 'orderNo', width: 200 },
|
||||
{ title: '用户', dataIndex: 'username', key: 'username', width: 120 },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 120, render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '支付方式', dataIndex: 'paymentMethod', key: 'paymentMethod', width: 100,
|
||||
render: (m: string) => {
|
||||
@@ -237,28 +241,28 @@ const AdminPaymentStats: React.FC = () => {
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}
|
||||
title={<Space><DollarOutlined />订单列表</Space>}>
|
||||
{/* Filters */}
|
||||
<Row gutter={[16, 16]} align="middle" style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<span style={{ marginRight: 8 }}>支付方式:</span>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={8} md={4}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>支付方式</Typography.Text>
|
||||
<Select
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
style={{ width: '100%' }}
|
||||
value={filters.paymentMethod}
|
||||
onChange={(value) => setFilters(prev => ({ ...prev, paymentMethod: value }))}
|
||||
onChange={(value) => { setFilters(prev => ({ ...prev, paymentMethod: value })); setOrderPage(1); }}
|
||||
>
|
||||
<Option value="alipay">支付宝</Option>
|
||||
<Option value="wechat">微信</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<span style={{ marginRight: 8 }}>状态:</span>
|
||||
<Col xs={24} sm={8} md={4}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>状态</Typography.Text>
|
||||
<Select
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
style={{ width: '100%' }}
|
||||
value={filters.status}
|
||||
onChange={(value) => setFilters(prev => ({ ...prev, status: value }))}
|
||||
onChange={(value) => { setFilters(prev => ({ ...prev, status: value })); setOrderPage(1); }}
|
||||
>
|
||||
<Option value="paid">已支付</Option>
|
||||
<Option value="pending">待支付</Option>
|
||||
@@ -266,9 +270,21 @@ const AdminPaymentStats: React.FC = () => {
|
||||
<Option value="refunded">已退款</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<span style={{ marginRight: 8 }}>日期范围:</span>
|
||||
<Col xs={24} sm={8} md={5}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>手机号</Typography.Text>
|
||||
<Input
|
||||
placeholder="搜索手机号"
|
||||
allowClear
|
||||
value={filters.phone}
|
||||
onChange={(e) => setFilters(prev => ({ ...prev, phone: e.target.value }))}
|
||||
onPressEnter={() => { setOrderPage(1); load(); }}
|
||||
suffix={<SearchOutlined style={{ color: '#94a3b8' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={7}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>日期范围</Typography.Text>
|
||||
<RangePicker
|
||||
style={{ width: '100%' }}
|
||||
value={[
|
||||
dayjs(filters.startDate),
|
||||
dayjs(filters.endDate),
|
||||
@@ -276,9 +292,9 @@ const AdminPaymentStats: React.FC = () => {
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={4}>
|
||||
<Button icon={<ReloadOutlined />} onClick={handleReset}>
|
||||
重置
|
||||
<Col xs={24} sm={12} md={4} style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<Button icon={<ReloadOutlined />} onClick={handleReset} style={{ marginBottom: 0 }}>
|
||||
重置筛选
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -35,7 +35,7 @@ const statusMap: Record<string, { text: string; color: string }> = {
|
||||
};
|
||||
|
||||
const defaultTextWatermark: HomeMaterialTextWatermarkConfig = {
|
||||
text: '民众普康 AI',
|
||||
text: '民众智创',
|
||||
opacityLevel: 2,
|
||||
fontSizePx: 28,
|
||||
color: '#ffffff',
|
||||
|
||||
@@ -27,7 +27,7 @@ const defaultConfig: HomeMaterialWatermarkConfig = {
|
||||
marginX: 24,
|
||||
marginY: 24,
|
||||
textWatermark: {
|
||||
text: '民众普康 AI',
|
||||
text: '民众智创',
|
||||
opacityLevel: 2,
|
||||
fontSizePx: 28,
|
||||
color: '#ffffff',
|
||||
|
||||
@@ -25,7 +25,7 @@ const positionOptions = [
|
||||
];
|
||||
|
||||
const defaultTextWatermark = {
|
||||
text: '民众普康 AI',
|
||||
text: '民众智创',
|
||||
opacityLevel: 2,
|
||||
fontSizePx: 28,
|
||||
color: '#ffffff',
|
||||
|
||||
@@ -836,13 +836,14 @@ async def list_payment_orders(
|
||||
page_size: int = Query(20, ge=1, le=500),
|
||||
payment_method: str | None = Query(None),
|
||||
status: str | None = Query(None),
|
||||
phone: str | None = Query(None, description="按用户手机号模糊搜索"),
|
||||
start_date: str | None = Query(None),
|
||||
end_date: str | None = Query(None),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Return paginated payment orders for admin dashboard."""
|
||||
query = select(PaymentOrder, User.username).join(User, PaymentOrder.user_id == User.id)
|
||||
query = select(PaymentOrder, User.username, User.phone).join(User, PaymentOrder.user_id == User.id)
|
||||
count_query = select(func.count(PaymentOrder.id))
|
||||
|
||||
filters = []
|
||||
@@ -850,6 +851,8 @@ async def list_payment_orders(
|
||||
filters.append(PaymentOrder.payment_method == payment_method)
|
||||
if status:
|
||||
filters.append(PaymentOrder.status == status)
|
||||
if phone:
|
||||
filters.append(User.phone.ilike(f"%{phone.strip()}%"))
|
||||
if start_date:
|
||||
filters.append(PaymentOrder.created_at >= datetime.fromisoformat(start_date).replace(tzinfo=CST))
|
||||
if end_date:
|
||||
@@ -873,6 +876,7 @@ async def list_payment_orders(
|
||||
"userId": o.user_id,
|
||||
"user_id": o.user_id,
|
||||
"username": username,
|
||||
"phone": user_phone,
|
||||
"amount": round(float(o.amount), 2),
|
||||
"credits": round(float(o.credits), 2),
|
||||
"paymentMethod": o.payment_method,
|
||||
@@ -885,7 +889,7 @@ async def list_payment_orders(
|
||||
"createdAt": o.created_at.isoformat() if o.created_at else None,
|
||||
"created_at": o.created_at.isoformat() if o.created_at else None,
|
||||
}
|
||||
for o, username in rows
|
||||
for o, username, user_phone in rows
|
||||
]
|
||||
|
||||
return {"items": items, "total": total, "page": page, "page_size": page_size}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
# VITE_API_BASE=http://192.168.120.17:8000
|
||||
#VITE_API_BASE=https://apiforeign.minzhong.cn
|
||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||
VITE_USE_MOCK=false
|
||||
# Encryption disabled for dev — enable in production
|
||||
|
||||
+45
-45
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-OcL9GWjl.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-a7nRC5ui.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-JhRVnnL-.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -165,7 +165,8 @@ const AIChatPage: React.FC = () => {
|
||||
} = useAppStore();
|
||||
|
||||
// 获取当前选中引擎的媒体上传限制
|
||||
const currentEngineList = mediaType === 'image' ? enginesele?.image : enginesele?.video;
|
||||
const enginesData = Array.isArray(enginesele) ? {} : enginesele;
|
||||
const currentEngineList = mediaType === 'image' ? enginesData.image : enginesData.video;
|
||||
const currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
||||
const maxImageCount = currentEngine?.maxImageCount ?? 4;
|
||||
const maxVideoCount = currentEngine?.maxVideoCount ?? 1;
|
||||
@@ -174,6 +175,8 @@ const AIChatPage: React.FC = () => {
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const [enginesLoaded, setEnginesLoaded] = useState(false);
|
||||
|
||||
const [referenceMode, setReferenceMode] = useState<'universal' | 'first_last_frame'>('universal');
|
||||
const [firstFrame, setFirstFrame] = useState<MediaReference | null>(null);
|
||||
const [lastFrame, setLastFrame] = useState<MediaReference | null>(null);
|
||||
@@ -203,6 +206,7 @@ const AIChatPage: React.FC = () => {
|
||||
// @ 提及相关状态
|
||||
const [mentionVisible, setMentionVisible] = useState(false);
|
||||
const mentionInputRef = useRef<any>(null);
|
||||
const isReEditingRef = useRef(false);
|
||||
const [mentionPosition, setMentionPosition] = useState({ top: 0, left: 0 });
|
||||
|
||||
// 数字转中文数字(一、二、三、四)
|
||||
@@ -539,16 +543,20 @@ const AIChatPage: React.FC = () => {
|
||||
}, [gen_list.length, scrollToBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
const newOptions = mediaType === 'image' ? enginesele.image : enginesele.video;
|
||||
if (isReEditingRef.current) {
|
||||
isReEditingRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (!enginesLoaded) return;
|
||||
const enginesData = Array.isArray(enginesele) ? {} : enginesele;
|
||||
const newOptions = mediaType === 'image' ? enginesData.image : enginesData.video;
|
||||
if (newOptions && newOptions.length > 0) {
|
||||
// 如果当前 countType 不在新选项中,重置为第一个选项
|
||||
const isValidOption = newOptions.some((item: any) => item.id === countType);
|
||||
if (!isValidOption) {
|
||||
setCountType(newOptions[0].id);
|
||||
}
|
||||
|
||||
}
|
||||
}, [mediaType, enginesele]);
|
||||
}, [mediaType, enginesele, enginesLoaded]);
|
||||
|
||||
// 点击外部关闭弹窗
|
||||
useEffect(() => {
|
||||
@@ -570,17 +578,25 @@ const AIChatPage: React.FC = () => {
|
||||
if (showVideoSettingsModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||
setShowVideoSettingsModal(false);
|
||||
}
|
||||
// 关闭参考模式选择弹窗
|
||||
if (referenceModeDropdownVisible && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||
setReferenceModeDropdownVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [showMediaTypeModal, showEngineModal, showImageSettingsModal, showVideoSettingsModal]);
|
||||
}, [showMediaTypeModal, showEngineModal, showImageSettingsModal, showVideoSettingsModal, referenceModeDropdownVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReEditingRef.current) {
|
||||
isReEditingRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (mediaType !== 'video') return;
|
||||
const engine = enginesele?.video?.find((e: any) => e.id === countType);
|
||||
const engine = enginesData.video?.find((e: any) => e.id === countType);
|
||||
if (!engine) return;
|
||||
|
||||
const supportsFLF = engine.supportsFirstLastFrame ?? false;
|
||||
@@ -602,6 +618,7 @@ const AIChatPage: React.FC = () => {
|
||||
// console.log('引擎', data);
|
||||
|
||||
setEnginesele(data.engine);
|
||||
setEnginesLoaded(true);
|
||||
|
||||
// 如果是视频模式,初始化视频参数选项
|
||||
if (data.engine.video && data.engine.video.length > 0) {
|
||||
@@ -2107,7 +2124,7 @@ const AIChatPage: React.FC = () => {
|
||||
)}
|
||||
{/* 遍历消息列表 */}
|
||||
{(() => {
|
||||
const msgApi = message;
|
||||
const msgApi = antdMessage;
|
||||
return gen_list.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
@@ -2183,7 +2200,61 @@ const AIChatPage: React.FC = () => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setInputValue(msg.originalPrompt || '');
|
||||
let engineFound = true;
|
||||
const engineId = msg.engineId || msg.engineSnapshot?.id || msg.engine_id;
|
||||
if (engineId && msg.genType) {
|
||||
const enginesData = Array.isArray(enginesele) ? {} : enginesele;
|
||||
const engineList = msg.genType === 'image' ? enginesData.image : enginesData.video;
|
||||
const engine = engineList?.find((e: any) => e.id === engineId);
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
if (msg.genType === 'video') {
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
if (msg.genType === 'image') {
|
||||
const supportedSizes = engine.supportedSizes || {};
|
||||
const resolutionLevels = Object.keys(supportedSizes).sort((a, b) => {
|
||||
const levelOrder = { '1K': 0, '2K': 1, '4K': 2 };
|
||||
return (levelOrder[a] || 0) - (levelOrder[b] || 0);
|
||||
});
|
||||
const primaryResolution = resolutionLevels[0] || '2K';
|
||||
const supportedResolutions = Object.keys(supportedSizes[primaryResolution] || {});
|
||||
const newRatioOptions = supportedResolutions.map((res: any) => ({
|
||||
value: res,
|
||||
label: res,
|
||||
}));
|
||||
setRatioOptions(newRatioOptions);
|
||||
setCurrentEngineSupportedSizes(supportedSizes);
|
||||
setResolutionOptions(resolutionLevels.map((level) => {
|
||||
const match = level.match(/(\d+)K/);
|
||||
const num = match ? parseInt(match[1]) : 1;
|
||||
const labels: Record<number, string> = { 1: '标清', 2: '高清', 4: '超清' };
|
||||
return {
|
||||
value: level,
|
||||
label: `${labels[num] || '高清'} ${level}`,
|
||||
};
|
||||
}));
|
||||
if (supportedSizes[primaryResolution] && supportedSizes[primaryResolution][supportedResolutions[0]]) {
|
||||
const defaultSize = supportedSizes[primaryResolution][supportedResolutions[0]];
|
||||
const [w, h] = defaultSize.split(/[×x]/);
|
||||
setWidth(Number(w));
|
||||
setHeight(Number(h));
|
||||
setSelectedRatio(supportedResolutions[0]);
|
||||
setSelectedResolution(primaryResolution);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msgApi.error('该引擎已删除,无法重新编辑');
|
||||
engineFound = false;
|
||||
}
|
||||
}
|
||||
if (!engineFound) return;
|
||||
if (msg.genType) {
|
||||
isReEditingRef.current = true;
|
||||
setMediaType(msg.genType);
|
||||
}
|
||||
if (msg.mediaReferences && msg.mediaReferences.length > 0) {
|
||||
@@ -2206,12 +2277,43 @@ const AIChatPage: React.FC = () => {
|
||||
})));
|
||||
setFirstFrame(null);
|
||||
setLastFrame(null);
|
||||
setReferenceMode('universal');
|
||||
if (msg.genType === 'video') {
|
||||
const videoEngine = enginesData.video?.find((e: any) => e.id === engineId);
|
||||
if (videoEngine) {
|
||||
const supportsUR = videoEngine.supportsUniversalReference ?? true;
|
||||
const supportsFLF = videoEngine.supportsFirstLastFrame ?? false;
|
||||
if (supportsUR) {
|
||||
setReferenceMode('universal');
|
||||
} else if (supportsFLF) {
|
||||
setReferenceMode('first_last_frame');
|
||||
} else {
|
||||
setReferenceMode('universal');
|
||||
}
|
||||
} else {
|
||||
setReferenceMode('universal');
|
||||
}
|
||||
} else {
|
||||
setReferenceMode('universal');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setCurrentMedia([]);
|
||||
setFirstFrame(null);
|
||||
setLastFrame(null);
|
||||
if (msg.genType === 'video') {
|
||||
const videoEngine = enginesele?.video?.find((e: any) => e.id === engineId);
|
||||
if (videoEngine) {
|
||||
const supportsUR = videoEngine.supportsUniversalReference ?? true;
|
||||
const supportsFLF = videoEngine.supportsFirstLastFrame ?? false;
|
||||
if (supportsUR) {
|
||||
setReferenceMode('universal');
|
||||
} else if (supportsFLF) {
|
||||
setReferenceMode('first_last_frame');
|
||||
} else {
|
||||
setReferenceMode('universal');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
msgApi.success('已加载到编辑区');
|
||||
}}
|
||||
@@ -3441,7 +3543,7 @@ const AIChatPage: React.FC = () => {
|
||||
fontWeight: 500,
|
||||
color: '#2f3440',
|
||||
}}>
|
||||
{mediaType === 'image' ? enginesele.image?.find((e: any) => e.id === countType)?.name : enginesele.video?.find((e: any) => e.id === countType)?.name || '选择引擎'}
|
||||
{mediaType === 'image' ? enginesData.image?.find((e: any) => e.id === countType)?.name : enginesData.video?.find((e: any) => e.id === countType)?.name || '选择引擎'}
|
||||
</Text>
|
||||
<CaretDownOutlined style={{ fontSize: 10, color: '#8b5cf6', marginLeft: 'auto' }} />
|
||||
</button>
|
||||
@@ -3480,7 +3582,7 @@ const AIChatPage: React.FC = () => {
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
}}>
|
||||
{(mediaType === 'image' ? enginesele.image : enginesele.video)?.map((engine: any) => (
|
||||
{(mediaType === 'image' ? enginesData.image : enginesData.video)?.map((engine: any) => (
|
||||
<button
|
||||
key={engine.id}
|
||||
onClick={() => {
|
||||
|
||||
@@ -1037,7 +1037,7 @@ const HomePage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 素材案例网格 */}
|
||||
<div className="stagger-children" style={{ display: 'flex', flexWrap: 'wrap', gap: 16, justifyContent: 'space-between', overflowX: 'auto', paddingBottom: 8 }}>
|
||||
<div className="stagger-children" style={{ display: 'flex', flexWrap: 'wrap', gap: 16, justifyContent: 'flex-start', overflowX: 'auto', paddingBottom: 8 }}>
|
||||
{caseAssets.length === 0 ? (
|
||||
<div style={{
|
||||
flex: 1,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button, Card, Result, Typography } from 'antd';
|
||||
import { Button, Card, Result, Typography, message } from 'antd';
|
||||
|
||||
const successStatuses = new Set(['group_active', 'callback_success']);
|
||||
|
||||
@@ -9,6 +9,40 @@ const PrivatePortraitAuthorizeResult: React.FC = () => {
|
||||
const resultCode = params.get('resultCode') || '';
|
||||
const isSuccess = successStatuses.has(status) || resultCode === '10000';
|
||||
|
||||
const handleClose = () => {
|
||||
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);
|
||||
const isAlipay = /AlipayClient/i.test(navigator.userAgent);
|
||||
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
|
||||
|
||||
if (isWeChat) {
|
||||
try {
|
||||
window.opener = null;
|
||||
window.open('', '_self');
|
||||
window.close();
|
||||
} catch {
|
||||
message.info('请点击右上角关闭按钮');
|
||||
}
|
||||
} else if (isAlipay) {
|
||||
try {
|
||||
window.close();
|
||||
} catch {
|
||||
message.info('请点击左上角返回');
|
||||
}
|
||||
} else if (isMobile) {
|
||||
try {
|
||||
window.history.back();
|
||||
} catch {
|
||||
window.location.href = '/';
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
window.close();
|
||||
} catch {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f8fafc', padding: 20 }}>
|
||||
<Card style={{ width: '100%', maxWidth: 520, borderRadius: 18 }}>
|
||||
@@ -17,11 +51,11 @@ const PrivatePortraitAuthorizeResult: React.FC = () => {
|
||||
title={isSuccess ? '真人认证已完成' : '真人认证未完成'}
|
||||
subTitle={isSuccess ? '请回到电脑端查看,项目组已创建成功。' : '请回到电脑端重新发起创建项目组。'}
|
||||
extra={[
|
||||
<Button key="close" type="primary" onClick={() => window.close()}>关闭页面</Button>,
|
||||
<Button key="close" type="primary" onClick={handleClose}>关闭页面</Button>,
|
||||
]}
|
||||
/>
|
||||
<Typography.Paragraph type="secondary" style={{ textAlign: 'center', marginBottom: 0 }}>
|
||||
当前状态:{status || '-'},结果码:{resultCode || '-'}
|
||||
<Typography.Paragraph type="secondary" style={{ textAlign: 'center', marginBottom: 0, fontSize: 13, marginTop: 12 }}>
|
||||
如无法自动关闭,请手动关闭页面
|
||||
</Typography.Paragraph>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, Drawer, Input, Modal, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd';
|
||||
import { ArrowLeftOutlined, PlusOutlined, XOutlined } from '@ant-design/icons';
|
||||
import { ArrowLeftOutlined, DeleteOutlined, PlusOutlined, XOutlined } from '@ant-design/icons';
|
||||
|
||||
import {uploadShotReplicateImage, createRemoveLens, deleteSegment, getShotReplicationDetail, reanalyzeSegment, retrySplit, Removelist, removeCreate, reanalyzeShotReplication, splitCustom, uploadImage } from '../api';
|
||||
|
||||
@@ -1133,21 +1133,51 @@ function RemoveInfo() {
|
||||
产品白底图 <span style={{ color: '#ef4444' }}>*</span>
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
onChange={handleProductImageChange}
|
||||
beforeUpload={beforeUploadProductImage}
|
||||
maxCount={1}
|
||||
accept="image/*"
|
||||
style={{ width: 120, height: 120, borderRadius: 8, border: '2px dashed rgba(99, 102, 241, 0.3)', background: 'rgba(99, 102, 241, 0.02)' }}
|
||||
>
|
||||
{!productImage && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
|
||||
<PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
|
||||
<span style={{ fontSize: 12, color: '#64748b' }}>上传产品图</span>
|
||||
</div>
|
||||
<div style={{ width: 120, height: 120, borderRadius: 8, border: '2px dashed rgba(99, 102, 241, 0.3)', background: 'rgba(99, 102, 241, 0.02)', position: 'relative', overflow: 'hidden' }}>
|
||||
{productImage ? (
|
||||
<>
|
||||
<img
|
||||
src={productImage}
|
||||
alt="产品图"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setProductImage('');
|
||||
setProductImageResourceId('');
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 4,
|
||||
right: 4,
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined style={{ color: '#fff', fontSize: 12 }} />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Upload
|
||||
beforeUpload={beforeUploadProductImage}
|
||||
maxCount={1}
|
||||
accept="image/*"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 6, width: '100%', height: '100%' }}>
|
||||
<PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
|
||||
<span style={{ fontSize: 12, color: '#64748b' }}>上传产品图</span>
|
||||
</div>
|
||||
</Upload>
|
||||
)}
|
||||
</Upload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user