diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts
index ebd13d6..9cbc787 100644
--- a/web/src/lib/api.ts
+++ b/web/src/lib/api.ts
@@ -168,7 +168,7 @@ export const adminApi = {
getTeamDetail: (teamId: number) =>
api.get(`/admin/teams/${teamId}`),
- updateTeam: (teamId: number, data: { name?: string; monthly_seconds_limit?: number; daily_member_limit_default?: number; is_active?: boolean; expected_regions?: string; anomaly_config?: Partial }) =>
+ updateTeam: (teamId: number, data: { name?: string; monthly_seconds_limit?: number; monthly_spending_limit?: number; daily_member_limit_default?: number; is_active?: boolean; expected_regions?: string; anomaly_config?: Partial }) =>
api.put(`/admin/teams/${teamId}`, data),
topUpTeam: (teamId: number, amount: number) =>
diff --git a/web/src/pages/AdminAssetsPage.tsx b/web/src/pages/AdminAssetsPage.tsx
index 93a70b6..a6197ad 100644
--- a/web/src/pages/AdminAssetsPage.tsx
+++ b/web/src/pages/AdminAssetsPage.tsx
@@ -32,6 +32,13 @@ function VideoThumbnail({ video, onClick }: { video: AssetVideo; onClick: () =>
}
function assetVideoToTask(v: AssetVideo): GenerationTask {
+ const references = (v.reference_urls || []).map((ref, i) => ({
+ id: `ref_${v.task_id}_${i}`,
+ type: (ref.type || 'image') as 'image' | 'video',
+ previewUrl: ref.url,
+ label: ref.label || `素材${i + 1}`,
+ role: ref.role,
+ }));
return {
id: String(v.id),
taskId: v.task_id,
@@ -41,7 +48,7 @@ function assetVideoToTask(v: AssetVideo): GenerationTask {
model: 'seedance_2.0',
aspectRatio: (v.aspect_ratio as any) || '16:9',
duration: v.duration as any,
- references: [],
+ references,
status: 'completed',
progress: 100,
resultUrl: v.result_url,
diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx
index b1480a3..036a4b8 100644
--- a/web/src/pages/DashboardPage.tsx
+++ b/web/src/pages/DashboardPage.tsx
@@ -193,32 +193,7 @@ export function DashboardPage() {
))}
-
-
-
- {sortedTeams.length > 0 && (
-
- )}
-
-
-
用户消费排行(Top 10 · 本月)
-
-
-
-
-
-
- {/* Profit Section */}
+ {/* Row 2: Profit cards */}
总收入
@@ -238,62 +213,32 @@ export function DashboardPage() {
- {(stats.team_profit_ranking || []).length > 0 && (() => {
- const profitRanking = [...(stats.team_profit_ranking || [])].sort((a, b) => a.profit - b.profit);
- const profitBarOption: echarts.EChartsCoreOption = {
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- backgroundColor: 'rgba(13, 13, 26, 0.95)',
- borderColor: 'rgba(255, 255, 255, 0.10)',
- textStyle: { color: '#f1f0ff', fontSize: 12 },
- formatter: (params: unknown) => {
- const p = (params as { name: string; value: number; dataIndex: number }[])[0];
- const team = profitRanking[p.dataIndex];
- return `${p.name}
收入: ¥${team.revenue.toFixed(2)}
成本: ¥${team.base_cost.toFixed(2)}
利润: ¥${team.profit.toFixed(2)}
加价率: ${team.markup_percentage}%`;
- },
- },
- grid: { left: 80, right: 40, top: 10, bottom: 20 },
- xAxis: {
- type: 'value',
- axisLabel: { color: '#8b8ea8', fontSize: 11 },
- splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.06)' } },
- },
- yAxis: {
- type: 'category',
- data: profitRanking.map((t) => t.name),
- axisLabel: { color: '#8b8ea8', fontSize: 12 },
- axisLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.08)' } },
- },
- series: [{
- type: 'bar',
- data: profitRanking.map((t) => t.profit),
- barWidth: 16,
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
- { offset: 0, color: '#06d6a0' },
- { offset: 1, color: '#00b8e6' },
- ]),
- borderRadius: [0, 4, 4, 0],
- },
- label: {
- show: true,
- position: 'right',
- color: '#8b8ea8',
- fontSize: 11,
- formatter: (p: { value: number }) => `¥${p.value.toFixed(2)}`,
- },
- }],
- };
- return (
+ {/* Row 3: Trend chart (full width) */}
+
+
+ {/* Row 4: Team + User ranking (two columns) */}
+
+ {sortedTeams.length > 0 && (
-
团队利润排行
+
团队消费排行(本月)
-
+
- );
- })()}
+ )}
+
+
+
用户消费排行(Top 10 · 本月)
+
+
+
+
+
);
}
diff --git a/web/src/pages/TeamAssetsPage.tsx b/web/src/pages/TeamAssetsPage.tsx
index b7b3d54..4b4072e 100644
--- a/web/src/pages/TeamAssetsPage.tsx
+++ b/web/src/pages/TeamAssetsPage.tsx
@@ -32,6 +32,13 @@ function VideoThumbnail({ video, onClick }: { video: AssetVideo; onClick: () =>
}
function assetVideoToTask(v: AssetVideo): GenerationTask {
+ const references = (v.reference_urls || []).map((ref, i) => ({
+ id: `ref_${v.task_id}_${i}`,
+ type: (ref.type || 'image') as 'image' | 'video',
+ previewUrl: ref.url,
+ label: ref.label || `素材${i + 1}`,
+ role: ref.role,
+ }));
return {
id: String(v.id),
taskId: v.task_id,
@@ -41,7 +48,7 @@ function assetVideoToTask(v: AssetVideo): GenerationTask {
model: 'seedance_2.0',
aspectRatio: (v.aspect_ratio as any) || '16:9',
duration: v.duration as any,
- references: [],
+ references,
status: 'completed',
progress: 100,
resultUrl: v.result_url,
diff --git a/web/src/pages/TeamsPage.tsx b/web/src/pages/TeamsPage.tsx
index f1d3a41..2e97e5a 100644
--- a/web/src/pages/TeamsPage.tsx
+++ b/web/src/pages/TeamsPage.tsx
@@ -7,6 +7,7 @@ import { Select } from '../components/Select';
import styles from './TeamsPage.module.css';
function fmtMoney(val: number): string {
+ if (val === -1) return '不限';
return '¥' + (val || 0).toFixed(2);
}
@@ -80,6 +81,10 @@ export function TeamsPage() {
const [learnOpen, setLearnOpen] = useState(false);
const [editingRegions, setEditingRegions] = useState(false);
const [editRegionsValue, setEditRegionsValue] = useState('');
+ const [editingMonthlyLimit, setEditingMonthlyLimit] = useState(false);
+ const [editMonthlyLimitValue, setEditMonthlyLimitValue] = useState('');
+ const [editingMarkup, setEditingMarkup] = useState(false);
+ const [editMarkupValue, setEditMarkupValue] = useState('');
const [editingAnomalyConfig, setEditingAnomalyConfig] = useState(false);
const [anomalyConfigDraft, setAnomalyConfigDraft] = useState>({});
@@ -430,13 +435,65 @@ export function TeamsPage() {
累计消费
{fmtMoney(detailTeam.total_spent)}
+
+ 月消费限额
+
+ {editingMonthlyLimit ? (
+
+ setEditMonthlyLimitValue(e.target.value)}
+ style={{ width: 80, padding: '3px 6px', borderRadius: 4, border: '1px solid var(--color-border-card)', background: 'var(--color-bg-page)', color: 'var(--color-text-primary)', fontSize: 13 }}
+ />
+
+
+
+ ) : (
+ <>
+ {fmtMoney(detailTeam.monthly_spending_limit)}
+
+ >
+ )}
+
+
可用余额
{fmtMoney(detailTeam.available_balance)}
- 月消费限额
- {fmtMoney(detailTeam.monthly_spending_limit)}
+ 冻结金额
+ {fmtMoney(detailTeam.frozen_amount)}
本月消费
@@ -444,7 +501,56 @@ export function TeamsPage() {
加价率
- {(detailTeam.markup_percentage || 0)}%
+
+ {editingMarkup ? (
+
+ setEditMarkupValue(e.target.value)}
+ style={{ width: 80, padding: '3px 6px', borderRadius: 4, border: '1px solid var(--color-border-card)', background: 'var(--color-bg-page)', color: 'var(--color-text-primary)', fontSize: 13 }}
+ />
+ %
+
+
+
+ ) : (
+ <>
+ {(detailTeam.markup_percentage || 0)}%
+
+ >
+ )}
+
成员数
@@ -497,14 +603,14 @@ export function TeamsPage() {
alert(e.response?.data?.error || '保存失败');
}
}}
- style={{ fontSize: 12, padding: '4px 12px' }}
+ style={{ fontSize: 12, padding: '4px 10px', whiteSpace: 'nowrap' }}
>
保存
@@ -642,6 +748,7 @@ export function TeamsPage() {
角色 |
状态 |
日生成上限 |
+ 月限额(次) |
今日生成/消费 |
本月生成/消费 |
@@ -667,6 +774,7 @@ export function TeamsPage() {
)}
{m.daily_generation_limit === -1 ? '不限' : (m.daily_generation_limit || 0) + '次'} |
+ {m.monthly_generation_limit === -1 ? '不限' : (m.monthly_generation_limit || 0) + '次'} |
{(m.generations_today || 0) + '次 / ' + fmtMoney(m.spent_today)} |
{(m.generations_this_month || 0) + '次 / ' + fmtMoney(m.spent_this_month)} |
diff --git a/web/src/types/index.ts b/web/src/types/index.ts
index 9985733..7b2cf9d 100644
--- a/web/src/types/index.ts
+++ b/web/src/types/index.ts
@@ -370,6 +370,7 @@ export interface AssetVideo {
seconds_consumed: number;
cost_amount?: number;
aspect_ratio: string;
+ reference_urls?: { url: string; type: string; role: string; label: string }[];
created_at: string;
}