初始化

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'] NAvatar: typeof import('naive-ui')['NAvatar']
NBadge: typeof import('naive-ui')['NBadge'] NBadge: typeof import('naive-ui')['NBadge']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NCarousel: typeof import('naive-ui')['NCarousel']
NCascader: typeof import('naive-ui')['NCascader'] NCascader: typeof import('naive-ui')['NCascader']
NCheckbox: typeof import('naive-ui')['NCheckbox'] NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker'] NDatePicker: typeof import('naive-ui')['NDatePicker']
NDropdown: typeof import('naive-ui')['NDropdown'] NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NFor: typeof import('naive-ui')['NFor'] NFor: typeof import('naive-ui')['NFor']
NForm: typeof import('naive-ui')['NForm'] NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']

View File

@ -1,52 +1,55 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import type { FormItemRule } from "naive-ui";
import { ref } from "vue";
const message = useMessage() const message = useMessage();
const userStore = useUserStore() const userStore = useUserStore();
const userInfo = userStore.userInfo const userInfo = userStore.userInfo;
const isVisible = ref(false) const isVisible = ref(false);
const rules = ref({ const rules = ref({
name: [ name: [
{ {
required: true, required: true,
validator(value: string) { validator(rule: FormItemRule, value: string) {
if (!value) { 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 true;
return new Error('输入正确的姓名')
}
return true
}, },
trigger: 'blur', trigger: "blur",
}, },
], ],
idCard: [ idCard: [
{ {
required: true, required: true,
validator(value: string) { validator(rule: FormItemRule, value: string) {
if (!value) { 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 true;
return new Error('输入正确的身份证号')
}
return true
}, },
trigger: 'blur', trigger: "blur",
}, },
], ],
}) });
const ruleForm = ref({ const ruleForm = ref({
name: '', name: "",
idCard: '', idCard: "",
userId: userInfo.userId, userId: userInfo.userId,
}) });
const formRef = ref(null) const formRef = ref(null);
async function saveInfo(e: MouseEvent) { async function saveInfo(e: MouseEvent) {
e.preventDefault() e.preventDefault();
formRef.value?.validate(async (errors) => { formRef.value?.validate(async (errors) => {
if (!errors) { if (!errors) {
// try{ // try{
@ -61,27 +64,33 @@ async function saveInfo(e: MouseEvent) {
// }catch(err) { // }catch(err) {
// console.log('err',err) // console.log('err',err)
// } // }
await request.post('/system/user/updateIdCard', ruleForm.value) try {
await userStore.getUserInfo() const res = await request.post("/system/user/updateIdCard", ruleForm.value);
isVisible.value = false if (res.code === 200) {
await userStore.getUserInfo();
isVisible.value = false;
} else {
message.warning(res.msg);
}
} catch (err) {
console.log(err);
}
} }
else { });
}
})
} }
function onCloseModel() { function onCloseModel() {
isVisible.value = false isVisible.value = false;
ruleForm.value.name = '' ruleForm.value.name = "";
ruleForm.value.idCard = '' ruleForm.value.idCard = "";
} }
defineExpose({ defineExpose({
isVisible, isVisible,
}) });
// //
onMounted(() => { onMounted(() => {
// initFormData() // initFormData()
}) });
</script> </script>
<template> <template>
@ -94,14 +103,25 @@ onMounted(() => {
> >
<n-form ref="formRef" :model="ruleForm" :rules="rules"> <n-form ref="formRef" :model="ruleForm" :rules="rules">
<n-form-item path="name" label="姓名"> <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>
<n-form-item path="idCard" label="身份证号"> <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-item>
</n-form> </n-form>
<div class="flex justify-center items-center "> <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"> <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> </button>
</div> </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"> <script setup lang="ts">
// import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui' // import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui'
import { import {
ThumbsUp, ThumbsUp
} from 'lucide-vue-next' } from 'lucide-vue-next';
const props = defineProps({ const props = defineProps({
height: { height: {
@ -45,30 +45,48 @@ const sortType = ref(1)
// //
const urlList = ref({ const urlList = ref({
workflow: '/WorkFlowComment/comment?', workflow: '/WorkFlowComment/comment?',
pictrue: '/imageComment/comment?',
model: '/ModelComment/comment?',
}) })
// //
const likeList = ref({ // const likeList = ref({
workflow: '/WorkFlowComment/commentLike?', workflow: '/WorkFlowComment/commentLike?',
pictrue: '/imageComment/commentLike?',
model:'ModelComment/commentLike?'
}) })
// //
const sendMessageList = ref({ // const sendMessageList = ref({
workflow: '/WorkFlowComment/comment', workflow: '/WorkFlowComment/comment',
pictrue: '/imageComment/comment',
model: '/ModelComment/comment',
}) })
// //
const deleteList = ref({ // const deleteList = ref({
workflow: '/WorkFlowComment/commentDelete?', workflow: '/WorkFlowComment/commentDelete?',
pictrue: '/imageComment/commentDelete?',
model: '/ModelComment/commentDelete?',
}) })
// //
const commentNumUrl = ref({ // const commentNumUrl = ref({
workflow: '/WorkFlowComment/commentCount?workFlowId', workflow: '/WorkFlowComment/commentCount?workFlowId',
model: '/ModelComment/commentCount?modelId',
}) })
const commentCount = ref(0) const commentCount = ref(0)
// //
const commentList = ref([]) const commentList = ref([])
async function getCommentList() { async function getCommentList() {
try { 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++) { for (let i = 0; i < res.data.length; i++) {
res.data[i].isShowInput = false res.data[i].isShowInput = false
res.data[i].isShowSend = false res.data[i].isShowSend = false
@ -104,6 +122,11 @@ async function sendMessage(ele, index) {
commentParams.value.content = ele.word commentParams.value.content = ele.word
commentParams.value.parentId = commentList.value[index].commentId commentParams.value.parentId = commentList.value[index].commentId
commentParams.value.replyUserId = ele.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) const res = await request.post(sendMessageList.value[props.type], commentParams.value)
if (res.code === 200) { if (res.code === 200) {
message.success('评论成功!') message.success('评论成功!')
@ -126,7 +149,12 @@ async function sendMessage(ele, index) {
commentParams.value.parentId = '' commentParams.value.parentId = ''
commentParams.value.content = publicWord.value commentParams.value.content = publicWord.value
commentParams.value.replyUserId = '' 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) { if (res.code === 200) {
message.success('评论成功!') message.success('评论成功!')
publicWord.value = '' publicWord.value = ''
@ -145,8 +173,10 @@ async function sendMessage(ele, index) {
} }
} }
//
async function getCommentNum() { async function getCommentNum() {
try { if(props.type !== 'pictrue'){
try {
const res = await request.get(`${commentNumUrl.value[props.type]}=${props.detailsInfo.id}`) const res = await request.get(`${commentNumUrl.value[props.type]}=${props.detailsInfo.id}`)
if (res.code === 200) { if (res.code === 200) {
commentCount.value = res.data commentCount.value = res.data
@ -155,10 +185,12 @@ async function getCommentNum() {
catch (error) { catch (error) {
console.log(error) console.log(error)
} }
}
} }
getCommentNum() getCommentNum()
// / // /
async function handleFocus(item) { async function handleFocus(item:any) {
await request.get(`${likeList.value[props.type]}commentId=${item.commentId}`) await request.get(`${likeList.value[props.type]}commentId=${item.commentId}`)
if (item.isLike === 0) { if (item.isLike === 0) {
item.isLike = 1 item.isLike = 1
@ -173,14 +205,14 @@ async function handleFocus(item) {
} }
// //
function handleMessage(item) { function handleMessage(item:any) {
if (!item.isShowInput) { if (!item.isShowInput) {
item.word = '' item.word = ''
} }
item.isShowInput = !item.isShowInput item.isShowInput = !item.isShowInput
} }
// //
async function handleDel(item) { async function handleDel(item:any) {
try { try {
const res = await request.get(`${deleteList.value[props.type]}commentId=${item.commentId}`) const res = await request.get(`${deleteList.value[props.type]}commentId=${item.commentId}`)
if (res.code === 200) { if (res.code === 200) {
@ -207,11 +239,11 @@ function changeType(type: string) {
<div class="left text-[20px] mr-2"> <div class="left text-[20px] mr-2">
讨论 讨论
</div> </div>
<div class="text-[#999]"> <div class="text-[#999]" v-if="props.type !== 'pictrue'">
{{ commentCount }} {{ commentCount }}
</div> </div>
</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 class="cursor-pointer" :class="sortType === 0 ? '' : 'text-[#999]'" @click="changeType(0)">
最热 最热
</div> </div>
@ -245,7 +277,6 @@ function changeType(type: string) {
</div> </div>
</div> </div>
</div> </div>
<NInfiniteScroll :style="{ height: `${height}px` }" :distance="10" @load="handleLoad">
<div <div
v-for="(item, index) in commentList" v-for="(item, index) in commentList"
:key="index" :key="index"
@ -347,7 +378,6 @@ function changeType(type: string) {
<div class="no-more"> <div class="no-more">
暂时没有更多评论 暂时没有更多评论
</div> </div>
</NInfiniteScroll>
</div> </div>
</template> </template>
@ -363,8 +393,8 @@ function changeType(type: string) {
margin-right: 12px; margin-right: 12px;
border: 1px solid #2d28ff; border: 1px solid #2d28ff;
img { img {
width: 40px; width: 100%;
height: 40px; height: 100%;
border-radius: 50%; border-radius: 50%;
} }
} }
@ -424,8 +454,8 @@ function changeType(type: string) {
border-radius: 50%; border-radius: 50%;
margin-right: 12px; margin-right: 12px;
img { img {
width: 40px; width: 100%;
height: 40px; height: 100%;
border-radius: 50%; border-radius: 50%;
} }
} }
@ -490,8 +520,8 @@ function changeType(type: string) {
border-radius: 50%; border-radius: 50%;
margin-right: 12px; margin-right: 12px;
img { img {
width: 24px; width: 100%;
height: 24px; height: 100%;
border-radius: 50%; border-radius: 50%;
} }
} }

View File

@ -1,153 +1,171 @@
<script setup lang="ts"> <script setup lang="ts">
// //
import { headerRole } from '@/constants/index' import { headerRole } from "@/constants/index";
import { import {
Bell, Bell,
CirclePlus, CirclePlus,
GraduationCap, GraduationCap,
HardDriveUpload, HardDriveUpload,
Image, Image,
Monitor, Monitor,
Workflow, Workflow
} from 'lucide-vue-next' } from "lucide-vue-next";
import { NConfigProvider, NMessageProvider } from 'naive-ui' import { NConfigProvider, NMessageProvider } from "naive-ui";
// import { AtCircle } from '@vicons/ionicons5' // import { AtCircle } from '@vicons/ionicons5'
import { NIcon } from 'naive-ui' import { NIcon } from "naive-ui";
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from "vue-router";
const route = useRoute() const route = useRoute();
const router = useRouter() const router = useRouter();
const userStore = useUserStore() const userStore = useUserStore();
const modalStore = useModalStore() const modalStore = useModalStore();
const currentUseRoute = ref('') const currentUseRoute = ref("");
const isShowPublishPicture = ref<boolean>(false) const isShowPublishPicture = ref<boolean>(false);
const PublishPictureRef = ref<Payment | null>(null) const PublishPictureRef = ref<Payment | null>(null);
const publishPicture = ref({ const publishPicture = ref({
title: '', title: "",
tags: [], tags: [],
description: '', description: "",
imagePaths: [], imagePaths: [],
}) });
watch( watch(
() => route.path, // route.path () => route.path, // route.path
(newPath) => { (newPath) => {
currentUseRoute.value = newPath currentUseRoute.value = newPath;
}, },
{ immediate: true }, // { immediate: true } //
) );
function hasItem(path: string, list: any) { function hasItem(path: string, list: any) {
return !list.includes(path) return !list.includes(path);
} }
const searchText = ref('') const searchText = ref("");
function onSearch(value: any) { function onSearch(value: any) {
console.log('搜索:', value) console.log("搜索:", value);
// //
} }
// //
const notificationOptions = [ const notificationOptions = [
{ {
label: '系统通知', label: "系统通知",
key: 'system', key: "system",
}, },
{ {
label: '互动消息', label: "互动消息",
key: 'interaction', key: "interaction",
}, },
] ];
function renderIcon(icon: Component) { function renderIcon(icon: Component) {
return () => { return () => {
return h(NIcon, null, { return h(NIcon, null, {
default: () => h(icon), default: () => h(icon),
}) });
} };
} }
// //
const publishOptions = [ const publishOptions = [
{ {
label: '模型', label: "模型",
key: 'publish-model', key: "publish-model",
icon: renderIcon(HardDriveUpload), icon: renderIcon(HardDriveUpload),
}, },
{ {
label: '图片', label: "图片",
key: 'picture', key: "picture",
icon: renderIcon(Image), icon: renderIcon(Image),
}, },
{ {
label: '工作流', label: "工作流",
key: 'publish-workflow', key: "publish-workflow",
icon: renderIcon(Workflow), icon: renderIcon(Workflow),
}, },
] ];
const userOptions = ref([ const userOptions = ref([
{ {
label: '我的模型', label: "我的模型",
key: 'model', key: "model",
}, },
{ {
label: '我的作品', label: "我的作品",
key: 'project', key: "project",
}, },
{ {
label: '我的点赞', label: "我的点赞",
key: 'like', key: "like",
}, },
{ {
label: '账号设置', label: "账号设置",
key: 'userSettings', key: "userSettings",
}, },
{ {
label: '退出登录', label: "退出登录",
key: 'logout', key: "logout",
}, },
]) ]);
// //
async function handleUserSelect(key: string) { async function handleUserSelect(key: string) {
if (key === 'logout') { if (key === "logout") {
try { try {
await request.post('/logout') await request.post("/logout");
userStore.logout() userStore.logout();
navigateTo('/model-square') navigateTo("/model-square");
} } catch (error) {
catch (error) { console.error("Logout failed:", error);
console.error('Logout failed:', error)
} }
} }
} }
// //
async function handlePublishSelect(key: string) { async function handlePublishSelect(key: string) {
if (key === 'picture') { if (key === "picture") {
isShowPublishPicture.value = true isShowPublishPicture.value = true;
if (PublishPictureRef.value) { if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = true PublishPictureRef.value.isVisible = true;
} }
} } else {
else { const baseUrl = window.location.origin;
router.push({ window.open(`${baseUrl}/${key}?type=add`, "_blank", "noopener,noreferrer");
path: `/${key}`, // router.push({
query: { // path: `/${key}`,
type: 'add', // query: {
}, // type: 'add',
}) // },
// })
} }
} }
function closePublishImg() { function closePublishImg() {
isShowPublishPicture.value = false isShowPublishPicture.value = false;
if (PublishPictureRef.value) { if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = false PublishPictureRef.value.isVisible = false;
} }
} }
function handleLogin() { 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> </script>
<template> <template>
@ -201,14 +219,70 @@ onMounted(() => {
</NDropdown> </NDropdown>
<!-- Notifications --> <!-- Notifications -->
<NDropdown :options="notificationOptions" trigger="click"> <!-- :options="notificationOptions" -->
<!-- <NDropdown trigger="click">
<NBadge :value="5" :max="99" processing> <NBadge :value="5" :max="99" processing>
<NButton text circle> <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> </NButton>
</NBadge> </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 --> <!-- User -->
<div class="min-w-10"> <div class="min-w-10">
<client-only> <client-only>
@ -239,7 +313,13 @@ onMounted(() => {
<div> <div>
<NConfigProvider> <NConfigProvider>
<NMessageProvider> <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> </NMessageProvider>
</NConfigProvider> </NConfigProvider>
</div> </div>
@ -247,7 +327,7 @@ onMounted(() => {
</template> </template>
<style scoped> <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]; @apply border border-solid border-[#3162ff] px-1 py-1 text-[#3162ff] rounded-md hover:text-[#3162ff];
} }
.header-btn { .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"> <script setup lang="ts">
import { import {
CircleAlert, CircleAlert,
Download, Download,
EllipsisVertical, EllipsisVertical,
Play, Play
} from 'lucide-vue-next' } from 'lucide-vue-next';
import { ref } from 'vue' import { nextTick, ref } from 'vue';
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router';
const props = defineProps({ const props = defineProps({
item: { item: {
@ -36,6 +36,8 @@ function toDetails() {
else if (props.currentType === '1') { else if (props.currentType === '1') {
// console.log('object', 111); // console.log('object', 111);
router.push(`/workflow-details/${props.item.id}`) router.push(`/workflow-details/${props.item.id}`)
}else if(props.currentType === '2'){
onEditPicture()
} }
} }
@ -65,11 +67,11 @@ function handleSelect(event: Event, key: string) {
handleDelete() handleDelete()
} }
else if (key === 'edit') { else if (key === 'edit') {
if (props.currentType === '2') { if (props.currentType === '2') { //
getPublishPicture() getPublishPicture()
showPublishImg() showPublishImg()
} }
else if (props.currentType === '1') { else if (props.currentType === '1') { //
router.push({ router.push({
path: `/publish-workflow`, path: `/publish-workflow`,
query: { 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 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> </script>
<template> <template>
@ -310,13 +350,16 @@ function closePublishImg() {
:src="item.userAvatar" :src="item.userAvatar"
alt="" alt=""
> >
<span>{{ item.userName }}</span> <span>{{ item.userName }} </span>
</div> </div>
</div> </div>
</div> </div>
<NConfigProvider> <NConfigProvider>
<NMessageProvider> <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> </NMessageProvider>
</NConfigProvider> </NConfigProvider>
</div> </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"> <script setup lang="ts">
import { commonApi } from '@/api/common' import { commonApi } from '@/api/common';
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es';
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui';
import { defineProps, ref } from 'vue' import { defineProps, ref } from 'vue';
const props = defineProps({ const props = defineProps({
formData: { formData: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
type:{
type: String,
default: "",
}
}) })
debugger
const emit = defineEmits(['closePublishImg']) const emit = defineEmits(['closePublishImg'])
const message = useMessage() const message = useMessage()

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue';
const props = defineProps({ const props = defineProps({
title: { 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"> <script setup lang="ts">
import type { FormInst } from 'naive-ui' import { commonApi } from '@/api/common';
import { commonApi } from '@/api/common' import type { FormInst } from 'naive-ui';
import { computed, watch } from 'vue' import { computed, watch } from 'vue';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -29,23 +29,17 @@ watch(
}, },
{ immediate: true, deep: true }, { 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 formRef = ref<FormInst | null>(null)
const rules = { const rules = {
'modelProduct.modelName': { 'modelProduct.modelName': {
required: true, required: true,
message: '请输入模型名称', message: '',
trigger: 'blur', trigger: 'blur',
}, },
'modelProduct.modelType': { 'modelProduct.modelType': {
required: true, required: true,
message: '请输入模型名称', message: '',
trigger: 'blur', trigger: 'blur',
}, },
} }
@ -103,11 +97,11 @@ getActivityList()
const originalBtnList = ref([ const originalBtnList = ref([
{ {
label: '原创', label: '原创',
value: 0, value: 1,
}, },
{ {
label: '转载', label: '转载',
value: 1, value: 0,
}, },
]) ])
function nextStep() { function nextStep() {
@ -122,67 +116,80 @@ function nextStep() {
} }
function handleIsOriginal(value: number) { function handleIsOriginal(value: number) {
localForm.value.modelProduct.isOriginal = value localForm.value.modelProduct.isOriginal = value
if (value === 0) { // if (value === 0) {
localForm.value.modelProduct.originalAuthorName = '' // localForm.value.modelProduct.originalAuthorName = ''
} // }
}
function handleCategoryUpdateValue(value) {
//
// if (value) {
// //
// localForm.value.modelProduct.category = value.includes('-') ? value : `${value}-0`
// }
}
function changeModelType(item){
debugger
} }
</script> </script>
<template> <template>
<div> <div>
<n-form <div class="bg-gray-100 p-4 rounded-lg">
ref="formRef" <n-form
:label-width="80" ref="formRef"
:model="localForm" :label-width="80"
:rules="rules" :model="localForm"
size="large" :rules="rules"
> size="large"
<n-form-item label="模型名称" path="modelProduct.modelName"> >
<n-input v-model:value="localForm.modelProduct.modelName" placeholder="输入模型名" /> <n-form-item label="模型名称" path="modelProduct.modelName">
</n-form-item> <n-input v-model:value="localForm.modelProduct.modelName" placeholder="输入模型名" />
<n-form-item label="模型类型" path="modelProduct.modelType"> </n-form-item>
<n-select <n-form-item label="模型类型" path="modelProduct.modelType">
v-model:value="localForm.modelProduct.modelType" <n-select
label-field="dictLabel" v-model:value="localForm.modelProduct.modelType"
value-field="dictValue" label-field="dictLabel"
placeholder="请选择模型类型" @update:value="changeModelType"
:options="model_category" value-field="dictValue"
/> placeholder="请选择模型类型"
</n-form-item> :options="model_category"
<div> />
内容类别 </n-form-item>
</div> <div>
<div class="-mb-5 text-gray-400 text-[12px]"> 内容类别
填写类别可让模型获得更精准的流量, 平台也有权基于标准修改你的类别标签 </div>
</div> <div class="-mb-5 text-gray-400 text-[12px]">
<n-form-item path="category"> 填写类别可让模型获得更精准的流量, 平台也有权基于标准修改你的类别标签
<n-cascader </div>
v-model:value="localForm.modelProduct.category" <n-form-item path="category">
placeholder="垂类" <n-cascader
:options="categoryList" v-model:value="localForm.modelProduct.category"
label-field="dictLabel" placeholder="垂类"
value-field="dictValue" :options="categoryList"
check-strategy="child" label-field="dictLabel"
/> value-field="dictValue"
</n-form-item> check-strategy="child"
<n-form-item path="functions" class="-mt-12"> @update:value="handleCategoryUpdateValue"
<n-select />
v-model:value="localForm.modelProduct.functions" </n-form-item>
label-field="dictLabel" <n-form-item path="functions" class="-mt-12" v-if="localForm.modelProduct.modelType !== '0'">
value-field="dictValue" <n-select
placeholder="功能" v-model:value="localForm.modelProduct.functions"
:options="work_flow_functions" label-field="dictLabel"
/> value-field="dictValue"
</n-form-item> placeholder="功能"
:options="work_flow_functions"
/>
</n-form-item>
<div> <div>
标签 标签
</div> </div>
<div class="-mb-5 text-gray-400 text-[12px]"> <div class="-mb-5 text-gray-400 text-[12px]">
添加标签将自动推荐给可能感兴趣的人 添加标签将自动推荐给可能感兴趣的人
</div> </div>
<n-form-item path="category"> <n-form-item path="category">
<n-select <!-- <n-select
v-model:value="localForm.modelProduct.tags" v-model:value="localForm.modelProduct.tags"
:options="options" :options="options"
filterable filterable
@ -190,69 +197,85 @@ function handleIsOriginal(value: number) {
multiple multiple
placeholder="请选择或输入标签" placeholder="请选择或输入标签"
@update:value="handleUpdate" @update:value="handleUpdate"
/> /> -->
</n-form-item>
<n-select
v-model:value="localForm.modelProduct.tagsList"
filterable
multiple
tag
placeholder="输入,按回车确认"
:show-arrow="false"
:show="false"
/>
</n-form-item>
<div>
参与活动
</div>
<div class="-mb-5 text-gray-400 text-[12px]">
参与特定活动或比赛,下拉选择
</div>
<n-form-item path="activityId">
<n-select
v-model:value="localForm.modelProduct.activityId"
label-field="activityName"
value-field="id"
placeholder="请选择参与哪个活动"
:options="activityList"
/>
</n-form-item>
</n-form>
<div> <div>
参与活动 原创内容
</div> </div>
<div class="-mb-5 text-gray-400 text-[12px]"> <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"
:style="{
backgroundColor:
localForm.modelProduct.isOriginal === item.value
? 'rgba(49, 98, 255, 0.1)'
: '#fff',
color: localForm.modelProduct.isOriginal === item.value ? '#3162ff' : '#000',
}" class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer" @click="handleIsOriginal(item.value)"
>
{{ item.label }}
</div>
</div> </div>
<n-form-item path="activityId"> <div v-if="localForm.modelProduct.isOriginal === 1" class="text-[12px]">
<n-select <div class="my-3">
v-model:value="localForm.modelProduct.activityId" 魔创未来原创模型加密保护计划
label-field="activityName" </div>
value-field="id" <div class="text-gray-400">
placeholder="请选择参与哪个活动" 原创模型加密保护计划是魔创未来推出的,为维护创作者权益保障平台健康发展,通过技术手段遏制爬取盗版侵权等不法行为,保证创作者的劳动成果得到合理回报,进而激励更多优秀原创模型的诞生
:options="activityList" </div>
/> <div class="text-gray-400">
</n-form-item> 谁能加入 所有发布原创模型的作者,均可以加入本计划
</n-form> </div>
<div class="flex justify-center items-center mt-5 bg-white h-10 rounded-lg"> <div class="my-3 text-gray-400">
<div 加入后有何好处 1.模型加密能力 2.流量扶持 3.创作激励
v-for="(item, index) in originalBtnList" </div>
:key="index" <div class="text-gray-400">
:style="{ 详情查看 <a href="" class="text-[#3162ff] underline">魔创未来原创模型加密保护计划</a>
backgroundColor: </div>
localForm.modelProduct.isOriginal === item.value <div class="my-3 ">
? 'rgba(49, 98, 255, 0.1)' 原创声明
: '#fff', </div>
color: localForm.modelProduct.isOriginal === item.value ? '#3162ff' : '#000', <div class="text-gray-400">
}" class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer" @click="handleIsOriginal(item.value)" 本人声明并承诺模型是由本人原创,相关的权利和义务由本人承担
> </div>
{{ item.label }} </div>
<div v-else>
<n-form-item path="modelProduct.originalAuthorName" size="large">
<n-input v-model:value="localForm.modelProduct.originalAuthorName" placeholder="输入原创作者" />
</n-form-item>
</div> </div>
</div> </div>
<div v-if="localForm.modelProduct.isOriginal === 0" class="text-[12px]"> <div class="flex w-full justify-center">
<div class="my-3"> <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 class="text-gray-400">
原创模型加密保护计划是魔创未来推出的,为维护创作者权益保障平台健康发展,通过技术手段遏制爬取盗版侵权等不法行为,保证创作者的劳动成果得到合理回报,进而激励更多优秀原创模型的诞生
</div>
<div class="text-gray-400">
谁能加入 所有发布原创模型的作者,均可以加入本计划
</div>
<div class="my-3 text-gray-400">
加入后有何好处 1.模型加密能力 2.流量扶持 3.创作激励
</div>
<div class="text-gray-400">
详情查看 <a href="" class="text-[#3162ff] underline">魔创未来原创模型加密保护计划</a>
</div>
<div class="my-3 ">
原创声明
</div>
<div class="text-gray-400">
本人声明并承诺模型是由本人原创,相关的权利和义务由本人承担
</div>
</div>
<div v-else>
<n-form-item path="modelProduct.originalAuthorName" size="large">
<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>
</div> </div>
</template> </template>

View File

@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FormInst } from 'naive-ui' import { commonApi } from "@/api/common";
import { commonApi } from '@/api/common' import { uploadImagesInBatches } from "@/utils/uploadImg.ts";
import { uploadImagesInBatches } from '@/utils/uploadImg.ts' import { cloneDeep } from 'lodash-es';
import { Asterisk, Trash } from 'lucide-vue-next' import { Asterisk, Trash } from "lucide-vue-next";
import { computed, onMounted, ref, watch } from 'vue' import type { FormInst } from "naive-ui";
import { computed, ref, watch } from "vue";
const message = useMessage()
// //
const props = defineProps({ const props = defineProps({
@ -11,321 +13,404 @@ const props = defineProps({
type: Object, type: Object,
required: true, required: true,
}, },
}) });
const emit = defineEmits(['update:modelValue', 'createModelsNext']) const emit = defineEmits(["update:modelValue", "nextStep", "prevStep"]);
const acceptTypes = '.safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft' const acceptTypes =
".safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft";
const modelVersionItem = { const modelVersionItem = {
versionName: '', // delFlag: '0', // 0 2
modelId: null, // versionName: "", //
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>"', // modelVersionType: null, //
filePath: '', // versionDescription: "", //
fileName: '', // filePath: "", //
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, fileName: "", //
triggerWords: '', // sampleImagePaths:[], // 20,
isPublic: 1, // 1 2 triggerWords: "", //
isFree: 0, // 0 1 isPublic: 1, //
isOnlineUse: 1, // 线使 isOnlineUse:1, //线使
allowDownloadImage: 1, //
allowSoftwareUse: 1, // 使
allowFusion: 1, // allowFusion: 1, //
allowCommercialUse: 1, // isFree: 0, // 0
allowDownloadImage: 1, //
allowSoftwareUse: 1, // 使
allowCommercialUse: 1, //
allowUsage: 1, //
isExclusiveModel: 1, //
hideImageGenInfo:0, //
// allowUsage: 1, // 使 };
//
isExclusiveModel: 0, //
}
const isPublicList = [ const isPublicList = [
{ {
label: '公开', label: "公开",
value: 1, value: 1,
}, },
{ {
label: '仅自己可见', label: "仅自己可见",
value: 2, value: 0,
}, },
] ];
defineExpose({ defineExpose({
addVersion, addVersion,
}) });
const localForm = computed({ const localForm = computed({
get() { get() {
return props.modelValue return props.modelValue;
}, },
set(value) { set(value) {
emit('update:modelValue', value) emit("update:modelValue", value);
}, },
}) });
watch( watch(
() => localForm.value, () => localForm.value,
(newVal) => { (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 = { const rules = {
versionName: { versionName: {
required: true, required: true,
message: '', message: "",
trigger: 'blur', trigger: "blur",
}, },
modelId: { modelVersionType: {
required: true, required: true,
message: '', message: "",
trigger: 'blur', trigger: "blur",
}, },
triggerWords: { };
required: true,
message: '请输入模型名称',
trigger: 'blur',
},
}
function addVersion() { function addVersion() {
localForm.value.modelVersionList.unshift(modelVersionItem) const param = cloneDeep(modelVersionItem)
localForm.value.modelVersionList.unshift(param);
} }
const originalBtnList = ref([ const originalBtnList = ref([
{ {
label: '免费', label: "免费",
value: 0,
},
{
label: '会员下载',
value: 1, value: 1,
}, },
]) {
label: "会员下载",
value: 0,
},
]);
function handleIsFree(index: number, value: number) { function handleIsFree(index: number, value: number) {
localForm.value.modelVersionList[index].isFree = value localForm.value.modelVersionList[index].isFree = value;
} }
const model_category = ref([]) async function nextStep() {
function nextStep() { for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
formRef.value?.validate((errors) => { if (
if (!errors) { localForm.value.modelVersionList[i].delFlag === "0" &&
emit('createModelsNext') localForm.value.modelVersionList[i].fileName === ""
) {
return message.error("请上传文件");
} }
else { const regex = /[\u4E00-\u9FA5]/; //
console.log('error', errors) 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 uploadFileIndex = ref(0);
const fileInput = ref<HTMLInputElement | null>(null) const fileInput = ref<HTMLInputElement | null>(null);
function triggerFileInput(index: number) { function triggerFileInput(index: number) {
(fileInput.value as HTMLInputElement)?.click() (fileInput.value as HTMLInputElement)?.click();
uploadFileIndex.value = index uploadFileIndex.value = index;
} }
async function handleFileChange(event: Event) { async function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement;
const files = target.files const files = target.files;
if (files && files.length > 0) { if (files && files.length > 0) {
const res = await uploadImagesInBatches(files) const res = await uploadImagesInBatches(files);
localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url;
localForm.value.modelVersionList[uploadFileIndex.value].fileName = res[0].fileName 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> </script>
<template> <template>
<div> <div>
<div v-for="(item, index) in localForm.modelVersionList" :key="index" class="bg-gray-100 p-4 rounded-lg"> <template
<n-form v-for="(item, index) in localForm.modelVersionList"
ref="formRef" :key="index"
:label-width="80" class="bg-gray-100 p-4 mt-4 rounded-lg"
:model="localForm" >
:rules="rules" <div v-if="item.delFlag === '0'" class="bg-gray-100 p-4 rounded-lg mt-4 relative">
size="large" <div class="absolute -right-10 top-4 cursor-pointer">
> <Trash class="cursor-pointer" @click="onDelete(index)" />
<n-form-item label="版本名称" path="versionName">
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
</n-form-item>
<n-form-item label="基础模型" path="modelId">
<n-select
v-model:value="item.modelId"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择基础模型"
:options="model_category"
/>
</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">
100%
</div> </div>
<div class="flex-1 flex items-center line-clamp"> <n-form
{{ :ref="(el) => setFormRef(el, index)"
item.fileName :label-width="80"
}} :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="modelVersionType">
<n-select
v-model:value="item.modelVersionType"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择基础模型"
:options="baseModelTypeList"
/>
</n-form-item>
<div class="flex">
上传文件 <Asterisk :size="10" color="#ff0000" class="mt-1" />
</div> </div>
<div> <div
<Trash class="cursor-pointer" @click="item.fileName = '', item.filePath = ''" /> 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 }}
</div>
<div>
<Trash
class="cursor-pointer"
@click="(item.fileName = ''), (item.filePath = '')"
/>
</div>
</div> </div>
</div> <div v-else>
<div v-else> <div class="upload-content">
<div class="upload-content"> <div
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white"> 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="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"
<div class="my-3"> @click="triggerFileInput(index)"
点击上传文件 >
</div> 上传文件
<div class="text-[#999999] text-xs"> </div>
.safetensors/.ckpt/.pt/.bin/.pth/.zip/.json/.flow/.lightflow/.yaml/.yml/.onnx/.gguf/.sft <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>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="flex mt-6">
<div class="flex mt-6"> 版本介绍 <Asterisk :size="10" color="#ff0000" class="mt-1" />
版本介绍 <Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div class="bg-white p-3 mt-2 rounded-lg">
<client-only>
<WangEditor v-model="item.versionDesc" />
</client-only>
</div>
<div v-html="item.versionDesc" />
<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>
<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">
{{ isPublicItem.label }}
</n-radio>
</n-space>
</n-radio-group>
</div>
</n-form>
<div v-if="item.isPublic === 1">
<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',
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)"
>
{{ subItem.label }}
</div> </div>
</div> <div class="bg-white p-3 mt-2 rounded-lg">
<div class="mt-1 mb-2 text-gray-500 text-[12px]"> <client-only>
选择会员专属或会员下载视为您已经阅读 <span class="text-[#3162ff] cursor-pointer underline">会员模型许可协议</span> 并同意其中条款 <WangEditor v-model="item.versionDescription" />
</div> </client-only>
<div v-if="item.isFree === 1" class="text-[12px]">
<div>会员下载模型</div>
<div class="text-gray-500">
下载模型需购买会员在线生图对所有人开放无生图次数限制会员下载的模型版本生成图片默认可商用
</div> </div>
</div> <div class="mt-6">触发词</div>
</div> <div class="-mb-5 text-gray-400 text-[12px]">请输入您用来训练的单词</div>
<div class="text-gray-400 text-[12px] my-4"> <n-form-item path="triggerWords">
许可范围 <n-input v-model:value="item.triggerWords" placeholder="例如: 1boy" />
</div> </n-form-item>
<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="允许在魔创未来在线使用"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowDownloadImage"
:checked-value="1"
: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="允许在魔创未来旗下其他产品在线使用"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowFusion"
:checked-value="1"
:unchecked-value="0" label="允许进行融合"
/>
</div>
</div>
<div class="text-gray-400 text-[12px] my-4"> <div v-if="localForm.modelProduct.modelType === '0'">
商用许可范围 <n-form-item label="采样方法" path="modelVersionType">
</div> <n-select
<div class="flex flex-wrap"> v-model:value="item.modelVersionType"
<div class="w-[50%] mb-2"> label-field="dictLabel"
<n-checkbox value-field="dictValue"
v-model:checked="item.allowCommercialUse" placeholder="请选择采样方法"
:checked-value="1" :options="baseModelTypeList"
:unchecked-value="0" label="生成图片可出售或用于商业目的" />
/> </n-form-item>
</div> <div>
<div class="w-[50%] mb-2"> <n-select v-model:value="item.modelVersionType" multiple :options="baseModelTypeList" />
<n-checkbox </div>
v-model:checked="item.allowDownloadImage" </div>
:checked-value="1"
:unchecked-value="0" label="允许模型转售或融合后出售"
/>
</div>
</div>
<div class="mb-4"> <div class="">权限设置</div>
<div class="text-gray-400 text-[12px] my-4"> <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"
>
{{ isPublicItem.label }}
</n-radio>
</n-space>
</n-radio-group>
</div>
</n-form>
<div v-if="item.isPublic === 1">
<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',
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)"
>
{{ subItem.label }}
</div>
</div>
<div class="mt-1 mb-2 text-gray-500 text-[12px]">
选择会员专属或会员下载视为您已经阅读
<span class="text-[#3162ff] cursor-pointer underline"
>会员模型许可协议</span
>
并同意其中条款
</div>
<div v-if="item.isFree === 1" class="text-[12px]">
<div>会员下载模型</div>
<div class="text-gray-500">
下载模型需购买会员在线生图对所有人开放无生图次数限制会员下载的模型版本生成图片默认可商用
</div>
</div>
</div> </div>
<div class="flex items-center mb-2"> <div class="text-gray-400 text-[12px] my-4">许可范围</div>
<n-checkbox <div class="flex flex-wrap">
v-model:checked="item.isExclusiveModel" <div class="w-[50%] mb-2">
:checked-value="1" <n-checkbox
:unchecked-value="0" v-model:checked="item.isOnlineUse"
/> :checked-value="1"
<div class="ml-3 text-[12px] text-gray-500"> :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="允许下载生图"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowSoftwareUse"
:checked-value="1"
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="允许进行融合"
/>
</div>
</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="生成图片可出售或用于商业目的"
/>
</div>
<div class="w-[50%] mb-2">
<n-checkbox
v-model:checked="item.allowUsage"
:checked-value="1"
:unchecked-value="0"
label="允许模型转售或融合后出售"
/>
</div>
</div>
<div class="mb-4">
<div class="text-gray-400 text-[12px] my-4">独家设置</div>
<div class="flex items-center mb-2">
<n-checkbox
v-model:checked="item.isExclusiveModel"
:checked-value="1"
:unchecked-value="0"
/>
<div class="ml-3 text-[12px] text-gray-500">
此版本为魔创未来独家模型 *获取更多流量独家模型规则
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </template>
<div class="flex items-center justify-center mt-5"> <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>
</div> </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>
</div>
</div> </div>
<input <input
ref="fileInput" ref="fileInput"
@ -333,7 +418,7 @@ async function handleFileChange(event: Event) {
class="hidden" class="hidden"
:accept="acceptTypes" :accept="acceptTypes"
@change="handleFileChange" @change="handleFileChange"
> />
</template> </template>
<style lang="scss" scoped> <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"> <script setup lang="ts">
import type { FormInst } from 'naive-ui' import { uploadFileBatches } from '@/utils/uploadImg.ts';
import { uploadFileBatches } from '@/utils/uploadImg.ts' import { cloneDeep } from 'lodash-es';
import { Asterisk, Trash } from 'lucide-vue-next' import { Asterisk, Trash } from 'lucide-vue-next';
import { computed, ref, watch } from 'vue' import type { FormInst } from 'naive-ui';
import { computed, ref, watch } from 'vue';
// //
const props = defineProps({ const props = defineProps({
@ -54,7 +55,8 @@ const rules = {
}, },
} }
function addVersion() { function addVersion() {
localForm.value.workFlowVersionList.unshift(modelVersionItem) const param = cloneDeep(modelVersionItem)
localForm.value.workFlowVersionList.unshift(param)
} }
const formRefs = ref<(FormInst | null)[]>([]) const formRefs = ref<(FormInst | null)[]>([])

View File

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

View File

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

View File

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

View File

@ -1,23 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
Binary, Binary,
Code2, Code2,
Crown, Crown,
Image, Image,
LayoutGrid, LayoutGrid,
Lightbulb, Lightbulb,
Maximize, Maximize,
User, User,
Workflow, Workflow
} from 'lucide-vue-next' } from 'lucide-vue-next';
const menuStore = useMenuStore() const menuStore = useMenuStore()
// //
const iconMap: any = { const iconMap: any = {
'/model-square': LayoutGrid, '/model-square': LayoutGrid,
'/works-inspire': Lightbulb, '/picture-square':Lightbulb,
'/workspace': Lightbulb, '/work-square':Workflow,
'/web-ui': Image, '/web-ui': Image,
'/comfy-ui': Workflow, '/comfy-ui': Workflow,
'/training-lora': Binary, '/training-lora': Binary,
@ -78,7 +78,7 @@ function handleSide(event: Event, path: string) {
v-for="item in menuStore.menuItems" v-for="item in menuStore.menuItems"
:key="item.path" :key="item.path"
:to="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="[ :class="[
menuStore.activeMenu === item.path menuStore.activeMenu === item.path
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400' ? '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"> <script setup lang="ts">
import { Heart } from '@vicons/ionicons5' import { Heart, PersonAddOutline } from '@vicons/ionicons5'
import {
Download,
Play,
} from 'lucide-vue-next'
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({ definePageMeta({
layout: 'default', 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> </script>
<template> <template>
<div class="flex justify-center"> <div class="flex justify-center">
<div class="w-[1125px]"> <div class="w-[1125px] p-4">
<div class="flex items-center"> <div class="flex items-center">
<div class="text-[26px] font-bold mr-2"> <div class="text-[26px] font-bold mr-4">
名称 {{ detailsInfo.modelName }}
</div>
<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-black menu-icon m-1"
/>
<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>
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
<span>
<!-- {{ item.reals }} -->0
</span>
<component
:is="Download"
class="h-[14px] w-[14px] text-white menu-icon m-1"
/>
<span>
<!-- {{ item.numbers }} -->0
</span>
</div> </div>
</div> </div>
</div> </div>
</template> </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"> <script setup lang="ts">
import { ref } from 'vue' import { commonApi } from "@/api/common";
import { ArrowBack, ArrowForward } from '@vicons/ionicons5';
import { ref } from "vue";
definePageMeta({ 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 onSearch() {
if (listRef.value) {
// listRef.value?.initPageNUm();
function handleChange(index) { }
console.log('Current index:', index) }
//
function changeCategory(item:any){
params.value.type = item.dictValue
onSearch()
} }
// // function onHot(name:string){
function handleItemClick({ index, item }) { // params.value.name = name
console.log('Clicked item:', item, 'at index:', index) // onSearch()
} // }
</script> </script>
<template> <template>
<div class="p-6"> <div class="p-6">
<h1 class="text-2xl font-bold"> <div class="header flex h-[150px] border-b border-b-[#ebebeb]">
模型广场 <div class="flex-1 py-2">
</h1> <div class="max-w-4xl mx-auto">
<!-- <ScrollListView <div class="relative flex items-center w-full">
ref="scrollListRef" <!-- 搜索输入框 -->
title="My Scroll List" <input
:items="myItems" type="text"
:initial-index="2" 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"
@change="handleChange" placeholder="搜索模型/图片/创作者寻找灵感"
@item-click="handleItemClick" @keyup.enter="onSearch"
> v-model="params.name"
<template #item="{ item }"> />
<div class="custom-item">
<span>{{ item }}</span> <!-- 相机图标 -->
<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"
>
<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>
</template> <!-- <div class="flex p-3">
</ScrollListView> --> <div v-for="(item, index) in hotNameList" @click="onHot(item)" :key="index" class="mr-3 mb-2 cursor-pointer">
<!-- <button @click="handleTest"></button> {{ item }}
<client-only> </div>
{{ useUser.brief }} </div> -->
</client-only> --> </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>
<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> </div>
</template> </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 EditUserInfo from '@/components/EditUserInfo.vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { formatDate } from '@/utils/index.ts' import { formatDate } from '@/utils/index.ts'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { nextTick, onMounted, onUnmounted, ref } from 'vue' import { nextTick, onMounted, onUnmounted, ref } from 'vue'
const loading = ref(false) const loading = ref(false)
@ -341,6 +342,11 @@ const attentionIsVisible = ref<boolean>(false)
function onCloseAttentionModel() { function onCloseAttentionModel() {
attentionIsVisible.value = false attentionIsVisible.value = false
} }
function onClearDate(){
publishParams.value.startTime = '',
publishParams.value.endTime = '',
initPageNUm()
}
</script> </script>
<template> <template>
@ -492,6 +498,7 @@ function onCloseAttentionModel() {
range-separator="至" range-separator="至"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
:on-clear="onClearDate"
clearable clearable
@update:value="changeDate" @update:value="changeDate"
/> />
@ -511,10 +518,13 @@ function onCloseAttentionModel() {
/> />
</div> </div>
</div> </div>
<NConfigProvider>
<NMessageProvider>
<Authentication ref="authenticationRef" />
<EditUserInfo ref="editUserInfoRef" />
</NMessageProvider>
</NConfigProvider>
<!-- Dialog Components --> <!-- Dialog Components -->
<Authentication ref="authenticationRef" />
<EditUserInfo ref="editUserInfoRef" />
<div class="login-content my-4 grid grid-cols-4 gap-4 px-5"> <div class="login-content my-4 grid grid-cols-4 gap-4 px-5">
<PersonalCenterCard <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"> <script setup lang="ts">
import type { FormInst, FormItemRule } from 'naive-ui' import CreateModels from "@/components/publishModel/CreateModels.vue";
import CreateModels from '@/components/publishModel/CreateModels.vue' import EditVersion from "@/components/publishModel/EditVersion.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 route = useRoute();
const currentStep = ref(2) const { type, id } = route.query;
const step2Ref = ref(null);
const currentStep = ref(1);
const formData = ref({ const formData = ref({
modelProduct: { modelProduct: {},
modelName: '模型名称', modelVersionList: [],
modelType: null, // , });
category: null, //
functions: null, // '',
tags: null, // string
activityId: null, // string
isOriginal: 1, async function initFormData() {
originalAuthorName: '', if (type === "add") {
}, formData.value = {
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, // 线使
allowDownloadImage: 1, //
allowUsage: 1, // 使
allowSoftwareUse: 1, // 使
allowCommercialUse: 1, //
//
isExclusiveModel: 0, //
},
],
})
async function addWorkflow() {
try {
const params = {
modelProduct: { modelProduct: {
modelName: '模型名称', modelName: "",
modelTypeId: 1, // , modelType: null, // ,
category: '1', // category: null, //
functions: '1', // '', functions: null, // '',
tags: '11', // string tags: '', // string
activityId: '1', // string tagsList:[],
activityId: null, // string
isOriginal: 1, // ??? 01 isOriginal: 1, //
originalAuthorName: '作者名称', originalAuthorName: "",
}, },
modelVersionList: [ modelVersionList: [
{ {
versionName: '1.0', // delFlag: "0", // 0 2
modelId: 1, // versionName: "", //
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>"', // modelVersionType: null, //
filePath: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', // filePath: "", //
fileName: '', // 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, versionDescription: "", //
triggerWords: '触发词', //
isPublic: 1, // 1 2 sampleImagePaths: [], // 20,
allowFusion: 1, // triggerWords: "", //
isFree: 0, // 0 isPublic: 1, // 1 0
allowDownloadImage: 1, // isFree: 0, //
allowUsage: 1, // 使
allowSoftwareUse: 1, // 使 isOnlineUse: 1, // 线使
allowCommercialUse: 1, // allowDownloadImage: 1, //
allowSoftwareUse: 1, // 使
allowFusion: 1, //
allowCommercialUse: 1, //
// //
isExclusiveModel: 1, // isExclusiveModel: 0, //
hideImageGenInfo:0, //
}, },
], ],
};
} else {
try {
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();
}
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);
} }
const res = await request.post('/model/insert', params)
}
catch (error) {
console.log(error)
} }
} }
function createModelsNext() { initFormData();
currentStep.value = 2
const timeLineList = ref([
{
name: "创建模型",
index: 1,
},
{
name: "编辑版本",
index: 2,
},
{
name: "上传图片",
index: 3,
},
]);
function nextStep() {
currentStep.value += 1;
}
function prevStep() {
currentStep.value -= 1;
} }
function handleAddVersion() { function handleAddVersion() {
if (step2Ref.value) { 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> </script>
<template> <template>
<div class="mx-auto py-6"> <div class="mx-auto py-6">
<div class="container mx-auto w-[700px]"> <div class="container mx-auto w-[700px]">
<div class="step-line flex"> <div class="flex items-center justify-between mb-4">
<div class="mr-10"> <div class="w-[60%]">
1创建模版 <TimeLine :time-line-list="timeLineList" :current-step="currentStep" />
</div> </div>
<div class="mr-10"> <div
2编辑版本 v-if="currentStep === 2"
</div> class="cursor-pointer flex items-center justify-center rounded-full border border-solid border-[#000] px-4 py-1"
<div class="mr-10"> @click="handleAddVersion"
3上传图片 >
</div> <CirclePlus class="mr-2" />
<div v-if="currentStep === 2" @click="handleAddVersion">
添加版本 添加版本
</div> </div>
</div> </div>
<div class="form-container"> <div class="form-container">
<div v-if="currentStep === 1" class="first-step"> <NConfigProvider>
<CreateModels v-model="formData" @create-models-next="createModelsNext" /> <NMessageProvider>
</div> <div v-if="currentStep === 1" class="first-step">
<CreateModels v-model="formData" @create-models-next="nextStep" />
</div>
<div v-if="currentStep === 2" class="second-step"> <div v-if="currentStep === 2" class="second-step">
<EditVersion ref="step2Ref" v-model="formData" /> <EditVersion
</div> ref="step2Ref"
v-model="formData"
<div v-if="currentStep === 3" class="third-step"> @prev-step="prevStep"
3 @next-step="nextStep"
</div> />
</div>
<div v-if="currentStep === 3" class="third-step">
<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> </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> </div>
<!-- <pre
>{{ JSON.stringify(model, null, 2) }}
</pre> -->
</template> </template>

View File

@ -1,101 +1,95 @@
<script setup lang="ts"> <script setup lang="ts">
import EditVersion from '@/components/publishWorkFlow/EditVersion.vue' import EditVersion from "@/components/publishWorkFlow/EditVersion.vue";
import EditWorkFlow from '@/components/publishWorkFlow/EditWorkFlow.vue' import EditWorkFlow from "@/components/publishWorkFlow/EditWorkFlow.vue";
import UploadImg from '@/components/publishWorkFlow/UploadImg.vue' import UploadImg from "@/components/publishWorkFlow/UploadImg.vue";
import { CirclePlus } from 'lucide-vue-next' import { CirclePlus } from "lucide-vue-next";
import { NConfigProvider, NMessageProvider } from 'naive-ui' import { NConfigProvider, NMessageProvider } from "naive-ui";
import { useRoute } from 'vue-router' import { useRoute } from "vue-router";
// definePageMeta({
// middleware: [
// function (to, from) {
// initFormData(to.query.type)
// },
// ],
// })
const route = useRoute()
const { type, id } = route.query
const formData = ref() const route = useRoute();
const { type, id } = route.query;
const formData = ref();
async function initFormData() { async function initFormData() {
if (type === 'add') { if (type === "add") {
formData.value = { formData.value = {
workFlow: { workFlow: {
jurisdiction: 1, // auditStatus 0 1- 2- 3 4 jurisdiction: 1, // auditStatus 0 1- 2- 3 4
original: 0, // original 0 1 original: 0, // original 0 1
onlineUse: 0, // 线使(0 1) onlineUse: 0, // 线使(0 1)
download: 0, // (0 1) download: 0, // (0 1)
sell: 1, // (0 1) sell: 1, // (0 1)
typeList: [], typeList: [],
}, },
workFlowVersionList: [ workFlowVersionList: [
{ {
versionName: '', versionName: "",
hideGenInfo: 0, // hideGenInfo: 0, //
versionDescription: '', // versionDescription: "", //
filePath: '', // filePath: "", //
fileName: '', // fileName: "", //
delFlag: '0', // 0 2 delFlag: "0", // 0 2
imagePaths: [], imagePaths: [],
}, },
], ],
} };
} } else {
else {
// type 1 // type 1
try { try {
const res = await request.get(`/WorkFlow/selectWorkFlowVersionById?id=${id}`) const res = await request.get(`/WorkFlow/selectWorkFlowVersionById?id=${id}`);
formData.value = res.data formData.value = res.data;
if (formData.value.workFlow.type) { if (formData.value.workFlow.type) {
formData.value.workFlow.typeList = formData.value.workFlow.type.split(',') formData.value.workFlow.typeList = formData.value.workFlow.type.split(",");
} } else {
else { formData.value.workFlow.typeList = [];
formData.value.workFlow.typeList = []
} }
if (formData.value.workFlow.activityParticipation) { if (formData.value.workFlow.activityParticipation) {
formData.value.workFlow.activityParticipation = Number(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.length > 0) {
if (formData.value.workFlowVersionList[i].imagePaths) { for (let i = 0; i < formData.value.workFlowVersionList.length; i++) {
formData.value.workFlowVersionList[i].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(',') if (formData.value.workFlowVersionList[i].imagePaths) {
formData.value.workFlowVersionList[
i
].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(",");
}
} }
} }
// else { } catch (error) {
// formData.value.workFlow.activityParticipationList = [] console.log(error);
// }
}
catch (error) {
console.log(error)
} }
} }
} }
initFormData() initFormData();
const EditVersionRef = ref() const EditVersionRef = ref();
const currentStep = ref(1) const currentStep = ref(1);
function handleAddVersion() { function handleAddVersion() {
EditVersionRef.value.addVersion() EditVersionRef.value.addVersion();
} }
function nextStep() { function nextStep() {
currentStep.value += 1 currentStep.value += 1;
} }
function preStep() { function preStep() {
currentStep.value -= 1 currentStep.value -= 1;
} }
const timeLineList = ref([ const timeLineList = ref([
{ {
name: '创建模版', name: "编辑工作流",
index: 1, index: 1,
}, },
{ {
name: '编辑版本', name: "编辑版本",
index: 2, index: 2,
}, },
{ {
name: '上传图片', name: "上传图片",
index: 3, index: 3,
}, },
]) ]);
</script> </script>
<template> <template>
@ -106,7 +100,11 @@ const timeLineList = ref([
<div class="w-[60%]"> <div class="w-[60%]">
<TimeLine :time-line-list="timeLineList" :current-step="currentStep" /> <TimeLine :time-line-list="timeLineList" :current-step="currentStep" />
</div> </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" /> <CirclePlus class="mr-2" />
添加版本 添加版本
</div> </div>
@ -118,7 +116,12 @@ const timeLineList = ref([
<EditWorkFlow v-model="formData" @next-step="nextStep" /> <EditWorkFlow v-model="formData" @next-step="nextStep" />
</div> </div>
<div v-if="currentStep === 2" class="second-step"> <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>
<div v-if="currentStep === 3" class="third-step"> <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 { Heart, PersonAddOutline } from '@vicons/ionicons5'
import { import {
CircleUser, CircleUser,
Download, Download,
EllipsisVertical, EllipsisVertical, Play
HardDriveUpload,
Play,
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui' import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { nextTick, ref } from 'vue' import { nextTick, ref } from 'vue'
@ -64,6 +62,7 @@ async function getInfo() {
catch (error) { catch (error) {
console.log(error) console.log(error)
} }
getAttention()
} }
} }
catch (error) { catch (error) {
@ -89,7 +88,7 @@ const selectUserInfo = ref<SelectUserInfo>({
// //
async function getAttention() { async function getAttention() {
try { try {
const res = await request.get('/attention/selectUserInfo') const res = await request.get(`/attention/selectUserInfo?userId=${detailsInfo.value.userId}`)
if (res.code === 200) { if (res.code === 200) {
selectUserInfo.value = res.data selectUserInfo.value = res.data
} }
@ -98,7 +97,6 @@ async function getAttention() {
console.log(err) console.log(err)
} }
} }
getAttention()
// // // //
const isDelete = ref(false) 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" class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
/> />
<span class="mr-2"> {{ selectUserInfo.bean }} </span> <span class="mr-2"> {{ selectUserInfo.bean }} </span>
<component <!-- <component
:is="HardDriveUpload" :is="HardDriveUpload"
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]" 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 <component
:is="Play" :is="Play"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]" 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 <component
:is="Download" :is="Download"
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]" 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> </div>
</div> </div>
@ -411,7 +409,7 @@ async function onLike() {
:mask-closable="false" :mask-closable="false"
preset="dialog" preset="dialog"
title="提示!" title="提示!"
content="确定要将模型删除? 模型删除后无法找回" content="确定要将工作流删除? 工作流删除后无法找回"
negative-text="取消" negative-text="取消"
positive-text="确认" positive-text="确认"
@negative-click="onDelete" @negative-click="onDelete"

View File

@ -4,8 +4,10 @@ export const useMenuStore = defineStore('menu', () => {
const activeMenu = ref('/model-square') const activeMenu = ref('/model-square')
const menuItems = [ const menuItems = [
{ path: '/model-square', icon: 'i-carbon-gallery', label: '模型广场' }, { 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: '/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: '/web-ui', icon: 'i-carbon-image-search', label: '在线生图' },
// { path: '/comfy-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' }, // { path: '/training-lora', icon: 'i-carbon-machine-learning', label: '训练LoRA' },

View File

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

0
netlify.toml 100755 → 100644
View File

View File

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