Files
django-vue3-admin-gd/web/apps/web-antd/src/views/ai/drawing/index.vue
2025-10-31 22:16:55 +08:00

280 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { Page } from '@vben/common-ui';
import {
Button,
Card,
Col,
Form,
Input,
message,
Pagination,
Row,
Select,
Spin,
} from 'ant-design-vue';
import { DrawingModel } from '#/models/ai/drawing';
// DrawingModel
// 定义图片对象类型
const operator = new DrawingModel();
interface DrawingImage {
id: number;
status: string;
pic_url?: string;
// 其他属性
}
// 表单选项
const platforms = [
{ label: '通义千问', value: 'tongyi' },
// { label: 'DeepSeek', value: 'deepseek' },
// { label: 'OpenAI', value: 'openai' },
// { label: 'Google GenAI', value: 'google-genai' },
];
const models: Record<string, { label: string; value: string }[]> = {
tongyi: [{ label: 'wanx_v1', value: 'wanx_v1' }],
// 其他平台...
};
const sizes = [
{ label: '1024*1024', value: '1024*1024' },
{ label: '720*1280', value: '720*1280' },
{ label: '768*1152', value: '768*1152' },
{ label: '1280*720', value: '1280*720' },
];
const styles = [
{ label: '默认(由模型随机输出风格)', value: 'auto' },
{ label: '摄影', value: 'photography' },
{ label: '人像写真', value: 'portrait' },
{ label: '3D卡通', value: '3d cartoon' },
{ label: '动画', value: 'anime' },
{ label: '油画', value: 'oil painting' },
{ label: '水彩', value: 'watercolor' },
{ label: '素描', value: 'sketch' },
{ label: '中国画', value: 'chinese painting' },
{ label: '扁平插画', value: 'flat illustration' },
];
// 表单数据
const form = reactive({
prompt:
'近景镜头18岁的中国女孩古代服饰圆脸正面看着镜头民族优雅的服装商业摄影室外电影级光照半身特写精致的淡妆锐利的边缘。',
platform: 'tongyi',
model: 'wanx-v1',
size: '1024*1024',
style: 'watercolor',
});
// 图片数据与分页
const images = ref<DrawingImage[]>([]);
const loading = ref(false);
const page = ref(1);
const pageSize = ref(9);
const total = ref(0);
// 平台切换时自动切换模型
const onPlatformChange = (value: number | string) => {
form.model = models[value as string]?.[0]?.value ?? '';
};
// 提交表单调用AI画图API
async function handleDraw() {
loading.value = true;
try {
// 这里调用你的AI画图API返回图片url数组
await operator.create(form);
page.value = 1;
await fetchDrawingList(page.value, pageSize.value); // 刷新第一页图片列表
// images.value = res.data.images;
// DEMO用假数据
message.success('生成成功');
} catch {
message.error('生成失败');
} finally {
loading.value = false;
}
}
let drawingTimer: NodeJS.Timeout | null = null;
// 轮询获取图片详情
const pollDrawingDetail = async (id: number) => {
try {
const res = await operator.retrieve(id); // 改用 await 简化代码
if (res?.status === 'RUNNING') {
// 保存定时器 ID方便后续清除
drawingTimer = setTimeout(() => pollDrawingDetail(id), 5000);
} else if (
res?.status === '"SUCCEEDED"' && // 当状态为 "SUCCEEDED" 时,清除定时器
drawingTimer
) {
clearTimeout(drawingTimer);
drawingTimer = null; // 重置定时器 ID
await fetchDrawingList();
// 可在此处添加任务成功后的其他逻辑(如刷新页面、提示用户等)
}
// 可根据需要添加对其他状态的处理(如 FAILED
} catch (error) {
// 处理请求失败的情况,避免轮询异常中断
console.error('轮询失败:', error);
if (drawingTimer) {
clearTimeout(drawingTimer);
drawingTimer = null;
}
}
};
// 获取图片分页列表
async function fetchDrawingList(pageNum = 1, pageSize = 9) {
try {
const res = await operator.list({
page: pageNum,
pageSize,
});
images.value = res.items;
// images.value = res.items.map(item => item.pic_url);
total.value = res.total;
// 检查每个 item 的状态
for (const item of res.items) {
if (item.status === 'PENDING' || item.status === 'RUNNING') {
pollDrawingDetail(item.id);
}
}
return res;
} catch {
message.error('获取图片列表失败');
return null;
}
}
// 页面加载时调用获取图片列表
onMounted(() => {
fetchDrawingList();
});
</script>
<template>
<Page auto-content-height>
<Row :gutter="24">
<!-- 左侧表单 -->
<Col :span="8">
<Card title="AI画图" bordered>
<Form layout="vertical" @submit.prevent="handleDraw">
<Form.Item label="画画描述">
<Input.TextArea
v-model:value="form.prompt"
:auto-size="true"
placeholder="请输入画面描述"
/>
</Form.Item>
<Form.Item label="平台选择">
<Select v-model:value="form.platform" @change="onPlatformChange">
<Select.Option
v-for="item in platforms"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="模型选择">
<Select v-model:value="form.model">
<Select.Option
v-for="item in models[form.platform]"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="图片尺寸">
<Select v-model:value="form.size">
<Select.Option
v-for="item in sizes"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="图像风格">
<Select v-model:value="form.style">
<Select.Option
v-for="item in styles"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" html-type="submit" :loading="loading">
生成图片
</Button>
</Form.Item>
</Form>
</Card>
</Col>
<!-- 右侧图片展示 -->
<Col :span="16">
<Card title="生成结果" bordered>
<Row :gutter="16">
<Col
v-for="(img, idx) in images"
:key="idx"
:span="8"
style="margin-bottom: 16px"
>
<Card hoverable>
<template #cover>
<div
v-if="img.status === 'PENDING' || img.status === 'RUNNING'"
style="
width: 100%;
height: 180px;
display: flex;
align-items: center;
justify-content: center;
"
>
<Spin size="large" />
</div>
<img
v-else
:src="img.pic_url"
style="width: 100%; height: 180px; object-fit: cover"
/>
</template>
</Card>
</Col>
</Row>
<Pagination
v-model:current="page"
:total="total"
:page-size="pageSize"
style="margin-top: 16px; text-align: right"
@change="
(p, ps) => {
page = p;
pageSize = ps;
fetchDrawingList(p, ps);
}
"
/>
</Card>
</Col>
</Row>
</Page>
</template>
<style scoped>
/* 可根据需要自定义样式 */
</style>