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=http://192.168.120.17:8000
|
||||||
|
#VITE_API_BASE=https://apiforeign.minzhong.cn
|
||||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||||
VITE_USE_MOCK=false
|
VITE_USE_MOCK=false
|
||||||
# Encryption disabled for dev — enable in production
|
# 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>
|
||||||
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -387,10 +387,11 @@ export async function getPaymentStats(params?: {
|
|||||||
return api.get(url);
|
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();
|
const qs = new URLSearchParams();
|
||||||
if (params?.method) qs.set('payment_method', params.method);
|
if (params?.method) qs.set('payment_method', params.method);
|
||||||
if (params?.status) qs.set('status', params.status);
|
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?.startDate) qs.set('start_date', params.startDate);
|
||||||
if (params?.endDate) qs.set('end_date', params.endDate);
|
if (params?.endDate) qs.set('end_date', params.endDate);
|
||||||
if (params?.page) qs.set('page', String(params.page));
|
if (params?.page) qs.set('page', String(params.page));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
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';
|
} from 'antd';
|
||||||
import zhCN from 'antd/locale/zh_CN';
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
import {
|
import {
|
||||||
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined
|
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined, SearchOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { getPaymentStats, getAdminPaymentOrders, refundPaymentOrder } from '../api';
|
import { getPaymentStats, getAdminPaymentOrders, refundPaymentOrder } from '../api';
|
||||||
import { formatDate } from '../utils/formatDate';
|
import { formatDate } from '../utils/formatDate';
|
||||||
@@ -22,6 +22,7 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
const [filters, setFilters] = useState<{
|
const [filters, setFilters] = useState<{
|
||||||
paymentMethod?: string;
|
paymentMethod?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
phone?: string;
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
}>({
|
}>({
|
||||||
@@ -36,6 +37,7 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
getPaymentStats(filters),
|
getPaymentStats(filters),
|
||||||
getAdminPaymentOrders({
|
getAdminPaymentOrders({
|
||||||
...filters,
|
...filters,
|
||||||
|
phone: filters.phone,
|
||||||
page: orderPage,
|
page: orderPage,
|
||||||
pageSize: orderPageSize,
|
pageSize: orderPageSize,
|
||||||
}),
|
}),
|
||||||
@@ -61,6 +63,7 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
startDate: dayjs().format('YYYY-MM-DD'),
|
startDate: dayjs().format('YYYY-MM-DD'),
|
||||||
endDate: dayjs().format('YYYY-MM-DD'),
|
endDate: dayjs().format('YYYY-MM-DD'),
|
||||||
});
|
});
|
||||||
|
setOrderPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefund = async (orderNo: string) => {
|
const handleRefund = async (orderNo: string) => {
|
||||||
@@ -101,6 +104,7 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
const columns = [
|
const columns = [
|
||||||
{ title: '订单号', dataIndex: 'orderNo', key: 'orderNo', width: 200 },
|
{ title: '订单号', dataIndex: 'orderNo', key: 'orderNo', width: 200 },
|
||||||
{ title: '用户', dataIndex: 'username', key: 'username', width: 120 },
|
{ 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,
|
title: '支付方式', dataIndex: 'paymentMethod', key: 'paymentMethod', width: 100,
|
||||||
render: (m: string) => {
|
render: (m: string) => {
|
||||||
@@ -237,28 +241,28 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}
|
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}
|
||||||
title={<Space><DollarOutlined />订单列表</Space>}>
|
title={<Space><DollarOutlined />订单列表</Space>}>
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<Row gutter={[16, 16]} align="middle" style={{ marginBottom: 24 }}>
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
<Col xs={24} sm={12} md={6}>
|
<Col xs={24} sm={8} md={4}>
|
||||||
<span style={{ marginRight: 8 }}>支付方式:</span>
|
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>支付方式</Typography.Text>
|
||||||
<Select
|
<Select
|
||||||
placeholder="全部"
|
placeholder="全部"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: 150 }}
|
style={{ width: '100%' }}
|
||||||
value={filters.paymentMethod}
|
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="alipay">支付宝</Option>
|
||||||
<Option value="wechat">微信</Option>
|
<Option value="wechat">微信</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={6}>
|
<Col xs={24} sm={8} md={4}>
|
||||||
<span style={{ marginRight: 8 }}>状态:</span>
|
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 4 }}>状态</Typography.Text>
|
||||||
<Select
|
<Select
|
||||||
placeholder="全部"
|
placeholder="全部"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: 150 }}
|
style={{ width: '100%' }}
|
||||||
value={filters.status}
|
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="paid">已支付</Option>
|
||||||
<Option value="pending">待支付</Option>
|
<Option value="pending">待支付</Option>
|
||||||
@@ -266,9 +270,21 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
<Option value="refunded">已退款</Option>
|
<Option value="refunded">已退款</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={8}>
|
<Col xs={24} sm={8} md={5}>
|
||||||
<span style={{ marginRight: 8 }}>日期范围:</span>
|
<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
|
<RangePicker
|
||||||
|
style={{ width: '100%' }}
|
||||||
value={[
|
value={[
|
||||||
dayjs(filters.startDate),
|
dayjs(filters.startDate),
|
||||||
dayjs(filters.endDate),
|
dayjs(filters.endDate),
|
||||||
@@ -276,9 +292,9 @@ const AdminPaymentStats: React.FC = () => {
|
|||||||
onChange={handleDateChange}
|
onChange={handleDateChange}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={4}>
|
<Col xs={24} sm={12} md={4} style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||||
<Button icon={<ReloadOutlined />} onClick={handleReset}>
|
<Button icon={<ReloadOutlined />} onClick={handleReset} style={{ marginBottom: 0 }}>
|
||||||
重置
|
重置筛选
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const statusMap: Record<string, { text: string; color: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const defaultTextWatermark: HomeMaterialTextWatermarkConfig = {
|
const defaultTextWatermark: HomeMaterialTextWatermarkConfig = {
|
||||||
text: '民众普康 AI',
|
text: '民众智创',
|
||||||
opacityLevel: 2,
|
opacityLevel: 2,
|
||||||
fontSizePx: 28,
|
fontSizePx: 28,
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const defaultConfig: HomeMaterialWatermarkConfig = {
|
|||||||
marginX: 24,
|
marginX: 24,
|
||||||
marginY: 24,
|
marginY: 24,
|
||||||
textWatermark: {
|
textWatermark: {
|
||||||
text: '民众普康 AI',
|
text: '民众智创',
|
||||||
opacityLevel: 2,
|
opacityLevel: 2,
|
||||||
fontSizePx: 28,
|
fontSizePx: 28,
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const positionOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const defaultTextWatermark = {
|
const defaultTextWatermark = {
|
||||||
text: '民众普康 AI',
|
text: '民众智创',
|
||||||
opacityLevel: 2,
|
opacityLevel: 2,
|
||||||
fontSizePx: 28,
|
fontSizePx: 28,
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
|
|||||||
@@ -836,13 +836,14 @@ async def list_payment_orders(
|
|||||||
page_size: int = Query(20, ge=1, le=500),
|
page_size: int = Query(20, ge=1, le=500),
|
||||||
payment_method: str | None = Query(None),
|
payment_method: str | None = Query(None),
|
||||||
status: str | None = Query(None),
|
status: str | None = Query(None),
|
||||||
|
phone: str | None = Query(None, description="按用户手机号模糊搜索"),
|
||||||
start_date: str | None = Query(None),
|
start_date: str | None = Query(None),
|
||||||
end_date: str | None = Query(None),
|
end_date: str | None = Query(None),
|
||||||
admin: User = Depends(get_admin_user),
|
admin: User = Depends(get_admin_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Return paginated payment orders for admin dashboard."""
|
"""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))
|
count_query = select(func.count(PaymentOrder.id))
|
||||||
|
|
||||||
filters = []
|
filters = []
|
||||||
@@ -850,6 +851,8 @@ async def list_payment_orders(
|
|||||||
filters.append(PaymentOrder.payment_method == payment_method)
|
filters.append(PaymentOrder.payment_method == payment_method)
|
||||||
if status:
|
if status:
|
||||||
filters.append(PaymentOrder.status == status)
|
filters.append(PaymentOrder.status == status)
|
||||||
|
if phone:
|
||||||
|
filters.append(User.phone.ilike(f"%{phone.strip()}%"))
|
||||||
if start_date:
|
if start_date:
|
||||||
filters.append(PaymentOrder.created_at >= datetime.fromisoformat(start_date).replace(tzinfo=CST))
|
filters.append(PaymentOrder.created_at >= datetime.fromisoformat(start_date).replace(tzinfo=CST))
|
||||||
if end_date:
|
if end_date:
|
||||||
@@ -873,6 +876,7 @@ async def list_payment_orders(
|
|||||||
"userId": o.user_id,
|
"userId": o.user_id,
|
||||||
"user_id": o.user_id,
|
"user_id": o.user_id,
|
||||||
"username": username,
|
"username": username,
|
||||||
|
"phone": user_phone,
|
||||||
"amount": round(float(o.amount), 2),
|
"amount": round(float(o.amount), 2),
|
||||||
"credits": round(float(o.credits), 2),
|
"credits": round(float(o.credits), 2),
|
||||||
"paymentMethod": o.payment_method,
|
"paymentMethod": o.payment_method,
|
||||||
@@ -885,7 +889,7 @@ async def list_payment_orders(
|
|||||||
"createdAt": o.created_at.isoformat() if o.created_at else None,
|
"createdAt": o.created_at.isoformat() if o.created_at else None,
|
||||||
"created_at": 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}
|
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=http://192.168.120.17:8000
|
||||||
|
#VITE_API_BASE=https://apiforeign.minzhong.cn
|
||||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||||
VITE_USE_MOCK=false
|
VITE_USE_MOCK=false
|
||||||
# Encryption disabled for dev — enable in production
|
# 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>
|
||||||
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-JhRVnnL-.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
} = useAppStore();
|
} = 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 currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
||||||
const maxImageCount = currentEngine?.maxImageCount ?? 4;
|
const maxImageCount = currentEngine?.maxImageCount ?? 4;
|
||||||
const maxVideoCount = currentEngine?.maxVideoCount ?? 1;
|
const maxVideoCount = currentEngine?.maxVideoCount ?? 1;
|
||||||
@@ -174,6 +175,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [enginesLoaded, setEnginesLoaded] = useState(false);
|
||||||
|
|
||||||
const [referenceMode, setReferenceMode] = useState<'universal' | 'first_last_frame'>('universal');
|
const [referenceMode, setReferenceMode] = useState<'universal' | 'first_last_frame'>('universal');
|
||||||
const [firstFrame, setFirstFrame] = useState<MediaReference | null>(null);
|
const [firstFrame, setFirstFrame] = useState<MediaReference | null>(null);
|
||||||
const [lastFrame, setLastFrame] = 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 [mentionVisible, setMentionVisible] = useState(false);
|
||||||
const mentionInputRef = useRef<any>(null);
|
const mentionInputRef = useRef<any>(null);
|
||||||
|
const isReEditingRef = useRef(false);
|
||||||
const [mentionPosition, setMentionPosition] = useState({ top: 0, left: 0 });
|
const [mentionPosition, setMentionPosition] = useState({ top: 0, left: 0 });
|
||||||
|
|
||||||
// 数字转中文数字(一、二、三、四)
|
// 数字转中文数字(一、二、三、四)
|
||||||
@@ -539,16 +543,20 @@ const AIChatPage: React.FC = () => {
|
|||||||
}, [gen_list.length, scrollToBottom]);
|
}, [gen_list.length, scrollToBottom]);
|
||||||
|
|
||||||
useEffect(() => {
|
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) {
|
if (newOptions && newOptions.length > 0) {
|
||||||
// 如果当前 countType 不在新选项中,重置为第一个选项
|
|
||||||
const isValidOption = newOptions.some((item: any) => item.id === countType);
|
const isValidOption = newOptions.some((item: any) => item.id === countType);
|
||||||
if (!isValidOption) {
|
if (!isValidOption) {
|
||||||
setCountType(newOptions[0].id);
|
setCountType(newOptions[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [mediaType, enginesele]);
|
}, [mediaType, enginesele, enginesLoaded]);
|
||||||
|
|
||||||
// 点击外部关闭弹窗
|
// 点击外部关闭弹窗
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -570,17 +578,25 @@ const AIChatPage: React.FC = () => {
|
|||||||
if (showVideoSettingsModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
if (showVideoSettingsModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||||
setShowVideoSettingsModal(false);
|
setShowVideoSettingsModal(false);
|
||||||
}
|
}
|
||||||
|
// 关闭参考模式选择弹窗
|
||||||
|
if (referenceModeDropdownVisible && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||||
|
setReferenceModeDropdownVisible(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
};
|
};
|
||||||
}, [showMediaTypeModal, showEngineModal, showImageSettingsModal, showVideoSettingsModal]);
|
}, [showMediaTypeModal, showEngineModal, showImageSettingsModal, showVideoSettingsModal, referenceModeDropdownVisible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isReEditingRef.current) {
|
||||||
|
isReEditingRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (mediaType !== 'video') 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;
|
if (!engine) return;
|
||||||
|
|
||||||
const supportsFLF = engine.supportsFirstLastFrame ?? false;
|
const supportsFLF = engine.supportsFirstLastFrame ?? false;
|
||||||
@@ -602,6 +618,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
// console.log('引擎', data);
|
// console.log('引擎', data);
|
||||||
|
|
||||||
setEnginesele(data.engine);
|
setEnginesele(data.engine);
|
||||||
|
setEnginesLoaded(true);
|
||||||
|
|
||||||
// 如果是视频模式,初始化视频参数选项
|
// 如果是视频模式,初始化视频参数选项
|
||||||
if (data.engine.video && data.engine.video.length > 0) {
|
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) => (
|
return gen_list.map((msg) => (
|
||||||
<div
|
<div
|
||||||
key={msg.id}
|
key={msg.id}
|
||||||
@@ -2183,7 +2200,61 @@ const AIChatPage: React.FC = () => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setInputValue(msg.originalPrompt || '');
|
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) {
|
if (msg.genType) {
|
||||||
|
isReEditingRef.current = true;
|
||||||
setMediaType(msg.genType);
|
setMediaType(msg.genType);
|
||||||
}
|
}
|
||||||
if (msg.mediaReferences && msg.mediaReferences.length > 0) {
|
if (msg.mediaReferences && msg.mediaReferences.length > 0) {
|
||||||
@@ -2206,12 +2277,43 @@ const AIChatPage: React.FC = () => {
|
|||||||
})));
|
})));
|
||||||
setFirstFrame(null);
|
setFirstFrame(null);
|
||||||
setLastFrame(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 {
|
} else {
|
||||||
setCurrentMedia([]);
|
setCurrentMedia([]);
|
||||||
setFirstFrame(null);
|
setFirstFrame(null);
|
||||||
setLastFrame(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('已加载到编辑区');
|
msgApi.success('已加载到编辑区');
|
||||||
}}
|
}}
|
||||||
@@ -3441,7 +3543,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: '#2f3440',
|
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>
|
</Text>
|
||||||
<CaretDownOutlined style={{ fontSize: 10, color: '#8b5cf6', marginLeft: 'auto' }} />
|
<CaretDownOutlined style={{ fontSize: 10, color: '#8b5cf6', marginLeft: 'auto' }} />
|
||||||
</button>
|
</button>
|
||||||
@@ -3480,7 +3582,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
}}>
|
}}>
|
||||||
{(mediaType === 'image' ? enginesele.image : enginesele.video)?.map((engine: any) => (
|
{(mediaType === 'image' ? enginesData.image : enginesData.video)?.map((engine: any) => (
|
||||||
<button
|
<button
|
||||||
key={engine.id}
|
key={engine.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1037,7 +1037,7 @@ const HomePage: React.FC = () => {
|
|||||||
</div>
|
</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 ? (
|
{caseAssets.length === 0 ? (
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
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']);
|
const successStatuses = new Set(['group_active', 'callback_success']);
|
||||||
|
|
||||||
@@ -9,6 +9,40 @@ const PrivatePortraitAuthorizeResult: React.FC = () => {
|
|||||||
const resultCode = params.get('resultCode') || '';
|
const resultCode = params.get('resultCode') || '';
|
||||||
const isSuccess = successStatuses.has(status) || resultCode === '10000';
|
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 (
|
return (
|
||||||
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f8fafc', padding: 20 }}>
|
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f8fafc', padding: 20 }}>
|
||||||
<Card style={{ width: '100%', maxWidth: 520, borderRadius: 18 }}>
|
<Card style={{ width: '100%', maxWidth: 520, borderRadius: 18 }}>
|
||||||
@@ -17,11 +51,11 @@ const PrivatePortraitAuthorizeResult: React.FC = () => {
|
|||||||
title={isSuccess ? '真人认证已完成' : '真人认证未完成'}
|
title={isSuccess ? '真人认证已完成' : '真人认证未完成'}
|
||||||
subTitle={isSuccess ? '请回到电脑端查看,项目组已创建成功。' : '请回到电脑端重新发起创建项目组。'}
|
subTitle={isSuccess ? '请回到电脑端查看,项目组已创建成功。' : '请回到电脑端重新发起创建项目组。'}
|
||||||
extra={[
|
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 }}>
|
<Typography.Paragraph type="secondary" style={{ textAlign: 'center', marginBottom: 0, fontSize: 13, marginTop: 12 }}>
|
||||||
当前状态:{status || '-'},结果码:{resultCode || '-'}
|
如无法自动关闭,请手动关闭页面
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Button, Drawer, Input, Modal, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd';
|
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';
|
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>
|
产品白底图 <span style={{ color: '#ef4444' }}>*</span>
|
||||||
</label>
|
</label>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 12 }}>
|
||||||
<Upload
|
<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' }}>
|
||||||
listType="picture-card"
|
{productImage ? (
|
||||||
onChange={handleProductImageChange}
|
<>
|
||||||
beforeUpload={beforeUploadProductImage}
|
<img
|
||||||
maxCount={1}
|
src={productImage}
|
||||||
accept="image/*"
|
alt="产品图"
|
||||||
style={{ width: 120, height: 120, borderRadius: 8, border: '2px dashed rgba(99, 102, 241, 0.3)', background: 'rgba(99, 102, 241, 0.02)' }}
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
>
|
/>
|
||||||
{!productImage && (
|
<button
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
|
onClick={() => {
|
||||||
<PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
|
setProductImage('');
|
||||||
<span style={{ fontSize: 12, color: '#64748b' }}>上传产品图</span>
|
setProductImageResourceId('');
|
||||||
</div>
|
}}
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user