This commit is contained in:
xingyu4j
2025-06-16 16:59:04 +08:00
parent d09b993bc8
commit 014785a1ad
71 changed files with 447 additions and 423 deletions

View File

@@ -9,7 +9,7 @@ import { confirm } from '@vben/common-ui';
import { Button, Card, Image, message } from 'ant-design-vue';
import { AiImageStatusEnum } from '#/utils/constants';
import { AiImageStatusEnum } from '#/utils';
// 消息
@@ -24,20 +24,18 @@ const emits = defineEmits(['onBtnClick', 'onMjBtnClick']);
const cardImageRef = ref<any>(); // 卡片 image ref
/** 处理点击事件 */
const handleButtonClick = async (type: string, detail: AiImageApi.ImageVO) => {
async function handleButtonClick(type: string, detail: AiImageApi.ImageVO) {
emits('onBtnClick', type, detail);
};
}
/** 处理 Midjourney 按钮点击事件 */
const handleMidjourneyBtnClick = async (
async function handleMidjourneyBtnClick(
button: AiImageApi.ImageMidjourneyButtonsVO,
) => {
) {
// 确认窗体
await confirm(`确认操作 "${button.label} ${button.emoji}" ?`);
emits('onMjBtnClick', button, props.detail);
};
// emits
}
/** 监听详情 */
const { detail } = toRefs(props);
@@ -46,7 +44,7 @@ watch(detail, async (newVal) => {
});
const loading = ref();
/** 处理加载状态 */
const handleLoading = async (status: number) => {
async function handleLoading(status: number) {
// 情况一:如果是生成中,则设置加载中的 loading
if (status === AiImageStatusEnum.IN_PROGRESS) {
loading.value = message.loading({
@@ -57,7 +55,7 @@ const handleLoading = async (status: number) => {
} else {
if (loading.value) setTimeout(loading.value, 100);
}
};
}
/** 初始化 */
onMounted(async () => {

View File

@@ -3,6 +3,8 @@ import type { AiImageApi } from '#/api/ai/image';
import { ref, toRefs, watch } from 'vue';
import { formatDate } from '@vben/utils';
import { Image } from 'ant-design-vue';
import { getImageMy } from '#/api/ai/image';
@@ -12,8 +14,7 @@ import {
StableDiffusionClipGuidancePresets,
StableDiffusionSamplers,
StableDiffusionStylePresets,
} from '#/utils/constants';
import { formatTime } from '#/utils/formatTime';
} from '#/utils';
// 图片详细信息
const props = defineProps({
@@ -25,9 +26,9 @@ const props = defineProps({
const detail = ref<AiImageApi.ImageVO>({} as AiImageApi.ImageVO);
/** 获取图片详情 */
const getImageDetail = async (id: number) => {
async function getImageDetail(id: number) {
detail.value = await getImageMy(id);
};
}
const { id } = toRefs(props);
watch(
@@ -53,10 +54,10 @@ watch(
<div class="tip text-lg font-bold">时间</div>
<div class="body mt-2 text-gray-600">
<div>
提交时间{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
提交时间{{ formatDate(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
</div>
<div>
生成时间{{ formatTime(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}
生成时间{{ formatDate(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}
</div>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { confirm, useVbenDrawer } from '@vben/common-ui';
import { downloadFileFromImageUrl } from '@vben/utils';
import { useDebounceFn } from '@vueuse/core';
import { Button, Card, message, Pagination } from 'ant-design-vue';
@@ -15,8 +16,7 @@ import {
getImagePageMy,
midjourneyAction,
} from '#/api/ai/image';
import { AiImageStatusEnum } from '#/utils/constants';
import { download } from '#/utils/download';
import { AiImageStatusEnum } from '#/utils';
import ImageCard from './ImageCard.vue';
import ImageDetail from './ImageDetail.vue';
@@ -43,18 +43,18 @@ const inProgressTimer = ref<any>(); // 生成中的 image 定时器,轮询生
const showImageDetailId = ref<number>(0); // 图片详情的图片编号
/** 处理查看绘图作品 */
const handleViewPublic = () => {
function handleViewPublic() {
router.push({
name: 'AiImageSquare',
});
};
}
/** 查看图片的详情 */
const handleDetailOpen = async () => {
async function handleDetailOpen() {
drawerApi.open();
};
}
/** 获得 image 图片列表 */
const getImageList = async () => {
async function getImageList() {
const loading = message.loading({
content: `加载中...`,
});
@@ -77,10 +77,10 @@ const getImageList = async () => {
// 关闭正在“加载中”的 Loading
loading();
}
};
}
const debounceGetImageList = useDebounceFn(getImageList, 80);
/** 轮询生成中的 image 列表 */
const refreshWatchImages = async () => {
async function refreshWatchImages() {
const imageIds = Object.keys(inProgressImageMap.value).map(Number);
if (imageIds.length === 0) {
return;
@@ -101,13 +101,13 @@ const refreshWatchImages = async () => {
}
});
inProgressImageMap.value = newWatchImages;
};
}
/** 图片的点击事件 */
const handleImageButtonClick = async (
async function handleImageButtonClick(
type: string,
imageDetail: AiImageApi.ImageVO,
) => {
) {
// 详情
if (type === 'more') {
showImageDetailId.value = imageDetail.id;
@@ -124,20 +124,23 @@ const handleImageButtonClick = async (
}
// 下载
if (type === 'download') {
await download.image({ url: imageDetail.picUrl });
await downloadFileFromImageUrl({
fileName: imageDetail.model,
source: imageDetail.picUrl,
});
return;
}
// 重新生成
if (type === 'regeneration') {
await emits('onRegeneration', imageDetail);
}
};
}
/** 处理 Midjourney 按钮点击事件 */
const handleImageMidjourneyButtonClick = async (
async function handleImageMidjourneyButtonClick(
button: AiImageApi.ImageMidjourneyButtonsVO,
imageDetail: AiImageApi.ImageVO,
) => {
) {
// 1. 构建 params 参数
const data = {
id: imageDetail.id,
@@ -147,7 +150,7 @@ const handleImageMidjourneyButtonClick = async (
await midjourneyAction(data);
// 3. 刷新列表
await getImageList();
};
}
defineExpose({ getImageList }); /** 组件挂在的时候 */
onMounted(async () => {

View File

@@ -10,11 +10,7 @@ import { confirm } from '@vben/common-ui';
import { Button, InputNumber, Select, Space, Textarea } from 'ant-design-vue';
import { drawImage } from '#/api/ai/image';
import {
AiPlatformEnum,
ImageHotWords,
OtherPlatformEnum,
} from '#/utils/constants';
import { AiPlatformEnum, ImageHotWords, OtherPlatformEnum } from '#/utils';
// 消息弹窗
@@ -39,7 +35,7 @@ const platformModels = ref<AiModelModelApi.ModelVO[]>([]); // 模型列表
const modelId = ref<number>(); // 选中的模型
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
async function handleHotWordClick(hotWord: string) {
// 情况一:取消选中
if (selectHotWord.value === hotWord) {
selectHotWord.value = '';
@@ -49,10 +45,10 @@ const handleHotWordClick = async (hotWord: string) => {
// 情况二:选中
selectHotWord.value = hotWord; // 选中
prompt.value = hotWord; // 替换提示词
};
}
/** 图片生成 */
const handleGenerateImage = async () => {
async function handleGenerateImage() {
// 二次确认
await confirm(`确认生成内容?`);
try {
@@ -76,17 +72,17 @@ const handleGenerateImage = async () => {
// 加载结束
drawIn.value = false;
}
};
}
/** 填充值 */
const settingValues = async (detail: AiImageApi.ImageVO) => {
async function settingValues(detail: AiImageApi.ImageVO) {
prompt.value = detail.prompt;
width.value = detail.width;
height.value = detail.height;
};
}
/** 平台切换 */
const handlerPlatformChange = async (platform: any) => {
async function handlerPlatformChange(platform: any) {
// 根据选择的平台筛选模型
platformModels.value = props.models.filter(
(item: AiModelModelApi.ModelVO) => item.platform === platform,
@@ -96,7 +92,7 @@ const handlerPlatformChange = async (platform: any) => {
? platformModels.value[0].id
: undefined;
// 切换平台,默认选择一个模型
};
}
/** 监听 models 变化 */
watch(

View File

@@ -2,7 +2,7 @@
<script setup lang="ts">
import type { AiImageApi } from '#/api/ai/image';
import type { AiModelModelApi } from '#/api/ai/model/model';
import type { ImageModelVO, ImageSizeVO } from '#/utils/constants';
import type { ImageModelVO, ImageSizeVO } from '#/utils';
import { ref } from 'vue';
@@ -17,7 +17,7 @@ import {
Dall3SizeList,
Dall3StyleList,
ImageHotWords,
} from '#/utils/constants';
} from '#/utils';
// 接收父组件传入的模型列表
const props = defineProps({
@@ -37,7 +37,7 @@ const selectSize = ref<string>('1024x1024'); // 选中 size
const style = ref<string>('vivid'); // style 样式
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
async function handleHotWordClick(hotWord: string) {
// 情况一:取消选中
if (selectHotWord.value === hotWord) {
selectHotWord.value = '';
@@ -47,10 +47,10 @@ const handleHotWordClick = async (hotWord: string) => {
// 情况二:选中
selectHotWord.value = hotWord;
prompt.value = hotWord;
};
}
/** 选择 model 模型 */
const handleModelClick = async (model: ImageModelVO) => {
async function handleModelClick(model: ImageModelVO) {
selectModel.value = model.key;
// 可以在这里添加模型特定的处理逻辑
// 例如,如果未来需要根据不同模型设置不同参数
@@ -73,20 +73,20 @@ const handleModelClick = async (model: ImageModelVO) => {
if (recommendedSize) {
selectSize.value = recommendedSize.key;
}
};
}
/** 选择 style 样式 */
const handleStyleClick = async (imageStyle: ImageModelVO) => {
async function handleStyleClick(imageStyle: ImageModelVO) {
style.value = imageStyle.key;
};
}
/** 选择 size 大小 */
const handleSizeClick = async (imageSize: ImageSizeVO) => {
async function handleSizeClick(imageSize: ImageSizeVO) {
selectSize.value = imageSize.key;
};
}
/** 图片生产 */
const handleGenerateImage = async () => {
async function handleGenerateImage() {
// 从 models 中查找匹配的模型
const matchedModel = props.models.find(
(item) =>
@@ -127,10 +127,10 @@ const handleGenerateImage = async () => {
// 加载结束
drawIn.value = false;
}
};
}
/** 填充值 */
const settingValues = async (detail: AiImageApi.ImageVO) => {
async function settingValues(detail: AiImageApi.ImageVO) {
prompt.value = detail.prompt;
selectModel.value = detail.model;
style.value = detail.options?.style;
@@ -138,7 +138,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
(item) => item.key === `${detail.width}x${detail.height}`,
) as ImageSizeVO;
await handleSizeClick(imageSize);
};
}
/** 暴露组件方法 */
defineExpose({ settingValues });

View File

@@ -2,7 +2,7 @@
<script setup lang="ts">
import type { AiImageApi } from '#/api/ai/image';
import type { AiModelModelApi } from '#/api/ai/model/model';
import type { ImageModelVO, ImageSizeVO } from '#/utils/constants';
import type { ImageModelVO, ImageSizeVO } from '#/utils';
import { ref } from 'vue';
@@ -26,7 +26,7 @@ import {
MidjourneySizeList,
MidjourneyVersions,
NijiVersionList,
} from '#/utils/constants';
} from '#/utils';
// 消息弹窗
@@ -51,7 +51,7 @@ const selectVersion = ref<any>('6.0'); // 选中的 version
const versionList = ref<any>(MidjourneyVersions); // version 列表
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
async function handleHotWordClick(hotWord: string) {
// 情况一:取消选中
if (selectHotWord.value === hotWord) {
selectHotWord.value = '';
@@ -61,23 +61,23 @@ const handleHotWordClick = async (hotWord: string) => {
// 情况二:选中
selectHotWord.value = hotWord; // 选中
prompt.value = hotWord; // 设置提示次
};
}
/** 点击 size 尺寸 */
const handleSizeClick = async (imageSize: ImageSizeVO) => {
async function handleSizeClick(imageSize: ImageSizeVO) {
selectSize.value = imageSize.key;
};
}
/** 点击 model 模型 */
const handleModelClick = async (model: ImageModelVO) => {
async function handleModelClick(model: ImageModelVO) {
selectModel.value = model.key;
versionList.value =
model.key === 'niji' ? NijiVersionList : MidjourneyVersions;
selectVersion.value = versionList.value[0].value;
};
}
/** 图片生成 */
const handleGenerateImage = async () => {
async function handleGenerateImage() {
// 从 models 中查找匹配的模型
const matchedModel = props.models.find(
(item) =>
@@ -115,10 +115,10 @@ const handleGenerateImage = async () => {
// 加载结束
drawIn.value = false;
}
};
}
/** 填充值 */
const settingValues = async (detail: AiImageApi.ImageVO) => {
async function settingValues(detail: AiImageApi.ImageVO) {
// 提示词
prompt.value = detail.prompt;
// image size
@@ -137,7 +137,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
).value;
// image
referImageUrl.value = detail.options.referImageUrl;
};
}
/** 暴露组件方法 */
defineExpose({ settingValues });

View File

@@ -23,10 +23,7 @@ import {
StableDiffusionClipGuidancePresets,
StableDiffusionSamplers,
StableDiffusionStylePresets,
} from '#/utils/constants';
import { hasChinese } from '#/utils/utils';
// 消息弹窗
} from '#/utils';
// 接收父组件传入的模型列表
const props = defineProps({
@@ -35,8 +32,13 @@ const props = defineProps({
default: () => [] as AiModelModelApi.ModelVO[],
},
});
const emits = defineEmits(['onDrawStart', 'onDrawComplete']);
function hasChinese(str: string) {
return /[\u4E00-\u9FA5]/.test(str);
}
// 定义属性
const drawIn = ref<boolean>(false); // 生成中
const selectHotWord = ref<string>(''); // 选中的热词
@@ -52,7 +54,7 @@ const clipGuidancePreset = ref<string>('NONE'); // 文本提示相匹配的图
const stylePreset = ref<string>('3d-model'); // 风格
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
async function handleHotWordClick(hotWord: string) {
// 情况一:取消选中
if (selectHotWord.value === hotWord) {
selectHotWord.value = '';
@@ -62,10 +64,10 @@ const handleHotWordClick = async (hotWord: string) => {
// 情况二:选中
selectHotWord.value = hotWord; // 选中
prompt.value = hotWord; // 替换提示词
};
}
/** 图片生成 */
const handleGenerateImage = async () => {
async function handleGenerateImage() {
// 从 models 中查找匹配的模型
const selectModel = 'stable-diffusion-v1-6';
const matchedModel = props.models.find(
@@ -112,10 +114,10 @@ const handleGenerateImage = async () => {
// 加载结束
drawIn.value = false;
}
};
}
/** 填充值 */
const settingValues = async (detail: AiImageApi.ImageVO) => {
async function settingValues(detail: AiImageApi.ImageVO) {
prompt.value = detail.prompt;
width.value = detail.width;
height.value = detail.height;
@@ -125,7 +127,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
sampler.value = detail.options?.sampler;
clipGuidancePreset.value = detail.options?.clipGuidancePreset;
stylePreset.value = detail.options?.stylePreset;
};
}
/** 暴露组件方法 */
defineExpose({ settingValues });

View File

@@ -9,7 +9,7 @@ import { Page } from '@vben/common-ui';
import { Segmented } from 'ant-design-vue';
import { getModelSimpleList } from '#/api/ai/model/model';
import { AiModelTypeEnum, AiPlatformEnum } from '#/utils/constants';
import { AiModelTypeEnum, AiPlatformEnum } from '#/utils';
import Common from './components/common/index.vue';
import Dall3 from './components/dall3/index.vue';

View File

@@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getSimpleUserList } from '#/api/system/user';
import { DICT_TYPE, getDictOptions } from '#/utils';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@@ -49,8 +49,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '创建时间',
component: 'RangePicker',
componentProps: {
placeholder: ['开始时间', '结束时间'],
valueFormat: 'YYYY-MM-DD HH:mm:ss',
...getRangePickerDefaultProps(),
allowClear: true,
},
},

View File

@@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteImage, getImagePage, updateImage } from '#/api/ai/image';
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
import { AiImageStatusEnum } from '#/utils/constants';
import { AiImageStatusEnum } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';

View File

@@ -9,7 +9,7 @@ import { useDebounceFn } from '@vueuse/core';
import { Input, Pagination } from 'ant-design-vue';
import { getImagePageMy } from '#/api/ai/image';
// TODO @fan加个 loading 加载中的状态
const loading = ref(true); // 列表的加载中
const list = ref<AiImageApi.ImageVO[]>([]); // 列表的数据
const total = ref(0); // 列表的总页数
@@ -21,7 +21,7 @@ const queryParams = reactive({
});
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
const data = await getImagePageMy(queryParams);
@@ -30,7 +30,7 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
const debounceGetList = useDebounceFn(getList, 80);
/** 搜索按钮操作 */
const handleQuery = () => {