初始化

test
Diyu0904 2025-02-18 11:36:49 +08:00
parent 654a427e48
commit 755c150220
37 changed files with 3539 additions and 1207 deletions

3
app/components.d.ts vendored
View File

@ -10,12 +10,15 @@ declare module 'vue' {
NAvatar: typeof import('naive-ui')['NAvatar']
NBadge: typeof import('naive-ui')['NBadge']
NButton: typeof import('naive-ui')['NButton']
NCarousel: typeof import('naive-ui')['NCarousel']
NCascader: typeof import('naive-ui')['NCascader']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NFor: typeof import('naive-ui')['NFor']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']

View File

@ -1,52 +1,55 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { FormItemRule } from "naive-ui";
import { ref } from "vue";
const message = useMessage()
const userStore = useUserStore()
const userInfo = userStore.userInfo
const isVisible = ref(false)
const message = useMessage();
const userStore = useUserStore();
const userInfo = userStore.userInfo;
const isVisible = ref(false);
const rules = ref({
name: [
{
required: true,
validator(value: string) {
validator(rule: FormItemRule, value: string) {
if (!value) {
return new Error('请填写姓名')
return new Error("请填写姓名");
} else if (!/^[\u4E00-\u9FA5·]{2,16}$/.test(value)) {
return new Error("输入正确的姓名");
}
else if (!/^[\u4E00-\u9FA5·]{2,16}$/.test(value)) {
return new Error('输入正确的姓名')
}
return true
return true;
},
trigger: 'blur',
trigger: "blur",
},
],
idCard: [
{
required: true,
validator(value: string) {
validator(rule: FormItemRule, value: string) {
if (!value) {
return new Error('请填身份证号')
return new Error("请填身份证号");
} else if (
!/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}([\dX])$)/i.test(
value
)
) {
return new Error("输入正确的身份证号");
}
else if (!/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}([\dX])$)/i.test(value)) {
return new Error('输入正确的身份证号')
}
return true
return true;
},
trigger: 'blur',
trigger: "blur",
},
],
})
});
const ruleForm = ref({
name: '',
idCard: '',
name: "",
idCard: "",
userId: userInfo.userId,
})
const formRef = ref(null)
});
const formRef = ref(null);
async function saveInfo(e: MouseEvent) {
e.preventDefault()
e.preventDefault();
formRef.value?.validate(async (errors) => {
if (!errors) {
// try{
@ -61,27 +64,33 @@ async function saveInfo(e: MouseEvent) {
// }catch(err) {
// console.log('err',err)
// }
await request.post('/system/user/updateIdCard', ruleForm.value)
await userStore.getUserInfo()
isVisible.value = false
try {
const res = await request.post("/system/user/updateIdCard", ruleForm.value);
if (res.code === 200) {
await userStore.getUserInfo();
isVisible.value = false;
} else {
message.warning(res.msg);
}
else {
} catch (err) {
console.log(err);
}
})
}
});
}
function onCloseModel() {
isVisible.value = false
ruleForm.value.name = ''
ruleForm.value.idCard = ''
isVisible.value = false;
ruleForm.value.name = "";
ruleForm.value.idCard = "";
}
defineExpose({
isVisible,
})
});
//
onMounted(() => {
// initFormData()
})
});
</script>
<template>
@ -94,14 +103,25 @@ onMounted(() => {
>
<n-form ref="formRef" :model="ruleForm" :rules="rules">
<n-form-item path="name" label="姓名">
<n-input v-model:value="ruleForm.name" placeholder="请输入姓名" @keydown.enter.prevent />
<n-input
v-model:value="ruleForm.name"
placeholder="请输入姓名"
@keydown.enter.prevent
/>
</n-form-item>
<n-form-item path="idCard" label="身份证号">
<n-input v-model:value="ruleForm.idCard" placeholder="请输入身份证号" @keydown.enter.prevent />
<n-input
v-model:value="ruleForm.idCard"
placeholder="请输入身份证号"
@keydown.enter.prevent
/>
</n-form-item>
</n-form>
<div class="flex justify-center items-center ">
<button class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle color-[#fff] border-0 cursor-pointer" @click="saveInfo">
<div class="flex justify-center items-center">
<button
class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle text-[#fff] border-0 cursor-pointer"
@click="saveInfo"
>
实名认证
</button>
</div>

View File

@ -1,328 +0,0 @@
<script setup>
import { ref } from 'vue'
const props = defineProps({
height: {
type: String,
default: '',
},
dataList: {
type: Array,
default: () => [],
},
userInfo: {
type: Object,
default: () => ({}),
},
productId: {
type: Number,
default: 0,
},
})
const message = useMessage()
//
const commentList = ref([])
async function getCommentList() {
try {
const res = await request.get(`/WorkFlowComment/comment/${props.productId}`)
for (let i = 0; i < res.data.length; i++) {
res.data[i].isShowInput = false
res.data[i].isShowSend = false
if (res.data[i].contentList.length > 0) {
for (let j = 0; j < res.data[i].contentList.length; j++) {
res.data[i].contentList[j].isShowInput = false
res.data[i].contentList[j].isShowSend = false
}
}
}
commentList.value = res.data
}
catch (error) {
console.log(error)
}
}
getCommentList()
// const $props = defineProps(['headUrl', 'dataList', 'height'])
// const $emit = defineEmits(['sendMessage'])
// const props = defineProps
const isShowSend = ref(false)
const publicWord = ref('')
// const message = useMessage()
function handleBlur(ele) {
ele.isShowInput = false
//
if (!ele) {
isShowSend.value = !!publicWord.value
}
else {
ele.isShowSend = !!ele.word
}
}
//
const commentParams = ref({
content: '',
parentId: '',
userId: props.userInfo.userId,
workFlowId: props.productId,
})
//
async function sendMessage(data, ele) {
debugger
try {
if (commentParams.value.content) {
const res = await request.post('WorkFlowComment/comment', commentParams.value)
if (res.code === 200) {
message.success('评论成功!')
}
}
else {
message.error('评论不能为空!')
}
}
catch (error) {
console.log(error)
}
// if(!publicWord.value){
// return message.info(
// '',
// { duration: 3000 }
// )
// }
if (!data) {
// $props.dataList.push({
// headUrl: 'https://avatars.githubusercontent.com/u/19239641?s=60&v=4', //
// name: '', //
// id: 6,
// des: publicWord.value, //
// time: '2025/02/08 14:52:06', //
// isAuthor: false, //
// isShowInput: false, //
// isFocus: true, //
// focusNum: 2, //
// word: '', //
// isShowSend: false, //
// })
publicWord.value = ''
// isShowSend.value = false
}
else {
data.push({
headUrl: 'https://avatars.githubusercontent.com/u/19239641?s=60&v=4', //
name: '星辰流连', //
id: 7,
des: ele.word, //
time: '2025/02/08 14:52:06', //
isAuthor: false, //
isShowInput: false, //
isFocus: true, //
focusNum: 2, //
word: '', //
isShowSend: false, //
})
ele.word = ''
ele.isShowInput = false
}
//
$emit('sendMessage', data)
}
//
function handleFocus(item) {
item.isFocus = !item.isFocus
}
function handleMessage(item) {
debugger
if (!item.isShowInput) {
item.word = ''
}
item.isShowInput = !item.isShowInput
}
function handleDel(ele, item) {
const index = item.findIndex(el => ele.id === el.id)
item.splice(index, 1)
}
</script>
<template>
<div class="base-comment">
<!-- 头部评论框 -->
<div class="flex justify-between mb-4">
<div v-if="props.userInfo.avatar" class="w-11 h-11 rounded-full mr-4 border border-[#2d28ff] border-solid">
<img class="w-10 h-10 rounded-full p-1" alt="avatar" :src="props.userInfo.avatar">
</div>
<div class="input-wrap flex items-center leading-normal rounded-lg text-sm bg-[#f2f5f9] px-4 py-2 flex-1">
<NConfigProvider inline-theme-disabled class="w-full">
<n-input
v-model:value="commentParams.content"
type="textarea"
:autosize="{ minRows: 1 }"
placeholder="善语结善缘,恶言伤人心~"
@focus="isShowSend = true"
@blur="handleBlur"
/>
</NConfigProvider>
<div
class="mx-4 flex-shrink-0 text-sm cursor-pointer text-gray-400 hover:text-gray-900"
:class="{ 'text-gray-900': commentParams.content }"
@click="sendMessage"
>
<span v-if="isShowSend">sendMessage</span>
</div>
</div>
</div>
<!-- 评论列表 -->
<NInfiniteScroll :style="{ height: `${height}px` }" :distance="10" @load="handleLoad">
<div v-for="(item, index) in commentList" :key="index">
<!-- 主评论 -->
<div class="flex text-sm">
<div class="w-10 h-10 rounded-full mr-3 flex-shrink-0">
<img :src="item.userAvatar" class="w-full h-full rounded-full">
</div>
<div class="w-full">
<div class="mt-[10px] mb-[7px] font-medium text-gray-700">
{{ item.userName }}
</div>
<div class="leading-[1.25]">
{{ item.content }}
</div>
<div class="mt-1 flex items-center justify-between">
<span class="text-xs text-gray-400">{{ item.createTime }}</span>
<div class="text-xs text-gray-400 flex items-center cursor-pointer">
<div class="flex items-center mr-4">
<img v-if="item.focus" src="@/assets/img/heart.png" class="w-4 h-4 mr-[3px]">
<img v-else src="@/assets/img/heart.png" class="w-4 h-4 mr-[3px]" @click="handleFocus(item)">
<span class="align-middle">{{ item.likeNum }}</span>
</div>
<span class="mr-4" @click="handleMessage(item)"></span>
<span @click="handleDel(item, dataList)">删除</span>
</div>
</div>
<!-- 评论回复框 -->
<div v-if="item.isShowInput" class="input-wrap mt-[10px] flex items-center rounded-lg text-sm bg-[#f2f5f9] px-3 py-2">
<NConfigProvider inline-theme-disabled class="w-full">
<n-input
v-model:value="item.word"
type="textarea"
:autosize="{ minRows: 1 }"
:placeholder="`回复: @${item.userName}`"
@focus="item.isShowSend = true"
@blur="handleBlur(item)"
/>
</NConfigProvider>
<div
class="mx-4 flex-shrink-0 text-sm cursor-pointer text-gray-400 hover:text-gray-900"
:class="{ 'text-gray-900': item.word }"
@click="sendMessage(item.children, item)"
>
<span v-if="item.isShowSend">sendMessage</span>
</div>
</div>
</div>
</div>
<!-- 子评论 -->
<div class="pl-[52px]">
<div v-for="(ele, subIndex) in item.contentList" :key="subIndex" class="flex text-sm mt-4">
<div class="w-6 h-6 rounded-full mr-3 flex-shrink-0">
<img src="https://avatars.githubusercontent.com/u/20943608?s=60&v=4" class="w-full h-full rounded-full">
</div>
<div class="w-full">
<div class="mb-[7px] font-medium text-gray-700 flex items-center">
{{ ele.userName }}
<div v-if="ele.userAvatar" class="ml-2 px-1 py-[3px] text-xs leading-[12px] text-white rounded bg-gradient-to-r from-[#2d28ff] to-[#1a7dff]">
作者
</div>
</div>
<div class="flex items-center">
<span>回复</span>
<span class="text-[#1880ff] mr-[6px]">@{{ ele.userName }}</span>
{{ ele.des }}
</div>
<div class="mt-1 flex items-center justify-between">
<span class="text-xs text-gray-400">{{ ele.createTime }}</span>
<div class="text-xs text-gray-400 flex items-center cursor-pointer">
<div class="flex items-center mr-4">
<img v-if="ele.isFocus" src="@/assets/img/heart.png" class="w-4 h-4 mr-[3px]" @click="handleFocus(ele, 'cancel')">
<img v-else src="@/assets/img/heart.png" class="w-4 h-4 mr-[3px]" @click="handleFocus(ele, 'add')">
<span class="align-middle">1</span>
</div>
<span class="mr-4" @click="handleMessage(ele)"></span>
<span @click="handleDel(ele, item.contentList)">删除</span>
</div>
</div>
<!-- 子评论回复框 -->
<div v-if="ele.isShowInput" class="input-wrap mt-[10px] flex items-center rounded-lg text-sm bg-[#f2f5f9] px-3 py-2">
<NConfigProvider inline-theme-disabled class="w-full">
<n-input
v-model:value="ele.word"
type="textarea"
:autosize="{ minRows: 1 }"
:placeholder="`回复: @${ele.userName}`"
@focus="ele.isShowSend = true"
@blur="handleBlur(ele)"
/>
</NConfigProvider>
<div
class="mx-4 flex-shrink-0 text-sm cursor-pointer text-gray-400 hover:text-gray-900"
:class="{ 'text-gray-900': ele.word }"
@click="sendMessage(item.children, ele)"
>
<span v-if="ele.isShowSend">sendMessage</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 加载更多 -->
<div class="text-sm text-gray-400 text-center py-[46px] pb-[118px]">
暂时没有更多评论
</div>
</NInfiniteScroll>
</div>
</template>
<style scoped lang="scss">
.input-wrap {
:deep(.n-input) {
--n-padding-left: 0 !important;
--n-border: 0 !important;
--n-border-hover: 0 !important;
--n-border-focus: 0 !important;
--n-box-shadow-focus: unset;
--n-padding-vertical: 0px !important;
}
&::hover {
border: 0;
}
&:active,
&:focus {
background-color: #f2f5f9;
border: 0 !important;
}
background-color: #f2f5f9;
}
.n-input--textarea {
background-color: #f2f5f9;
}
.n-input:not(.n-input--disabled).n-input--foucs {
background-color: #f2f5f9 !important;
}
.n-input--focus {
background-color: #f2f5f9 !important;
}
</style>

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
// import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui'
import {
ThumbsUp,
} from 'lucide-vue-next'
ThumbsUp
} from 'lucide-vue-next';
const props = defineProps({
height: {
@ -45,30 +45,48 @@ const sortType = ref(1)
//
const urlList = ref({
workflow: '/WorkFlowComment/comment?',
pictrue: '/imageComment/comment?',
model: '/ModelComment/comment?',
})
//
const likeList = ref({ //
const likeList = ref({
workflow: '/WorkFlowComment/commentLike?',
pictrue: '/imageComment/commentLike?',
model:'ModelComment/commentLike?'
})
//
const sendMessageList = ref({ //
const sendMessageList = ref({
workflow: '/WorkFlowComment/comment',
pictrue: '/imageComment/comment',
model: '/ModelComment/comment',
})
//
const deleteList = ref({ //
const deleteList = ref({
workflow: '/WorkFlowComment/commentDelete?',
pictrue: '/imageComment/commentDelete?',
model: '/ModelComment/commentDelete?',
})
//
const commentNumUrl = ref({ //
const commentNumUrl = ref({
workflow: '/WorkFlowComment/commentCount?workFlowId',
model: '/ModelComment/commentCount?modelId',
})
const commentCount = ref(0)
//
const commentList = ref([])
async function getCommentList() {
try {
const res = await request.get(`${urlList.value[props.type]}commentId=${props.detailsInfo.id}&sortType=${sortType.value}`)
let url = ''
if(props.type === 'workflow'){
url = `${urlList.value[props.type]}commentId=${props.detailsInfo.id}&sortType=${sortType.value}`
}else if(props.type === 'pictrue'){
url = `${urlList.value[props.type]}imageId=${props.detailsInfo.id}&sortType=${sortType.value}`
}else{
url = `${urlList.value[props.type]}modelId=${props.detailsInfo.id}&sortType=${sortType.value}`
}
const res = await request.get(url)
for (let i = 0; i < res.data.length; i++) {
res.data[i].isShowInput = false
res.data[i].isShowSend = false
@ -104,6 +122,11 @@ async function sendMessage(ele, index) {
commentParams.value.content = ele.word
commentParams.value.parentId = commentList.value[index].commentId
commentParams.value.replyUserId = ele.commentId
if(props.type === 'pictrue'){
commentParams.value.modelImageId = props.detailsInfo.id
}else if(props.type === 'model'){
commentParams.value.modelId = props.detailsInfo.id
}
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
if (res.code === 200) {
message.success('评论成功!')
@ -126,7 +149,12 @@ async function sendMessage(ele, index) {
commentParams.value.parentId = ''
commentParams.value.content = publicWord.value
commentParams.value.replyUserId = ''
const res = await request.post('WorkFlowComment/comment', commentParams.value)
if(props.type === 'pictrue'){
commentParams.value.modelImageId = props.detailsInfo.id
}else if(props.type === 'model'){
commentParams.value.modelId = props.detailsInfo.id
}
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
if (res.code === 200) {
message.success('评论成功!')
publicWord.value = ''
@ -145,7 +173,9 @@ async function sendMessage(ele, index) {
}
}
//
async function getCommentNum() {
if(props.type !== 'pictrue'){
try {
const res = await request.get(`${commentNumUrl.value[props.type]}=${props.detailsInfo.id}`)
if (res.code === 200) {
@ -155,10 +185,12 @@ async function getCommentNum() {
catch (error) {
console.log(error)
}
}
}
getCommentNum()
// /
async function handleFocus(item) {
async function handleFocus(item:any) {
await request.get(`${likeList.value[props.type]}commentId=${item.commentId}`)
if (item.isLike === 0) {
item.isLike = 1
@ -173,14 +205,14 @@ async function handleFocus(item) {
}
//
function handleMessage(item) {
function handleMessage(item:any) {
if (!item.isShowInput) {
item.word = ''
}
item.isShowInput = !item.isShowInput
}
//
async function handleDel(item) {
async function handleDel(item:any) {
try {
const res = await request.get(`${deleteList.value[props.type]}commentId=${item.commentId}`)
if (res.code === 200) {
@ -207,11 +239,11 @@ function changeType(type: string) {
<div class="left text-[20px] mr-2">
讨论
</div>
<div class="text-[#999]">
<div class="text-[#999]" v-if="props.type !== 'pictrue'">
{{ commentCount }}
</div>
</div>
<div class="flex items-center">
<div class="flex items-center" v-if="props.type !== 'pictrue'">
<div class="cursor-pointer" :class="sortType === 0 ? '' : 'text-[#999]'" @click="changeType(0)">
最热
</div>
@ -245,7 +277,6 @@ function changeType(type: string) {
</div>
</div>
</div>
<NInfiniteScroll :style="{ height: `${height}px` }" :distance="10" @load="handleLoad">
<div
v-for="(item, index) in commentList"
:key="index"
@ -347,7 +378,6 @@ function changeType(type: string) {
<div class="no-more">
暂时没有更多评论
</div>
</NInfiniteScroll>
</div>
</template>
@ -363,8 +393,8 @@ function changeType(type: string) {
margin-right: 12px;
border: 1px solid #2d28ff;
img {
width: 40px;
height: 40px;
width: 100%;
height: 100%;
border-radius: 50%;
}
}
@ -424,8 +454,8 @@ function changeType(type: string) {
border-radius: 50%;
margin-right: 12px;
img {
width: 40px;
height: 40px;
width: 100%;
height: 100%;
border-radius: 50%;
}
}
@ -490,8 +520,8 @@ function changeType(type: string) {
border-radius: 50%;
margin-right: 12px;
img {
width: 24px;
height: 24px;
width: 100%;
height: 100%;
border-radius: 50%;
}
}

View File

@ -1,153 +1,171 @@
<script setup lang="ts">
//
import { headerRole } from '@/constants/index'
import { headerRole } from "@/constants/index";
import {
Bell,
CirclePlus,
GraduationCap,
HardDriveUpload,
Image,
Monitor,
Workflow,
} from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
Bell,
CirclePlus,
GraduationCap,
HardDriveUpload,
Image,
Monitor,
Workflow
} from "lucide-vue-next";
import { NConfigProvider, NMessageProvider } from "naive-ui";
// import { AtCircle } from '@vicons/ionicons5'
import { NIcon } from 'naive-ui'
import { onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NIcon } from "naive-ui";
import { onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const modalStore = useModalStore()
const currentUseRoute = ref('')
const isShowPublishPicture = ref<boolean>(false)
const PublishPictureRef = ref<Payment | null>(null)
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const modalStore = useModalStore();
const currentUseRoute = ref("");
const isShowPublishPicture = ref<boolean>(false);
const PublishPictureRef = ref<Payment | null>(null);
const publishPicture = ref({
title: '',
title: "",
tags: [],
description: '',
description: "",
imagePaths: [],
})
});
watch(
() => route.path, // route.path
(newPath) => {
currentUseRoute.value = newPath
currentUseRoute.value = newPath;
},
{ immediate: true }, //
)
{ immediate: true } //
);
function hasItem(path: string, list: any) {
return !list.includes(path)
return !list.includes(path);
}
const searchText = ref('')
const searchText = ref("");
function onSearch(value: any) {
console.log('搜索:', value)
console.log("搜索:", value);
//
}
//
const notificationOptions = [
{
label: '系统通知',
key: 'system',
label: "系统通知",
key: "system",
},
{
label: '互动消息',
key: 'interaction',
label: "互动消息",
key: "interaction",
},
]
];
function renderIcon(icon: Component) {
return () => {
return h(NIcon, null, {
default: () => h(icon),
})
}
});
};
}
//
const publishOptions = [
{
label: '模型',
key: 'publish-model',
label: "模型",
key: "publish-model",
icon: renderIcon(HardDriveUpload),
},
{
label: '图片',
key: 'picture',
label: "图片",
key: "picture",
icon: renderIcon(Image),
},
{
label: '工作流',
key: 'publish-workflow',
label: "工作流",
key: "publish-workflow",
icon: renderIcon(Workflow),
},
]
];
const userOptions = ref([
{
label: '我的模型',
key: 'model',
label: "我的模型",
key: "model",
},
{
label: '我的作品',
key: 'project',
label: "我的作品",
key: "project",
},
{
label: '我的点赞',
key: 'like',
label: "我的点赞",
key: "like",
},
{
label: '账号设置',
key: 'userSettings',
label: "账号设置",
key: "userSettings",
},
{
label: '退出登录',
key: 'logout',
label: "退出登录",
key: "logout",
},
])
]);
//
async function handleUserSelect(key: string) {
if (key === 'logout') {
if (key === "logout") {
try {
await request.post('/logout')
userStore.logout()
navigateTo('/model-square')
}
catch (error) {
console.error('Logout failed:', error)
await request.post("/logout");
userStore.logout();
navigateTo("/model-square");
} catch (error) {
console.error("Logout failed:", error);
}
}
}
//
async function handlePublishSelect(key: string) {
if (key === 'picture') {
isShowPublishPicture.value = true
if (key === "picture") {
isShowPublishPicture.value = true;
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = true
PublishPictureRef.value.isVisible = true;
}
}
else {
router.push({
path: `/${key}`,
query: {
type: 'add',
},
})
} else {
const baseUrl = window.location.origin;
window.open(`${baseUrl}/${key}?type=add`, "_blank", "noopener,noreferrer");
// router.push({
// path: `/${key}`,
// query: {
// type: 'add',
// },
// })
}
}
function closePublishImg() {
isShowPublishPicture.value = false
isShowPublishPicture.value = false;
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = false
PublishPictureRef.value.isVisible = false;
}
}
function handleLogin() {
modalStore.showLoginModal()
modalStore.showLoginModal();
}
onMounted(() => {
})
const msgList = ref([]);
async function getAllMessage() {
try {
const res = await request.get("/advice/getAllMsg");
if (res.code == 200) {
msgList.value = res.data;
}
} catch (err) {
console.log(err);
}
}
getAllMessage();
//
function toDetail(){
const baseUrl = window.location.origin
window.open(`${baseUrl}/message`, '_blank', 'noopener,noreferrer')
}
onMounted(() => {});
</script>
<template>
@ -201,14 +219,70 @@ onMounted(() => {
</NDropdown>
<!-- Notifications -->
<NDropdown :options="notificationOptions" trigger="click">
<!-- :options="notificationOptions" -->
<!-- <NDropdown trigger="click">
<NBadge :value="5" :max="99" processing>
<NButton text circle>
<Bell class="h-5 w-5 mr-1" />
<Bell class="h-5 w-5 mr-1 relative" />
<div class="absolute top-2 right-1 border-solid border-2 border-[#f0f0f0] py-2">
<div class="border-b-solid border-b-2 border-b-[#f0f0f0]">
通知
</div>
<div v-for="(item,index) in 3">
1111
</div>
</div>
</NButton>
</NBadge>
</NDropdown>
</NDropdown> -->
<div
class="p-1 bg-[#f1f1f6] rounded-full flex items-center justify-center relative group"
>
<Bell class="h-5 w-5relative cursor-pointer" @mouseenter="getAllMessage" />
<div class="pt-4 absolute top-5 -right-4 hidden group-hover:block">
<div
class="border-solid border border-[#f0f0f0] py-2 w-[300px] rounded-lg bg-white"
>
<div
class="border-b-solid border-b border-b-[#f0f0f0] p-2 text-sm font-bold"
>
通知
</div>
<div class="p-2 max-h-[300px] overflow-y-auto">
<div
v-for="(item, index) in msgList"
:key="index"
class="flex items-center my-2 cursor-pointer"
>
<div class="flex-1">
<n-ellipsis
style="max-width: 250px"
:tooltip="false"
class="font-bold text-gray-600"
>
{{ item.content }}
</n-ellipsis>
<div class="text-[12px] text-gray-400">{{ item.createTime }}</div>
</div>
<div class="w-4 flex h-[100%] items-center justify-center">
<div
class="w-2 h-2 bg-[#ea5049] rounded"
v-if="item.isRead === '0'"
></div>
</div>
</div>
</div>
<div class="my-4 text-gray-500 text-sm text-center">
更多通知可点击查看全部~
</div>
<div class="flex justify-center">
<div class="bg-[#4c79ee] w-[270px] h-10 flex items-center justify-center rounded-lg text-white cursor-pointer" @click="toDetail">
查看全部
</div>
</div>
</div>
</div>
</div>
<!-- User -->
<div class="min-w-10">
<client-only>
@ -239,7 +313,13 @@ onMounted(() => {
<div>
<NConfigProvider>
<NMessageProvider>
<Publish-picture v-if="isShowPublishPicture" ref="PublishPictureRef" :form-data="publishPicture" @close-publish-img="closePublishImg" />
<Publish-picture
v-if="isShowPublishPicture"
ref="PublishPictureRef"
type="add"
:form-data="publishPicture"
@close-publish-img="closePublishImg"
/>
</NMessageProvider>
</NConfigProvider>
</div>
@ -247,7 +327,7 @@ onMounted(() => {
</template>
<style scoped>
.header-btn-primary {
.header-btn-primary {
@apply border border-solid border-[#3162ff] px-1 py-1 text-[#3162ff] rounded-md hover:text-[#3162ff];
}
.header-btn {

View File

@ -0,0 +1,175 @@
<template>
<div class="flex flex-wrap justify-center">
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
<div
v-for="item in dataList"
:key="item.id"
class="relative rounded-lg overflow-hidden"
>
<!-- 图片 -->
<div class="relative h-[300px] overflow-hidden rounded-lg" @click="toDetail(item)">
<img
:src="item.surfaceUrl"
class="w-full h-full object-cover rounded-lg cursor-pointer ransform transition-transform duration-300 hover:scale-110"
alt=""
/>
<!-- 左上角标签 -->
<div
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
>
{{ item.type }}
</div>
<!-- 底部数据统计 -->
<div
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
>
<div class="flex items-center">
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
{{ item.reals || 0 }}
</div>
<div class="flex items-center">
<component
:is="Download"
class="h-[14px] w-[14px] text-white menu-icon m-1"
/>
{{ item.numbers || 0 }}
</div>
</div>
</div>
<!-- 作者信息条 -->
<div class="mt-1 px-2 py-1">
<div>
{{ item.modelName }}
</div>
<div class="flex mt-2">
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
</div>
</div>
</div>
</div>
<div
ref="loadingTrigger"
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
>
<div v-if="loading">...</div>
<div v-if="finished && dataList.length >= 20"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { Download, Play } from "lucide-vue-next";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import { useRouter } from 'vue-router';
const router = useRouter()
const loading = ref(false);
const finished = ref(false);
const total = ref(0); //
const loadingTrigger = ref(null);
const observer = ref<IntersectionObserver | null>(null);
const props = defineProps({
params: {
type: Object,
default: () => {},
},
});
const listParams = ref({
...props.params,
pageNumber: 1,
pageSize: 20,
});
function initPageNUm() {
listParams.value.pageNumber = 1;
finished.value = false; //
listParams.value = Object.assign({}, listParams.value, props.params);
getDataList();
}
const dataList = ref([]);
async function getDataList() {
if (loading.value || finished.value) return;
loading.value = true;
try {
const res = await request.post("/model/modelSquare", { ...listParams.value });
if (res.code === 200) {
//
if (listParams.value.pageNumber === 1) {
dataList.value = res.data.list;
} else {
dataList.value = [...dataList.value, ...res.data.list];
}
total.value = res.data.total; //
//
if (dataList.value.length >= total.value) {
finished.value = true;
}
//
listParams.value.pageNumber++;
}
} catch (err) {
dataList.value = [];
finished.value = true;
console.log(err);
} finally {
loading.value = false;
}
}
getDataList();
//
function toDetail(item:any){
router.push(`/model-details/${item.id}`)
}
onMounted(() => {
window.addEventListener("scroll", topedRefresh);
observer.value = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !loading.value && !finished.value) {
getDataList();
}
},
{
threshold: 0.1,
}
);
if (loadingTrigger.value) {
observer.value.observe(loadingTrigger.value);
}
});
onUnmounted(() => {
window.removeEventListener("scroll", topedRefresh);
if (observer.value) {
observer.value.disconnect();
}
});
async function topedRefresh() {
if (import.meta.client) {
await nextTick();
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
initPageNUm();
}
defineExpose({
initPageNUm,
});
</script>
<style scoped></style>

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import {
CircleAlert,
Download,
EllipsisVertical,
Play,
} from 'lucide-vue-next'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
CircleAlert,
Download,
EllipsisVertical,
Play
} from 'lucide-vue-next';
import { nextTick, ref } from 'vue';
import { useRouter } from 'vue-router';
const props = defineProps({
item: {
@ -36,6 +36,8 @@ function toDetails() {
else if (props.currentType === '1') {
// console.log('object', 111);
router.push(`/workflow-details/${props.item.id}`)
}else if(props.currentType === '2'){
onEditPicture()
}
}
@ -65,11 +67,11 @@ function handleSelect(event: Event, key: string) {
handleDelete()
}
else if (key === 'edit') {
if (props.currentType === '2') {
if (props.currentType === '2') { //
getPublishPicture()
showPublishImg()
}
else if (props.currentType === '1') {
else if (props.currentType === '1') { //
router.push({
path: `/publish-workflow`,
query: {
@ -78,6 +80,15 @@ function handleSelect(event: Event, key: string) {
},
})
}
else if (props.currentType === '0') { //
router.push({
path: `/publish-model`,
query: {
type: 'edit',
id: props.item.id,
},
})
}
}
}
@ -160,6 +171,35 @@ function closePublishImg() {
PublishPictureRef.value.isVisible = false
}
}
//
const isShowEditorPicture = ref(false)
interface EditUserInfoType {
isVisible: boolean
}
const editUserInfoRef = ref<EditUserInfoType | null>(null)
function onEditPicture() {
isShowEditorPicture.value = true
nextTick(()=>{
if(editUserInfoRef.value){
editUserInfoRef.value.isVisible = true
}
})
}
function closeEditorPicture(){
isShowEditorPicture.value = false
}
function updateLike(type:number){
if (props.item.isLike === 1) {
props.item.isLike = 0;
props.item.likeNum -= 1;
} else {
props.item.isLike = 1;
props.item.likeNum += 1;
}
}
</script>
<template>
@ -310,13 +350,16 @@ function closePublishImg() {
:src="item.userAvatar"
alt=""
>
<span>{{ item.userName }}</span>
<span>{{ item.userName }} </span>
</div>
</div>
</div>
<NConfigProvider>
<NMessageProvider>
<Publish-picture v-if="isShowPublishPicture" ref="PublishPictureRef" :form-data="publishPictureData" @close-publish-img="closePublishImg" />
<div v-if="isShowEditorPicture">
<PictureDetail @close-editor-picture="closeEditorPicture" ref="editUserInfoRef" :item="item" @update-like="updateLike"/>
</div>
<Publish-picture v-if="isShowPublishPicture" type="edit" ref="PublishPictureRef" :form-data="publishPictureData" @close-publish-img="closePublishImg" />
</NMessageProvider>
</NConfigProvider>
</div>

View File

@ -0,0 +1,190 @@
<script setup lang="ts">
import { commonApi } from "@/api/common";
import { CopyOutline, Heart } from "@vicons/ionicons5";
import { Download, PartyPopper } from "lucide-vue-next";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { ref } from "vue";
const emit = defineEmits(["updateLike"]);
const userStore = useUserStore();
const userInfo = userStore.userInfo;
const message = useMessage();
const props = defineProps({
item: {
type: Object,
default: () => {},
},
});
const dataInfo = ref({});
async function getDetail() {
if (props.item && props.item.id) {
const res = await request.get(`/image/detail?id=${props.item.id}`);
if (res.code === 200) {
dataInfo.value = res.data;
getDictType();
}
}
}
getDetail();
const tagsList = ref([]);
//
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "image_label" });
if (res.code === 200 && res.data.length > 0) {
for (let i = 0; i < res.data.length; i++) {
for (let j = 0; j < dataInfo.value.tags.length; j++) {
if (res.data[i].dictValue === dataInfo.value.tags[j]) {
tagsList.value.push(res.data[i].dictLabel);
}
}
}
}
} catch (error) {
console.log(error);
}
}
function onShowModel() {
// ruleForm.value.nickName = userInfo.nickName
// ruleForm.value.avatar = userInfo.avatar
// ruleForm.value.brief = userInfo.brief
// ruleForm.value.userId = userInfo.userId
}
const isVisible = ref(false);
function onCloseModel() {
isVisible.value = false;
}
const commentHeight = ref(200);
//
async function onLike() {
try {
const res = await request.get(`/image/imageLike?id=${props.item.id}`);
if (res.code === 200) {
// emit('updateLike')
if (dataInfo.value.isLike === 1) {
dataInfo.value.isLike = 0;
dataInfo.value.likeNum -= 1;
message.success("取消点赞成功");
} else {
dataInfo.value.isLike = 1;
dataInfo.value.likeNum += 1;
message.success("点赞成功");
}
}
} catch (err) {
console.log(err);
}
}
defineExpose({
isVisible,
});
</script>
<template>
<NModal
v-model:show="isVisible"
:on-after-leave="onCloseModel"
:on-after-enter="onShowModel"
preset="card"
style="width: auto; border-radius: 10px; box-sizing: border-box"
:mask-closable="false"
>
<div class="p-2 w-[700px]" style="box-sizing: border-box">
<div class="flex w-full">
<div class="flex-1 p-1">
<img class="w-full h-[300px]" :src="dataInfo.imagePaths" alt="" />
<div class="flex mt-2">
<div
class="mr-2 w-40 px2 py-3 flex items-center justify-center text-[#8a4200] cursor-pointer rounded-lg bg-gradient-to-r from-[#ffe9c8] to-[#ffd264]"
>
<Download size="16" class="mr-1" />
下载无水印原图
</div>
<div
class="flex items-center bg-[#eceef4] px2 py-3 w-20 justify-center cursor-pointer rounded-lg"
>
<n-icon
class="mr-2"
size="20"
:color="dataInfo.isLike === 1 ? '#ff0000' : '#ccc'"
@click="onLike"
>
<Heart />
</n-icon>
{{ dataInfo.likeNum }}
</div>
</div>
<div class="bg-[#f6f9fe] p-2 text-[12px] mt-2 rounded-lg">
作者添加了水印成为会员后可下载无水印原图
<span class="text-[#4a69ed] cursor-pointer">直接下载</span>带水印的图片
</div>
</div>
<div class="flex-1 p-1">
<div class="flex items-center">
<img class="w-10 h-10 rounded-full mr-2" :src="dataInfo.userAvatar" alt="" />
<div>
{{ dataInfo.userName }}
</div>
</div>
<div class="text-[20px] mt-3">
{{ dataInfo.title }}
</div>
<div class="text-[14px] mt-2 text-gray-400">
{{ dataInfo.description }}
</div>
<div class="text-[14px] mt-2 text-gray-400">
{{ dataInfo.createTime }}
</div>
<div class="flex mt-2">
<div
class="flex w-[100px] items-center bg-[#eceef4] px2 py-3 justify-center cursor-pointer rounded-lg mr-2"
>
<n-icon class="mr-2" size="20" color="#ccc">
<CopyOutline />
</n-icon>
复制全部
</div>
<div
class="flex w-[200px] items-center px2 py-3 bg-[#416af6] text-white rounded-lg justify-center cursor-pointer"
>
<PartyPopper size="16" class="mr-1" />一键生图
</div>
</div>
</div>
</div>
<div>
<div class="p-1 flex flex-wrap">
<div
v-for="(item, index) in tagsList"
:key="index"
class="text-[#5d79ba] bg-[#ecf2fe] p-2 text-[12px] font-bold mr-2 mt-2 w-auto rounded-lg"
>
{{ item }}
</div>
</div>
</div>
<div class="mt-4">
<div style="padding: 20px">
<NConfigProvider>
<NMessageProvider>
<BaseComment
v-if="dataInfo.id"
type="pictrue"
:height="commentHeight"
:details-info="dataInfo"
/>
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</div>
</NModal>
</template>
<style scoped></style>

View File

@ -0,0 +1,210 @@
<template>
<div class="flex flex-wrap justify-center">
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
<div
v-for="item in dataList"
:key="item.id"
class="relative rounded-lg overflow-hidden"
>
<!-- 图片 -->
<div class="relative h-[300px] overflow-hidden rounded-lg group">
<img
:src="item.avatar"
class="w-full h-full object-cover rounded-lg cursor-pointer"
alt=""
/>
<div
class="px-2 py-1 absolute top-0 left-0 w-full h-full hidden group-hover:block bg-black bg-opacity-40 cursor-pointer"
>
<div class="flex justify-between w-full">
<div class="flex mt-2">
<div class="w-5 h-5 border border-white mr-2 rounded-full">
<img :src="item.avatar" class="w-full h-full rounded-full" alt="" />
</div>
<span class="text-sm text-white truncate">{{ item.nickName }}</span>
</div>
<div class="p-2 rounded-full bg-white flex items-center cursor-pointer">
<n-icon
class="mr-2"
size="20"
:color="item.isLike === 0 ? '#ccc' : '#ff0000'"
@click="onLike(item)"
>
<Heart />
</n-icon>
{{ item.likeNum }}
</div>
</div>
</div>
<!-- 左上角标签 -->
<!-- <div
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
>
{{ item.type }}
</div> -->
<!-- 底部数据统计 -->
<!-- <div
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
>
<div class="flex items-center">
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
{{ item.reals || 0 }}
</div>
<div class="flex items-center">
<component
:is="Download"
class="h-[14px] w-[14px] text-white menu-icon m-1"
/>
{{ item.numbers || 0 }}
</div>
</div> -->
</div>
<!-- 作者信息条 -->
<!-- <div class="mt-1 px-2 py-1">
<div>
{{ item.modelName }}
</div>
<div class="flex mt-2">
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
</div>
</div> -->
</div>
</div>
<div
ref="loadingTrigger"
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
>
<div v-if="loading">...</div>
<div v-if="finished && dataList.length >= 20"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { Heart } from "@vicons/ionicons5";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
const loading = ref(false);
const finished = ref(false);
const total = ref(0); //
const loadingTrigger = ref(null);
const observer = ref<IntersectionObserver | null>(null);
const message = useMessage();
const props = defineProps({
params: {
type: Object,
default: () => {},
},
});
const listParams = ref({
...props.params,
pageNumber: 1,
pageSize: 20,
});
function initPageNUm() {
listParams.value.pageNumber = 1;
finished.value = false; //
listParams.value = Object.assign({}, listParams.value, props.params);
getDataList();
}
const dataList = ref([]);
async function getDataList() {
if (loading.value || finished.value) return;
loading.value = true;
try {
const res = await request.get("/image/imageList", { ...listParams.value });
if (res.code === 200) {
//
if (listParams.value.pageNumber === 1) {
dataList.value = res.data.list;
} else {
dataList.value = [...dataList.value, ...res.data.list];
}
total.value = res.data.total; //
//
if (dataList.value.length >= total.value) {
finished.value = true;
}
//
listParams.value.pageNumber++;
}
} catch (err) {
dataList.value = [];
finished.value = true;
console.log(err);
} finally {
loading.value = false;
}
}
getDataList();
onMounted(() => {
window.addEventListener("scroll", topedRefresh);
observer.value = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !loading.value && !finished.value) {
getDataList();
}
},
{
threshold: 0.1,
}
);
if (loadingTrigger.value) {
observer.value.observe(loadingTrigger.value);
}
});
onUnmounted(() => {
window.removeEventListener("scroll", topedRefresh);
if (observer.value) {
observer.value.disconnect();
}
});
async function topedRefresh() {
if (import.meta.client) {
await nextTick();
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
initPageNUm();
}
async function onLike(item: any) {
try {
const res = await request.get(`/image/imageLike?id=${item.id}`);
if (res.code === 200) {
if (item.isLike === 1) {
item.isLike = 0;
item.likeNum -= 1;
message.success("取消点赞成功");
} else {
item.isLike = 1;
item.likeNum += 1;
message.success("点赞成功");
}
}
} catch (err) {
console.log(err);
}
}
defineExpose({
initPageNUm,
});
</script>
<style scoped></style>

View File

@ -1,15 +1,20 @@
<script setup lang="ts">
import { commonApi } from '@/api/common'
import { cloneDeep } from 'lodash-es'
import { useMessage } from 'naive-ui'
import { defineProps, ref } from 'vue'
import { commonApi } from '@/api/common';
import { cloneDeep } from 'lodash-es';
import { useMessage } from 'naive-ui';
import { defineProps, ref } from 'vue';
const props = defineProps({
formData: {
type: Object,
default: () => ({}),
},
type:{
type: String,
default: "",
}
})
debugger
const emit = defineEmits(['closePublishImg'])
const message = useMessage()

View File

@ -1,5 +1,5 @@
<script setup>
import { onMounted, ref } from 'vue'
import { onMounted, ref } from 'vue';
const props = defineProps({
title: {

View File

@ -0,0 +1,189 @@
<template>
<div class="flex flex-wrap justify-center">
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
<div
v-for="item in dataList"
:key="item.id"
class="relative rounded-lg overflow-hidden"
>
<!-- 图片 -->
<div class="relative h-[300px] overflow-hidden rounded-lg" @click="toDetail(item)">
<img
:src="item.coverPath"
class="w-full h-full object-cover rounded-lg cursor-pointer ransform transition-transform duration-300 hover:scale-110"
alt=""
/>
<!-- 左上角标签 -->
<div
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
>
{{ item.type }}
</div>
<!-- 底部数据统计 -->
<div
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
>
<div class="flex items-center">
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
{{ item.useNumber || 0 }}
</div>
<div class="flex items-center">
<component
:is="Download"
class="h-[14px] w-[14px] text-white menu-icon m-1"
/>
{{ item.downloadNumber || 0 }}
</div>
</div>
</div>
<!-- 作者信息条 -->
<div class="mt-1 px-2 py-1">
<div>
{{ item.workflowNam }}
</div>
<div class="flex mt-2">
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
</div>
</div>
</div>
</div>
<div
ref="loadingTrigger"
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
>
<div v-if="loading">...</div>
<div v-if="finished && dataList.length >= 20"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { Download, Play } from "lucide-vue-next";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import { useRouter } from 'vue-router';
const router = useRouter()
const loading = ref(false);
const finished = ref(false);
const total = ref(0); //
const loadingTrigger = ref(null);
const observer = ref<IntersectionObserver | null>(null);
const props = defineProps({
params: {
type: Object,
default: () => {},
},
});
const listParams = ref({
...props.params,
pageNumber: 1,
pageSize: 20,
});
function initPageNUm() {
listParams.value.pageNumber = 1;
finished.value = false; //
listParams.value = Object.assign({}, listParams.value, props.params);
getDataList();
}
const dataList = ref([]);
async function getDataList() {
if (loading.value || finished.value) return;
loading.value = true;
try {
const res = await request.post("/WorkFlow/workFlowList", { ...listParams.value });
if (res.code === 200) {
//
if (listParams.value.pageNumber === 1) {
dataList.value = res.data.list;
} else {
dataList.value = [...dataList.value, ...res.data.list];
}
total.value = res.data.total; //
//
if (dataList.value.length >= total.value) {
finished.value = true;
}
//
listParams.value.pageNumber++;
}
} catch (err) {
dataList.value = [];
finished.value = true;
console.log(err);
} finally {
loading.value = false;
}
}
getDataList();
//
function toDetail(item:any){
router.push(`/workflow-details/${item.id}`)
}
onMounted(() => {
window.addEventListener("scroll", topedRefresh);
observer.value = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !loading.value && !finished.value) {
getDataList();
}
},
{
threshold: 0.1,
}
);
if (loadingTrigger.value) {
observer.value.observe(loadingTrigger.value);
}
});
onUnmounted(() => {
window.removeEventListener("scroll", topedRefresh);
if (observer.value) {
observer.value.disconnect();
}
});
async function topedRefresh() {
if (import.meta.client) {
await nextTick();
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
initPageNUm();
}
const workFlowCategoryList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "work_flow_type_child" });
if (res.code === 200) {
workFlowCategoryList.value = res.data;
}
} catch (error) {
console.log(error);
}
}
getDictType();
defineExpose({
initPageNUm,
});
</script>
<style scoped></style>

View File

@ -0,0 +1,48 @@
<template>
<div>
<div
v-for="(item, index) in dataList"
:key="index"
@click="toDetail(item)"
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
>
<div class="flex-1">
<div class="text-base ellipsis w-[650px]">{{ item.content }}</div>
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
</div>
<div
v-if="item.isRead === 0"
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
></div>
</div>
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
</n-empty>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
dataList: {
type: Object,
default: () => [],
},
});
async function toDetail(item){
try{
const res = await request.get(`/advice/read?adviceId=${item.id}`)
debugger
}catch(err){
console.log(err);
}
}
</script>
<style scoped>
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<div>
<div
v-for="(item, index) in dataList"
:key="index"
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
>
<div class="flex-1">
<div class="text-base ellipsis w-[600px]">{{ item.content }}</div>
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
</div>
<div class="w-10 h-10">
<img class="w-full h-full rounded-lg" :src="item.userAvatar" />
</div>
<div
v-if="item.isRead === 0"
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
></div>
</div>
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
</n-empty>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
dataList: {
type: Object,
default: () => [],
},
});
</script>
<style scoped>
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -0,0 +1,19 @@
<template>
<div>
<div
v-for="item in 15"
class="bg-white h-20 rounded-lg p-3 mb-2 flex flex-col justify-center cursor-pointer"
>
<div class="text-base">你师傅说房东说短发女生的方式对佛呢</div>
<div class="text-[12px] text-gray-400 mt-1">2010-22-12 18:32:11</div>
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -0,0 +1,62 @@
<template>
<div>
<div
v-for="(item, index) in dataList" :key="index"
@click="toDetail(item)"
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
>
<div class="flex-1">
<div class="text-base ellipsis w-[600px]">{{ item.content }}</div>
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
</div>
<div class="w-10 h-10 ">
<img class="w-full h-full rounded-lg" :src="item.userAvatar" />
</div>
<div
v-if="item.isRead === 0"
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
></div>
</div>
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
</n-empty>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const props = defineProps({
dataList: {
type: Object,
default: () => [],
},
});
const router = useRouter()
async function toDetail(item){
try{
debugger
const res = await request.get(`/advice/read?adviceId=${item.id}`)
if(res.code === 200){
// 0 1 2
if(item.productType === 0){
router.push(`/model-details/${item.id}`)
}else if(item.productType === 1){
router.push(`/workflow-details/${item.id}`)
}else{
// onEditPicture()
}
}
}catch(err){
console.log(err);
}
}
</script>
<style scoped>
.ellipsis{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { commonApi } from '@/api/common'
import { computed, watch } from 'vue'
import { commonApi } from '@/api/common';
import type { FormInst } from 'naive-ui';
import { computed, watch } from 'vue';
const props = defineProps({
modelValue: {
@ -29,23 +29,17 @@ watch(
},
{ immediate: true, deep: true },
)
//
function handleUpdate(value: Array<string>) {
if (value.length > 3) {
return
}
localForm.value.modelProduct.tags = value.join(',')
}
const formRef = ref<FormInst | null>(null)
const rules = {
'modelProduct.modelName': {
required: true,
message: '请输入模型名称',
message: '',
trigger: 'blur',
},
'modelProduct.modelType': {
required: true,
message: '请输入模型名称',
message: '',
trigger: 'blur',
},
}
@ -103,11 +97,11 @@ getActivityList()
const originalBtnList = ref([
{
label: '原创',
value: 0,
value: 1,
},
{
label: '转载',
value: 1,
value: 0,
},
])
function nextStep() {
@ -122,14 +116,25 @@ function nextStep() {
}
function handleIsOriginal(value: number) {
localForm.value.modelProduct.isOriginal = value
if (value === 0) {
localForm.value.modelProduct.originalAuthorName = ''
}
// if (value === 0) {
// localForm.value.modelProduct.originalAuthorName = ''
// }
}
function handleCategoryUpdateValue(value) {
//
// if (value) {
// //
// localForm.value.modelProduct.category = value.includes('-') ? value : `${value}-0`
// }
}
function changeModelType(item){
debugger
}
</script>
<template>
<div>
<div class="bg-gray-100 p-4 rounded-lg">
<n-form
ref="formRef"
:label-width="80"
@ -144,6 +149,7 @@ function handleIsOriginal(value: number) {
<n-select
v-model:value="localForm.modelProduct.modelType"
label-field="dictLabel"
@update:value="changeModelType"
value-field="dictValue"
placeholder="请选择模型类型"
:options="model_category"
@ -163,9 +169,10 @@ function handleIsOriginal(value: number) {
label-field="dictLabel"
value-field="dictValue"
check-strategy="child"
@update:value="handleCategoryUpdateValue"
/>
</n-form-item>
<n-form-item path="functions" class="-mt-12">
<n-form-item path="functions" class="-mt-12" v-if="localForm.modelProduct.modelType !== '0'">
<n-select
v-model:value="localForm.modelProduct.functions"
label-field="dictLabel"
@ -182,7 +189,7 @@ function handleIsOriginal(value: number) {
添加标签将自动推荐给可能感兴趣的人
</div>
<n-form-item path="category">
<n-select
<!-- <n-select
v-model:value="localForm.modelProduct.tags"
:options="options"
filterable
@ -190,6 +197,16 @@ function handleIsOriginal(value: number) {
multiple
placeholder="请选择或输入标签"
@update:value="handleUpdate"
/> -->
<n-select
v-model:value="localForm.modelProduct.tagsList"
filterable
multiple
tag
placeholder="输入,按回车确认"
:show-arrow="false"
:show="false"
/>
</n-form-item>
<div>
@ -208,7 +225,10 @@ function handleIsOriginal(value: number) {
/>
</n-form-item>
</n-form>
<div class="flex justify-center items-center mt-5 bg-white h-10 rounded-lg">
<div>
原创内容
</div>
<div class="flex justify-center items-center mt-5 bg-white h-12 rounded-lg border border-gray-100">
<div
v-for="(item, index) in originalBtnList"
:key="index"
@ -223,7 +243,7 @@ function handleIsOriginal(value: number) {
{{ item.label }}
</div>
</div>
<div v-if="localForm.modelProduct.isOriginal === 0" class="text-[12px]">
<div v-if="localForm.modelProduct.isOriginal === 1" class="text-[12px]">
<div class="my-3">
魔创未来原创模型加密保护计划
</div>
@ -251,10 +271,13 @@ function handleIsOriginal(value: number) {
<n-input v-model:value="localForm.modelProduct.originalAuthorName" placeholder="输入原创作者" />
</n-form-item>
</div>
<div class="flex justify-center items-center mt-5 text-white w-30 h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
</div>
<div class="flex w-full justify-center">
<div class="flex justify-center items-center mt-5 text-white w-[200px] h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
下一步
</div>
</div>
</div>
</template>
<style lang="scss" scoped>

View File

@ -1,9 +1,11 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { commonApi } from '@/api/common'
import { uploadImagesInBatches } from '@/utils/uploadImg.ts'
import { Asterisk, Trash } from 'lucide-vue-next'
import { computed, onMounted, ref, watch } from 'vue'
import { commonApi } from "@/api/common";
import { uploadImagesInBatches } from "@/utils/uploadImg.ts";
import { cloneDeep } from 'lodash-es';
import { Asterisk, Trash } from "lucide-vue-next";
import type { FormInst } from "naive-ui";
import { computed, ref, watch } from "vue";
const message = useMessage()
//
const props = defineProps({
@ -11,174 +13,235 @@ const props = defineProps({
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue', 'createModelsNext'])
const acceptTypes = '.safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft'
});
const emit = defineEmits(["update:modelValue", "nextStep", "prevStep"]);
const acceptTypes =
".safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft";
const modelVersionItem = {
versionName: '', //
modelId: null, //
versionDescription: '"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //
filePath: '', //
fileName: '', //
sampleImagePaths: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', // 20,
triggerWords: '', //
isPublic: 1, // 1 2
isFree: 0, // 0 1
isOnlineUse: 1, // 线使
delFlag: '0', // 0 2
versionName: "", //
modelVersionType: null, //
versionDescription: "", //
filePath: "", //
fileName: "", //
sampleImagePaths:[], // 20,
triggerWords: "", //
isPublic: 1, //
isOnlineUse:1, //线使
allowFusion: 1, //
isFree: 0, // 0
allowDownloadImage: 1, //
allowSoftwareUse: 1, // 使
allowFusion: 1, //
allowCommercialUse: 1, //
allowUsage: 1, //
isExclusiveModel: 1, //
hideImageGenInfo:0, //
// allowUsage: 1, // 使
//
isExclusiveModel: 0, //
}
};
const isPublicList = [
{
label: '公开',
label: "公开",
value: 1,
},
{
label: '仅自己可见',
value: 2,
label: "仅自己可见",
value: 0,
},
]
];
defineExpose({
addVersion,
})
});
const localForm = computed({
get() {
return props.modelValue
return props.modelValue;
},
set(value) {
emit('update:modelValue', value)
emit("update:modelValue", value);
},
})
});
watch(
() => localForm.value,
(newVal) => {
console.log('newVal', newVal)
emit('update:modelValue', newVal)
emit("update:modelValue", newVal);
},
{ immediate: true, deep: true },
)
{ immediate: true, deep: true }
);
const formRef = ref<FormInst | null>(null)
const formRefs = ref<(FormInst | null)[]>([]);
function setFormRef(el: FormInst | null, index: number) {
if (el) {
formRefs.value[index] = el;
}
}
const rules = {
versionName: {
required: true,
message: '',
trigger: 'blur',
message: "",
trigger: "blur",
},
modelId: {
modelVersionType: {
required: true,
message: '',
trigger: 'blur',
message: "",
trigger: "blur",
},
triggerWords: {
required: true,
message: '请输入模型名称',
trigger: 'blur',
},
}
};
function addVersion() {
localForm.value.modelVersionList.unshift(modelVersionItem)
const param = cloneDeep(modelVersionItem)
localForm.value.modelVersionList.unshift(param);
}
const originalBtnList = ref([
{
label: '免费',
value: 0,
},
{
label: '会员下载',
label: "免费",
value: 1,
},
])
{
label: "会员下载",
value: 0,
},
]);
function handleIsFree(index: number, value: number) {
localForm.value.modelVersionList[index].isFree = value
localForm.value.modelVersionList[index].isFree = value;
}
const model_category = ref([])
function nextStep() {
formRef.value?.validate((errors) => {
if (!errors) {
emit('createModelsNext')
async function nextStep() {
for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
if (
localForm.value.modelVersionList[i].delFlag === "0" &&
localForm.value.modelVersionList[i].fileName === ""
) {
return message.error("请上传文件");
}
else {
console.log('error', errors)
const regex = /[\u4E00-\u9FA5]/; //
if (
localForm.value.modelVersionList[i].delFlag === "0" &&
!regex.test(localForm.value.modelVersionList[i].versionDescription)
) {
return message.error("请用中文填写版本介绍");
}
}
try {
const promises = formRefs.value
.filter((form): form is FormInst => form !== null)
.map((form) => form.validate());
await Promise.all(promises);
emit("nextStep");
} catch (errors) {
console.error("部分表单验证失败:", errors);
}
})
}
//
const uploadFileIndex = ref(0)
const fileInput = ref<HTMLInputElement | null>(null)
const uploadFileIndex = ref(0);
const fileInput = ref<HTMLInputElement | null>(null);
function triggerFileInput(index: number) {
(fileInput.value as HTMLInputElement)?.click()
uploadFileIndex.value = index
(fileInput.value as HTMLInputElement)?.click();
uploadFileIndex.value = index;
}
async function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement
const files = target.files
const target = event.target as HTMLInputElement;
const files = target.files;
if (files && files.length > 0) {
const res = await uploadImagesInBatches(files)
localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url
localForm.value.modelVersionList[uploadFileIndex.value].fileName = res[0].fileName
const res = await uploadImagesInBatches(files);
localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url;
localForm.value.modelVersionList[uploadFileIndex.value].fileName = res[0].fileName;
}
target.value = ''
target.value = "";
}
function prevStep() {
emit("prevStep");
}
const baseModelTypeList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "mode_type" });
if (res.code === 200) {
baseModelTypeList.value = res.data;
}
} catch (error) {
console.log(error);
}
}
getDictType();
function computedDelFlag() {
return localForm.value.modelVersionList.filter(item => item.delFlag === '0')
}
function onDelete(index: number) {
if (computedDelFlag().length === 1)
return
localForm.value.modelVersionList[index].delFlag = '2'
}
</script>
<template>
<div>
<div v-for="(item, index) in localForm.modelVersionList" :key="index" class="bg-gray-100 p-4 rounded-lg">
<template
v-for="(item, index) in localForm.modelVersionList"
:key="index"
class="bg-gray-100 p-4 mt-4 rounded-lg"
>
<div v-if="item.delFlag === '0'" class="bg-gray-100 p-4 rounded-lg mt-4 relative">
<div class="absolute -right-10 top-4 cursor-pointer">
<Trash class="cursor-pointer" @click="onDelete(index)" />
</div>
<n-form
ref="formRef"
:ref="(el) => setFormRef(el, index)"
:label-width="80"
:model="localForm"
:model="localForm.modelVersionList[index]"
:rules="rules"
size="large"
>
<n-form-item label="版本名称" path="versionName">
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
</n-form-item>
<n-form-item label="基础模型" path="modelId">
<n-form-item label="基础模型" path="modelVersionType">
<n-select
v-model:value="item.modelId"
v-model:value="item.modelVersionType"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择基础模型"
:options="model_category"
:options="baseModelTypeList"
/>
</n-form-item>
<div class="flex">
上传文件 <Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div v-if="item.fileName" class="flex justify-between items-center bg-white p-3 mt-2 rounded-lg">
<div class="bg-[#d8e5fd] text-[12px] text-[#3162ff] w-16 h-7 rounded-lg flex justify-center items-center">
<div
v-if="item.fileName"
class="flex justify-between items-center bg-white p-3 mt-2 rounded-lg"
>
<div
class="bg-[#d8e5fd] text-[12px] text-[#3162ff] w-16 h-7 rounded-lg flex justify-center items-center"
>
100%
</div>
<div class="flex-1 flex items-center line-clamp">
{{
item.fileName
}}
{{ item.fileName }}
</div>
<div>
<Trash class="cursor-pointer" @click="item.fileName = '', item.filePath = ''" />
<Trash
class="cursor-pointer"
@click="(item.fileName = ''), (item.filePath = '')"
/>
</div>
</div>
<div v-else>
<div class="upload-content">
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
<div
class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white"
>
<div
class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center"
@click="triggerFileInput(index)"
>
上传文件
</div>
<div class="my-3">
点击上传文件
</div>
<div class="my-3">点击上传文件</div>
<div class="text-[#999999] text-xs">
.safetensors/.ckpt/.pt/.bin/.pth/.zip/.json/.flow/.lightflow/.yaml/.yml/.onnx/.gguf/.sft
</div>
@ -190,29 +253,40 @@ async function handleFileChange(event: Event) {
</div>
<div class="bg-white p-3 mt-2 rounded-lg">
<client-only>
<WangEditor v-model="item.versionDesc" />
<WangEditor v-model="item.versionDescription" />
</client-only>
</div>
<div v-html="item.versionDesc" />
<div class="mt-6">
触发词
</div>
<div class="-mb-5 text-gray-400 text-[12px]">
请输入您用来训练的单词
</div>
<div class="mt-6">触发词</div>
<div class="-mb-5 text-gray-400 text-[12px]">请输入您用来训练的单词</div>
<n-form-item path="triggerWords">
<n-input v-model:value="item.triggerWords" placeholder="例如: 1boy" />
</n-form-item>
<div class="">
权限设置
<div v-if="localForm.modelProduct.modelType === '0'">
<n-form-item label="采样方法" path="modelVersionType">
<n-select
v-model:value="item.modelVersionType"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择采样方法"
:options="baseModelTypeList"
/>
</n-form-item>
<div>
<n-select v-model:value="item.modelVersionType" multiple :options="baseModelTypeList" />
</div>
<div class="mt-1 mb-2 text-gray-400 text-[12px]">
可见范围
</div>
<div class="">权限设置</div>
<div class="mt-1 mb-2 text-gray-400 text-[12px]">可见范围</div>
<div>
<n-radio-group v-model:value="item.isPublic" name="radiogroup">
<n-space>
<n-radio v-for="(isPublicItem, isPublicIndex) in isPublicList" :key="isPublicIndex" :value="isPublicItem.value">
<n-radio
v-for="(isPublicItem, isPublicIndex) in isPublicList"
:key="isPublicIndex"
:value="isPublicItem.value"
>
{{ isPublicItem.label }}
</n-radio>
</n-space>
@ -220,26 +294,28 @@ async function handleFileChange(event: Event) {
</div>
</n-form>
<div v-if="item.isPublic === 1">
<div class="mt-4 mb-1 text-gray-400 text-[12px]">
付费设置
</div>
<div class="mt-4 mb-1 text-gray-400 text-[12px]">付费设置</div>
<div class="flex justify-center items-center bg-white h-10 rounded-lg">
<div
v-for="(subItem, subIndex) in originalBtnList"
:key="subIndex"
:style="{
backgroundColor:
item.isFree === subItem.value
? 'rgba(49, 98, 255, 0.1)'
: '#fff',
item.isFree === subItem.value ? 'rgba(49, 98, 255, 0.1)' : '#fff',
color: item.isFree === subItem.value ? '#3162ff' : '#000',
}" class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer" @click="handleIsFree(index, subItem.value)"
}"
class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer"
@click="handleIsFree(index, subItem.value)"
>
{{ subItem.label }}
</div>
</div>
<div class="mt-1 mb-2 text-gray-500 text-[12px]">
选择会员专属或会员下载视为您已经阅读 <span class="text-[#3162ff] cursor-pointer underline">会员模型许可协议</span> 并同意其中条款
选择会员专属或会员下载视为您已经阅读
<span class="text-[#3162ff] cursor-pointer underline"
>会员模型许可协议</span
>
并同意其中条款
</div>
<div v-if="item.isFree === 1" class="text-[12px]">
<div>会员下载模型</div>
@ -248,64 +324,66 @@ async function handleFileChange(event: Event) {
</div>
</div>
</div>
<div class="text-gray-400 text-[12px] my-4">
许可范围
</div>
<div class="text-gray-400 text-[12px] my-4">许可范围</div>
<div class="flex flex-wrap">
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.isOnlineUse"
:checked-value="1"
:unchecked-value="0" label="允许在魔创未来在线使用"
:unchecked-value="0"
disabled="true"
label="允许在魔创未来在线使用"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowDownloadImage"
:checked-value="1"
:unchecked-value="0" label="允许下载生图"
:unchecked-value="0"
label="允许下载生图"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowSoftwareUse"
:checked-value="1"
:unchecked-value="0" label="允许在魔创未来旗下其他产品在线使用"
disabled="true"
:unchecked-value="0"
label="允许在魔创未来旗下其他产品在线使用"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowFusion"
:checked-value="1"
:unchecked-value="0" label="允许进行融合"
:unchecked-value="0"
label="允许进行融合"
/>
</div>
</div>
<div class="text-gray-400 text-[12px] my-4">
商用许可范围
</div>
<div class="text-gray-400 text-[12px] my-4">商用许可范围</div>
<div class="flex flex-wrap">
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowCommercialUse"
:checked-value="1"
:unchecked-value="0" label="生成图片可出售或用于商业目的"
:unchecked-value="0"
label="生成图片可出售或用于商业目的"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowDownloadImage"
v-model:checked="item.allowUsage"
:checked-value="1"
:unchecked-value="0" label="允许模型转售或融合后出售"
:unchecked-value="0"
label="允许模型转售或融合后出售"
/>
</div>
</div>
<div class="mb-4">
<div class="text-gray-400 text-[12px] my-4">
独家设置
</div>
<div class="text-gray-400 text-[12px] my-4">独家设置</div>
<div class="flex items-center mb-2">
<n-checkbox
v-model:checked="item.isExclusiveModel"
@ -318,11 +396,18 @@ async function handleFileChange(event: Event) {
</div>
</div>
</div>
</template>
<div class="flex items-center justify-center mt-5">
<div class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer" @click="nextStep">
<div
class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer"
@click="prevStep"
>
上一步
</div>
<div class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
<div
class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer"
@click="nextStep"
>
下一步
</div>
</div>
@ -333,7 +418,7 @@ async function handleFileChange(event: Event) {
class="hidden"
:accept="acceptTypes"
@change="handleFileChange"
>
/>
</template>
<style lang="scss" scoped>

View File

@ -0,0 +1,197 @@
<script setup lang="ts">
import { cloneDeep } from 'lodash-es';
import { Asterisk } from 'lucide-vue-next';
import { useRoute, useRouter } from 'vue-router';
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue', 'preStep'])
const router = useRouter()
const route = useRoute()
const { type } = route.query
const message = useMessage()
const localForm = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
function preStep() {
emit('preStep')
}
const showSuccessModal = ref(false)
async function handlePublish() {
for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
if (localForm.value.modelVersionList[i].delFlag === '0' && localForm.value.modelVersionList[i].sampleImagePaths.length === 0) {
return message.error('请上传图片')
}
}
try {
const param = cloneDeep(localForm.value)
if (param.modelProduct.tagsList.length !== 0) {
param.modelProduct.tags = JSON.stringify(param.modelProduct.tagsList)
}
else {
param.modelProduct.type = '[]'
}
for (let i = 0; i < param.modelVersionList.length; i++) {
if (param.modelVersionList[i].sampleImagePaths.length !== 0) {
param.modelVersionList[i].sampleImagePaths = param.modelVersionList[i].sampleImagePaths.join(',')
}
else {
param.modelVersionList[i].sampleImagePaths = ''
}
}
if (type === 'add') {
try {
const res = await request.post('/model/insert', param)
if (res.code === 200) {
showSuccessModal.value = true
}
}
catch (error) {
console.log(error)
}
}
else {
try {
const res = await request.post('/model/update', param)
if (res.code === 200) {
showSuccessModal.value = true
}
}
catch (error) {
console.log(error)
}
}
}
catch (error) {
console.log(error)
}
}
watch(
() => localForm.value,
(newVal) => {
emit('update:modelValue', newVal)
},
{ immediate: true, deep: true },
)
const fileInput = ref<HTMLInputElement | null>(null)
const uploadFileIndex = ref(0)
function triggerFileInput(index: number) {
(fileInput.value as HTMLInputElement)?.click()
uploadFileIndex.value = index
}
async function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement
const files = target.files
if (files && files.length > 0) {
const sum = localForm.value.modelVersionList[uploadFileIndex.value].sampleImagePaths.length + files.length
if (sum >= 20)
return message.error('最多20张')
const res = await uploadImagesInBatches(files)
const urlList = res.map(item => item.url)
localForm.value.modelVersionList[uploadFileIndex.value].sampleImagePaths.push(...urlList)
}
target.value = ''
}
function onPositiveClick() {
showSuccessModal.value = false
router.push('/personal-center')
}
</script>
<template>
<div class="mx-auto mt-10">
<div class="w-[700px] mx-auto items-start rounded-lg">
<template v-for="(item, index) in localForm.modelVersionList" :key="index">
<div v-if="item.delFlag === '0'" class="w-full bg-gray-100 p-4 rounded-lg mt-2">
<div class="flex justify-between items-center">
<div>
版本名: <span>{{ item.versionName }}</span>
</div>
<div>
<n-checkbox
v-model:checked="item.hideImageGenInfo"
:checked-value="1" :unchecked-value="0"
/>
隐藏图片生成信息
</div>
</div>
<div class="flex justify-between items-center mt-4">
<div class="flex">
添加版本示例图片
<Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div class="text-[12px] text-gray-400">
最多20张图片, 图片不超过30M
</div>
</div>
<div>
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
上传文件
</div>
<div class="my-3">
点击上传文件
</div>
<div class="text-[#999999] text-xs">
请勿上传裸露暴力血腥或其他包含非法信息图片
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2.5 mt-4 w-full">
<div v-for="(subItem, subIndex) in item.sampleImagePaths" :key="subIndex">
<img class="w-full h-[200px] object-cover rounded-lg" :src="subItem" alt="">
</div>
</div>
</div>
</template>
</div>
<div class="flex items-center justify-center mt-5">
<div class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer" @click="preStep">
上一步
</div>
<div class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="handlePublish">
发布
</div>
</div>
</div>
<input
ref="fileInput"
type="file"
class="hidden"
accept="image/*"
multiple
@change="handleFileChange"
>
<!-- 成功之后的弹框 -->
<n-modal
v-model:show="showSuccessModal"
:mask-closable="false"
preset="dialog"
title="发布成功!"
content="工作流发布成功, 可至个人中心查看状态"
positive-text="确认"
@positive-click="onPositiveClick"
/>
</template>
<style scoped>
</style>

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { uploadFileBatches } from '@/utils/uploadImg.ts'
import { Asterisk, Trash } from 'lucide-vue-next'
import { computed, ref, watch } from 'vue'
import { uploadFileBatches } from '@/utils/uploadImg.ts';
import { cloneDeep } from 'lodash-es';
import { Asterisk, Trash } from 'lucide-vue-next';
import type { FormInst } from 'naive-ui';
import { computed, ref, watch } from 'vue';
//
const props = defineProps({
@ -54,7 +55,8 @@ const rules = {
},
}
function addVersion() {
localForm.value.workFlowVersionList.unshift(modelVersionItem)
const param = cloneDeep(modelVersionItem)
localForm.value.workFlowVersionList.unshift(param)
}
const formRefs = ref<(FormInst | null)[]>([])

View File

@ -70,9 +70,9 @@ function handleIsPublic(value: number) {
}
function handleIsOriginal(value: number) {
localForm.value.workFlow.original = value
if (value === 0) {
localForm.value.workFlow.authorName = ''
}
// if (value === 0) {
// localForm.value.workFlow.authorName = ''
// }
}
//

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { cloneDeep } from 'lodash-es'
import { Asterisk } from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep } from 'lodash-es';
import { Asterisk } from 'lucide-vue-next';
import { useRoute, useRouter } from 'vue-router';
const props = defineProps({
modelValue: {
@ -53,6 +53,9 @@ async function handlePublish() {
param.workFlowVersionList[i].imagePaths = ''
}
}
if(param.modelProduct.workFlow === 0){
param.modelProduct.originalAuthorName = ''
}
// await request.post('/WorkFlow/addWorkFlow', param)
if (type === 'add') {
try {

View File

@ -1,4 +1,3 @@
import type { SearchCheckIcon } from 'lucide-vue-next'
export const appName = '魔创未来'
export const appDescription = '魔创未来'
@ -19,5 +18,5 @@ export const isPublicList = [{
value: 2,
}]
export const headerRole = { // 包括就隐藏
inputSearch: ['/model-square'],
inputSearch: ['/model-square','/picture-square', '/work-square'],
}

View File

@ -1,23 +1,23 @@
<script setup lang="ts">
import {
Binary,
Code2,
Crown,
Image,
LayoutGrid,
Lightbulb,
Maximize,
User,
Workflow,
} from 'lucide-vue-next'
Binary,
Code2,
Crown,
Image,
LayoutGrid,
Lightbulb,
Maximize,
User,
Workflow
} from 'lucide-vue-next';
const menuStore = useMenuStore()
//
const iconMap: any = {
'/model-square': LayoutGrid,
'/works-inspire': Lightbulb,
'/workspace': Lightbulb,
'/picture-square':Lightbulb,
'/work-square':Workflow,
'/web-ui': Image,
'/comfy-ui': Workflow,
'/training-lora': Binary,
@ -78,7 +78,7 @@ function handleSide(event: Event, path: string) {
v-for="item in menuStore.menuItems"
:key="item.path"
:to="item.path"
class="flex items-center gap-3 rounded-lg px-4 py-2.5 text-[15px] font-medium no-underline transition-colors"
class="flex items-center gap-3 rounded-lg px-4 py-4 text-[15px] font-medium no-underline transition-colors"
:class="[
menuStore.activeMenu === item.path
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'

View File

@ -0,0 +1,272 @@
<script setup lang="ts">
import AttentionMsg from "@/components/message/AttentionMsg.vue";
import LikeMsg from "@/components/message/LikeMsg.vue";
import OfficialMsg from "@/components/message/OfficialMsg.vue";
import ReplyMsg from "@/components/message/ReplyMsg.vue";
import { Check, Heart, MessageSquareMore, UserPlus, Volume2 } from "lucide-vue-next";
const currentMsgType = ref("reply");
const MsgTypeList = ref([
// {
// label:'',
// value:'official'
// icon:'Volume2'
// },
{
label: "回复我的",
value: "reply",
icon: "MessageSquareMore",
},
{
label: "收到的赞",
value: "like",
icon: "Heart",
},
{
label: "关注我的",
value: "attention",
icon: "UserPlus",
},
]);
const iconMap: any = {
'official':Volume2,
'reply': MessageSquareMore,
'like':Heart,
'attention':UserPlus,
}
MsgTypeList.value = MsgTypeList.value.map(item => ({
...item,
LucideIcon: iconMap[item.value], // Lucide
}))
const urlList = ref({
official: "",
reply: "/advice/getCommentMsg",
like: "/advice/getLikeMsg",
attention: "/advice/getAttentionMsg",
});
const allList = ref({
reply: [],
like: [],
attention: [],
});
// const officialList = ref([])
// const replyList = ref([])
// const likeList = ref([])
// const attentionList = ref([])
// const officialParamsType = ref('')
// const officialParamsTypeList = ref([
// {
// label:'',
// value:'4'
// }
// ])
const replyParamsType = ref("3");
const replyParamsTypeList = ref([
{
label: "全部通知",
value: "3",
},
{
label: "模型评论",
value: "0",
},
{
label: "图片评论",
value: "2",
},
{
label: "工作流评论",
value: "1",
},
]);
const likeParamsType = ref("4");
const likeParamsTypeList = ref([
{
label: "全部通知",
value: "4",
},
{
label: "模型点赞",
value: "0",
},
{
label: "图片点赞",
value: "2",
},
{
label: "工作流点赞",
value: "1",
},
{
label: "评论点赞",
value: "3",
},
]);
async function getList() {
const url = urlList.value[currentMsgType.value];
let productType = "";
if (currentMsgType.value === "reply") {
productType = replyParamsType.value;
} else if (currentMsgType.value === "like") {
productType = likeParamsType.value;
} else {
productType = "";
}
const res = await request.get(url, { productType });
if (res.code === 200) {
allList.value[currentMsgType.value] = res.data;
}
}
getList();
function changeType(item: any) {
currentMsgType.value = item.value;
likeParamsType.value = "4";
replyParamsType.value = "3";
getList();
}
function handleSelect(item: any, type: string) {
if (type === "like") {
likeParamsType.value = item.value;
} else {
replyParamsType.value = item.value;
}
getList();
}
//
async function onAllRead() {
try {
const res = await request.get("/advice/readAll");
if (res.code === 200) {
getList();
}
} catch (err) {
console.log(err);
}
}
definePageMeta({
layout: "default",
});
</script>
<template>
<div
class="bg-[#f0f1f5] w-full flex items-center justify-center"
:style="{ height: `calc(100vh - 48px)` }"
>
<div
class="card w-[1036px] min-h-[500px] m-auto rounded-lg flex border border-solid border-gray-200"
:style="{ height: `calc(100vh - 100px)` }"
>
<div
class="w-[287px] bg-white p-3 box-content rounded-l-lg text-gray-600 flex flex-col justify-between"
>
<div>
<div class="p-3 text-xl">通知</div>
<div
class="msg-item"
:style="{ background: item.value === currentMsgType ? '#f5f6fa' : '' }"
v-for="(item, index) in MsgTypeList"
:key="index"
@click="changeType(item)"
>
<!-- <Volume2 size="18" /> -->
<!-- <MessageSquareMore size="18" /> -->
<!-- <Heart size="18" /> -->
<!-- <UserPlus
size="18"
:style="{ color: item.value === currentMsgType ? '#3541ec' : '' }"
class="mr-2"
/> -->
<component
:is="item.LucideIcon"
size="18"
:style="{ color: item.value === currentMsgType ? '#3541ec' : '' }"
class="mr-2"
/>
{{ item.label }}
</div>
</div>
<div class="w-full flex py-2 cursor-pointer justify-center">
<div class="flex justify-center items-center cursor-pointer" @click="onAllRead">
<Check size="14" class="mr-1" />
一键已读
</div>
</div>
</div>
<div class="box-content p-3 flex-1">
<div class="w-full h-10 flex justify-end items-center cursor-pointer relative">
<div class="group py-2">
<template v-if="currentMsgType === 'reply' || currentMsgType === 'like'">
全部通知
<n-icon size="14" class="ml-1">
<ChevronDown />
</n-icon>
</template>
<div
v-if="currentMsgType === 'reply'"
class="absolute right-0 top-[30px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
>
<div
v-for="(item, index) in replyParamsTypeList"
:style="{
color: item.value === replyParamsType ? '#3a75f6' : '',
}"
:key="index"
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
@click="(event) => handleSelect(item, 'reply')"
>
{{ item.label }}
</div>
</div>
<div
v-if="currentMsgType === 'like'"
class="absolute right-0 top-[30px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
>
<div
v-for="(item, index) in likeParamsTypeList"
:style="{
color: item.value === likeParamsType ? '#3a75f6' : '',
}"
:key="index"
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
@click="(event) => handleSelect(item, 'like')"
>
{{ item.label }}
</div>
</div>
</div>
</div>
<div class="p-2 overflow-y-auto" :style="{ height: `calc(100% - 48px)` }">
<OfficialMsg v-if="currentMsgType === 'official'" />
<ReplyMsg v-if="currentMsgType === 'reply'" :data-list="allList.reply" />
<LikeMsg v-if="currentMsgType === 'like'" :data-list="allList.like" />
<AttentionMsg
v-if="currentMsgType === 'attention'"
:data-list="allList.attention"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.msg-item {
@apply h-14 flex items-center mb-2 py-3 pl-6 rounded-lg cursor-pointer;
@apply hover:bg-[#f5f6fa];
}
.card {
@apply rounded-lg overflow-hidden transition-all duration-300;
@apply [box-shadow:0_0_20px_0_rgba(0,0,0,0.05)];
@apply hover:bg-[#f5f6fa] hover:[box-shadow:0_0_20px_0_rgba(0,0,0,0.1)];
}
</style>

View File

@ -1,37 +1,462 @@
<script setup lang="ts">
import { Heart } from '@vicons/ionicons5'
import {
Download,
Play,
} from 'lucide-vue-next'
import { Heart, PersonAddOutline } from '@vicons/ionicons5'
import {
CircleUser,
Download,
EllipsisVertical, Play,
SquarePlus
} from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const message = useMessage()
const router = useRouter()
// tabs
definePageMeta({
layout: 'default',
})
// const route = useRoute<'publishDetails-id'>()
// const userStore = useUserStore()
const route = useRoute()
const { id } = route.params as { id: string }
const activeTab = ref(null)
const commentHeight = ref(800)
const detailsInfo = ref({})
const currentUserInfo = ref<any>({})
// const id = route.params.id
//
const versionByWorkInfo = ref([])
async function getInfo() {
try {
const res = await request.get(`/model/selectModelById?id=${id}`)
if (res.code === 200) {
detailsInfo.value = res.data
// detailsInfo.value.styleList =JSON.parse(res.data.styleList)
// // 1
try {
const res1 = await request.get(`/ModelVersion/finbyid?id=${res.data.id}`)//
if (res1.code === 200 && res1.data.length > 0) {
versionByWorkInfo.value = res1.data
versionByWorkInfo.value.forEach((item) => {
item.sampleImagePaths = item.sampleImagePaths.split(',')
})
nextTick(() => {
activeTab.value = versionByWorkInfo.value[0].id
})
// const commentRes = await request.get(`/WorkFlowComment/comment/${res.data.id}`)
}
}
catch (error) {
console.log(error)
}
// //
try {
const res = await request.get(`/system/user/selectUserById?id=${detailsInfo.value.userId}`)
if (res.code === 200) {
currentUserInfo.value = res.data
}
}
catch (error) {
console.log(error)
}
getAttention()
}
}
catch (error) {
console.log(error)
}
}
getInfo()
// //
interface SelectUserInfo {
attention: number
bean: number
imageLikeNum:number
modelDownLoadNum:number
modelLikeNum:number
modelRunNum:number
}
const selectUserInfo = ref<SelectUserInfo>({
attention: 0,
bean: 0,
imageLikeNum:0,
modelDownLoadNum:0,
modelLikeNum:0,
modelRunNum:0,
})
//
async function getAttention() {
try {
const res = await request.get(`/attention/selectUserInfo?userId=${detailsInfo.value.userId}`)
if (res.code === 200) {
selectUserInfo.value = res.data
}
}
catch (err) {
console.log(err)
}
}
// //
const isDelete = ref(false)
async function handleSelect(event: Event, type: string) {
event.stopPropagation() //
if (type === 'report') {
await request.get(`/WorkFlow/report?id=${id}`) //
}
else if (type === 'edit') {
router.push({
path: `/publish-model`,
query: {
type: 'edit',
id: detailsInfo.value.id,
},
})
}
else {
isDelete.value = true
}
}
//
async function onDelete() {
try {
const res = await request.get(`/model/delete?id=${detailsInfo.value.id}`)
if (res.code === 200) {
message.success('删除成功')
isDelete.value = false
router.push('/personal-center')
}
}
catch (err) {
console.log(err)
}
}
// /
async function onChangeAttention() {
try {
const res = await request.get(`/attention/addAttention?userId=${detailsInfo.value.userId}`)
if (res.code === 200) {
if (res.data) {
detailsInfo.value.isAttention = 1
message.success('关注成功')
}
else {
detailsInfo.value.isAttention = 0
message.success('取消关注成功')
}
}
}
catch (err) {
console.log(err)
}
}
//
async function onLike() {
try {
const res = await request.get(`/ModelComment/modelLike?modelId=${detailsInfo.value.id}`)
if (res.code === 200) {
detailsInfo.value.isLike === 0 ? detailsInfo.value.isLike = 1 : detailsInfo.value.isLike = 0
if (detailsInfo.value.isLike === 0) {
message.success('取消点赞成功')
}
else {
message.success('点赞成功')
}
}
}
catch (err) {
console.log(err)
}
}
</script>
<template>
<div class="flex justify-center">
<div class="w-[1125px]">
<div class="w-[1125px] p-4">
<div class="flex items-center">
<div class="text-[26px] font-bold mr-2">
名称
<div class="text-[26px] font-bold mr-4">
{{ detailsInfo.modelName }}
</div>
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
<span>
<!-- {{ item.reals }} -->0
</span>
<n-tooltip trigger="hover">
<template #trigger>
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
<component :is="Play" class="h-[14px] w-[14px] text-black menu-icon m-1" />
<span> {{ detailsInfo.reals || 0 }} </span>
</div>
</template>
在线生成数
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full mx-4">
<component
:is="Download"
class="h-[14px] w-[14px] text-white menu-icon m-1"
class="h-[14px] w-[14px] text-black menu-icon m-1"
/>
<span>
<!-- {{ item.numbers }} -->0
<span> {{ detailsInfo.numbers || 0 }} </span>
</div>
</template>
下载数
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
<img src="@/assets/img/heart.png" class="w-[14px] h-[14px] mr-1" alt="">
<span>{{ detailsInfo.likeNum || 0}} </span>
</div>
</template>
点赞数
</n-tooltip>
</div>
<div class="flex items-center mt-3 mb-5">
<div
v-for="(item, index) in detailsInfo.styleList"
:key="index"
class="text-[12px] bg-[#ebf2fe] p-1 px-2 text-[#557abf] mr-4 rounded-md"
>
{{ item }}
</div>
</div>
<div class="flex w-full gap-1">
<div class="w-2/3">
<div class="w-full">
<n-tabs v-model:value="activeTab" type="line" animated>
<n-tab-pane
v-for="(item, index) in versionByWorkInfo"
:key="index"
:name="item.id"
:tab="item.versionName"
>
<!-- 显示最后一步上传图片的图片 -->
<div class="grid grid-cols-2 gap-2.5 box-border">
<img
v-for="(subItem, subIndex) in item.sampleImagePaths"
:key="subIndex"
:src="subItem"
class="w-full h-[300px]"
alt=""
>
</div>
<div v-if="detailsInfo.original === 1" class="font-bold text-[20px] my-6">
转载自作者: {{ detailsInfo.authorName }}
</div>
<!-- 富文本中输入的文字 图片 -->
<div class="w-full mt-2">
<div v-html="item.versionDescription" />
</div>
</n-tab-pane>
</n-tabs>
</div>
<div class="mt-4">
<div style="padding: 20px">
<NConfigProvider>
<NMessageProvider>
<BaseComment v-if="detailsInfo.id" type="model" :height="commentHeight" :details-info="detailsInfo" />
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</div>
<div class="w-1/3 mt-3">
<div
class="flex justify-between text-[#a3a1a1] text-[12px] items-center -ml-60"
>
<div class="flex justify-end">
<div v-if="detailsInfo.createTime" class="mr-2">
首发时间{{ detailsInfo.createTime }}
</div>
<div v-if="detailsInfo.updateTime">
更新时间{{ detailsInfo.updateTime }}
</div>
</div>
<div class="flex items-center relative">
<n-icon size="20" :color="detailsInfo.isLike === 0 ? '#ccc' : '#ff0000'" @click="onLike">
<Heart class="cursor-pointer" />
</n-icon>
<div class="group relative">
<component
:is="EllipsisVertical"
class="h-[18px] w-[48px] text-[#557abf] cursor-pointer"
/>
<div
class="absolute right-0 top-[10px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
>
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'report')">
举报
</div>
<div
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
@click="(event) => handleSelect(event, 'edit')"
>
编辑
</div>
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'delete')">
删除
</div>
</div>
</div>
</div>
</div>
<div
class="flex items-center mt-10 p-2 bg-[#f3f5f9] w-full rounded-md h-[80px] box-border"
>
<div class="w-[70px] h-[70px] rounded-full overflow-hidden mr-4">
<client-only>
<NAvatar
class="w-full h-full mr-2 block"
round
size="small"
:src="currentUserInfo.avatar"
/>
</client-only>
</div>
<div class="flex-1">
<div class="flex items-center justify-between">
<div class="flex-1">
<client-only>
<div class="text[20px] font-bold">
{{ currentUserInfo.nickName }}
</div>
</client-only>
<!-- 0代表原创 1代表转载 -->
<div v-if="detailsInfo.original === 0" class="text-[14px]">
原创作者
</div>
</div>
<div class="flex-1 flex justify-end">
<div class="flex items-center font-bold px-1 justify-center w-24 h-10 rounded-full text-[#426af7] border-2 border-[#426af7] border-solid cursor-pointer" @click="onChangeAttention">
<n-icon v-if="detailsInfo.isAttention === 0" size="20" class="mr-2">
<PersonAddOutline />
</n-icon>
{{ detailsInfo.isAttention === 1 ? '已关注' : '关注' }}
</div>
</div>
</div>
<div class="flex items-center text-[#969798]">
<component
:is="CircleUser"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
/>
<span class="mr-2"> {{ selectUserInfo.bean }} </span>
<!-- <component
:is="HardDriveUpload"
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> 2 </span> -->
<component
:is="Play"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> {{ selectUserInfo.modelRunNum }} </span>
<component
:is="Download"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> {{ selectUserInfo.modelDownloadNum }} </span>
</div>
</div>
</div>
<!-- 不支持的bg #b1b2b2 -->
<div
class="flex items-center justify-center mt-4 w-full text-white bg-[#3c7af6] w-full rounded-md h-[50px] cursor-pointer"
>
<component
:is="Play"
class="h-[20px] w-[20px] text-white menu-icon m-1 text-[#969798]"
/>
<span class="mr-1"> 立即生图</span>
</div>
<div class="flex gap-y-2">
<div class="flex flex-1 items-center justify-center bg-[#eceef4] h-[50px] mt-4 mr-1 rounded-md">
<component
:is="SquarePlus"
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-1">加入模型课</span>
</div>
<div
class="flex flex-1 items-center bg-gradient-to-r from-[#ffe9c8] to-[#ffd264] justify-center ml-1 mt-4 text-black bg-[#eceef4] w-full rounded-md h-[50px] cursor-pointer"
>
<component
:is="Download"
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-1"> 下载 (122.22MB) </span>
</div>
</div>
<!-- <div style="background: linear-gradient(135deg,#3cc9ff, #8fa6ff, 41%, #d8b4ff 74%,#326bff)" class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#fff] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]">
<component :is="Download" class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]" />
<span class="mr-1">
下载客户端
</span>
</div> -->
<n-modal
v-model:show="isDelete"
:mask-closable="false"
preset="dialog"
title="提示!"
content="确定要将模型删除? 模型删除后无法找回"
negative-text="取消"
positive-text="确认"
@negative-click="onDelete"
@positive-click="onDelete"
/>
<div />
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.header-num {
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
}
.header-tag {
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
display: flex;
align-self: flex-start;
justify-content: space-between;
}
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab,
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab {
color: #949494;
}
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab--active,
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab--active {
color: #000;
font-weight: 700;
font-size: 24px;
}
.n-tabs .n-tabs-bar {
position: absolute;
bottom: 0;
height: 4px;
background: linear-gradient(90deg, #173eff 0%, #1b7dff 100%);
border-radius: 2px;
transition:
left 0.2s var(--n-bezier),
max-width 0.2s var(--n-bezier),
opacity 0.3s var(--n-bezier),
background-color 0.3s var(--n-bezier);
}
</style>

View File

@ -1,47 +1,246 @@
<script setup lang="ts">
import { ref } from 'vue'
import { commonApi } from "@/api/common";
import { ArrowBack, ArrowForward } from '@vicons/ionicons5';
import { ref } from "vue";
definePageMeta({
layout: 'default',
layout: "default",
});
const params = ref({
name: null,
order: null,
type: null
})
// const hotNameList = ref([
// '',
// '',
// '',
// '',
// '',
// '',
// '',
// 'XL',
// ])
const listRef = ref(null);
// function handleSearch() {
// onSearch()
// }
const modelCategoryList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "model_child_category" });
if (res.code === 200) {
modelCategoryList.value = res.data;
}
} catch (error) {
console.log(error);
}
}
getDictType();
const myItems = [1, 2, 3, 4, 5, 6, 7]
const scrollListRef = ref(null)
//
function handleChange(index) {
console.log('Current index:', index)
//
function onSearch() {
if (listRef.value) {
listRef.value?.initPageNUm();
}
}
//
function changeCategory(item:any){
params.value.type = item.dictValue
onSearch()
}
//
function handleItemClick({ index, item }) {
console.log('Clicked item:', item, 'at index:', index)
}
// function onHot(name:string){
// params.value.name = name
// onSearch()
// }
</script>
<template>
<div class="p-6">
<h1 class="text-2xl font-bold">
模型广场
</h1>
<!-- <ScrollListView
ref="scrollListRef"
title="My Scroll List"
:items="myItems"
:initial-index="2"
@change="handleChange"
@item-click="handleItemClick"
<div class="header flex h-[150px] border-b border-b-[#ebebeb]">
<div class="flex-1 py-2">
<div class="max-w-4xl mx-auto">
<div class="relative flex items-center w-full">
<!-- 搜索输入框 -->
<input
type="text"
class="w-full py-3 px-6 text-lg text-gray-600 placeholder-gray-400 bg-white border-2 border-blue-500 rounded-full focus:outline-none focus:border-blue-600"
placeholder="搜索模型/图片/创作者寻找灵感"
@keyup.enter="onSearch"
v-model="params.name"
/>
<!-- 相机图标 -->
<button class="absolute right-24 p-2 mr-2 text-gray-400 hover:text-gray-600">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<template #item="{ item }">
<div class="custom-item">
<span>{{ item }}</span>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button>
<!-- 搜索按钮 -->
<button
class="absolute right-0 px-8 py-3 mr-2 text-white bg-blue-500 rounded-full hover:bg-blue-600 focus:outline-none"
@click="onSearch"
>
搜索
</button>
</div>
</div>
<!-- <div class="flex p-3">
<div v-for="(item, index) in hotNameList" @click="onHot(item)" :key="index" class="mr-3 mb-2 cursor-pointer">
{{ item }}
</div>
</div> -->
</div>
<div class="w-[500px] ml-4">
<!-- <img
class="h-[130px] w-full rounded-lg"
src="https://img.zcool.cn/tubelocation/e41767ac6706cd0109100099e04b.jpg?imageMogr2/format/webp"
alt=""
/> -->
<n-carousel show-arrow autoplay>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
/>
<template #arrow="{ prev, next }">
<div class="custom-arrow">
<button type="button" class="custom-arrow--left" @click="prev">
<n-icon><ArrowBack /></n-icon>
</button>
<button type="button" class="custom-arrow--right" @click="next">
<n-icon><ArrowForward /></n-icon>
</button>
</div>
</template>
</ScrollListView> -->
<!-- <button @click="handleTest"></button>
<client-only>
{{ useUser.brief }}
</client-only> -->
<!-- 这里添加模型广场的具体内容 -->
<template #dots="{ total, currentIndex, to }">
<ul class="custom-dots">
<li
v-for="index of total"
:key="index"
:class="{ ['is-active']: currentIndex === index - 1 }"
@click="to(index - 1)"
/>
</ul>
</template>
</n-carousel>
</div>
</div>
<div class="flex flex-wrap">
<div class="flex flex-wrap">
<div
v-for="(item, index) in modelCategoryList"
:key="index"
:style="{color: item.dictValue === params.type ? '#000' : '#6f6f6f'}"
@click="changeCategory(item)"
class="pt-4 mr-4 cursor-pointer"
>
{{ item.dictLabel }}
</div>
</div>
</div>
<div class="flex flex-wrap">
<ModelList ref="listRef" :params="params"></ModelList>
</div>
</div>
</template>
<style lang="scss" scoped>
.grid > div {
transition: transform 0.2s;
}
.grid > div:hover {
transform: translateY(-2px);
}
.carousel-img {
width: 100%;
height: 120px;
border-radius: 6px;
object-fit: cover;
}
.custom-arrow {
display: flex;
position: absolute;
bottom: 25px;
right: 10px;
}
.custom-arrow button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
margin-right: 12px;
color: #fff;
background-color: rgba(255, 255, 255, 0.1);
border-width: 0;
border-radius: 8px;
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-arrow button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.custom-arrow button:active {
transform: scale(0.95);
transform-origin: center;
}
.custom-dots {
display: flex;
margin: 0;
padding: 0;
position: absolute;
bottom: 20px;
left: 20px;
}
.custom-dots li {
display: inline-block;
width: 12px;
height: 4px;
margin: 0 3px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.4);
transition:
width 0.3s,
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-dots li.is-active {
width: 40px;
background: #fff;
}
</style>

View File

@ -3,6 +3,7 @@ import { commonApi } from '@/api/common'
import EditUserInfo from '@/components/EditUserInfo.vue'
import { useUserStore } from '@/stores/user'
import { formatDate } from '@/utils/index.ts'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
const loading = ref(false)
@ -341,6 +342,11 @@ const attentionIsVisible = ref<boolean>(false)
function onCloseAttentionModel() {
attentionIsVisible.value = false
}
function onClearDate(){
publishParams.value.startTime = '',
publishParams.value.endTime = '',
initPageNUm()
}
</script>
<template>
@ -492,6 +498,7 @@ function onCloseAttentionModel() {
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:on-clear="onClearDate"
clearable
@update:value="changeDate"
/>
@ -511,10 +518,13 @@ function onCloseAttentionModel() {
/>
</div>
</div>
<!-- Dialog Components -->
<NConfigProvider>
<NMessageProvider>
<Authentication ref="authenticationRef" />
<EditUserInfo ref="editUserInfoRef" />
</NMessageProvider>
</NConfigProvider>
<!-- Dialog Components -->
<div class="login-content my-4 grid grid-cols-4 gap-4 px-5">
<PersonalCenterCard

View File

@ -0,0 +1,189 @@
<script setup lang="ts">
import { commonApi } from "@/api/common";
import { ArrowBack, ArrowForward } from "@vicons/ionicons5";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { ref } from "vue";
definePageMeta({
layout: "default",
});
const params = ref({
name: null,
order: null,
type: null,
});
const listRef = ref(null);
// function handleSearch() {
// onSearch()
// }
const modelCategoryList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "image_label" });
if (res.code === 200) {
modelCategoryList.value = res.data;
modelCategoryList.value.unshift({
dictValue: null,
dictLabel: "全部",
});
}
} catch (error) {
console.log(error);
}
}
getDictType();
//
function onSearch() {
if (listRef.value) {
listRef.value?.initPageNUm();
}
}
//
function changeCategory(item: any) {
params.value.type = item.dictValue;
onSearch();
}
</script>
<template>
<div class="p-6">
<div class="w-full">
<n-carousel show-arrow autoplay>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
/>
<template #arrow="{ prev, next }">
<div class="custom-arrow">
<button type="button" class="custom-arrow--left" @click="prev">
<n-icon><ArrowBack /></n-icon>
</button>
<button type="button" class="custom-arrow--right" @click="next">
<n-icon><ArrowForward /></n-icon>
</button>
</div>
</template>
<template #dots="{ total, currentIndex, to }">
<ul class="custom-dots">
<li
v-for="index of total"
:key="index"
:class="{ ['is-active']: currentIndex === index - 1 }"
@click="to(index - 1)"
/>
</ul>
</template>
</n-carousel>
</div>
<div class="flex items-center mt-4">
<div class="text-[20px] mr-4">作品灵感</div>
<HeaderSearchInput v-model="params.name" @search="onSearch" />
</div>
<div class="flex flex-wrap mt-2">
<div class="flex flex-wrap">
<div
v-for="(item, index) in modelCategoryList"
:key="index"
:style="{
color: item.dictValue === params.type ? '#fff' : '#000',
background: item.dictValue === params.type ? '#000' : '#fff',
}"
@click="changeCategory(item)"
class="px-2 py-1 rounded-full mr-4 cursor-pointer"
>
{{ item.dictLabel }}
</div>
</div>
</div>
<div class="flex flex-wrap">
<NConfigProvider>
<NMessageProvider>
<PictureList ref="listRef" :params="params" />
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</template>
<style lang="scss" scoped>
.grid > div {
transition: transform 0.2s;
}
.grid > div:hover {
transform: translateY(-2px);
}
.carousel-img {
width: 100%;
height: 180px;
border-radius: 10px;
object-fit: cover;
}
.custom-arrow {
display: flex;
position: absolute;
bottom: 25px;
right: 10px;
}
.custom-arrow button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
margin-right: 12px;
color: #fff;
background-color: rgba(255, 255, 255, 0.1);
border-width: 0;
border-radius: 8px;
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-arrow button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.custom-arrow button:active {
transform: scale(0.95);
transform-origin: center;
}
.custom-dots {
display: flex;
margin: 0;
padding: 0;
position: absolute;
bottom: 20px;
left: 20px;
}
.custom-dots li {
display: inline-block;
width: 12px;
height: 4px;
margin: 0 3px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.4);
transition: width 0.3s, background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-dots li.is-active {
width: 40px;
background: #fff;
}
</style>

View File

@ -1,215 +1,166 @@
<script setup lang="ts">
import type { FormInst, FormItemRule } from 'naive-ui'
import CreateModels from '@/components/publishModel/CreateModels.vue'
import EditVersion from '@/components/publishModel/EditVersion.vue'
import CreateModels from "@/components/publishModel/CreateModels.vue";
import EditVersion from "@/components/publishModel/EditVersion.vue";
import UploadImg from "@/components/publishModel/UploadImg.vue";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { useRoute } from "vue-router";
const step2Ref = ref(null)
const currentStep = ref(2)
const route = useRoute();
const { type, id } = route.query;
const step2Ref = ref(null);
const currentStep = ref(1);
const formData = ref({
modelProduct: {},
modelVersionList: [],
});
async function initFormData() {
if (type === "add") {
formData.value = {
modelProduct: {
modelName: '模型名称',
modelName: "",
modelType: null, // ,
category: null, //
functions: null, // '',
tags: null, // string
tags: '', // string
tagsList:[],
activityId: null, // string
isOriginal: 1,
originalAuthorName: '',
isOriginal: 1, //
originalAuthorName: "",
},
modelVersionList: [
{
versionName: '', //
modelId: null, //
versionDescription: '"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //
filePath: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //
fileName: '这是一个文件名文件名。。。。这是一个文件名文件这是一个文件名文件这是一个文件名文件这是一个文件名文件', //
sampleImagePaths: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', // 20,
triggerWords: '触发词', //
isPublic: 1, // 1 2
isFree: 0, // 0
allowFusion: 1, //
isOnlineUse: 0, // 线使
delFlag: "0", // 0 2
versionName: "", //
modelVersionType: null, //
filePath: "", //
fileName: "", //
versionDescription: "", //
sampleImagePaths: [], // 20,
triggerWords: "", //
isPublic: 1, // 1 0
isFree: 0, //
isOnlineUse: 1, // 线使
allowDownloadImage: 1, //
allowUsage: 1, // 使
allowSoftwareUse: 1, // 使
allowFusion: 1, //
allowCommercialUse: 1, //
//
isExclusiveModel: 0, //
hideImageGenInfo:0, //
},
],
})
async function addWorkflow() {
};
} else {
try {
const params = {
modelProduct: {
modelName: '模型名称',
modelTypeId: 1, // ,
category: '1', //
functions: '1', // '',
tags: '11', // string
activityId: '1', // string
isOriginal: 1, // ??? 01
originalAuthorName: '作者名称',
},
modelVersionList: [
{
versionName: '1.0', //
modelId: 1, //
versionDescription: '"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //
filePath: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //
fileName: '', //
sampleImagePaths: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', // 20,
triggerWords: '触发词', //
isPublic: 1, // 1 2
allowFusion: 1, //
isFree: 0, // 0
allowDownloadImage: 1, //
allowUsage: 1, // 使
allowSoftwareUse: 1, // 使
allowCommercialUse: 1, //
//
isExclusiveModel: 1, //
},
],
const res = await request.get(`/model/finbyid?id=${id}`);
formData.value = res.data;
if(res.data.modelProduct.modelType && res.data.modelProduct.modelType != 0){
formData.value.modelProduct.modelType = res.data.modelProduct.modelType.toString();
}
const res = await request.post('/model/insert', params)
if(res.data.modelProduct.activityId && res.data.modelProduct.activityId != 0){
formData.value.modelProduct.activityId = Number(res.data.modelProduct.activityId);
}
if(formData.value.modelProduct.tags){
formData.value.modelProduct.tagsList = JSON.parse(formData.value.modelProduct.tags);
}else{
formData.value.modelProduct.tagsList = []
}
if(formData.value.modelVersionList.length > 0){
for(let i = 0; i < formData.value.modelVersionList.length; i ++){
formData.value.modelVersionList[i].sampleImagePaths = formData.value.modelVersionList[i].sampleImagePaths.split(',')
formData.value.modelVersionList[i].modelVersionType = formData.value.modelVersionList[i].modelVersionType.toString()
}
}
// if (formData.value.workFlow.type) {
// formData.value.workFlow.typeList = formData.value.workFlow.type.split(',')
// }
// else {
// formData.value.workFlow.typeList = []
// }
// if (formData.value.workFlow.activityParticipation) {
// formData.value.workFlow.activityParticipation = Number(formData.value.workFlow.activityParticipation)
// }
// for (let i = 0; i < formData.value.workFlowVersionList.length; i++) {
// if (formData.value.workFlowVersionList[i].imagePaths) {
// formData.value.workFlowVersionList[i].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(',')
// }
// }
} catch (error) {
console.log(error);
}
catch (error) {
console.log(error)
}
}
function createModelsNext() {
currentStep.value = 2
initFormData();
const timeLineList = ref([
{
name: "创建模型",
index: 1,
},
{
name: "编辑版本",
index: 2,
},
{
name: "上传图片",
index: 3,
},
]);
function nextStep() {
currentStep.value += 1;
}
function prevStep() {
currentStep.value -= 1;
}
function handleAddVersion() {
if (step2Ref.value) {
step2Ref.value?.addVersion()
step2Ref.value?.addVersion();
}
}
// const handleValidateButtonClick = (e: MouseEvent) => {
// e.preventDefault();
// formRef.value?.validate((errors) => {
// if (!errors) {
// message.success("");
// } else {
// console.log(errors);
// message.error("");
// }
// });
// };
</script>
<template>
<div class="mx-auto py-6">
<div class="container mx-auto w-[700px]">
<div class="step-line flex">
<div class="mr-10">
1创建模版
<div class="flex items-center justify-between mb-4">
<div class="w-[60%]">
<TimeLine :time-line-list="timeLineList" :current-step="currentStep" />
</div>
<div class="mr-10">
2编辑版本
</div>
<div class="mr-10">
3上传图片
</div>
<div v-if="currentStep === 2" @click="handleAddVersion">
<div
v-if="currentStep === 2"
class="cursor-pointer flex items-center justify-center rounded-full border border-solid border-[#000] px-4 py-1"
@click="handleAddVersion"
>
<CirclePlus class="mr-2" />
添加版本
</div>
</div>
<div class="form-container">
<NConfigProvider>
<NMessageProvider>
<div v-if="currentStep === 1" class="first-step">
<CreateModels v-model="formData" @create-models-next="createModelsNext" />
<CreateModels v-model="formData" @create-models-next="nextStep" />
</div>
<div v-if="currentStep === 2" class="second-step">
<EditVersion ref="step2Ref" v-model="formData" />
<EditVersion
ref="step2Ref"
v-model="formData"
@prev-step="prevStep"
@next-step="nextStep"
/>
</div>
<div v-if="currentStep === 3" class="third-step">
3
<UploadImg v-model="formData" @pre-step="prevStep" />
</div>
</NMessageProvider>
</NConfigProvider>
</div>
</div>
<!-- <div class="flex items-center justify-center mt-4">
<div>
<div class="flex justify-center items-center mt-5 text-white w-30 h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
下一步
</div>
</div>
</div> -->
</div>
<!-- <n-form
ref="formRef"
:model="formData"
:rules="rules"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
size="large"
:style="{
maxWidth: '640px',
}"
>
<n-form-item label="模型名称" path="inputValue">
<n-input v-model:value="formData.inputValue" placeholder="Input" />
</n-form-item>
<n-form-item label="模型类型" path="inputValue">
<n-select v-model:value="formData.value" :options="cuileioptions" />
</n-form-item>
<n-form-item label="Checkbox Group" path="checkboxGroupValue">
<n-checkbox-group v-model:value="formData.checkboxGroupValue">
<n-space>
<n-checkbox value="Option 1">
Option 1
</n-checkbox>
<n-checkbox value="Option 2">
Option 2
</n-checkbox>
<n-checkbox value="Option 3">
Option 3
</n-checkbox>
</n-space>
</n-checkbox-group>
</n-form-item>
<n-form-item label="Radio Group" path="radioGroupValue">
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup1">
<n-space>
<n-radio value="Radio 1">
Radio 1
</n-radio>
<n-radio value="Radio 2">
Radio 2
</n-radio>
<n-radio value="Radio 3">
Radio 3
</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="Radio Button Group" path="radioGroupValue">
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup2">
<n-radio-button value="Radio 1">
Radio 1
</n-radio-button>
<n-radio-button value="Radio 2">
Radio 2
</n-radio-button>
<n-radio-button value="Radio 3">
Radio 3
</n-radio-button>
</n-radio-group>
</n-form-item>
<div style="display: flex; justify-content: flex-end">
<n-button round type="primary" @click="addWorkflow">
验证
</n-button>
</div>
</n-form> -->
</div>
<!-- <pre
>{{ JSON.stringify(model, null, 2) }}
</pre> -->
</template>

View File

@ -1,24 +1,18 @@
<script setup lang="ts">
import EditVersion from '@/components/publishWorkFlow/EditVersion.vue'
import EditWorkFlow from '@/components/publishWorkFlow/EditWorkFlow.vue'
import UploadImg from '@/components/publishWorkFlow/UploadImg.vue'
import { CirclePlus } from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { useRoute } from 'vue-router'
// definePageMeta({
// middleware: [
// function (to, from) {
// initFormData(to.query.type)
// },
// ],
// })
const route = useRoute()
const { type, id } = route.query
import EditVersion from "@/components/publishWorkFlow/EditVersion.vue";
import EditWorkFlow from "@/components/publishWorkFlow/EditWorkFlow.vue";
import UploadImg from "@/components/publishWorkFlow/UploadImg.vue";
import { CirclePlus } from "lucide-vue-next";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { useRoute } from "vue-router";
const formData = ref()
const route = useRoute();
const { type, id } = route.query;
const formData = ref();
async function initFormData() {
if (type === 'add') {
if (type === "add") {
formData.value = {
workFlow: {
jurisdiction: 1, // auditStatus 0 1- 2- 3 4
@ -30,72 +24,72 @@ async function initFormData() {
},
workFlowVersionList: [
{
versionName: '',
versionName: "",
hideGenInfo: 0, //
versionDescription: '', //
filePath: '', //
fileName: '', //
delFlag: '0', // 0 2
versionDescription: "", //
filePath: "", //
fileName: "", //
delFlag: "0", // 0 2
imagePaths: [],
},
],
}
}
else {
};
} else {
// type 1
try {
const res = await request.get(`/WorkFlow/selectWorkFlowVersionById?id=${id}`)
formData.value = res.data
const res = await request.get(`/WorkFlow/selectWorkFlowVersionById?id=${id}`);
formData.value = res.data;
if (formData.value.workFlow.type) {
formData.value.workFlow.typeList = formData.value.workFlow.type.split(',')
}
else {
formData.value.workFlow.typeList = []
formData.value.workFlow.typeList = formData.value.workFlow.type.split(",");
} else {
formData.value.workFlow.typeList = [];
}
if (formData.value.workFlow.activityParticipation) {
formData.value.workFlow.activityParticipation = Number(formData.value.workFlow.activityParticipation)
formData.value.workFlow.activityParticipation = Number(
formData.value.workFlow.activityParticipation
);
}
if (formData.value.workFlowVersionList.length > 0) {
for (let i = 0; i < formData.value.workFlowVersionList.length; i++) {
if (formData.value.workFlowVersionList[i].imagePaths) {
formData.value.workFlowVersionList[i].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(',')
formData.value.workFlowVersionList[
i
].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(",");
}
}
// else {
// formData.value.workFlow.activityParticipationList = []
// }
}
catch (error) {
console.log(error)
} catch (error) {
console.log(error);
}
}
}
initFormData()
const EditVersionRef = ref()
const currentStep = ref(1)
initFormData();
const EditVersionRef = ref();
const currentStep = ref(1);
function handleAddVersion() {
EditVersionRef.value.addVersion()
EditVersionRef.value.addVersion();
}
function nextStep() {
currentStep.value += 1
currentStep.value += 1;
}
function preStep() {
currentStep.value -= 1
currentStep.value -= 1;
}
const timeLineList = ref([
{
name: '创建模版',
name: "编辑工作流",
index: 1,
},
{
name: '编辑版本',
name: "编辑版本",
index: 2,
},
{
name: '上传图片',
name: "上传图片",
index: 3,
},
])
]);
</script>
<template>
@ -106,7 +100,11 @@ const timeLineList = ref([
<div class="w-[60%]">
<TimeLine :time-line-list="timeLineList" :current-step="currentStep" />
</div>
<div v-if="currentStep === 2" class="cursor-pointer flex items-center justify-center rounded-full border border-solid border-[#000] px-4 py-1" @click="handleAddVersion">
<div
v-if="currentStep === 2"
class="cursor-pointer flex items-center justify-center rounded-full border border-solid border-[#000] px-4 py-1"
@click="handleAddVersion"
>
<CirclePlus class="mr-2" />
添加版本
</div>
@ -118,7 +116,12 @@ const timeLineList = ref([
<EditWorkFlow v-model="formData" @next-step="nextStep" />
</div>
<div v-if="currentStep === 2" class="second-step">
<EditVersion ref="EditVersionRef" v-model="formData" @pre-step="preStep" @next-step="nextStep" />
<EditVersion
ref="EditVersionRef"
v-model="formData"
@pre-step="preStep"
@next-step="nextStep"
/>
</div>
<div v-if="currentStep === 3" class="third-step">

View File

@ -0,0 +1,188 @@
<script setup lang="ts">
import { commonApi } from "@/api/common";
import { ArrowBack, ArrowForward } from "@vicons/ionicons5";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { ref } from "vue";
definePageMeta({
layout: "default",
});
const params = ref({
name: null,
order: null,
type: null,
});
const listRef = ref(null);
// function handleSearch() {
// onSearch()
// }
const workFlowCategoryList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "work_flow_type_child" });
if (res.code === 200) {
workFlowCategoryList.value = res.data;
workFlowCategoryList.value.unshift({
dictValue: null,
dictLabel: "全部",
});
}
} catch (error) {
console.log(error);
}
}
getDictType();
//
function onSearch() {
if (listRef.value) {
listRef.value?.initPageNUm();
}
}
//
function changeCategory(item: any) {
params.value.type = item.dictValue;
onSearch();
}
</script>
<template>
<div class="p-6">
<div class="w-full">
<n-carousel show-arrow autoplay>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
/>
<img
class="carousel-img"
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
/>
<template #arrow="{ prev, next }">
<div class="custom-arrow">
<button type="button" class="custom-arrow--left" @click="prev">
<n-icon><ArrowBack /></n-icon>
</button>
<button type="button" class="custom-arrow--right" @click="next">
<n-icon><ArrowForward /></n-icon>
</button>
</div>
</template>
<template #dots="{ total, currentIndex, to }">
<ul class="custom-dots">
<li
v-for="index of total"
:key="index"
:class="{ ['is-active']: currentIndex === index - 1 }"
@click="to(index - 1)"
/>
</ul>
</template>
</n-carousel>
</div>
<div class="flex items-center mt-4">
<div class="text-[20px] mr-4">工作流</div>
<HeaderSearchInput v-model="params.name" @search="onSearch" />
</div>
<div class="flex flex-wrap mt-2">
<div class="flex flex-wrap">
<div
v-for="(item, index) in workFlowCategoryList"
:key="index"
:style="{
color: item.dictValue === params.type ? '#fff' : '#000',
background: item.dictValue === params.type ? '#000' : '#fff',
}"
@click="changeCategory(item)"
class="px-2 py-1 rounded-full mr-4 cursor-pointer"
>
{{ item.dictLabel }}
</div>
</div>
</div>
<div class="flex flex-wrap">
<NConfigProvider>
<NMessageProvider>
<WorkFlowList ref="listRef" :params="params" />
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</template>
<style lang="scss" scoped>
.grid > div {
transition: transform 0.2s;
}
.grid > div:hover {
transform: translateY(-2px);
}
.carousel-img {
width: 100%;
height: 180px;
border-radius: 10px;
object-fit: cover;
}
.custom-arrow {
display: flex;
position: absolute;
bottom: 25px;
right: 10px;
}
.custom-arrow button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
margin-right: 12px;
color: #fff;
background-color: rgba(255, 255, 255, 0.1);
border-width: 0;
border-radius: 8px;
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-arrow button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.custom-arrow button:active {
transform: scale(0.95);
transform-origin: center;
}
.custom-dots {
display: flex;
margin: 0;
padding: 0;
position: absolute;
bottom: 20px;
left: 20px;
}
.custom-dots li {
display: inline-block;
width: 12px;
height: 4px;
margin: 0 3px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.4);
transition: width 0.3s, background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.custom-dots li.is-active {
width: 40px;
background: #fff;
}
</style>

View File

@ -2,11 +2,9 @@
import { Heart, PersonAddOutline } from '@vicons/ionicons5'
import {
CircleUser,
Download,
EllipsisVertical,
HardDriveUpload,
Play,
CircleUser,
Download,
EllipsisVertical, Play
} from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { nextTick, ref } from 'vue'
@ -64,6 +62,7 @@ async function getInfo() {
catch (error) {
console.log(error)
}
getAttention()
}
}
catch (error) {
@ -89,7 +88,7 @@ const selectUserInfo = ref<SelectUserInfo>({
//
async function getAttention() {
try {
const res = await request.get('/attention/selectUserInfo')
const res = await request.get(`/attention/selectUserInfo?userId=${detailsInfo.value.userId}`)
if (res.code === 200) {
selectUserInfo.value = res.data
}
@ -98,7 +97,6 @@ async function getAttention() {
console.log(err)
}
}
getAttention()
// //
const isDelete = ref(false)
@ -361,21 +359,21 @@ async function onLike() {
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
/>
<span class="mr-2"> {{ selectUserInfo.bean }} </span>
<component
<!-- <component
:is="HardDriveUpload"
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> 2 </span>
<span class="mr-2"> 2 </span> -->
<component
:is="Play"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> {{ detailsInfo.useNumber }} </span>
<span class="mr-2"> {{ selectUserInfo.modelRunNum }} </span>
<component
:is="Download"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
/>
<span class="mr-2"> {{ detailsInfo.downloadNumber }} </span>
<span class="mr-2"> {{ selectUserInfo.modelDownloadNum }} </span>
</div>
</div>
</div>
@ -411,7 +409,7 @@ async function onLike() {
:mask-closable="false"
preset="dialog"
title="提示!"
content="确定要将模型删除? 模型删除后无法找回"
content="确定要将工作流删除? 工作流删除后无法找回"
negative-text="取消"
positive-text="确认"
@negative-click="onDelete"

View File

@ -4,8 +4,10 @@ export const useMenuStore = defineStore('menu', () => {
const activeMenu = ref('/model-square')
const menuItems = [
{ path: '/model-square', icon: 'i-carbon-gallery', label: '模型广场' },
{ path: '/picture-square', icon: 'i-carbon-light-filled', label: '作品灵感' },
// { path: '/works-inspire', icon: 'i-carbon-light-filled', label: '作品灵感' },
// { path: '/workspace', icon: 'i-carbon-workspace', label: '工作台' },
// { path: '/workspace', icon: 'i-carbon-workspace', label: '工作流' },
{ path: '/work-square', icon: 'i-carbon-workspace', label: '工作流' },
// { path: '/web-ui', icon: 'i-carbon-image-search', label: '在线生图' },
// { path: '/comfy-ui', icon: 'i-carbon-image-search', label: '在线工作流' },
// { path: '/training-lora', icon: 'i-carbon-machine-learning', label: '训练LoRA' },

View File

@ -100,7 +100,7 @@ class RequestHttp {
message.error('请求参数错误')
break
case 401:
message.error('未登录或登录已过期')
// message.error('未登录或登录已过期')
break
case 403:
message.error('没有权限')
@ -108,9 +108,9 @@ class RequestHttp {
case 404:
message.error('请求的资源不存在')
break
case 500:
message.error('服务器错误')
break
// case 500:
// message.error('服务器错误')
// break
default:
message.error('未知错误')
}

0
netlify.toml 100755 → 100644
View File

View File

@ -84,8 +84,8 @@ export default defineNuxtConfig({
devProxy: {
'/api': {
// target: 'http://1.13.246.108:8080', // 线上
target: 'http://192.168.2.10:8080', // 代
// target: 'http://192.168.1.69:8080', // 嗨
// target: 'http://192.168.2.10:8080', // 代
target: 'http://192.168.1.69:8080', // 嗨
// target: 'https://2d1a399f.r27.cpolar.top', // 嗨
changeOrigin: true,
prependPath: true,