ai-ground-quasar/src/pages/create/index.vue
2024-05-23 10:20:16 +08:00

1330 lines
35 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.

<template>
<q-page>
<!-- <article></article>
<div class="gallery-track">
<div class="card">
<div class="card-image-wrapper">
<img src="https://source.unsplash.com/kL3u4Tqfn1s" />
</div>
</div>
<div class="card">
<div class="card-image-wrapper">
<img src="https://source.unsplash.com/yVUQlyRlJSw" />
</div>
</div>
</div> -->
<div class="silder">
<div class="menu-box">
<div class="menu-bg"></div>
<div class="menu-button" @click="setActiveButton(1)">
<div class="no-act" v-show="activeButton === 2">
<img src="../../assets/image/ai/ttp.png" />
<div>AI生图</div>
</div>
<div class="act" v-show="activeButton === 1">
<img src="../../assets/image/ai/ttpact.png" />
<div>AI生图</div>
</div>
</div>
<div class="menu-button" @click="setActiveButton(2)">
<div class="no-act" v-show="activeButton === 1">
<img src="../../assets/image/ai/ptp.png" />
<div>生图PLUS</div>
</div>
<div class="act" v-show="activeButton === 2">
<img src="../../assets/image/ai/ptpact.png" />
<div>生图PLUS</div>
</div>
</div>
</div>
</div>
<div style="display: flex; width: 100%">
<div class="setting-content">
<q-scroll-area style="height: 87%">
<div class="setting">
<div class="prompt-title">
<div style="display: flex; align-items: center">
<img class="cirl" src="../../assets/image/ai/cirl.png" />
<span
style="
color: #fff;
font-weight: bold;
margin-left: 10px;
font-size: 16px;
"
>描述内容</span
>
</div>
<n-popover trigger="hover" content-class="popover">
<template #trigger>
<img
@click="beautify"
src="../../assets/image/ai/run.png"
class="cursor-pointer runse"
/>
</template>
<span>润色</span>
</n-popover>
</div>
<div class="card ms-content">
<n-spin :show="beautifyLoading">
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 6V3"></path>
<path d="M16.25 7.75L18.4 5.6"></path>
<path d="M18 12h3"></path>
<path d="M16.25 16.25l2.15 2.15"></path>
<path d="M12 18v3"></path>
<path d="M7.75 16.25L5.6 18.4"></path>
<path d="M6 12H3"></path>
<path d="M7.75 7.75L5.6 5.6"></path>
</g>
</svg>
</template>
<n-input
v-model:value="prompt"
type="textarea"
class="content-input"
:maxlength="200"
show-count
placeholder="请输入一段话或短语、词汇,描述你的想法,用逗号隔开。可以是中文或英文"
/>
</n-spin>
</div>
<div
ref="fadeOut1"
class="prompt-title animate__animated animate__fadeInLeft animate__fast"
v-if="activeButton === 2"
>
<div style="display: flex; align-items: center">
<img class="cirl" src="../../assets/image/ai/cirl.png" />
<span
style="
color: #fff;
font-weight: bold;
margin-left: 10px;
font-size: 16px;
"
>反向词</span
>
</div>
</div>
<div
ref="fadeOut2"
v-if="activeButton === 2"
class="ms-content animate__animated animate__fadeInLeft animate__fast"
style="border: 1px solid #7e7e7e; height: 100px"
>
<n-input
v-model:value="negative_prompt"
type="textarea"
class="content-input"
:bordered="false"
:maxlength="50"
show-count
placeholder="输入你画面中不想要的内容,中文或英文的短语、词汇"
/>
</div>
<div
ref="fadeOut3"
class="prompt-title animate__animated animate__fadeInLeft animate__fast"
v-if="activeButton === 2"
>
<div style="display: flex; align-items: center">
<img class="cirl" src="../../assets/image/ai/cirl.png" />
<span
style="
color: #fff;
font-weight: bold;
margin-left: 10px;
font-size: 16px;
"
>参考图</span
>
</div>
</div>
<div
ref="fadeOut4"
v-if="activeButton === 2"
class="animate__animated animate__fadeInLeft animate__fast"
>
<n-upload
list-type="image-card"
:max="1"
:on-finish="handleChange"
action="http://192.168.1.244:8085/api/ai/upload-file"
>
<n-upload-dragger>
<div style="margin-bottom: 12px">
<img src="../../assets/image/ai/plus.png" />
</div>
<div
style="font-size: 16px; color: #7e7e7e; font-weight: bold"
>
拖拽图片至此或点击上传
</div>
</n-upload-dragger>
</n-upload>
</div>
<div class="prompt-title" style="margin-top: 10px">
<div style="display: flex; align-items: center">
<img class="cirl" src="../../assets/image/ai/cirl.png" />
<span
style="
color: #fff;
font-weight: bold;
margin-left: 10px;
font-size: 16px;
"
>画风类型</span
>
</div>
</div>
<div class="model-box">
<div class="row animate__animated animate__bounceIn">
<div
class="style-box card cursor-pointer"
v-for="(n, index) in modelArr"
:key="`lg-${n}`"
:class="n.nowSelect ? 'border-change' : 'border-default'"
@click="chooseModel(n)"
:style="{
marginLeft: index === 0 || index === 4 ? '0' : '32px',
marginBottom: '25px',
}"
>
<img style="width: 100%; height: 100%" :src="n.replaceImg" />
</div>
<div class="all-box cursor-pointer" @click="openAll">
全部模型
</div>
</div>
</div>
<div class="prompt-title">
<div style="display: flex; align-items: center">
<img class="cirl" src="../../assets/image/ai/cirl.png" />
<span
style="
color: #fff;
font-weight: bold;
margin-left: 10px;
font-size: 16px;
"
>图片尺寸</span
>
</div>
</div>
<div class="model-box">
<div class="row">
<div
class="size-box card cursor-pointer"
v-for="(n, index) in sizeArr"
:key="`lg-${n}`"
:class="n.nowSelect ? 'border-change' : 'border-default'"
@click="chooseSize(n)"
:style="{
marginLeft: index === 0 || index === 3 ? '0' : '18px',
marginBottom: '10px',
}"
>
<div
style="
display: flex;
align-items: center;
justify-content: center;
background: #212121;
width: 128px;
height: 65px;
margin-top: 5px;
"
>
<div style="position: relative">
<span
style="
position: absolute;
top: 45%;
left: 38%;
transform: translate(-50%, -50%);
font-size: 10px;
"
>{{ n.proportion }}</span
>
<img class="left-img" :src="n.img" />
</div>
<div>
<div>{{ n.word }}</div>
<div>{{ n.sizeWord }}</div>
</div>
</div>
</div>
</div>
<div class="wh-box">
<n-input-number
v-model:value="width"
:bordered="false"
placeholder="请输入数值"
></n-input-number>
px
<q-separator
vertical
style="
background: #383838;
margin-left: 16px;
margin-right: 40px;
"
/>
<n-input-number
v-model:value="height"
:bordered="false"
placeholder="请输入数值"
></n-input-number>
px
</div>
</div>
</div>
</q-scroll-area>
<div class="bottom-box">
<div class="num-box">
<n-select
class="num-select"
v-model:value="batch_size"
:options="numOptions"
placeholder="请选择"
/>
<div>图片数量</div>
</div>
<div class="btn-box">
<n-spin :show="createLoading">
<template #icon> </template>
<n-button @click="generate" class="create" quaternary>
<img
style="width: 24px; height: 28px; margin-right: 10px"
src="../../assets/image/ai/star.png"
/>
<div style="display: grid" v-show="!createLoading">
<div
style="
color: #fff;
font-weight: bolder;
font-size: 17px;
margin-bottom: 5px;
"
>
立即生成
</div>
<div style="color: #fff; font-size: 12px">
消耗{{ batch_size }}积分
</div>
</div>
<div
v-show="createLoading"
style="
color: #fff;
font-weight: bolder;
font-size: 17px;
margin-bottom: 5px;
"
>
正在生成中
</div>
</n-button>
</n-spin>
<div>当前剩余积分:{{ coin }}</div>
</div>
</div>
</div>
<div class="img-content">
<div class="plane" v-if="noImgData">
<img
ref="plane"
class="animate__animated animate__fadeInBottomLeft"
src="../../assets/image/ai/plane.png"
/>
<div ref="planeWord" class="word animate__animated animate__zoomIn">
快开启您的AI之旅吧
</div>
</div>
<div class="created-img-box" v-else>
<q-scroll-area style="height: 820px" ref="scrollAreaRef">
<div
class="img-list-page"
v-for="(n, index) in historyList"
:key="index"
>
<div
class="animate__animated animate__slideInUp"
style="display: flex; align-items: center"
>
<span style="color: #7e7e7e; margin-right: 10px">{{
n.dateTime
}}</span
><q-separator style="width: 86%; background-color: #7e7e7e" />
</div>
<div
class="animate__animated animate__slideInUp"
style="color: #bebebe"
>
{{ n.prompt }}
</div>
<div class="img-list-box">
<div
v-for="(item, index2) in n.imgList"
:key="index2"
class="img-detail-box animate__animated animate__slideInUp"
>
<n-spin :show="item.length < 1">
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 6V3"></path>
<path d="M16.25 7.75L18.4 5.6"></path>
<path d="M18 12h3"></path>
<path d="M16.25 16.25l2.15 2.15"></path>
<path d="M12 18v3"></path>
<path d="M7.75 16.25L5.6 18.4"></path>
<path d="M6 12H3"></path>
<path d="M7.75 7.75L5.6 5.6"></path>
</g>
</svg>
</template>
<template #description>
<span style="color: #bebebe">正在生成中,请稍等</span>
</template>
<q-parallax
@click="checkDetail(item)"
v-show="item.length"
:src="item"
>
</q-parallax>
</n-spin>
</div>
</div>
</div>
</q-scroll-area>
</div>
</div>
</div>
<image-detail
:show="showModal"
:detailSrc="detailSrc"
@close-dialog="closeModal"
/>
<mode-list
:show="showListModal"
@handCloseDialog="closeListModal"
@selectModal="selectedModel"
/>
</q-page>
</template>
<script setup>
import "animate.css";
import { ref, onMounted, onBeforeMount, computed, reactive, watch } from "vue";
import { ModelApi,UserApi } from "src/api";
import { processError, processSuccess } from "../../utils/message";
import moment from "moment";
import { LocalStorage, SessionStorage } from "quasar";
import imageDetail from "./components/imageDetail.vue";
import modeList from "./components/modeList.vue";
import { useRoute, useRouter } from "vue-router";
import img34 from "../../assets/image/ai/34.png";
import img43 from "../../assets/image/ai/43.png";
import img916 from "../../assets/image/ai/169.png";
const router = useRouter();
const scrollAreaRef = ref(null);
const plane = ref(null);
const planeWord = ref(null);
const activeButton = ref(1);
const createLoading = ref(false);
const beautifyLoading = ref(false);
const noImgData = ref(true);
const prompt = ref("");
const negative_prompt = ref("");
const apiprompt = ref("");
const init_images = ref([]);
const seletedModel = ref({});
const fadeOut1 = ref(null);
const fadeOut2 = ref(null);
const fadeOut3 = ref(null);
const fadeOut4 = ref(null);
const showModal = ref(false);
const detailSrc = ref("");
const coin = ref(0);
const showListModal = ref(false);
const selectedSize = ref({
nowSelect: true,
img: img34,
word: "社交媒体",
sizeWord: "600*800",
proportion: "3:4",
width: 600,
height: 800,
});
const width = ref(0);
const height = ref(0);
const apiWidth = ref(0);
const apiHeight = ref(0);
const imgList = ref([]);
const historyList = ref([]);
//每次张数
const batch_size = ref(1);
const numOptions = ref([
{
label: "1",
value: 1,
},
{
label: "2",
value: 2,
},
{
label: "3",
value: 3,
},
{
label: "4",
value: 4,
},
{
label: "5",
value: 5,
},
{
label: "6",
value: 6,
},
{
label: "7",
value: 7,
},
{
label: "8",
value: 8,
},
{
label: "9",
value: 9,
},
{
label: "10",
value: 10,
},
]);
const modelArr = ref([]);
const sizeArr = ref([
{
nowSelect: true,
img: img34,
word: "社交媒体",
sizeWord: "600*800",
proportion: "3:4",
width: 600,
height: 800,
},
{
nowSelect: false,
img: img43,
word: "文章配图",
sizeWord: "800*600",
proportion: "4:3",
width: 800,
height: 600,
},
{
nowSelect: false,
img: img916,
word: "海报",
sizeWord: "468*832",
proportion: "9:16",
width: 468,
height: 832,
},
{
nowSelect: false,
img: img34,
word: "电脑桌面",
sizeWord: "832*468",
proportion: "16:9",
width: 832,
height: 468,
},
{
nowSelect: false,
img: img34,
word: "正方形",
sizeWord: "512*512",
proportion: "1:1",
width: 512,
height: 512,
},
]);
// 打开详情弹窗
const checkDetail = (item) => {
showModal.value = true;
detailSrc.value = item;
};
// 关闭详情弹窗
const closeModal = () => {
showModal.value = false;
};
// 打开模型列表
const openAll = () => {
showListModal.value = true;
};
// 关闭模型列表
const closeListModal = () => {
showListModal.value = false;
};
// 获取用户信息
const getUserInfo = async() => {
try {
await UserApi.getUserInfo().then((res) => {
if (res.status === 0) {
coin.value = res.data.coin;
} else {
processError(res.msg);
}
});
} catch (error) {
console.error(error);
}
};
// 模型列表中选择模型
const selectedModel = (model) => {
console.log(model,111);
seletedModel.value = model;
// 将所有模型的nowSelect设置为false
modelArr.value.forEach((item) => {
item.nowSelect = false;
});
// 判断列表中选择的模型是否在初始化的七个模型中
const isExist = modelArr.value.some((item) => item.hash === model.hash);
// 如果存在,则将选中的模型放到第一个
if (isExist) {
modelArr.value.forEach((item) => {
if (item.hash === model.hash) {
item.nowSelect = true;
} else {
item.nowSelect = false;
}
});
} else {
// 如果不存在,则将选中的模型放到第一个,并将其他模型放到后面
modelArr.value = [model, ...modelArr.value.slice(0, 6)];
modelArr.value.forEach((item, index) => {
item.nowSelect = index === 0;
});
}
};
// 上传完毕
const handleChange = (file, List) => {
showModal.value = true;
let resData = JSON.parse(file.event.currentTarget.response);
if (resData.status === 0) {
processSuccess("上传成功");
init_images.value = [resData.data.ori_url];
} else {
processError(resData.msg);
}
};
// 获取模型列表
const getModelList = async () => {
try {
await ModelApi.getModelList({
page: 1,
pageSize: 10,
}).then((res) => {
if (res.status === 0) {
modelArr.value = res.data.list.slice(0, 7).map((item, index) => ({
...item,
nowSelect: index === 0,
}));
seletedModel.value = modelArr.value[0];
} else {
processError(res.msg);
}
});
} catch (error) {
console.error(error);
}
};
// 美化
const beautify = async () => {
beautifyLoading.value = true;
try {
await ModelApi.beautify({
txt: prompt.value,
}).then((res) => {
if (res.status === 0) {
beautifyLoading.value = false;
processSuccess("美化成功");
prompt.value = res.data.content;
} else {
processError(res.msg);
beautifyLoading.value = false;
}
});
} catch (error) {
beautifyLoading.value = false;
console.error(error);
}
};
// 生成
const generate = async () => {
// 如果没登录,跳转登录
const token = LocalStorage.getItem("sd-token");
if (!token) {
router.push("/login");
return;
}
if (!prompt.value) {
createLoading.value = false;
processError("请输入描述内容");
return;
}
if (JSON.stringify(selectedSize.value) !== "{}") {
apiWidth.value = selectedSize.value.width;
apiHeight.value = selectedSize.value.height;
} else {
apiHeight.value = height.value;
apiWidth.value = width.value;
}
if (!apiWidth.value || !apiHeight.value) {
createLoading.value = false;
processError("请输入宽高");
return;
}
createLoading.value = true;
apiprompt.value = await getTranslatePrompt();
if (!apiprompt.value) {
processError("描述内容不合法");
createLoading.value = false;
return;
}
if (activeButton.value === 1) {
txt2img();
} else {
// 如果没有上传图片
if (!init_images.value.length) {
createLoading.value = false;
processError("请上传参考图");
return;
}
img2img();
}
if (plane.value) {
plane.value.classList.add("animate__fadeOutTopRight");
planeWord.value.classList.add("animate__zoomOut");
}
// 放入和生成数量相同的空字符串
historyList.value.unshift({
imgList: new Array(batch_size.value).fill(""),
prompt: prompt.value,
dateTime: moment().format("YYYY年MM月DD日 HH:mm:ss"),
});
// 滚动到顶部
if (scrollAreaRef.value) {
scrollAreaRef.value.setScrollPosition("vertical", 0, 300);
}
setTimeout(() => {
noImgData.value = false;
}, 1000);
};
// 翻译描述内容
const getTranslatePrompt = async () => {
return new Promise((resolve, reject) => {
const data = {
txt: prompt.value,
};
ModelApi.translate(data).then((res) => {
if (res.status === 0) {
resolve(res.data.content);
} else {
processError(res.msg);
createLoading.value = false;
reject();
}
});
});
};
// 文生图
const txt2img = async () => {
try {
let data = {
prompt: apiprompt.value,
real_prompt: prompt.value,
sd_model_hash: seletedModel.value.hash,
width: apiWidth.value,
height: apiHeight.value,
batch_size: batch_size.value, //每次张数
n_iter: 1, //生成批次
steps: 30, //生成步数
override_settings_restore_afterwards: true,
override_settings: {
sd_model_checkpoint: seletedModel.value.title,
},
};
await ModelApi.txt2img(data).then((res) => {
if (res.status === 0) {
createLoading.value = false;
historyList.value[0].imgList = res.data.images;
getUserInfo();
} else {
processError(res.msg);
createLoading.value = false;
historyList.value[0].imgList = new Array(batch_size.value).fill(
"https://cdns.fontree.cn/fonchain-main/prod/image/default/ai/wrong.png"
);
}
});
} catch (error) {
createLoading.value = false;
console.error(error);
}
};
// 图生图
const img2img = async () => {
try {
let data = {
prompt: apiprompt.value,
real_prompt: prompt.value,
init_images: init_images.value,
sd_model_hash: seletedModel.value.hash,
real_negative_prompt: negative_prompt.value,
negative_prompt: negative_prompt.value,
width: apiWidth.value,
height: apiHeight.value,
batch_size: batch_size.value, //每次张数
n_iter: 1, //生成批次
steps: 30, //生成步数
override_settings_restore_afterwards: true,
override_settings: {
sd_model_checkpoint: seletedModel.value.title,
},
};
await ModelApi.img2img(data).then((res) => {
if (res.status === 0) {
createLoading.value = false;
historyList.value[0].imgList = res.data.images;
getUserInfo();
} else {
processError(res.msg);
createLoading.value = false;
historyList.value[0].imgList = new Array(batch_size.value).fill(
"https://cdns.fontree.cn/fonchain-main/prod/image/default/ai/wrong.png"
);
}
});
} catch (error) {
createLoading.value = false;
console.error(error);
}
};
// 切换模式
const setActiveButton = (buttonNumber) => {
if (buttonNumber === 1) {
// 添加fade动画
fadeOut1.value.classList.add("animate__fadeOutLeft");
fadeOut2.value.classList.add("animate__fadeOutLeft");
fadeOut3.value.classList.add("animate__fadeOutLeft");
fadeOut4.value.classList.add("animate__fadeOutLeft");
init_images.value = [];
negative_prompt.value = "";
}
let timer = buttonNumber === 1 ? 500 : 0;
setTimeout(() => {
activeButton.value = buttonNumber;
}, timer);
// 背景menu-bg跟随点击的按钮移动
const menuBg = document.querySelector(".menu-bg");
const menuButton = document.querySelectorAll(".menu-button");
const button = menuButton[buttonNumber - 1];
const { top, left, width, height } = button.getBoundingClientRect();
menuBg.style.top = `${top - height + 10}px`;
menuBg.style.left = `${left}px`;
};
const chooseModel = (model) => {
// 如果点击的是已经选中的模型,不做任何操作
if (model.nowSelect) {
return;
}
seletedModel.value = model;
model.nowSelect = !model.nowSelect;
modelArr.value.forEach((item) => {
if (item !== model) {
item.nowSelect = false;
}
});
console.log(seletedModel.value);
};
const chooseSize = (size) => {
// 如果点击的是已经选中的尺寸,不做任何操作
if (size.nowSelect) {
return;
}
selectedSize.value = size;
size.nowSelect = !size.nowSelect;
sizeArr.value.forEach((item) => {
if (item !== size) {
item.nowSelect = false;
}
});
};
// 监听with和height的值如果都有值取消图片和尺寸的选中状态
watch([width, height], ([newWidth, newHeight]) => {
if (newWidth && newHeight) {
selectedSize.value = {};
sizeArr.value.forEach((item) => {
item.nowSelect = false;
});
}
});
onBeforeMount(() => {
getModelList();
getUserInfo();
});
</script>
<style scoped lang="scss">
.q-page {
background: #19191a;
display: flex;
height: 100%;
width: 100%;
}
.q-gutter-sm > * {
margin-left: 15px;
}
.silder {
width: 110px;
background: #212121;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
.menu-box {
position: relative;
display: flex;
flex-direction: column;
margin-top: 30px;
align-items: center;
width: 100%;
height: 100%;
.menu-button {
z-index: 20;
height: 90px;
width: 90px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: transparent;
padding: 20px 10px 20px 10px;
margin-bottom: 20px;
cursor: pointer;
transition: background-color 0.5s ease;
.act img {
width: 55px;
height: auto;
}
.no-act img {
width: 40px;
height: 40px;
margin-bottom: 10px;
}
.act {
color: #fff;
font-weight: bold;
text-shadow: 0px 3px 4px #de53ab;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.no-act {
color: #fff;
font-weight: bold;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.menu-bg {
position: absolute;
padding: 20px 10px 20px 10px;
margin-bottom: 20px;
background: linear-gradient(to right, #62d1ef, #5f33f3, #dc4cac);
z-index: 10;
height: 90px;
width: 90px;
border-radius: 10px;
transition: top 0.5s ease, left 0.5s ease;
top: 0;
left: 10px;
}
}
}
.img-content {
height: 100%;
padding: 15px;
background: #19191a;
flex: 1;
background-image: url("../../assets/image/ai/img-bg.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.plane {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.word {
color: #fff;
font-size: 20px;
font-weight: bold;
margin-top: 20px;
}
}
.created-img-box {
.img-page {
display: flex;
justify-content: space-between;
align-items: center;
span {
color: #7e7e7e;
font-size: 14px;
}
}
.img-list-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 20px;
margin-bottom: 20px;
.img-detail-box {
width: 400px;
height: 400px;
background: #212121;
border-radius: 10px;
overflow: hidden;
cursor: pointer;
:deep(.n-spin-content) {
height: 100%;
&:hover {
transform: scale(1.1);
transition: transform 0.5s ease-in-out;
}
&:not(:hover) {
transform: scale(1);
transition: transform 0.5s ease-in-out;
}
}
}
}
}
}
.setting-content {
width: 490px;
padding: 15px;
.setting {
width: 100%;
margin-bottom: 14px;
height: 87%;
background: #212121;
border-radius: 10px;
padding: 20px 20px 20px 20px;
.prompt-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.cirl {
width: 17px;
height: 20px;
}
// 鼠标悬浮放大后在点击按钮上添加动画
.runse {
width: 22px;
height: 22px;
cursor: pointer;
transition: transform 0.5s ease;
&:hover {
transform: scale(1.2);
}
}
}
}
.ms-content {
border: 2px solid transparent;
animation-play-state: running !important;
height: 125px;
width: 100%;
margin-bottom: 14px;
border-radius: 10px;
.content-input {
background: #212121;
height: 100%;
border-radius: 10px;
}
}
}
.model-box {
width: 100%;
height: 200px;
text-align: center;
.style-box {
width: 80px;
height: 80px;
animation-play-state: running !important;
border-radius: 6px;
display: inline-block;
}
.all-box {
width: 80px;
height: 80px;
// 文字上下左右居中
display: flex;
font-size: 14px;
color: #ffffff;
font-weight: bold;
justify-content: center;
margin-left: 32px;
align-items: center;
background: linear-gradient(260deg, #c448b8, #591df5);
border-radius: 6px;
}
.size-box {
display: flex;
align-items: center;
justify-content: center;
background-color: #212121 !important;
width: 128px;
height: 65px;
animation-play-state: running !important;
border-radius: 6px;
.left-img {
margin-right: 10px;
}
div {
font-size: 11px;
color: #ffffff;
}
}
}
.border-change {
border: 2px solid transparent;
animation-play-state: running;
}
.border-default {
border: 2px solid #7e7e7e;
}
.wh-box {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #ffffff;
padding: 10px 30px 10px 20px;
margin-top: 20px;
border-radius: 6px;
border: 2px solid #7e7e7e;
background: #19191a;
:deep(.n-input) {
background-color: #19191a;
}
:deep(.n-input__suffix) {
display: none;
}
}
.bottom-box {
height: 13%;
margin-top: 10px;
background: #212121;
border-radius: 10px;
padding: 17px 17px 27px 27px;
display: flex;
.num-box {
width: 120px;
text-align: center;
.num-select {
height: 57px;
:deep(.n-base-selection-label) {
height: 57px;
background-color: #212121;
width: 110px;
color: #fff;
}
:deep(.n-base-selection-input__content) {
color: #fff;
}
margin-bottom: 5px;
}
div {
color: #7e7e7e;
}
}
.btn-box {
flex: 1;
text-align: center;
.create {
width: 260px;
height: 55px;
background: linear-gradient(260deg, #c448b8, #591df5);
border-radius: 6px;
color: #fff;
font-weight: bold;
margin-left: 15px;
margin-bottom: 5px;
}
div {
color: #7e7e7e;
}
}
}
.popover {
background: red;
}
.popout {
background: #212121;
}
@property --bg-angle {
inherits: false;
initial-value: 0deg;
syntax: "<angle>";
}
@keyframes spin {
to {
--bg-angle: 360deg;
}
}
//
.gallery-track {
position: fixed;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.25rem;
padding: 0.25rem;
will-change: transform;
}
.card {
border-radius: 1rem;
color: white;
width: min(400px, 90vw);
height: 400px;
overflow: hidden;
animation: spin 2.5s infinite linear paused;
background: linear-gradient(
to bottom,
oklch(0.1 0 240 / 0.95),
oklch(0.1 0 240 / 0.95)
)
padding-box,
conic-gradient(
from var(--bg-angle) in oklch longer hue,
oklch(0.85 0.17 0) 0 0
)
border-box;
& .card-image-wrapper {
height: 135%;
will-change: transform;
& img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
@media (width < 800px) {
.gallery-track {
grid-template-columns: repeat(2, 1fr);
}
}
@media (width < 550px) {
.gallery-track {
grid-template-columns: repeat(1, 1fr);
}
}
//
:deep(.n-input .n-input__border) {
border: 0;
}
// 更改placeholder的颜色
:deep(.n-input__placeholder) {
color: #7e7e7e;
}
:deep(.n-input__textarea-el) {
color: #7e7e7e;
}
:deep(.n-input__input-el) {
color: #7e7e7e;
}
:deep(.n-spin) {
color: #c348b8;
}
:deep(.n-spin-container) {
height: 100%;
}
:deep(.n-spin-content) {
height: 100%;
}
:deep(.n-upload-dragger) {
background: #212121;
height: 420px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
:deep(.n-upload-trigger.n-upload-trigger--image-card) {
width: 420px;
height: 400px;
margin-bottom: 10px;
}
:deep(.n-upload-file) {
width: 420px !important;
height: 400px !important;
}
</style>