工作流/图片的发布、编辑、详情

test
shenhan 2025-02-13 13:59:12 +08:00
parent 21e94f048c
commit 654a427e48
33 changed files with 4041 additions and 783 deletions

14
app/components.d.ts vendored
View File

@ -7,28 +7,22 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
NAtePicker: typeof import('naive-ui')['NAtePicker']
NAvatar: typeof import('naive-ui')['NAvatar']
NBadge: typeof import('naive-ui')['NBadge']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCascader: typeof import('naive-ui')['NCascader']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDropdown: typeof import('naive-ui')['NDropdown']
NDynamicTags: typeof import('naive-ui')['NDynamicTags']
NFor: typeof import('naive-ui')['NFor']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NFormTtem: typeof import('naive-ui')['NFormTtem']
NGrid: typeof import('naive-ui')['NGrid']
NIcon: typeof import('naive-ui')['NIcon']
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
NInput: typeof import('naive-ui')['NInput']
NInputgroup: typeof import('naive-ui')['NInputgroup']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NQrCode: typeof import('naive-ui')['NQrCode']
@ -36,14 +30,10 @@ declare module 'vue' {
NRadioButton: typeof import('naive-ui')['NRadioButton']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NSelect: typeof import('naive-ui')['NSelect']
NSlider: typeof import('naive-ui')['NSlider']
NSpace: typeof import('naive-ui')['NSpace']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTimePicker: typeof import('naive-ui')['NTimePicker']
NTooltip: typeof import('naive-ui')['NTooltip']
NTransfer: typeof import('naive-ui')['NTransfer']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

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

View File

@ -0,0 +1,328 @@
<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

@ -0,0 +1,569 @@
<script setup lang="ts">
// import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui'
import {
ThumbsUp,
} from 'lucide-vue-next'
const props = defineProps({
height: {
type: Number,
default: 800,
},
type: {
type: String,
default: '',
},
detailsInfo: {
type: Object,
default: () => ({}),
},
})
// const $props = defineProps(['headUrl', 'dataList', 'height'])
const userStore = useUserStore()
const message = useMessage()
//
const commentParams = ref({
content: '',
parentId: '',
replyUserId: '',
userId: userStore.userInfo.userId,
workFlowId: props.detailsInfo.id,
})
function commentParamsInit() {
commentParams.value.content = ''
commentParams.value.replyUserId = ''
commentParams.value.parentId = ''
}
const isShowSend = ref(false)
const publicWord = ref('')
const sortType = ref(1)
//
const urlList = ref({
workflow: '/WorkFlowComment/comment?',
})
//
const likeList = ref({ //
workflow: '/WorkFlowComment/commentLike?',
})
//
const sendMessageList = ref({ //
workflow: '/WorkFlowComment/comment',
})
//
const deleteList = ref({ //
workflow: '/WorkFlowComment/commentDelete?',
})
//
const commentNumUrl = ref({ //
workflow: '/WorkFlowComment/commentCount?workFlowId',
})
const commentCount = ref(0)
//
const commentList = ref([])
async function getCommentList() {
try {
const res = await request.get(`${urlList.value[props.type]}commentId=${props.detailsInfo.id}&sortType=${sortType.value}`)
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()
function handleBlur(ele) {
//
if (!ele) {
isShowSend.value = !!publicWord.value
}
else {
ele.isShowSend = !!ele.word
}
}
//
async function sendMessage(ele, index) {
if (ele && ele.userId) {
try {
if (ele.word) {
commentParams.value.content = ele.word
commentParams.value.parentId = commentList.value[index].commentId
commentParams.value.replyUserId = ele.commentId
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
if (res.code === 200) {
message.success('评论成功!')
getCommentList()
getCommentNum()
commentParamsInit()
}
}
else {
message.warning('评论不能为空!')
}
}
catch (error) {
console.log(error)
}
}
else {
try {
if (publicWord.value) {
commentParams.value.parentId = ''
commentParams.value.content = publicWord.value
commentParams.value.replyUserId = ''
const res = await request.post('WorkFlowComment/comment', commentParams.value)
if (res.code === 200) {
message.success('评论成功!')
publicWord.value = ''
getCommentList()
getCommentNum()
commentParamsInit()
}
}
else {
message.warning('评论不能为空!')
}
}
catch (error) {
console.log(error)
}
}
}
async function getCommentNum() {
try {
const res = await request.get(`${commentNumUrl.value[props.type]}=${props.detailsInfo.id}`)
if (res.code === 200) {
commentCount.value = res.data
}
}
catch (error) {
console.log(error)
}
}
getCommentNum()
// /
async function handleFocus(item) {
await request.get(`${likeList.value[props.type]}commentId=${item.commentId}`)
if (item.isLike === 0) {
item.isLike = 1
item.likeNum++
message.success('点赞成功!')
}
else {
item.isLike = 0
item.likeNum--
message.success('取消点赞成功!')
}
}
//
function handleMessage(item) {
if (!item.isShowInput) {
item.word = ''
}
item.isShowInput = !item.isShowInput
}
//
async function handleDel(item) {
try {
const res = await request.get(`${deleteList.value[props.type]}commentId=${item.commentId}`)
if (res.code === 200) {
message.success('删除成功!')
getCommentList()
getCommentNum()
}
}
catch (error) {
console.log(error)
}
}
function changeType(type: string) {
sortType.value = type
getCommentList()
}
</script>
<template>
<div class="base-comment">
<div class="flex justify-between items-center mb-4">
<div class="flex items-center">
<div class="left text-[20px] mr-2">
讨论
</div>
<div class="text-[#999]">
{{ commentCount }}
</div>
</div>
<div class="flex items-center">
<div class="cursor-pointer" :class="sortType === 0 ? '' : 'text-[#999]'" @click="changeType(0)">
最热
</div>
<div class="cursor-pointer" :class="sortType === 1 ? '' : 'text-[#999]'" @click="changeType(1)">
最新
</div>
</div>
</div>
<div class="header-wrap">
<div v-if="userStore.userInfo.avatar" class="left">
<img
alt="avatar"
:src="userStore.userInfo.avatar"
>
</div>
<div class="input-wrap">
<NConfigProvider inline-theme-disabled style="width:100%">
<NInput
v-model:value="publicWord"
type="textarea"
:autosize="{ minRows: 1 }"
placeholder="善语结善缘,恶言伤人心~"
class="text"
@focus="isShowSend = true"
@blur="handleBlur"
/>
</NConfigProvider>
<div class="send-btn" :class="{ active: publicWord }" @click="sendMessage">
<span v-if="isShowSend"></span>
</div>
</div>
</div>
<NInfiniteScroll :style="{ height: `${height}px` }" :distance="10" @load="handleLoad">
<div
v-for="(item, index) in commentList"
:key="index"
class="px-4"
>
<div class="nav-wrap">
<div class="left-img">
<img :src="item.userAvatar">
</div>
<div class="right">
<div class="name">
{{ item.userName }}
</div>
<div class="des">
{{ item.content }}
</div>
<div class="star-wrap">
<span class="time">{{ item.createTime }}</span>
<div class="star-operator">
<div class="icon-wrap">
<!-- <img v-if="item.focus" src="@/assets/img/heart.png" alt=""> -->
<ThumbsUp size="16" class="mr-1" :color="item.isLike !== 0 ? '#ff0000' : '#000000'" @click="handleFocus(item)" />
<!-- <img v-else src="@/assets/img/heart.png" alt="" @click="handleFocus(item)"> -->
<span style="vertical-align: middle">{{ item.likeNum }}</span>
</div>
<span class="cursor-pointer m-r16" @click="handleMessage(item)"></span>
<span v-if="item.userId === userStore.userInfo.userId" class="cursor-pointer" @click="handleDel(item)"></span>
</div>
</div>
<!-- 父项评论回复操作 -->
<div v-if="item.isShowInput" class="input-wrap" style="margin-top: 10px;">
<NConfigProvider inline-theme-disabled style="width:100%">
<NInput
v-model:value="item.word"
type="textarea"
:autosize="{ minRows: 1 }"
:placeholder="`回复: @${item.userName}`"
class="text"
@focus="item.isShowSend = true"
@blur="handleBlur(item)"
/>
</NConfigProvider>
<div class="send-btn" :class="{ active: publicWord }" @click="sendMessage(item, index)">
<span v-if="item.isShowSend"></span>
</div>
</div>
</div>
</div>
<div class="child-wrap">
<div v-for="(ele, subIndex) in item.contentList" :key="subIndex" class="child-wrap-item">
<div class="left-img">
<img :src="ele.userAvatar">
</div>
<div class="right">
<div class="name">
{{ ele.userName }}
<div v-if="ele.userId === props.detailsInfo.userId" class="author">
作者
</div>
</div>
<div class="des">
<span>回复</span>
<span class="u-name">@{{ ele.userName }}</span>
{{ ele.content }}
</div>
<div class="star-wrap">
<span class="time">{{ ele.createTime }}</span>
<div class="star-operator">
<div class="icon-wrap">
<ThumbsUp size="16" class="mr-1" :color="ele.isLike !== 0 ? '#ff0000' : '#000000'" @click="handleFocus(ele)" />
<span style="vertical-align: middle">{{ ele.likeNum }}</span>
</div>
<span class="cursor-pointer m-r16" @click="handleMessage(ele)"></span>
<span v-if="item.userId === userStore.userInfo.userId" class="cursor-pointer" @click="handleDel(ele)"></span>
</div>
</div>
<!-- 子项评论回复操作 -->
<div v-if="ele.isShowInput" class="input-wrap" style="margin-top: 10px;">
<NConfigProvider inline-theme-disabled style="width:100%">
<NInput
v-model:value="ele.word"
type="textarea"
:autosize="{ minRows: 1 }"
:placeholder="`回复: @${ele.userName}`"
class="text"
@focus="ele.isShowSend = true"
@blur="handleBlur(ele)"
/>
</NConfigProvider>
<div class="send-btn" :class="{ active: publicWord }" @click="sendMessage(ele, index)">
<span v-if="ele.isShowSend"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="no-more">
暂时没有更多评论
</div>
</NInfiniteScroll>
</div>
</template>
<style scoped lang="scss">
.header-wrap {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
.left {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 12px;
border: 1px solid #2d28ff;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
}
.right {
}
}
.input-wrap {
display: flex;
align-items: center;
line-height: 1.55;
border-radius: 8px;
caret-color: #999;
font-size: 14px;
background: #f2f5f9;
padding: 10px 12px;
flex: 1;
: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 {
border: 0 !important;
}
background-color: transparent;
}
.send-btn {
margin-left: 16px;
margin-right: 16px;
flex-shrink: 0;
color: #999;
font-size: 14px;
cursor: pointer;
&.active {
color: #222;
}
}
}
.nav-wrap {
margin: 10px 0;
display: flex;
font-size: 14px;
// align-items: center;
.left-img {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 12px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
}
.right {
width: 100%;
.name {
margin-top: 10px;
margin-bottom: 7px;
font-weight: 500;
color: #333;
}
.des {
line-height: 1.25;
}
.star-wrap {
margin-top: 4px;
display: flex;
align-items: center;
justify-content: space-between;
.time {
color: #999;
font-size: 12px;
}
.star-operator {
font-size: 12px;
color: #999;
display: flex;
align-items: center;
cursor: pointer;
.icon-wrap {
display: flex;
align-items: center;
margin-right: 16px;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 3px;
}
}
.m-r16 {
margin-right: 16px;
}
}
}
}
}
.child-wrap {
padding-left: 52px;
.child-wrap-item {
display: flex;
font-size: 14px;
margin-top: 16px;
// align-items: center;
.left-img {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 12px;
img {
width: 24px;
height: 24px;
border-radius: 50%;
}
}
.right {
width: 100%;
.name {
margin-bottom: 7px;
font-weight: 500;
color: #333;
display: flex;
align-items: center;
.author {
margin-left: 8px;
color: #fff;
background: linear-gradient(90deg, #2d28ff, #1a7dff);
padding: 3px 4px;
font-size: 12px;
line-height: 12px;
border-radius: 4px;
}
}
.des {
line-height: 1.5;
// display: flex;
align-items: center;
.u-name {
color: #1880ff;
margin-right: 6px;
}
}
.star-wrap {
margin-top: 4px;
display: flex;
align-items: center;
justify-content: space-between;
.time {
color: #999;
font-size: 12px;
}
.star-operator {
font-size: 12px;
color: #999;
display: flex;
align-items: center;
cursor: pointer;
.icon-wrap {
display: flex;
align-items: center;
margin-right: 16px;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 3px;
}
}
.m-r16 {
margin-right: 16px;
}
}
}
}
}
}
.no-more {
color: #999;
font-size: 14px;
text-align: center;
padding: 46px 0 118px;
}
</style>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import defaultAvatar from '@/assets/img/default-avatar.png'
import { ref } from 'vue'
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
@ -46,8 +45,8 @@ async function handlePictureChange(event: Event) {
try {
const pictureResultList = await uploadImagesInBatches(imageFiles)
ruleForm.value.avatar = pictureResultList[0].url
event.target.value = ''
ruleForm.value.avatar = pictureResultList[0]
}
catch (error: any) {
message.error('图片上传失败')
@ -60,6 +59,19 @@ async function saveInfo() {
onCloseModel()
}
watch(
() => userStore.userInfo, // userInfo
(newUserInfo) => {
if (newUserInfo) {
ruleForm.value.nickName = newUserInfo.nickName
ruleForm.value.avatar = newUserInfo.avatar
ruleForm.value.brief = newUserInfo.brief
ruleForm.value.userId = newUserInfo.userId
}
},
{ immediate: true }, // ruleForm
)
const isVisible = ref(false)
function onCloseModel() {
isVisible.value = false
@ -84,7 +96,7 @@ onMounted(() => {
>
<div class="rounded-full flex justify-center overflow-hidden mb-4">
<client-only>
<img class="block w-[60px] h-[60px] rounded-full" :src="ruleForm.avatar || defaultAvatar" alt="编辑">
<img class="block w-[60px] h-[60px] rounded-full" :src="ruleForm.avatar" alt="编辑">
</client-only>
</div>
<div class="flex justify-center mb-2">
@ -123,7 +135,7 @@ onMounted(() => {
暂不修改
</div> -->
<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>
<!-- <div class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle color-[#fff]">

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import defaultAvatar from '@/assets/img/default-avatar.png'
//
import { headerRole } from '@/constants/index'
import {
Bell,
CirclePlus,
@ -9,17 +10,36 @@ import {
Monitor,
Workflow,
} from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
// import { AtCircle } from '@vicons/ionicons5'
import { NIcon } from 'naive-ui'
import { onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const modalStore = useModalStore()
//
const currentUseRoute = ref('')
const isShowPublishPicture = ref<boolean>(false)
const PublishPictureRef = ref<Payment | null>(null)
const publishPicture = ref({
title: '',
tags: [],
description: '',
imagePaths: [],
})
watch(
() => route.path, // route.path
(newPath) => {
currentUseRoute.value = newPath
},
{ immediate: true }, //
)
function hasItem(path: string, list: any) {
return !list.includes(path)
}
const searchText = ref('')
function onSearch(value: any) {
console.log('搜索:', value)
@ -101,18 +121,31 @@ async function handleUserSelect(key: string) {
//
async function handlePublishSelect(key: string) {
if (key === 'picture') {
debugger
isShowPublishPicture.value = true
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = true
}
}
else {
router.push({
path: `/${key}`,
query: {
type: 'add',
},
})
}
}
function closePublishImg() {
isShowPublishPicture.value = false
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = false
}
}
function handleLogin() {
modalStore.showLoginModal()
}
onMounted(() => {
})
</script>
@ -136,21 +169,10 @@ onMounted(() => {
<!-- <HeaderSearchInput /> -->
<HeaderSearchInput
v-if="hasItem(currentUseRoute, headerRole.inputSearch)"
v-model="searchText"
@search="onSearch"
/>
<!-- Search -->
<!-- <n-input class="w-[580px]" size="large" placeholder="搜索模型/图片/创作者寻找灵感" round /> -->
<!-- <NInput
round
clearable
placeholder="搜索模型/图片/创作者寻找灵感"
class="w-[580px]"
>
<template #prefix>
<Search class="h-4 w-4 text-gray-400" />
</template>
</NInput> -->
</div>
<!-- Right Actions -->
@ -200,11 +222,7 @@ onMounted(() => {
class="cursor-pointer w-10 h-10"
round
size="small"
:src="
userStore.userInfo && userStore.userInfo.avatar
? userStore.userInfo.avatar
: defaultAvatar
"
:src="userStore.userInfo.avatar"
/>
</NDropdown>
<div
@ -218,6 +236,13 @@ onMounted(() => {
</div>
</NSpace>
</header>
<div>
<NConfigProvider>
<NMessageProvider>
<Publish-picture v-if="isShowPublishPicture" ref="PublishPictureRef" :form-data="publishPicture" @close-publish-img="closePublishImg" />
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</template>

View File

@ -0,0 +1,238 @@
<script setup lang="ts">
import { RotateCcw, X } from 'lucide-vue-next'
import { defineProps, onBeforeMount, onBeforeUnmount, ref } from 'vue'
const props = defineProps({
isMember: {
type: Object,
default: () => {},
},
})
const emit = defineEmits(['closePayment', 'paymentSuccess'])
const message = useMessage()
let pollingTimer: ReturnType<typeof setInterval> | undefined
const formRef = ref<FormInst | null>(null)
// const isShowPayment = ref(false)
const userStore = useUserStore()
interface UserInfoType {
nickName: string
avatar: string
brief: string
userId: number | string
}
const userInfo = {
nickName: userStore.userInfo?.nickName ?? '',
avatar: userStore.userInfo?.avatar ?? '',
brief: userStore.userInfo?.brief ?? '',
userId: userStore.userInfo?.userId ?? '',
} as UserInfoType
function onCloseModel() {
emit('closePayment')
}
function onPaymentSuccess() {
emit('paymentSuccess')
}
const qrUrl = ref('')
//
const paymentStatus = ref(1)
const amount = ref(10)
const paymentParams = ref({
amount: 10,
type: 'points',
})
async function getQrCode() {
try {
paymentParams.value.amount = amount.value
const res = await request.post(`/ali/pay/doPay`, paymentParams.value)
if (res.code === 200) {
paymentStatus.value = 1
qrUrl.value = res.data.url
pollingTimer && clearTimeout(pollingTimer)
pollingTimer = setInterval(async () => {
try {
const res2 = await request.get(`/web/pay/queryTradeStatus?outTradeNo=${res.data.orderNo}`)
if (res2.data === 2) { // 1 2 4
paymentStatus.value = 2
clearTimeout(pollingTimer)
onPaymentSuccess()
emit('closePayment')
message.success('支付成功!')
}
else if (res2.data === 4) {
paymentStatus.value = 4
clearTimeout(pollingTimer)
}
}
catch (err) {
console.log('object', err)
}
}, 2000)
}
}
catch (err) {
console.log(err)
}
}
//
const isVisible = ref(true)
//
const points = ref(0)
async function getPoints() {
try {
const res = await request.get('/member/getPoints')
if (res.code === 200) {
points.value = res.data.points
}
}
catch (err) {
console.log(err)
}
}
getPoints()
//
// const isMember = ref(false)
// async function getIsMember() {
// try {
// const res = await request.get('/member/isMember')
// if (res.code === 200) {
// isMember.value = res.data
// }
// }
// catch (err) {
// console.log(err)
// }
// }
// getIsMember()
onBeforeMount (() => {
getQrCode()
})
defineExpose({
isVisible,
})
onBeforeUnmount(() => {
clearInterval(pollingTimer)
})
</script>
<template>
<n-modal
v-model:show="isVisible"
:preset="null"
:mask-closable="false"
transform-origin="center"
class="custom-modal"
>
<div class="bg-white rounded-xl w-200">
<div
class="p-4 flex justify-between rounded-t-xl bg-gradient-to-r from-[#fcf2da] to-[#f9db9f]"
>
<div class="flex items-center">
<div>
<img :src="userInfo.avatar" class="w-14 h-14 rounded-full mr-4">
</div>
<div class="flex flex-col">
<div class="text-[#814600] text-xl">
{{ userInfo.nickName }}
</div>
<div class="text-[#6a6a6a] text-xs mt-1">
{{ props.isMember.result === '1' ? `会员到期时间: ${props.isMember.endDate}` : '您还不是魔创未来的会员' }}
</div>
</div>
</div>
<div @click="onCloseModel">
<component :is="X" class="h-[18px] w-[18px] text-[#61666d] cursor-pointer" />
</div>
</div>
<div class="p-4">
余额{{ points }}积分
</div>
<div class="mt-4 px-6 flex">
<n-form
ref="formRef"
:label-width="80"
:model="paymentParams"
size="large"
label-placement="left"
style="width: 240px"
>
<n-form-item label="输入积分" path="modelProduct.modelName">
<n-input v-model:value="amount" type="number" placeholder="输入积分" />
</n-form-item>
</n-form>
<!-- <div>
确定
</div> -->
<n-button type="primary" class="mt-1 ml-2" @click="getQrCode">
确定
</n-button>
</div>
<!-- <div v-for="(item, index) in activityList" :key="index">
{{ item.activityName }}
</div> -->
<div class="mt-1 text-[#9d8c75] text-[12px] w-[500px] px-3">
1000个算力约可生图1000张或训练5次按照生图默认参数或训练基础参数预估即训练图片张数20张*单张训练次数15*训练轮数10
您充值的算力永久有效
算力充值不退不换
</div>
<div class="flex justify-center items-center my-4">
<div class="w-30 h-30 flex justify-center items-center relative">
<n-qr-code :value="qrUrl" :size="90" style="padding: 0;" />
<div v-if="paymentStatus === 4" class="absolute top-0 left-0 w-full h-full flex cursor-pointer flex-col justify-center items-center bg-opacity-50 bg-black text-white" @click="getQrCode">
请点击刷新<RotateCcw />
</div>
</div>
<div class="flex justify-center ml-2 flex-col pb-[14px]">
<div class="flex items-baseline">
<div class="text-[#222] font-medium mr-1">
支付
</div>
<div class="text-[#814600] mr-1">
<span class="text-[16px] mr-1">¥</span>
<span class="text-[40px]">{{ paymentParams.amount }}</span>
</div>
<!-- <div class="px-2 bg-gradient-to-r from-[#ffa700] to-[#ff006b] text-[#814600] text-white rounded-[4px] ml-1 text-[12px">
已减¥11
</div> -->
</div>
<div class="flex items-center mb-1">
<img src="@/assets/img/alipay.png" class="mr-1"> 请扫码完成支付
</div>
<div class="text-[12px]">
<span class="text-[#999]">
开通即代表同意
</span>
<span class="text-[#814600]">
魔创未来会员协议
</span>
</div>
</div>
</div>
</div>
</n-modal>
</template>
<style lang="scss" scoped>
.member-item {
display: flex;
justify-content: center;
align-items: center;
width: 16.66%;
height: 40px;
border: 1px solid #f0f0f0;
font-size: 12px;
font-weight: 400;
margin-right: -1px;
margin-bottom: -1px;
}
</style>

View File

@ -1,77 +1,175 @@
<script setup lang="ts">
import { ref } from "vue";
import { X, RotateCcw } from "lucide-vue-next";
const userStore = useUserStore();
import { RotateCcw, X } from 'lucide-vue-next'
import { useMessage } from 'naive-ui'
import { defineProps, onBeforeMount, onBeforeUnmount, ref } from 'vue'
const props = defineProps({
info: {
type: Array,
default: () => [],
},
isMember: {
type: Object,
default: () => {},
},
})
const emit = defineEmits(['closePayment', 'paymentSuccess'])
const message = useMessage()
let pollingTimer: ReturnType<typeof setInterval> | undefined
const currentNeedPayment = ref<number>(0)
// const isShowPayment = ref(false)
const userStore = useUserStore()
interface UserInfoType {
nickName: string;
avatar: string;
brief: string;
userId: number | string;
nickName: string
avatar: string
brief: string
userId: number | string
}
const userInfo = {
nickName: userStore.userInfo?.nickName ?? "",
avatar: userStore.userInfo?.avatar ?? "",
brief: userStore.userInfo?.brief ?? "",
userId: userStore.userInfo?.userId ?? "",
} as UserInfoType;
nickName: userStore.userInfo?.nickName ?? '',
avatar: userStore.userInfo?.avatar ?? '',
brief: userStore.userInfo?.brief ?? '',
userId: userStore.userInfo?.userId ?? '',
} as UserInfoType
const isVisible = ref(false);
function onCloseModel() {
isVisible.value = false;
emit('closePayment')
}
function onPaymentSuccess() {
emit('paymentSuccess')
}
const qrUrl = ref('')
const qrUrl = ref("你好达内的房东说房东扫烦恼的");
//
interface Member {
id: number;
title: string;
price: number;
originalPrice: number;
desc: string;
id: number
title: string
price: number
originalPrice: number
desc: string
}
const memberList = ref<Member[]>([])
const currentMember = ref<Member>({
id: 1,
title: "基础会员",
price: 39,
originalPrice: 50,
desc: "次月续费¥39",
})
//
const bindTimeout = ref(false)
//
const currentActivity = ref(null)
const activityList = ref<Member[]>([])
async function getPromotionList() {
try {
const res = await request.get(`/promotion/promotionList`)
if (res.code === 200) {
activityList.value = res.data
}
}
catch (err) {
console.log(err)
}
}
getPromotionList()
//
//
// const paymentParams = ref({})
const paymentStatus = ref(1)
const paymentParams = ref({
amount: props.info[0].unitPrice,
productId: props.info[0].id,
promotionId: 0,
type: 'member',
})
async function getQrCode() {
try {
const res = await request.post(`/ali/pay/doPay`, paymentParams.value)
if (res.code === 200) {
paymentStatus.value = 1
qrUrl.value = res.data.url
pollingTimer && clearTimeout(pollingTimer)
pollingTimer = setInterval(async () => {
try {
const res2 = await request.get(`/web/pay/queryTradeStatus?outTradeNo=${res.data.orderNo}`)
if (res2.data === 2) {
paymentStatus.value = 2
clearTimeout(pollingTimer)
message.success('支付成功!')
onPaymentSuccess()
emit('closePayment')
}
else if (res2.data === 4) {
paymentStatus.value = 4
clearTimeout(pollingTimer)
}
}
catch (err) {
console.log('object', err)
}
}, 2000)
}
}
catch (err) {
console.log(err)
}
}
//
async function handleActivityChange(value: number) {
paymentParams.value.promotionId = value
const res1 = await request.get(`/member/calculatePayment?promotionId=${value}&memberLevelId=${paymentParams.value.productId}`)
if (res1.code === 200) {
paymentParams.value.amount = res1.data
getQrCode()
}
}
// pay Card
async function changeCurrentNeedPayment(index: number) {
currentNeedPayment.value = index
paymentParams.value.productId = props.info[index].id
const res1 = await request.get(`/member/calculatePayment?promotionId=${paymentParams.value.promotionId}&memberLevelId=${paymentParams.value.productId}`)
if (res1.code === 200) {
paymentParams.value.amount = res1.data
getQrCode()
}
}
//
const isVisible = ref(true)
onBeforeMount (() => {
getQrCode()
})
defineExpose({
isVisible,
});
//
onMounted(() => {
// initFormData()
});
})
onBeforeUnmount(() => {
clearInterval(pollingTimer)
})
</script>
<template>
<n-modal
v-model:show="isVisible"
:preset="null"
:mask-closable="false"
transform-origin="center"
class="custom-modal"
>
<div class="bg-white rounded-xl w-200">
<div
class="p-4 flex justify-between rounded-t-xl h-14 bg-gradient-to-r from-[#fcf2da] to-[#f9db9f]"
class="p-4 flex justify-between rounded-t-xl bg-gradient-to-r from-[#fcf2da] to-[#f9db9f]"
>
<div class="flex items-center">
<div>
<img :src="userInfo.avatar" class="w-14 h-14 rounded-full mr-4" />
<img :src="userInfo.avatar" class="w-14 h-14 rounded-full mr-4">
</div>
<div class="flex flex-col">
<div class="text-[#814600] text-xl">
{{ userInfo.nickName }}
</div>
<div class="text-[#6a6a6a] text-xs mt-1">您还不是会员</div>
<div class="text-[#6a6a6a] text-xs mt-1">
{{ props.isMember.result === '1' ? `会员到期时间: ${props.isMember.endDate}` : '您还不是魔创未来的会员' }}
</div>
</div>
</div>
<div @click="onCloseModel">
@ -80,39 +178,84 @@ onMounted(() => {
</div>
<div class="grid grid-cols-4 gap-1 mt-4 px-6">
<div
v-for="item in 4"
v-for="(item, index) in info"
:key="item"
:style="[currentNeedPayment === index
? {
background: 'linear-gradient(90deg, #fffaf1, #ffeccf)',
border: '1px solid #f7b252',
}
: {}]"
class="select-none h-44 border-1 border-solid border-[#e9e9e9] rounded-lg bg-[#f9f9f9] flex justify-between items-center flex-col p-4 box-border cursor-pointer"
@click="changeCurrentNeedPayment(index)"
>
<!-- bg-gradient-to-b from-[#fdf0dd] to-[#fef8ef] -->
<!-- 1px solid #f7b252; -->
<div class="text-[#814600] card-item">基本版本Vip连续包月</div>
<!-- bg-gradient-to-b from-[#fdf0dd] to-[#fef8ef] -->
<!-- 1px solid #f7b252; -->
<div class="text-[#814600] card-item">
{{ item.memberName }}
</div>
<div class="card-item">
<span class="text-[#814600] text-[20px] mr-2">¥</span>
<span class="text-[#814600] text-[36px] mr-2">39</span>
<span class="text-gray-400 text-[20px] line-through">¥50</span>
<span class="text-[#814600] text-[36px] mr-2">{{ item.unitPrice }}</span>
<span class="text-gray-400 text-[20px] line-through">{{ item.originalPrice }}</span>
</div>
<div class="card-item text-[#814600] w-7/10 text-center text-[12px]">
次月续费¥39
{{ item.subscriptionPeriod }}
</div>
</div>
</div>
<div class="flex items-center my-4">
<div class="w-[50px] text-right mr-2">
活动:
</div>
<n-select
v-model:value="currentActivity"
placeholder="请选择活动" class="w-[200px]" label-field="activityName" value-field="id" :options="activityList" @change="handleActivityChange"
/>
</div>
<!-- <div v-for="(item, index) in activityList" :key="index">
{{ item.activityName }}
</div> -->
<div class="mt-4 px-6">
<div class="flex border-collapse ">
<div class="member-item bg-[#fef9f8]">基础版VIP</div>
<div class="member-item">每月15000点算力</div>
<div class="member-item">20G存储空间</div>
<div class="member-item">每月800次生图</div>
<div class="member-item">2个生图任务并行</div>
<div class="member-item">--</div>
<div class="member-item bg-[#fef9f8]">
基础版VIP
</div>
<div class="member-item">
每月15000点算力
</div>
<div class="member-item">
20G存储空间
</div>
<div class="member-item">
每月800次生图
</div>
<div class="member-item">
2个生图任务并行
</div>
<div class="member-item">
--
</div>
</div>
<div class="flex border-collapse ">
<div class="member-item bg-[#fef9f8]">专业版</div>
<div class="member-item">每月35000点算力</div>
<div class="member-item">50G存储空间</div>
<div class="member-item">每月5000次生图</div>
<div class="member-item">3个生图任务并行</div>
<div class="member-item">可训练XL模型</div>
<div class="member-item bg-[#fef9f8]">
专业版
</div>
<div class="member-item">
每月35000点算力
</div>
<div class="member-item">
50G存储空间
</div>
<div class="member-item">
每月5000次生图
</div>
<div class="member-item">
3个生图任务并行
</div>
<div class="member-item">
可训练XL模型
</div>
</div>
</div>
<div class="px-6 mt-1 text-[#9d8c75] text-[12px]">
@ -120,34 +263,40 @@ onMounted(() => {
</div>
<div class="flex justify-center items-center my-4">
<div class="w-30 h-30 flex justify-center items-center relative">
<n-qr-code :value="qrUrl" :size="80" class="p-0" />
<div v-if="bindTimeout" class="absolute top-0 left-0 w-full h-full flex cursor-pointer flex-col justify-center items-center bg-opacity-50 bg-black text-white">
请点击刷新<rotate-ccw/>
<n-qr-code :value="qrUrl" :size="90" style="padding: 0;" />
<div v-if="paymentStatus === 4" class="absolute top-0 left-0 w-full h-full flex cursor-pointer flex-col justify-center items-center bg-opacity-50 bg-black text-white" @click="getQrCode">
请点击刷新<RotateCcw />
</div>
</div>
<div class="flex justify-center ml-2 flex-col pb-[14px]">
<div class="flex items-baseline">
<div class="text-[#222] font-medium mr-1">支付</div>
<div class="text-[#814600] mr-1">
<span class="text-[16px] mr-1">¥</span>
<span class="text-[40px]">39</span>
</div>
<div class="px-2 bg-gradient-to-r from-[#ffa700] to-[#ff006b] text-[#814600] text-white rounded-[4px] ml-1 text-[12px">已减¥11</div>
<div class="flex items-baseline">
<div class="text-[#222] font-medium mr-1">
支付
</div>
<div class="flex items-center mb-1">
<img src="@/assets/img/alipay.png" class="mr-1"> 请扫码完成支付
</div>
<div class="text-[12px]">
<span class="text-[#999]">
开通即代表同意
</span>
<span class="text-[#814600]">
魔创未来会员协议
</span>
<div class="text-[#814600] mr-1">
<span class="text-[16px] mr-1">¥</span>
<span class="text-[40px]">{{ paymentParams.amount }}</span>
</div>
<!-- <div class="px-2 bg-gradient-to-r from-[#ffa700] to-[#ff006b] text-[#814600] text-white rounded-[4px] ml-1 text-[12px">
已减¥11
</div> -->
</div>
<div class="flex items-center mb-1">
<img src="@/assets/img/alipay.png" class="mr-1"> 请扫码完成支付
</div>
<div class="text-[12px]">
<span class="text-[#999]">
开通即代表同意
</span>
<span class="text-[#814600]">
魔创未来会员协议
</span>
</div>
</div>
</div>
<div class="bg-[#fefaee] text-center text-[#AF7F1A] text-[12px] py-1 rounded-b-lg">
需支付差价计算规则为专业版年包价格-剩余未下发基础版年包月数*基础版年包购买价格/12
</div>
<div class="bg-[#fefaee] text-center text-[#AF7F1A] text-[12px] py-1 rounded-b-lg">需支付差价计算规则为专业版年包价格-剩余未下发基础版年包月数*基础版年包购买价格/12</div>
</div>
</n-modal>
</template>

View File

@ -39,6 +39,22 @@ function toDetails() {
}
}
//
const publishPictureData = ref<any>({})
async function getPublishPicture() {
try {
const res = await request.get(`/image/detail?id=${props.item.id}`)
if (res.code === 200) {
publishPictureData.value = res.data
publishPictureData.value.imagePaths = res.data.imagePaths.split(',')
// publishPictureData.value.tags = res.data.tags.split(',')
}
}
catch (e) {
console.log(e)
}
}
// //
function handleSelect(event: Event, key: string) {
event.stopPropagation() //
@ -48,6 +64,21 @@ function handleSelect(event: Event, key: string) {
else if (key === 'delete') {
handleDelete()
}
else if (key === 'edit') {
if (props.currentType === '2') {
getPublishPicture()
showPublishImg()
}
else if (props.currentType === '1') {
router.push({
path: `/publish-workflow`,
query: {
type: 'edit',
id: props.item.id,
},
})
}
}
}
//
@ -112,6 +143,23 @@ function getFirstImagePath(imagePaths: string): string {
return ''
return imagePaths.split(',')[0] || ''
}
//
const isShowPublishPicture = ref<boolean>(false)
const PublishPictureRef = ref<Payment | null>(null)
function showPublishImg() {
isShowPublishPicture.value = true
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = true
}
}
function closePublishImg() {
isShowPublishPicture.value = false
if (PublishPictureRef.value) {
PublishPictureRef.value.isVisible = false
}
}
</script>
<template>
@ -155,7 +203,7 @@ function getFirstImagePath(imagePaths: string): string {
>
<img v-if="currentType === '0'" class="w-full h-full object-cover block" :src="item.surfaceUrl" alt="">
<img v-if="currentType === '1'" class="w-full h-full object-cover block" :src="item.coverPath" alt="">
<img v-if="currentType === '2'" class="w-full h-full object-cover block" :src="item.imagePaths" alt="">
<img v-if="currentType === '2'" class="w-full h-full object-cover block" :src="getFirstImagePath(item.imagePaths)" alt="">
<div
v-if="currentState === 'mallProduct' && item.isTop === 1"
class="text-[#58c08e] border-[#58c08e] border-solid border-[1px] bg-white rounded-lg px-1 w-10 text-[12px] ml-2 text-center absolute top-4 right-8"
@ -164,14 +212,14 @@ function getFirstImagePath(imagePaths: string): string {
</div>
<!-- 在发布中 auditStatus等于代表没有审批通过 -->
<div
v-if="currentState === 'mallProduct' && item.auditStatus === 4"
v-if="currentState === 'mallProduct' && item.auditStatus === 4 || item.auditStatus === 3"
class="absolute top-0 left-0 w-full h-full text-gray-400 bg-black/50 flex justify-center items-center flex-col"
>
<component
:is="CircleAlert"
class="h-[40px] w-[40px] text-white menu-icon m-1 text-gray-400"
/>
未通过
{{ item.auditStatus === 3 ? '审核中' : '未通过' }}
</div>
<div
class="modelSelectByUserIdModel w-full h-full top-0 left-0 flex px-4 py-4 box-border"
@ -266,6 +314,11 @@ function getFirstImagePath(imagePaths: string): string {
</div>
</div>
</div>
<NConfigProvider>
<NMessageProvider>
<Publish-picture v-if="isShowPublishPicture" ref="PublishPictureRef" :form-data="publishPictureData" @close-publish-img="closePublishImg" />
</NMessageProvider>
</NConfigProvider>
</div>
</template>

View File

@ -0,0 +1,183 @@
<script setup lang="ts">
import { commonApi } from '@/api/common'
import { cloneDeep } from 'lodash-es'
import { useMessage } from 'naive-ui'
import { defineProps, ref } from 'vue'
const props = defineProps({
formData: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['closePublishImg'])
const message = useMessage()
const isVisible = ref(true)
const fileInput = ref<HTMLInputElement | null>(null)
const rules = {
title: {
required: true,
message: '',
trigger: 'blur',
},
}
function triggerFileInput() {
(fileInput.value as HTMLInputElement)?.click()
}
async function handleFileChange(event: Event) {
// console.log('object', formData)
const target = event.target as HTMLInputElement
const files = target.files
if (files && files.length > 0) {
const res = await uploadImagesInBatches(files)
const urlList = res.map(item => item.url)
props.formData.imagePaths.push(...urlList)
}
target.value = ''
}
//
const imgLabelList = ref([])
async function getDictType() {
try {
const res = await commonApi.dictType({ type: 'image_label' })
imgLabelList.value = res.data
}
catch (error) {
console.log(error)
}
}
//
async function onPublish() {
try {
const param = cloneDeep(props.formData)
param.imagePaths = param.imagePaths.join(',')
param.tags = param.tags.join(',')
if (param.id) {
const res = await request.post('/image/update', param)
if (res.code === 200) {
message.success('发布成功')
emit('closePublishImg')
}
}
else {
const res = await request.post('/image/publish', param)
if (res.code === 200) {
message.success('发布成功')
emit('closePublishImg')
}
}
}
catch (error) {
console.log(error)
}
}
getDictType()
defineExpose({
isVisible,
})
function closePublishImg() {
emit('closePublishImg')
}
</script>
<template>
<div>
<n-modal
v-model:show="isVisible"
:preset="null"
transform-origin="center"
:mask-closable="false"
class="custom-modal"
>
<div class="bg-white p-4 rounded-lg w-[600px]">
<div class="flex justify-between items-center">
<div class="text-[18px]">
上传我的图片
</div>
<div
class="w-[20px] h-[20px] text-[#999] text-center cursor-pointer" @click="closePublishImg"
>
X
</div>
</div>
<div>
<div class="upload-content">
<div class="flex flex-col bg-[#f3f5f9] justify-center items-center w-30 h-30 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 mt-6" @click="triggerFileInput()">
上传文件
</div>
<div class="my-3">
点击上传文件
</div>
<!-- <div class="text-[#999999] text-xs">
.json/.zip
</div> -->
</div>
</div>
<div class="grid grid-cols-3 gap-2.5 mt-4 w-full">
<div v-for="(item, index) in props.formData.imagePaths" :key="index">
<img class="w-full h-[180px] object-cover rounded-lg" :src="item" alt="">
</div>
</div>
<!-- <div class="flex justify-center items-center my-2 gap-1 flex-wrap">
<div v-for="(item, index) in formData.imagePaths" :key="index" class="w-1/3">
<img :src="item" alt="" class="w-full h-[200px]">
</div>
</div> -->
<div class="mt-2">
<n-form
:label-width="80"
:model="formData"
:rules="rules"
size="large"
>
<n-form-item label="图片标题" path="title">
<n-input v-model:value="props.formData.title" placeholder="好的标题可以获得更好的曝光率" />
</n-form-item>
<n-form-item label="图片标签" path="tags" class="w-full">
<n-select
v-model:value="props.formData.tags"
placeholder="选择你的图片类型,比如 男生, 机械, 建筑设计 等"
multiple label-field="dictLabel"
value-field="dictValue" :options="imgLabelList"
/>
</n-form-item>
<n-form-item label="描述信息" path="description">
<n-input
v-model:value="props.formData.description"
placeholder="填写更全面的描述信息"
type="textarea"
:autosize="{
minRows: 3,
maxRows: 5,
}"
/>
</n-form-item>
</n-form>
</div>
</div>
<div class="flex justify-center items-center mt-5 text-white w-30 h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="onPublish">
发布
</div>
</div>
</n-modal>
<input
ref="fileInput"
type="file"
class="hidden"
accept="image/*"
multiple
@change="handleFileChange"
>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
const props = defineProps({
timeLineList: {
type: Array, //
required: true, //
},
currentStep: {
type: Number,
default: 0, //
},
});
</script>
<template>
<div class="flex justify-between items-center">
<div v-for="(item, index) in props.timeLineList" :key="index" class="flex items-center text-[18px]">
<div
:style="props.currentStep >= item.index ? { border: '2px solid #203df5' } : {}"
class="w-[40px] h-[40px] rounded-full bg-[#eee] text-center flex items-center justify-center"
>
{{ item.index }}
</div>
<div
class="ml-2 flex items-center font-bold"
:style="props.currentStep >= item.index ? { color: '#203df5' } : {}"
>
{{ item.name }}
</div>
</div>
</div>
</template>
<style></style>

View File

@ -1,49 +1,117 @@
<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import WangEditor from 'wangeditor'
import { uploadImagesInBatches } from '@/utils/uploadImg.ts'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
import '@wangeditor/editor/dist/css/style.css'
const props = defineProps({
modelValue: {
type: String,
default: '',
type: Object,
required: true,
},
})
// css
const emit = defineEmits(['update:modelValue'])
const editorRef = ref(null)
let editor = null
onMounted(() => {
editor = new WangEditor(editorRef.value)
editor.config.placeholder = '请输入内容...'
editor.config.height = 130
editor.config.onchange = (newHtml) => {
emit('update:modelValue', newHtml)
}
editor.create()
editor.txt.html(props.modelValue)
const mode = 'default'
// shallowRef
const editorRef = shallowRef()
const localForm = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
watch(
() => props.modelValue,
(newValue) => {
if (editor && newValue !== editor.txt.html()) {
editor.txt.html(newValue)
() => localForm.value,
(newVal) => {
emit('update:modelValue', newVal)
},
{ immediate: true, deep: true },
)
// HTML
const valueHtml = ref('')
// ajax
onMounted(() => {
// setTimeout(() => {
// valueHtml.value = '<p> Ajax </p>'
// }, 1500)
})
const toolbarConfig = {
}
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {},
}
editorConfig.MENU_CONF.uploadImage = {
//
async customUpload(file, insertFn) {
try {
const files = [file]
const res = await uploadImagesInBatches(files)
for (let i = 0; i < res.length; i++) {
insertFn(res[i].url)
}
}
catch (error) {
console.log('error', error)
}
},
)
}
//
onBeforeUnmount(() => {
if (editor) {
editor.destroy()
editor = null
}
const editor = editorRef.value
if (editor == null)
return
editor.destroy()
})
function handleCreated(editor) {
editorRef.value = editor // editor
}
function handleChange(editor) {
const dom = editor.getHtml()
emit('update:modelValue', dom)
}
</script>
<template>
<div>
<div ref="editorRef" />
<client-only>
<Toolbar
style="border-bottom: 1px solid rgb(240 240 240)"
:editor="editorRef"
:default-config="toolbarConfig"
:mode="mode"
/>
<Editor
v-model="localForm"
class="editor"
style="max-height: 500px; min-height: 100px; overflow-y: auto;"
:default-config="editorConfig"
:mode="mode"
@on-created="handleCreated"
@on-change="handleChange"
/>
</client-only>
</div>
</template>
<style scoped lang='scss'>
.editor .w-e-text {
line-height: 1.2; /* 行高为字体大小的 2 倍 */
}
</style>

View File

@ -56,18 +56,6 @@ async function onGetUUid() {
})
userStore.setUserInfo(res1.user)
window.location.href = '/'
// const parent = getCurrentInstance().parent
// parent.exposed.onCloseLogin()
// store.userInfo = res.data
// router.push('./home')
// store.dispatch("uuidLogin", res)
// that.$store.dispatch("uuidLogin", res)
// setTimeout(() => {
// that.$router.push({
// path: that.redirect || "/"
// }).catch(() => {});
// }, 1500)
}
}).catch(() => {
clearTimeout(pollingTimer)

View File

@ -14,24 +14,25 @@ const props = defineProps({
})
const emit = defineEmits(['update:modelValue', 'createModelsNext'])
const acceptTypes = '.safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft'
const isDataReady = ref(false)
const modelVersionItem = {
versionName: '1.0', //
modelId: 1, //
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: 'ddefd反问句说的', //
filePath: '', //
fileName: '', //
sampleImagePaths: 'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', // 20,
triggerWords: '触发词', //
triggerWords: '', //
isPublic: 1, // 1 2
allowFusion: 1, //
allowDownloadImage: 1, //
allowUsage: 1, // 使
isFree: 0, // 0 1
isOnlineUse: 1, // 线使
allowDownloadImage: 1, //
allowSoftwareUse: 1, // 使
allowFusion: 1, //
allowCommercialUse: 1, //
// allowUsage: 1, // 使
//
isExclusiveModel: 1, //
isExclusiveModel: 0, //
}
const isPublicList = [
{
@ -46,12 +47,7 @@ const isPublicList = [
defineExpose({
addVersion,
})
onMounted(() => {
//
nextTick(() => {
isDataReady.value = true
})
})
const localForm = computed({
get() {
return props.modelValue
@ -74,17 +70,22 @@ const formRef = ref<FormInst | null>(null)
const rules = {
versionName: {
required: true,
message: '请输入模型名称',
message: '',
trigger: 'blur',
},
modelId: {
required: true,
message: '',
trigger: 'blur',
},
triggerWords: {
required: true,
message: '请输入模型名称',
trigger: 'blur',
},
}
function addVersion() {
localForm.value.modelVersionList.push(modelVersionItem)
localForm.value.modelVersionList.unshift(modelVersionItem)
}
const originalBtnList = ref([
{
@ -132,165 +133,198 @@ async function handleFileChange(event: Event) {
</script>
<template>
<div v-for="(item, index) in localForm.modelVersionList" :key="index">
<n-form
ref="formRef"
:label-width="80"
:model="localForm"
:rules="rules"
size="large"
>
<n-form-item label="版本名称" path="versionName">
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
</n-form-item>
<n-form-item label="基础模型" path="modelId">
<n-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 v-for="(item, index) in localForm.modelVersionList" :key="index" class="bg-gray-100 p-4 rounded-lg">
<n-form
ref="formRef"
:label-width="80"
:model="localForm"
:rules="rules"
size="large"
>
<n-form-item label="版本名称" path="versionName">
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
</n-form-item>
<n-form-item label="基础模型" path="modelId">
<n-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 class="flex-1 flex items-center line-clamp">
{{
item.fileName
}}
<div v-if="item.fileName" class="flex justify-between items-center bg-white p-3 mt-2 rounded-lg">
<div class="bg-[#d8e5fd] text-[12px] text-[#3162ff] w-16 h-7 rounded-lg flex justify-center items-center">
100%
</div>
<div class="flex-1 flex items-center line-clamp">
{{
item.fileName
}}
</div>
<div>
<Trash class="cursor-pointer" @click="item.fileName = '', item.filePath = ''" />
</div>
</div>
<div>
<Trash class="cursor-pointer" @click="item.fileName = '', item.filePath = ''" />
</div>
</div>
<div v-else>
<div class="upload-content">
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
上传文件
</div>
<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 v-else>
<div class="upload-content">
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
上传文件
</div>
<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 class="flex mt-6">
版本介绍 <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" />
<!-- <WangEditor
ref="editorRef"
v-model="content"
:height="400"
placeholder="请输入文章内容..."
:disabled="isDisabled"
:upload-img-server="/model/file"
:upload-img-headers="uploadHeaders"
@change="handleContentChange"
@focus="handleFocus"
@blur="handleBlur"
@upload-success="handleUploadSuccess"
@upload-error="handleUploadError"
/> -->
</client-only>
</div>
<!-- <div class="mt-4 space-x-4">
<n-button @click="toggleDisabled">
{{ isDisabled ? '启用编辑器' : '禁用编辑器' }}
</n-button>
<n-button @click="clearContent">
清空内容
</n-button>
<n-button @click="setContent">
设置内容
</n-button>
</div> -->
<!-- 预览区域 -->
<!-- <div class="mt-4">
<h3 class="text-lg font-bold">
预览内容
</h3>
<div
class="p-4 border rounded-lg mt-2"
v-html="content"
/>
</div> -->
<div class="mt-6">
触发词
</div>
<div class="-mb-5 text-gray-400 text-[12px]">
请输入您用来训练的单词
</div>
<n-form-item path="triggerWords">
<n-input v-model:value="item.triggerWords" placeholder="例如: 1boy" />
</n-form-item>
<div class="">
权限设置
</div>
<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 class="flex mt-6">
版本介绍 <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 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 class="mt-1 mb-2 text-gray-500 text-[12px]">
选择会员专属或会员下载视为您已经阅读 <span class="text-[#3162ff] cursor-pointer underline">会员模型许可协议</span> 并同意其中条款
<div class="text-gray-400 text-[12px] my-4">
许可范围
</div>
<div v-if="item.isFree === 1" class="text-[12px]">
<div>会员下载模型</div>
<div class="text-gray-500">
下载模型需购买会员在线生图对所有人开放无生图次数限制会员下载的模型版本生成图片默认可商用
<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>
<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.allowDownloadImage"
: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 class="text-gray-400 text-[12px] mt-4">
许可范围
</div>
<div>
<div />
</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 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>
<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>
<input

View File

@ -0,0 +1,220 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { uploadFileBatches } from '@/utils/uploadImg.ts'
import { Asterisk, Trash } from 'lucide-vue-next'
import { computed, ref, watch } from 'vue'
//
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:modelValue', 'nextStep', 'preStep'])
const localForm = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
watch(
() => localForm.value,
(newVal) => {
console.log('newVal', newVal)
emit('update:modelValue', newVal)
},
{ immediate: true, deep: true },
)
const message = useMessage()
const acceptTypes = '.json,.zip'
defineExpose({
addVersion,
})
const modelVersionItem = {
versionName: '',
versionDescription: '', //
filePath: '', //
fileName: '', //
delFlag: '0',
imagePaths: [],
}
const rules = {
versionName: {
required: true,
message: '',
trigger: 'blur',
},
}
function addVersion() {
localForm.value.workFlowVersionList.unshift(modelVersionItem)
}
const formRefs = ref<(FormInst | null)[]>([])
function setFormRef(el: FormInst | null, index: number) {
if (el) {
formRefs.value[index] = el
}
}
async function nextStep() {
for (let i = 0; i < localForm.value.workFlowVersionList.length; i++) {
if (localForm.value.workFlowVersionList[i].delFlag === '0' && localForm.value.workFlowVersionList[i].fileName === '') {
return message.error('请上传文件')
}
const regex = /[\u4E00-\u9FA5]/ //
if (localForm.value.workFlowVersionList[i].delFlag === '0' && !regex.test(localForm.value.workFlowVersionList[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)
}
// formRef.value?.validate((errors) => {
// if (!errors) {
// emit('nextStep')
// }
// else {
// console.log('error', errors)
// }
// })
}
function preStep() {
emit('preStep')
}
//
const uploadFileIndex = ref(0)
const fileInput = ref<HTMLInputElement | null>(null)
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 res = await uploadFileBatches(files)
localForm.value.workFlowVersionList[uploadFileIndex.value].filePath = res[0].url
localForm.value.workFlowVersionList[uploadFileIndex.value].fileName = res[0].fileName
}
target.value = ''
}
function computedDelFlag() {
return localForm.value.workFlowVersionList.filter(item => item.delFlag === '0')
}
function onDelete(index: number) {
if (computedDelFlag().length === 1)
return
localForm.value.workFlowVersionList[index].delFlag = '2'
}
</script>
<template>
<div class="flex justify-center items-center">
<div class="w-[960px]">
<template v-for="(item, index) in localForm.workFlowVersionList" :key="index">
<div v-if="item.delFlag === '0'" class="bg-gray-100 p-4 rounded-lg mt-4 relative">
<div class="absolute -right-10 top-4 cursor-pointer">
<Trash class="cursor-pointer" @click="onDelete(index)" />
</div>
<n-form
:ref="(el) => setFormRef(el, index)"
:label-width="80"
:model="localForm.workFlowVersionList[index]"
:rules="rules"
size="large"
>
<n-form-item label="版本名称" path="versionName">
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
</n-form-item>
<div class="flex">
版本介绍 <Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div class="text-gray-400 text-[12px]">
填写类别可让模型获得更精准的流量, 平台也有权基于标准修改你的类别标签
</div>
<div class="bg-white p-3 mt-2 rounded-lg">
<client-only>
<WangEditor v-model="item.versionDescription" />
</client-only>
</div>
<div class="flex mt-4">
上传文件 <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 class="flex-1 flex items-center line-clamp">
{{
item.fileName
}}
</div>
<div>
<Trash class="cursor-pointer" @click="item.fileName = '', item.filePath = ''" />
</div>
</div>
<div v-else>
<div class="upload-content">
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
上传文件
</div>
<div class="my-3">
点击上传文件
</div>
<div class="text-[#999999] text-xs">
.json/.zip
</div>
</div>
</div>
</div>
</n-form>
</div>
</template>
<div class="flex items-center justify-center mt-5">
<div class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer" @click="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="nextStep">
下一步
</div>
</div>
</div>
</div>
<input
ref="fileInput"
type="file"
class="hidden"
:accept="acceptTypes"
@change="handleFileChange"
>
</template>
<style lang="scss" scoped>
.line-clamp {
margin: 0 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: rgb(72, 71, 71);
}
</style>

View File

@ -0,0 +1,328 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { commonApi } from '@/api/common'
import { isOriginalList, isPublicList } from '@/constants/index'
import { Asterisk } from 'lucide-vue-next'
const props = defineProps({
modelValue: {
type: Object,
required: true,
default: () => ({ workFlow: {}, workFlowVersionList: [] }),
},
})
const emit = defineEmits(['update:modelValue', 'nextStep'])
const message = useMessage()
const formRef = ref<FormInst | null>(null)
const rules = {
'workFlow.workflowName': {
required: true,
message: '',
trigger: 'blur',
},
'workFlow.modelType': {
required: true,
message: '',
trigger: 'blur',
},
}
function handleValidateClick() {
formRef.value?.validate((errors) => {
if (!errors) {
emit('nextStep')
}
else {
console.log(errors)
}
})
}
const activityList = ref([])
async function getActivityList() {
try {
const res = await request.post('/ToActivity/list', {})
activityList.value = res.rows
}
catch (error) {
console.log(error)
}
}
getActivityList()
const localForm = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
watch(
() => localForm.value,
(newVal) => {
emit('update:modelValue', newVal)
},
{ immediate: true, deep: true },
)
function handleIsPublic(value: number) {
localForm.value.workFlow.jurisdiction = value
}
function handleIsOriginal(value: number) {
localForm.value.workFlow.original = value
if (value === 0) {
localForm.value.workFlow.authorName = ''
}
}
//
interface DictItem {
dictLabel: string
dictValue: string | number
children?: DictItem[]
}
// interface FormData {
// workFlow: {
// type: (string | number)[]
// }
// }
// //
// const localForm = ref<FormData>({
// workFlow: {
// type: [],
// },
// })
const workFlowTypeList = ref<DictItem[]>([])
async function getDictType() {
try {
const [res1, res2] = await Promise.all([
commonApi.dictType({ type: 'work_flow_type' }),
commonApi.dictType({ type: 'work_flow_type_child' }),
])
// modelPartCategory.value = res1.data
workFlowTypeList.value = res1.data
//
workFlowTypeList.value.forEach((item: any) => {
// children
item.children = []
//
res2.data.forEach((child: any) => {
// parentIddictValue
if (child.partId === item.dictCode) {
// children
item.children.push(child)
}
})
})
console.log('object', workFlowTypeList.value)
}
catch (error) {
console.log(error)
}
}
getDictType()
function handleUpdateValue(value: any) {
localForm.value.workFlow.typeList = value
if (value.length > 2) {
message.error('最多只能选择两项')
value = value.splice(0, 2)
localForm.value.workFlow.typeList = value
}
else {
localForm.value.workFlow.typeList = value
}
}
</script>
<template>
<div class="flex justify-center mx-auto mt-10">
<div class="w-[1137px] flex items-start">
<div class="bg-gray-100 p-4 h-auto w-[60%] mr-2 rounded-lg">
<n-form
ref="formRef"
:label-width="80"
:model="localForm"
:rules="rules"
size="large"
>
<n-form-item label="工作流名称" path="workFlow.workflowName">
<n-input v-model:value="localForm.workFlow.workflowName" placeholder="简单描述用途, 如: 图像一键放大" />
</n-form-item>
<div>
内容类别
</div>
<div class="text-gray-400 -mb-4 text-[12px]">
填写类别可让工作流获得更精准的流量平台也有权基于标准修改你的类别标签
</div>
<n-form-item path="workFlow.activityId">
<!-- <n-select
v-model:value="localForm.workFlow.typeList" label-field="dictLabel"
value-field="dictValue" filterable :options="workFlowTypeList"
@update:value="handleUpdateValue"
placeholder="请选择类型"
/> -->
<n-cascader
v-model:value="localForm.workFlow.typeList"
multiple
clearable
placeholder="请选择类型"
max-tag-count="responsive"
:options="workFlowTypeList"
check-strategy="child"
label-field="dictLabel"
value-field="dictValue"
@update:value="handleUpdateValue"
/>
</n-form-item>
<div>
参与活动
</div>
<div class="text-gray-400 -mb-4 text-[12px]">
参与特定活动或比赛,下拉选择
</div>
<n-form-item path="workFlow.activityParticipation">
<n-select
v-model:value="localForm.workFlow.activityParticipation"
label-field="activityName"
value-field="id"
placeholder="请选择参与哪个活动"
:options="activityList"
/>
</n-form-item>
</n-form>
</div>
<div class="w-[40%]">
<div class=" bg-gray-100 p-4 rounded-lg">
<div class="flex -mb-2">
是否公开<Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div class="flex justify-center items-center mt-5 bg-white h-10 rounded-lg">
<div
v-for="(item, index) in isPublicList"
:key="index"
:style="{
backgroundColor:
localForm.workFlow.jurisdiction === item.value
? 'rgba(49, 98, 255, 0.1)'
: '#fff',
color: localForm.workFlow.jurisdiction === item.value ? '#3162ff' : '#000',
}" class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer" @click="handleIsPublic(item.value)"
>
{{ item.label }}
</div>
</div>
<div class="text-gray-400 text-[12px] mt-2">
<div v-if="localForm.workFlow.jurisdiction === 1">
公开状态: 对网站所有人可见
</div>
<div v-if="localForm.workFlow.jurisdiction === 2">
非公开状态: 仅自己可见可在在线生图自用
</div>
</div>
<div class="flex -mb-2 mt-2">
是否原创<Asterisk :size="10" color="#ff0000" class="mt-1" />
</div>
<div class="flex justify-center items-center mt-5 bg-white h-10 rounded-lg">
<div
v-for="(item, index) in isOriginalList"
:key="index"
:style="{
backgroundColor:
localForm.workFlow.original === item.value
? 'rgba(49, 98, 255, 0.1)'
: '#fff',
color: localForm.workFlow.original === 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 v-if="localForm.workFlow.original === 0" class="text-gray-400 text-[12px] mt-2">
发布原创模型拿收益<span class="text-[#3162ff] cursor-pointer">创作者激励计划</span>
</div>
<n-form
v-if="localForm.workFlow.original === 1"
:label-width="80"
:model="localForm"
:rules="rules"
size="large"
>
<n-form-item path="workFlow.authorName">
<n-input v-model:value="localForm.workFlow.authorName" placeholder="请输入原创作者" />
</n-form-item>
</n-form>
</div>
<div class="mt-4 bg-gray-100 p-4 rounded-lg">
<div class="text-[#3162ff] mb-4">
用户使用时, 我授予用户以下权限
</div>
<div>
<div class="flex mb-2">
<n-checkbox
v-model:checked="localForm.workFlow.onlineUse"
:checked-value="0"
:unchecked-value="1"
/>
<div class="text-[12px] ml-2">
<div class="text-gray-600">
允许在线使用
</div>
<div class="text-gray-400">
您的工作流为公开状态时默认同意此条款他人不能随意转载
</div>
</div>
</div>
<div class="flex mb-2">
<n-checkbox
v-model:checked="localForm.workFlow.download"
:checked-value="0"
:unchecked-value="1"
/>
<div class="text-[12px] ml-2">
<div class="text-gray-600">
允许下载工作流
</div>
<div class="text-gray-400">
允许下载后您的工作流可能会被他人转载我们无法控制该行为
</div>
</div>
</div>
</div>
<div class="text-[#3162ff] mb-4">
用户使用时我授予用户以下商业用途权限:
</div>
<div>
<div class="flex mb-2">
<n-checkbox
v-model:checked="localForm.workFlow.sell"
:checked-value="0"
:unchecked-value="1"
/>
<div class="text-[12px] ml-2">
<div class="text-gray-600">
生成图片可出售或用于商业目的
</div>
</div>
</div>
</div>
</div>
<div
class="flex justify-center items-center mt-5 text-white w-30 h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="handleValidateClick"
>
下一步
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,198 @@
<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.workFlowVersionList.length; i++) {
if (localForm.value.workFlowVersionList[i].delFlag === '0' && localForm.value.workFlowVersionList[i].imagePaths.length === 0) {
return message.error('请上传图片')
}
}
try {
const param = cloneDeep(localForm.value)
if (param.workFlow.typeList.length !== 0) {
param.workFlow.type = param.workFlow.typeList.join(',')
}
else {
param.workFlow.type = ''
}
for (let i = 0; i < param.workFlowVersionList.length; i++) {
if (param.workFlowVersionList[i].imagePaths.length !== 0) {
param.workFlowVersionList[i].imagePaths = param.workFlowVersionList[i].imagePaths.join(',')
}
else {
param.workFlowVersionList[i].imagePaths = ''
}
}
// await request.post('/WorkFlow/addWorkFlow', param)
if (type === 'add') {
try {
const res = await request.post('/WorkFlow/addWorkFlow', param)
if (res.code === 200) {
showSuccessModal.value = true
}
}
catch (error) {
console.log(error)
}
}
else {
try {
const res = await request.post('/WorkFlow/updateWorkFlow', 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.workFlowVersionList[uploadFileIndex.value].imagePaths.length + files.length
if (sum >= 20)
return message.error('最多20张')
const res = await uploadImagesInBatches(files)
const urlList = res.map(item => item.url)
localForm.value.workFlowVersionList[uploadFileIndex.value].imagePaths.push(...urlList)
}
target.value = ''
}
function onPositiveClick() {
showSuccessModal.value = false
router.push('/personal-center')
}
</script>
<template>
<div class="mx-auto mt-10">
<div class="w-[960px] items-start rounded-lg">
<template v-for="(item, index) in localForm.workFlowVersionList" :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.hideGenInfo"
: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.imagePaths" :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,4 +1,23 @@
import type { SearchCheckIcon } from 'lucide-vue-next'
export const appName = '魔创未来'
export const appDescription = '魔创未来'
export const authRoutes = ['/personal-center', '/publish-model', '/publish-workflow']
export const verifyBlankRoute = ['/member-center']
export const verifyBlankRoute = ['/member-center', 'int-detail']
export const isOriginalList = [{
label: '原创',
value: 0,
}, {
label: '转载',
value: 1,
}]
export const isPublicList = [{
label: '公开',
value: 1,
}, {
label: '自己',
value: 2,
}]
export const headerRole = { // 包括就隐藏
inputSearch: ['/model-square'],
}

View File

@ -72,7 +72,7 @@ function handleSide(event: Event, path: string) {
<!-- Main Content -->
<div class="flex flex-1 overflow-hidden">
<!-- Sidebar -->
<nav class="w-[240px] border-r border-gray-100 bg-gray-50/50 py-2 dark:border-dark-700 dark:bg-dark-800/50">
<nav class="w-[200px] border-r border-gray-100 bg-gray-50/50 py-2 dark:border-dark-700 dark:bg-dark-800/50">
<div class="space-y-1 px-2">
<NuxtLink
v-for="item in menuStore.menuItems"

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { NButton, NInput, NDataTable, useMessage } from 'naive-ui'
import { ref, onMounted } from 'vue'
import { NButton, NDataTable, NInput, useMessage } from 'naive-ui'
import { onMounted, ref } from 'vue'
import request from '~/utils/request'
definePageMeta({
@ -34,23 +34,23 @@ const userData = ref<UserData[]>([])
const columns = [
{
title: 'ID',
key: 'id'
key: 'id',
},
{
title: '姓名',
key: 'name'
key: 'name',
},
{
title: '邮箱',
key: 'email'
key: 'email',
},
{
title: '状态',
key: 'status',
render: (row: UserData) => {
return row.status === 1 ? '激活' : '禁用'
}
}
},
},
]
//
@ -59,19 +59,22 @@ async function fetchUserList() {
try {
const response = await request.get<ApiResponse<UserData[]>>('/api/users', {
params: {
keyword: searchText.value
}
keyword: searchText.value,
},
})
if (response.code === 200) {
// userData.value = response.data
message.success('数据加载成功')
} else {
}
else {
message.error(response.message || '获取数据失败')
}
} catch (error: any) {
}
catch (error: any) {
message.error(error.message || '请求出错')
} finally {
}
finally {
loading.value = false
}
}
@ -87,16 +90,18 @@ async function handleAddUser() {
const response = await request.post<ApiResponse<UserData>>('/api/users', {
name: '测试用户',
email: 'test@example.com',
status: 1
status: 1,
})
if (response.code === 200) {
message.success('添加成功')
fetchUserList() //
} else {
}
else {
message.error(response.message || '添加失败')
}
} catch (error: any) {
}
catch (error: any) {
message.error(error.message || '请求出错')
}
}
@ -110,21 +115,21 @@ onMounted(() => {
<template>
<div class="p-4">
<div class="mb-4 flex gap-4 items-center">
<n-input
v-model:value="searchText"
placeholder="请输入搜索关键词"
<NInput
v-model:value="searchText"
placeholder="请输入搜索关键词"
class="w-64"
@keyup.enter="handleSearch"
/>
<n-button type="primary" :loading="loading" @click="handleSearch">
<NButton type="primary" :loading="loading" @click="handleSearch">
搜索
</n-button>
<n-button type="info" @click="handleAddUser">
</NButton>
<NButton type="info" @click="handleAddUser">
添加用户
</n-button>
</NButton>
</div>
<n-data-table
<NDataTable
:loading="loading"
:columns="columns"
:data="userData"
@ -153,4 +158,4 @@ onMounted(() => {
.w-64 {
width: 16rem;
}
</style>
</style>

View File

@ -0,0 +1,125 @@
<script setup lang="ts">
import { nextTick, onMounted } from 'vue'
definePageMeta({
layout: 'header',
})
const activeTab = ref('')
// const route = useRoute();
// const { id } = route.params as { id: string };
interface PointsResult {
points: number
memberConsumeList: any[]
}
const pointsResult = ref<PointsResult>(null)
async function getPoints() {
try {
const res = await request.get('/member/getPoints')
if (res.code === 200) {
pointsResult.value = res.data as PointsResult
nextTick(() => {
activeTab.value = 'beatles'
})
}
}
catch (err) {
console.log(err)
}
}
getPoints()
onMounted(() => {
// nextTick(() => {
// activeTab.value = 'beatles'
// })
})
</script>
<template>
<div class="flex justify-center bg-[#f2f5f8] items-center h-[calc(100vh-48px)]">
<div class="w-[1200px] bg-white rounded-lg shadow-lg p-10 h-[calc(100%-40px)]">
<h1 class="text-2xl font-bold mb-4">
算力明细
</h1>
<n-tabs v-model:value="activeTab" type="line" animated>
<!-- <n-tab-pane name="oasis" tab="已获取">
<div class="h-[500px] overflow-y-auto">
<div class="mc-table flex w-full">
<div class="w-[250px]">
获取时间
</div>
<div class="w-[250px]">
获取来源
</div>
<div class="flex-1">
算力生效时间
</div>
<div class="flex-1">
算力到期时间
</div>
<div class="w-[200px]">
算力值
</div>
</div>
<div v-for="item in pointsResult.memberConsumeList" :key="item" class="mc-table flex w-full">
<div class="w-[250px]">
{{ item.consumeTime }}
</div>
<div class="w-[250px]">
每日赠送算力清零
</div>
<div class="flex-1">
-
</div>
<div class="w-[180px]">
{{ item.consumePoints }}
</div>
</div>
</div>
</n-tab-pane> -->
<n-tab-pane name="beatles" tab="已消耗">
<div class="h-[500px] overflow-y-auto">
<div class="mc-table flex w-full">
<div class="w-[250px]">
消耗时间
</div>
<div class="w-[250px]">
消耗类型
</div>
<div class="flex-1">
任务详情
</div>
<div class="w-[200px]">
算力值
</div>
</div>
<div v-for="item in pointsResult.memberConsumeList" :key="item" class="mc-table flex w-full">
<div class="w-[250px]">
{{ item.consumeTime }}
</div>
<div class="w-[250px]">
每日赠送算力清零
</div>
<div class="flex-1">
-
</div>
<div class="w-[180px]">
{{ item.consumePoints }}
</div>
</div>
</div>
</n-tab-pane>
</n-tabs>
</div>
</div>
</template>
<style lang="scss" scoped>
.mc-table {
> div {
line-height: 40px;
height: 50px;
border-bottom: 1px solid #eee;
}
}
</style>

View File

@ -1,20 +1,24 @@
<script setup lang="ts">
import { CheckmarkCircleSharp, Close } from '@vicons/ionicons5'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
definePageMeta({
layout: 'header',
})
//
const needPayment = ref([{
amount: '',
productId: '',
promotionId: '',
type: 'member',
}])
const userStore = useUserStore()
const userInfo = userStore.userInfo as UserInfoType
//
interface Payment {
isVisible: boolean
}
const PaymentRef = ref<Payment | null>(null)
function showPayment() {
if (PaymentRef.value) {
PaymentRef.value.isVisible = true
}
}
//
const isMember = ref(false)
@ -29,22 +33,7 @@ async function getIsMember() {
console.log(err)
}
}
// getIsMember()
//
const points = ref(0)
async function getPoints() {
try {
const res = await request.get('/member/getPoints')
if (res.code === 200) {
points.value = res.data
}
}
catch (err) {
console.log(err)
}
}
// getPoints()
getIsMember()
//
const MemberBenefitList = ref([])
@ -59,7 +48,7 @@ async function getMemberBenefitList() {
console.log(err)
}
}
// getMemberBenefitList()
getMemberBenefitList()
//
interface memberLevel {
@ -81,6 +70,59 @@ async function getMemberLevelList() {
}
}
getMemberLevelList()
//
const isShowPayment = ref(false)
const PaymentRef = ref<Payment | null>(null)
function showPayment(info: any) {
if (info === 1) {
needPayment.value = memberLevelList.value
}
else {
needPayment.value = [info]
}
isShowPayment.value = true
if (PaymentRef.value) {
PaymentRef.value.isVisible = true
}
}
//
function closePayment() {
isShowPayment.value = false
if (PaymentRef.value) {
PaymentRef.value.isVisible = false
}
}
//
const isShowIntPayment = ref(false)
const IntPaymentRef = ref<Payment | null>(null)
function showIntPayment() {
isShowIntPayment.value = true
if (IntPaymentRef.value) {
IntPaymentRef.value.isVisible = true
}
}
//
function closeIntPayment() {
isShowIntPayment.value = false
if (IntPaymentRef.value) {
IntPaymentRef.value.isVisible = false
}
}
//
function paymentSuccess() {
getIsMember()
}
//
function toIntDetail() {
const baseUrl = window.location.origin
window.open(`${baseUrl}/int-detail`, '_blank', 'noopener,noreferrer')
}
</script>
<template>
@ -88,7 +130,7 @@ getMemberLevelList()
<div class="test bg-[#403833] flex justify-center items-center h-90">
<div class="flex gap-6 w-[1145px]">
<!-- 左侧用户信息 -->
<div class="w-60 bg-[#2b2421] rounded-lg p-6">
<div class="w-60 bg-[#2b2421] rounded-lg p-6 my-3">
<!-- 右上角订阅管理 -->
<div class="text-right">
<button class="text-[#8b8685] text-sm bg-inherit border-none cursor-pointer">
@ -113,14 +155,14 @@ getMemberLevelList()
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
/>
</svg>
您还不是魔创未来的会员
{{ isMember.result === '1' ? `会员到期时间: ${isMember.endDate}` : '您还不是魔创未来的会员' }}
</div>
</div>
<!-- 开通会员按钮 -->
<button
class="w-full bg-[#f2d5bc] text-[#6b4f3f] rounded-full py-2.5 mt-8 font-medium cursor-pointer"
@click="showPayment"
@click="showPayment(1)"
>
开通会员
</button>
@ -136,7 +178,7 @@ getMemberLevelList()
<!-- 右侧内容 -->
<div class="flex-1 space-y-4">
<!-- 余额信息卡片 -->
<div class="bg-[#2b2421] rounded-lg p-6">
<div class="bg-[#2b2421] rounded-lg p-6 mt-3">
<div class="grid grid-cols-3 gap-8">
<!-- 算力余额 -->
<div>
@ -145,11 +187,11 @@ getMemberLevelList()
</h3>
<div class="flex items-center mb-2">
<span class="text-[#c3986b] mr-2">300</span>
<button class="text-[#c3986b] bg-inherit border-none cursor-pointer">
<button class="text-[#c3986b] bg-inherit border-none cursor-pointer" @click="showIntPayment">
充值 >
</button>
</div>
<button class="text-white bg-inherit border-none cursor-pointer p-0">
<button class="text-white bg-inherit border-none cursor-pointer p-0" @click="toIntDetail">
算力明细 >
</button>
</div>
@ -226,7 +268,7 @@ getMemberLevelList()
<div
v-for="(item, index) in memberLevelList"
:key="index"
class="h-56 border-1 border-solid border-[#fff] rounded-lg bg-gradient-to-b from-[#fdf0dd] to-[#fef8ef] flex justify-between items-center flex-col p-4 box-border"
class="h-56 border-2 border-solid border-[#fff] rounded-lg bg-gradient-to-b from-[#f7e8d3] to-[#fef8ef] flex justify-between items-center flex-col p-4 box-border"
>
<div class="text-[#e08909] card-item">
{{ item.memberName }}
@ -241,7 +283,7 @@ getMemberLevelList()
</div>
<div class="card-item">
<button
class="bg-gradient-to-r from-[#fbdfa4] to-[#f3c180] text-[#4c3d33] w-40 h-10 rounded-full text-[12px] border-none cursor-pointer"
class="bg-gradient-to-r from-[#fbdfa4] to-[#f3c180] text-[#4c3d33] w-40 h-10 rounded-full text-[12px] border-none cursor-pointer" @click="showPayment(item)"
>
立即开通
</button>
@ -254,74 +296,172 @@ getMemberLevelList()
会员权益
</div>
</div>
<div class="flex gap-2">
<div class="w-1/4 h-20 bg-[#f7f0ea] flex justify-center items-center">
会员权益
<!-- <div class="flex items-center">
<div v-for="(item, index) in MemberBenefitList" :key="index" class="w-1/4 w-full">
<div class="flex items-center justify-center">
{{ item.memberLevelName }}
</div>
<div v-for="(item2, index2) in item.memberBenefitList" :key="index2" class="flex w-full">
<div class="h-20 bg-[#f7f0ea] flex justify-center items-center w-full">
{{ item2.benefitName ? item2.benefitName : '无' }}
</div>
</div>
</div>
<div class="w-1/4 h-20 bg-[#f5f5f5] flex justify-center items-center">
用户免费
</div> -->
<div class="text-[#525252]">
<div class="flex gap-2">
<div class="member-item bg-[#f7f0ea]">
会员权益
</div>
<div class="member-item bg-[#f5f5f5]">
用户免费
</div>
<div class="member-item bg-[#fdf6ea]">
基础版VIP
</div>
<div class="member-item bg-[#fce6bf]">
专业版VIP
</div>
</div>
<div class="w-1/4 h-20 bg-[#fdf6ea] flex justify-center items-center">
基础版VIP
<div class="flex gap-2">
<div class="member-item bg-[#fff]">
算力
</div>
<div class="member-item bg-[#fff]">
每天300点
</div>
<div class="member-item bg-[#fff]">
每月15000点
</div>
<div class="member-item bg-[#fff]member-item">
每月35000点
</div>
</div>
<div class="w-1/4 h-20 bg-[#fce6bf] flex justify-center items-center">
专业版VIP
<div class="flex gap-2">
<div class="member-item bg-[#fafafa]">
云端存储空间
</div>
<div class="member-item bg-[#fafafa]">
3GB
</div>
<div class="member-item bg-[#fdf6ea]">
20GB
</div>
<div class="member-item bg-[#fce6bf]">
50GB
</div>
</div>
</div>
<div class="flex gap-2">
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
算力
<div class="flex gap-2">
<div class="member-item bg-[#fff]">
生图加速特权
</div>
<div class="member-item bg-[#fff]member-item">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fff]">
每月800次
</div>
<div class="member-item bg-[#fff]">
每月5000次
</div>
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每天300点
<div class="flex gap-2">
<div class="member-item bg-[#fafafa]">
会员专属模型
</div>
<div class="member-item bg-[#fafafa]">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fdf6ea]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
<div class="member-item bg-[#fce6bf]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每月15000点
<div class="flex gap-2">
<div class="member-item bg-[#fff]">
生图高级功能
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每月35000点
<div class="flex gap-2">
<div class="member-item bg-[#fafafa]">
训练XL模型
</div>
<div class="member-item bg-[#fafafa]">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fdf6ea]">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fce6bf]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
</div>
</div>
<div class="flex gap-2">
<div class="w-1/4 h-10 bg-[#f7f0ea] flex justify-center items-center">
算力
<div class="flex gap-2">
<div class="member-item bg-[#fff]">
图片去水印下载
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#ed7470">
<Close />
</n-icon>
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
<div class="member-item bg-[#fff]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
</div>
<div class="w-1/4 h-10 bg-[#f5f5f5] flex justify-center items-center">
每天300点
</div>
<div class="w-1/4 h-10 bg-[#fdf6ea] flex justify-center items-center">
每月15000点
</div>
<div class="w-1/4 h-10 bg-[#fce6bf] flex justify-center items-center">
每月35000点
</div>
</div>
<div class="flex gap-2">
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
算力
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每天300点
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每月15000点
</div>
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
每月35000点
</div>
</div>
<div class="flex gap-2">
<div class="w-1/4 h-10 bg-[#f7f0ea] flex justify-center items-center">
算力
</div>
<div class="w-1/4 h-10 bg-[#f5f5f5] flex justify-center items-center">
每天300点
</div>
<div class="w-1/4 h-10 bg-[#fdf6ea] flex justify-center items-center">
每月15000点
</div>
<div class="w-1/4 h-10 bg-[#fce6bf] flex justify-center items-center">
每月35000点
<div class="flex gap-2">
<div class="member-item bg-[#fafafa]">
多任务并行生图
</div>
<div class="member-item bg-[#fafafa]">
<n-icon size="20" color="#58c08f">
<CheckmarkCircleSharp />
</n-icon>
</div>
<div class="member-item bg-[#fdf6ea]">
2
</div>
<div class="member-item bg-[#fce6bf] ">
3
</div>
</div>
</div>
</div>
@ -330,7 +470,7 @@ getMemberLevelList()
会员每月算力和加速特权按月下发有效期31天到期重置会员模型下载次数上限为每月200次
</div>
<div class="mt-1">
发票或团队/企业定制需求请点击立即咨询联系我们企业合作需求也可直接联系chujie@liblib.ai
发票或团队/企业定制需求请点击立即咨询联系我们企业合作需求也可直接联系xxxx
</div>
<div class="mt-1">
更多问题可见帮助中心
@ -338,7 +478,12 @@ getMemberLevelList()
</div>
</div>
</div>
<Payment ref="PaymentRef" />
<NConfigProvider>
<NMessageProvider>
<Payment v-if="isShowPayment" ref="PaymentRef" :info="needPayment" :is-member="isMember" @close-payment="closePayment" />
<IntPayment v-if="isShowIntPayment" ref="IntPaymentRef" :is-member="isMember" @payment-success="paymentSuccess" @close-payment="closeIntPayment" />
</NMessageProvider>
</NConfigProvider>
</div>
</template>
@ -346,4 +491,7 @@ getMemberLevelList()
.card-item {
@apply flex items-center justify-center h-1/4;
}
.member-item {
@apply w-1/4 h-12 flex justify-center items-center;
}
</style>

View File

@ -1,11 +1,13 @@
<script setup lang="ts">
import { Heart } from '@vicons/ionicons5'
import {
Download,
Play,
} from 'lucide-vue-next'
definePageMeta({
layout: 'default',
})
import {
Play,
Download,
} from "lucide-vue-next";
// const route = useRoute<'publishDetails-id'>()
// const id = route.params.id
@ -14,20 +16,22 @@ import {
<template>
<div class="flex justify-center">
<div class="w-[1125px]">
<div class="flex items-center">
<div class="text-[26px] font-bold mr-2">名称</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 class="flex items-center">
<div class="text-[26px] font-bold mr-2">
名称
</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>
</template>

View File

@ -1,11 +1,8 @@
<script setup lang="ts">
import { debug } from 'node:console'
import { commonApi } from '@/api/common'
import defaultAvatar from '@/assets/img/default-avatar.png'
import EditUserInfo from '@/components/EditUserInfo.vue'
import { useUserStore } from '@/stores/user'
import { formatDate } from '@/utils/index.ts'
import { debounce } from 'lodash-es'
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
const loading = ref(false)
@ -334,6 +331,16 @@ async function topedRefresh() {
}
initPageNUm()
}
// /
const currentAttentionType = ref<string>('attention')
const attentionIsVisible = ref<boolean>(false)
// function onShowAttentionModel(type: string) {
// attentionIsVisible.value = true
// }
function onCloseAttentionModel() {
attentionIsVisible.value = false
}
</script>
<template>
@ -350,12 +357,12 @@ async function topedRefresh() {
<div class="mc-head-inner h-18 w-18 m-1 rounded-full bg-blue-200">
<client-only>
<img
v-if="userInfo.avatar"
class="head-img m-1 h-16 w-16 rounded-full bg-white"
:src="userInfo.avatar ? userInfo.avatar : defaultAvatar"
:src="userStore.userInfo.avatar"
alt="User Avatar"
>
</client-only>
<!-- {{ userStore.userInfo.avatar }} -->
</div>
</div>
@ -384,20 +391,21 @@ async function topedRefresh() {
<!-- User Details -->
<div class="user-info mt-4">
<div v-if="userInfo.nickName" class="nickname text-2xl font-bold">
{{ userInfo.nickName }}
<div v-if="userStore.userInfo" class="nickname text-2xl font-bold">
{{ userStore.userInfo.nickName }}
<!-- {{ userInfo.nickName }} -->
</div>
<div v-if="userInfo.brief" class="info-desc mt-1 text-sm text-gray-700">
{{ userInfo.brief }}
<div v-if="userStore.userInfo.brief" class="info-desc mt-1 text-sm text-gray-700">
{{ userStore.userInfo.brief }}
</div>
<div class="production-state mt-4 flex text-sm text-gray-700">
<div class="production-state-item mr-5">
<div class="production-state-item mr-5 cursor-pointer" @click="onShowAttentionModel('bean')">
<span class="production-state-number font-bold">{{
selectUserInfo.bean ? selectUserInfo.bean : 0
}}</span>
粉丝
</div>
<div class="production-state-item mr-5">
<div class="production-state-item mr-5 cursor-pointer" @click="onShowAttentionModel('attention')">
<span class="production-state-number font-bold">{{
selectUserInfo.attention ? selectUserInfo.attention : 0
}}</span>
@ -526,6 +534,20 @@ async function topedRefresh() {
没有更多数据了
</div>
</div>
<NModal
v-model:show="attentionIsVisible"
:on-after-leave="onCloseAttentionModel"
preset="card"
style="width: 400px"
:mask-closable="false"
>
<div>
<div class="">
<div>粉丝</div>
<div>关注</div>
</div>
</div>
</NModal>
</div>
</template>

View File

@ -30,12 +30,13 @@ const formData = ref({
isPublic: 1, // 1 2
isFree: 0, // 0
allowFusion: 1, //
isOnlineUse: 0, // 线使
allowDownloadImage: 1, //
allowUsage: 1, // 使
allowSoftwareUse: 1, // 使
allowCommercialUse: 1, //
//
isExclusiveModel: 1, //
isExclusiveModel: 0, //
},
],
})
@ -119,7 +120,7 @@ function handleAddVersion() {
添加版本
</div>
</div>
<div class="form-container bg-gray-100 p-4 rounded-lg">
<div class="form-container">
<div v-if="currentStep === 1" class="first-step">
<CreateModels v-model="formData" @create-models-next="createModelsNext" />
</div>
@ -132,6 +133,13 @@ function handleAddVersion() {
3
</div>
</div>
<!-- <div class="flex items-center justify-center mt-4">
<div>
<div class="flex justify-center items-center mt-5 text-white w-30 h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
下一步
</div>
</div>
</div> -->
</div>
<!-- <n-form
ref="formRef"

View File

@ -1,182 +1,132 @@
<template>
<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-input v-model:value="model.inputValue" placeholder="Input" /> -->
<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>
<!-- <pre
>{{ JSON.stringify(model, null, 2) }}
</pre> -->
</template>
<script setup lang="ts">
import type { FormInst, FormItemRule } from "naive-ui";
import { useMessage } from "naive-ui";
import { defineComponent, ref } from "vue";
import { commonApi } from "@/api/common";
const formRef = ref<FormInst | null>(null);
const cuileioptions = ref([
{ label: "Option 1", value: "Option 1" },
{ label: "Option 2", value: "Option 1" },
{ label: "Option 3", value: "Option 1" },
]);
const message = useMessage();
const rules: Record<string, FormItemRule[]> = {
}
const formData = ref({
inputValue: null,
textareaValue: null,
selectValue: null,
multipleSelectValue: null,
datetimeValue: null,
nestedValue: {
path1: null,
path2: null,
},
switchValue: false,
checkboxGroupValue: null,
radioGroupValue: null,
radioButtonGroupValue: null,
inputNumberValue: null,
timePickerValue: null,
transferValue: null,
})
const addWorkflow = async() => {
try {
const params = {
workFlow:{
workflowName:'工作流名称',
category:'1', //
theme:'1', //
style:'1', //
functions:'1', //
activityParticipation:'1', //
jurisdiction:1, // 1 2
original:1, //0 1
authorName:'作者名称', //
onlineUse:0, //线使0 1
download: 1, //0 1
sell:1, //(0 1)
},
workFlowVersionList:[
{
versionName:'1.0', //
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:'文件名称其实是个图片', //
imagePaths:'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,
hideGenInfo:0, // 0 1
},
{
versionName:'2.0', //
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:'文件名称其实是个图片', //
imagePaths:'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,
hideGenInfo:0, // 0 1
}
]
}
const res = await request.post("/WorkFlow/addWorkFlow", params);
}catch (error) {
console.log(error);
import EditVersion from '@/components/publishWorkFlow/EditVersion.vue'
import EditWorkFlow from '@/components/publishWorkFlow/EditWorkFlow.vue'
import UploadImg from '@/components/publishWorkFlow/UploadImg.vue'
import { CirclePlus } from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { useRoute } from 'vue-router'
// definePageMeta({
// middleware: [
// function (to, from) {
// initFormData(to.query.type)
// },
// ],
// })
const route = useRoute()
const { type, id } = route.query
const formData = ref()
async function initFormData() {
if (type === 'add') {
formData.value = {
workFlow: {
jurisdiction: 1, // auditStatus 0 1- 2- 3 4
original: 0, // original 0 1
onlineUse: 0, // 线使(0 1)
download: 0, // (0 1)
sell: 1, // (0 1)
typeList: [],
},
workFlowVersionList: [
{
versionName: '',
hideGenInfo: 0, //
versionDescription: '', //
filePath: '', //
fileName: '', //
delFlag: '0', // 0 2
imagePaths: [],
},
],
}
}
// const handleValidateButtonClick = (e: MouseEvent) => {
// e.preventDefault();
// formRef.value?.validate((errors) => {
// if (!errors) {
// message.success("");
// } else {
// console.log(errors);
// message.error("");
// }
// });
// };
const modelPartCategory = ref([]); //
const modelChildCategory = ref([]); //
const workFlowFunctions = ref([]); //
const getDictType = async() => {
// model_part_category:[], //
// model_child_category:[], //
// work_flow_functions:[], //
try {
const [res1, res2, res3, res4] = await Promise.all([
commonApi.dictType({ type: "model_part_category" }),
commonApi.dictType({ type: "model_child_category" }),
commonApi.dictType({ type: "work_flow_functions" }),
commonApi.dictType({ type: "work_flow_style" }), //
]);
modelPartCategory.value = res1.data;
modelChildCategory.value = res2.data;
workFlowFunctions.value = res3.data;
let categoryList = modelPartCategory;
//
categoryList.value.forEach((item) => {
// children
item.children = [];
//
modelChildCategory.value.forEach((child) => {
// parentIddictValue
if (child.partId === item.dictCode) {
// children
item.children.push(child);
}
});
});
categoryList = categoryList;
} catch (error) {
console.log(error);
}
};
getDictType()
else {
// type 1
try {
const res = await request.get(`/WorkFlow/selectWorkFlowVersionById?id=${id}`)
formData.value = res.data
if (formData.value.workFlow.type) {
formData.value.workFlow.typeList = formData.value.workFlow.type.split(',')
}
else {
formData.value.workFlow.typeList = []
}
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(',')
}
}
// else {
// formData.value.workFlow.activityParticipationList = []
// }
}
catch (error) {
console.log(error)
}
}
}
initFormData()
const EditVersionRef = ref()
const currentStep = ref(1)
function handleAddVersion() {
EditVersionRef.value.addVersion()
}
function nextStep() {
currentStep.value += 1
}
function preStep() {
currentStep.value -= 1
}
const timeLineList = ref([
{
name: '创建模版',
index: 1,
},
{
name: '编辑版本',
index: 2,
},
{
name: '上传图片',
index: 3,
},
])
</script>
<template>
<div class="mx-auto py-6">
<div class="mx-auto flex justify-center">
<div class="form-container mx-auto">
<div class="flex items-center justify-between">
<div class="w-[60%]">
<TimeLine :time-line-list="timeLineList" :current-step="currentStep" />
</div>
<div v-if="currentStep === 2" class="cursor-pointer flex items-center justify-center rounded-full border border-solid border-[#000] px-4 py-1" @click="handleAddVersion">
<CirclePlus class="mr-2" />
添加版本
</div>
</div>
<NConfigProvider>
<NMessageProvider>
<div v-if="currentStep === 1" class="first-step mx-auto">
<EditWorkFlow v-model="formData" @next-step="nextStep" />
</div>
<div v-if="currentStep === 2" class="second-step">
<EditVersion ref="EditVersionRef" v-model="formData" @pre-step="preStep" @next-step="nextStep" />
</div>
<div v-if="currentStep === 3" class="third-step">
<UploadImg v-model="formData" @pre-step="preStep" />
</div>
</NMessageProvider>
</NConfigProvider>
</div>
</div>
</div>
</template>

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import defaultAvatar from '@/assets/img/default-avatar.png'
import { Heart, PersonAddOutline } from '@vicons/ionicons5'
import {
CircleUser,
Download,
@ -7,19 +8,26 @@ import {
HardDriveUpload,
Play,
} from 'lucide-vue-next'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import { nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
const message = useMessage()
const router = useRouter()
// tabs
definePageMeta({
layout: 'default',
})
const userStore = useUserStore()
// 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 versionByWorkInfo = ref([])
async function getInfo() {
try {
@ -27,18 +35,34 @@ async function getInfo() {
if (res.code === 200) {
detailsInfo.value = res.data
// 1
const res1 = await request.get(
`/WorkFlowVersion/selectVersionByWorkId?workId=${res.data.id}`,
)
if (res1.code === 200 && res1.data.length > 0) {
versionByWorkInfo.value = res1.data
versionByWorkInfo.value.forEach((item) => {
item.imagePathsList = item.imagePaths.split(',')
})
nextTick(() => {
activeTab.value = versionByWorkInfo.value[0].id
})
const commentRes = await request.get(`/WorkFlowComment/comment/${res.data.id}`)
try {
const res1 = await request.get(
`/WorkFlowVersion/selectVersionByWorkId?workId=${res.data.id}`, //
)
if (res1.code === 200 && res1.data.length > 0) {
versionByWorkInfo.value = res1.data
versionByWorkInfo.value.forEach((item) => {
item.imagePathsList = item.imagePaths.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)
}
}
}
@ -76,32 +100,80 @@ async function getAttention() {
}
getAttention()
function handleSelect(event: Event, id: string) {
// //
const isDelete = ref(false)
async function handleSelect(event: Event, type: string) {
event.stopPropagation() //
console.log(id)
if (type === 'report') {
await request.get(`/WorkFlow/report?id=${id}`) //
}
else if (type === 'edit') {
router.push({
path: `/publish-workflow`,
query: {
type: 'edit',
id: detailsInfo.value.id,
},
})
}
else {
isDelete.value = true
}
}
// async function getAttention() {
// try {
// const res = await request.get("/WorkFlowComment/comment/{modelId}");
// if (res.code === 200) {
// selectUserInfo.value = res.data;
// }
// } catch (err) {
// console.log(err);
// }
// }
// getAttention();
// async function getVersionByWork() {
// try {
// if (res.code === 200) {
// }
// } catch (err) {
// console.log(err);
// }
// }
// getVersionByWork();
//
async function onDelete() {
try {
const res = await request.get(`/WorkFlow/deleteWorkFlow?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(`/WorkFlowComment/like?workFlowId=${detailsInfo.value.id}`)
if (res.code === 200) {
detailsInfo.value.isLike === 0 ? detailsInfo.value.isLike = 1 : detailsInfo.value.isLike = 0
if (detailsInfo.value.isLike === 0) {
message.success('取消点赞成功')
}
else {
message.success('点赞成功')
}
}
}
catch (err) {
console.log(err)
}
}
</script>
<template>
@ -148,7 +220,7 @@ function handleSelect(event: Event, id: string) {
<div class="flex items-center mt-3 mb-5">
<div
v-for="(item, index) in detailsInfo.styleList"
v-for="(item, index) in detailsInfo.typeList"
:key="index"
class="text-[12px] bg-[#ebf2fe] p-1 px-2 text-[#557abf] mr-4 rounded-md"
>
@ -168,8 +240,8 @@ function handleSelect(event: Event, id: string) {
<!-- 显示最后一步上传图片的图片 -->
<div class="grid grid-cols-2 gap-2.5 box-border">
<img
v-for="(subItem, index) in item.imagePathsList"
:key="index"
v-for="(subItem, subIndex) in item.imagePathsList"
:key="subIndex"
:src="subItem"
class="w-full h-[300px]"
alt=""
@ -186,12 +258,21 @@ function handleSelect(event: Event, id: string) {
</n-tab-pane>
</n-tabs>
</div>
<div class="mt-4">
<div style="padding: 20px">
<NConfigProvider>
<NMessageProvider>
<BaseComment v-if="detailsInfo.id" type="workflow" :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="flexm justify-end">
<div class="flex justify-end">
<div v-if="detailsInfo.createTime" class="mr-2">
首发时间{{ detailsInfo.createTime }}
</div>
@ -201,11 +282,14 @@ function handleSelect(event: Event, id: string) {
</div>
<div class="flex items-center relative">
<img
<!-- <img
src="@/assets/img/heart.png"
class="w-[14px] h-[14px] cursor-pointer"
alt=""
>
> -->
<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"
@ -244,24 +328,33 @@ function handleSelect(event: Event, id: string) {
class="w-full h-full mr-2 block"
round
size="small"
:src="
userStore.userInfo && userStore.userInfo.avatar
? userStore.userInfo.avatar
: defaultAvatar
"
:src="currentUserInfo.avatar"
/>
</client-only>
</div>
<div>
<client-only>
<div class="text[20px] font-bold">
{{ userStore.userInfo.nickName }}
<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>
</client-only>
<!-- 0代表原创 1代表转载 -->
<div v-if="detailsInfo.original === 0" class="text-[14px]">
原创作者
</div>
<div class="flex items-center text-[#969798]">
<component
:is="CircleUser"
@ -313,7 +406,17 @@ function handleSelect(event: Event, id: string) {
下载客户端
</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>

View File

@ -0,0 +1,6 @@
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default defineNuxtPlugin((nuxt) => {
nuxt.vueApp.component('WeEditor', Editor)
nuxt.vueApp.component('WeToolbar', Toolbar)
})

View File

@ -1,5 +1,7 @@
import defaultAvatar from '@/assets/img/default-avatar.png'
// stores/user.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import {
parse,
@ -32,6 +34,7 @@ export const useUserStore = defineStore('user', () => {
token: token.value,
})
if (res.code === 200) {
res.data.avatar = res.data.avatar || defaultAvatar
setUserInfo(res.data)
}
}

View File

@ -8,7 +8,7 @@ import type { ApiResponse } from '~/types/api'
* @returns {Promise<{ success: boolean, message: string, data: any[] }>} -
*/
export async function uploadImagesInBatches(files, batchSize = 3) {
export async function uploadImagesInBatches(files: File[], batchSize = 3) {
const uploadResults = []
for (let i = 0; i < files.length; i++) {
@ -20,13 +20,15 @@ export async function uploadImagesInBatches(files, batchSize = 3) {
// 上传当前图片
try {
const res = await request.post<ApiResponse<{ url: string }>>('/model/file', formData, {
const res = await request.post<ApiResponse<{ url: string }>>('/file/imgUpload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
// const res = await mallProductFile(formData)
uploadResults.push(res.data)
if (res && res.code === 200) {
uploadResults.push(res.data)
}
}
catch (error) {
console.error(`图片上传失败: ${file.name}`, error)
@ -35,3 +37,31 @@ export async function uploadImagesInBatches(files, batchSize = 3) {
}
return uploadResults
}
export async function uploadFileBatches(files, batchSize = 3) {
const uploadResults = []
for (let i = 0; i < files.length; i++) {
const file = files[i]
// 创建 FormData 对象
const formData = new FormData()
formData.append('file', file) // 假设后端接收字段是 `file`
// 上传当前图片
try {
const res = await request.post<ApiResponse<{ url: string }>>('/file/fileUpload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
// const res = await mallProductFile(formData)
uploadResults.push(res.data)
}
catch (error) {
console.error(`文件上传失败: ${file.name}`, error)
uploadResults.push({ success: false, error })
}
}
return uploadResults
}

View File

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

View File

@ -15,6 +15,7 @@
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"@wangeditor/editor": "^5.1.12",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.7.9",
"date-fns-tz": "^3.2.0",

View File

@ -13,9 +13,12 @@ dependencies:
'@vicons/ionicons5':
specifier: ^0.13.0
version: 0.13.0
'@wangeditor/editor':
specifier: ^5.1.12
version: 5.1.23
'@wangeditor/editor-for-vue':
specifier: ^5.1.12
version: 5.1.12
version: 5.1.12(@wangeditor/editor@5.1.23)
axios:
specifier: ^1.7.9
version: 1.7.9
@ -3022,6 +3025,10 @@ packages:
string.prototype.matchall: 4.0.12
dev: true
/@transloadit/prettier-bytes@0.0.7:
resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==}
dev: false
/@trysound/sax@0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
@ -3051,6 +3058,10 @@ packages:
/@types/estree@1.0.6:
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
/@types/event-emitter@0.3.5:
resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==}
dev: false
/@types/http-proxy@1.17.15:
resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
dependencies:
@ -3271,6 +3282,47 @@ packages:
vue: 3.5.13(typescript@5.7.3)
dev: true
/@uppy/companion-client@2.2.2:
resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==}
dependencies:
'@uppy/utils': 4.1.3
namespace-emitter: 2.0.1
dev: false
/@uppy/core@2.3.4:
resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==}
dependencies:
'@transloadit/prettier-bytes': 0.0.7
'@uppy/store-default': 2.1.1
'@uppy/utils': 4.1.3
lodash.throttle: 4.1.1
mime-match: 1.0.2
namespace-emitter: 2.0.1
nanoid: 3.3.8
preact: 10.25.4
dev: false
/@uppy/store-default@2.1.1:
resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==}
dev: false
/@uppy/utils@4.1.3:
resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==}
dependencies:
lodash.throttle: 4.1.1
dev: false
/@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4):
resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==}
peerDependencies:
'@uppy/core': ^2.3.3
dependencies:
'@uppy/companion-client': 2.2.2
'@uppy/core': 2.3.4
'@uppy/utils': 4.1.3
nanoid: 3.3.8
dev: false
/@vercel/nft@0.27.10(rollup@4.34.0):
resolution: {integrity: sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg==}
engines: {node: '>=16'}
@ -3618,11 +3670,189 @@ packages:
- typescript
dev: true
/@wangeditor/editor-for-vue@5.1.12:
/@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==}
peerDependencies:
'@wangeditor/core': 1.x
dom7: ^3.0.0
lodash.throttle: ^4.1.1
nanoid: ^3.2.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
is-url: 1.2.4
lodash.throttle: 4.1.1
nanoid: 3.3.8
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==}
peerDependencies:
'@wangeditor/core': 1.x
dom7: ^3.0.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
prismjs: 1.29.0
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==}
peerDependencies:
'@uppy/core': ^2.1.1
'@uppy/xhr-upload': ^2.0.3
dom7: ^3.0.0
is-hotkey: ^0.2.0
lodash.camelcase: ^4.3.0
lodash.clonedeep: ^4.5.0
lodash.debounce: ^4.0.8
lodash.foreach: ^4.5.0
lodash.isequal: ^4.5.0
lodash.throttle: ^4.1.1
lodash.toarray: ^4.4.0
nanoid: ^3.2.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@types/event-emitter': 0.3.5
'@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
dom7: 3.0.0
event-emitter: 0.3.5
html-void-elements: 2.0.1
i18next: 20.6.1
is-hotkey: 0.2.0
lodash.camelcase: 4.3.0
lodash.clonedeep: 4.5.0
lodash.debounce: 4.0.8
lodash.foreach: 4.5.0
lodash.isequal: 4.5.0
lodash.throttle: 4.1.1
lodash.toarray: 4.4.0
nanoid: 3.3.8
scroll-into-view-if-needed: 2.2.31
slate: 0.72.8
slate-history: 0.66.0(slate@0.72.8)
snabbdom: 3.6.2
dev: false
/@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23):
resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==}
peerDependencies:
'@wangeditor/editor': '>=5.1.0'
vue: ^3.0.5
dependencies:
'@wangeditor/editor': 5.1.23
dev: false
/@wangeditor/editor@5.1.23:
resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==}
dependencies:
'@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/basic-modules@1.1.7)(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/core@1.1.19)(dom7@3.0.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
is-hotkey: 0.2.0
lodash.camelcase: 4.3.0
lodash.clonedeep: 4.5.0
lodash.debounce: 4.0.8
lodash.foreach: 4.5.0
lodash.isequal: 4.5.0
lodash.throttle: 4.1.1
lodash.toarray: 4.4.0
nanoid: 3.3.8
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==}
peerDependencies:
'@wangeditor/core': 1.x
dom7: ^3.0.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==}
peerDependencies:
'@wangeditor/core': 1.x
dom7: ^3.0.0
lodash.isequal: ^4.5.0
lodash.throttle: ^4.1.1
nanoid: ^3.2.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
lodash.isequal: 4.5.0
lodash.throttle: 4.1.1
nanoid: 3.3.8
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/basic-modules@1.1.7)(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==}
peerDependencies:
'@uppy/core': ^2.0.3
'@uppy/xhr-upload': ^2.0.3
'@wangeditor/basic-modules': 1.x
'@wangeditor/core': 1.x
dom7: ^3.0.0
lodash.foreach: ^4.5.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
lodash.foreach: 4.5.0
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/core@1.1.19)(dom7@3.0.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2):
resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==}
peerDependencies:
'@uppy/core': ^2.1.4
'@uppy/xhr-upload': ^2.0.7
'@wangeditor/core': 1.x
dom7: ^3.0.0
nanoid: ^3.2.0
slate: ^0.72.0
snabbdom: ^3.1.0
dependencies:
'@uppy/core': 2.3.4
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2)
dom7: 3.0.0
nanoid: 3.3.8
slate: 0.72.8
snabbdom: 3.6.2
dev: false
/abbrev@3.0.0:
@ -4277,6 +4507,10 @@ packages:
readable-stream: 4.7.0
dev: true
/compute-scroll-into-view@1.0.20:
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
dev: false
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
@ -4505,6 +4739,14 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
/d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
dependencies:
es5-ext: 0.10.64
type: 2.7.3
dev: false
/data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@ -4759,6 +5001,12 @@ packages:
entities: 4.5.0
dev: true
/dom7@3.0.0:
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
dependencies:
ssr-window: 3.0.0
dev: false
/domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
dev: true
@ -4963,6 +5211,33 @@ packages:
is-symbol: 1.1.1
dev: true
/es5-ext@0.10.64:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'}
requiresBuild: true
dependencies:
es6-iterator: 2.0.3
es6-symbol: 3.1.4
esniff: 2.0.1
next-tick: 1.1.0
dev: false
/es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
dependencies:
d: 1.0.2
es5-ext: 0.10.64
es6-symbol: 3.1.4
dev: false
/es6-symbol@3.1.4:
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
engines: {node: '>=0.12'}
dependencies:
d: 1.0.2
ext: 1.7.0
dev: false
/esbuild@0.24.2:
resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
engines: {node: '>=18'}
@ -5460,6 +5735,16 @@ packages:
- supports-color
dev: true
/esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
dependencies:
d: 1.0.2
es5-ext: 0.10.64
event-emitter: 0.3.5
type: 2.7.3
dev: false
/espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -5519,6 +5804,13 @@ packages:
engines: {node: '>= 0.6'}
dev: true
/event-emitter@0.3.5:
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
dependencies:
d: 1.0.2
es5-ext: 0.10.64
dev: false
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@ -5562,6 +5854,12 @@ packages:
strip-final-newline: 3.0.0
dev: true
/ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
dependencies:
type: 2.7.3
dev: false
/externality@1.0.2:
resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==}
dependencies:
@ -6075,6 +6373,10 @@ packages:
engines: {node: '>=8'}
dev: true
/html-void-elements@2.0.1:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
dev: false
/http-assert@1.5.0:
resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==}
engines: {node: '>= 0.8'}
@ -6144,6 +6446,12 @@ packages:
engines: {node: '>=16.17.0'}
dev: true
/i18next@20.6.1:
resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==}
dependencies:
'@babel/runtime': 7.26.7
dev: false
/idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
dev: true
@ -6164,6 +6472,10 @@ packages:
resolution: {integrity: sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==}
dev: true
/immer@9.0.21:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
dev: false
/immutable@5.0.3:
resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
dev: true
@ -6384,6 +6696,10 @@ packages:
dependencies:
is-extglob: 2.1.1
/is-hotkey@0.2.0:
resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==}
dev: false
/is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
@ -6431,6 +6747,11 @@ packages:
engines: {node: '>=12'}
dev: true
/is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
dev: false
/is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
dependencies:
@ -6504,6 +6825,10 @@ packages:
which-typed-array: 1.1.18
dev: true
/is-url@1.2.4:
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
dev: false
/is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@ -6886,18 +7211,34 @@ packages:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
dev: false
/lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
dev: false
/lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: true
/lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
dev: true
/lodash.foreach@4.5.0:
resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
dev: false
/lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
dev: true
/lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
dev: false
/lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
dev: true
@ -6910,6 +7251,14 @@ packages:
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
dev: true
/lodash.throttle@4.1.1:
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
dev: false
/lodash.toarray@4.4.0:
resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==}
dev: false
/lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
dev: true
@ -7378,6 +7727,12 @@ packages:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
/mime-match@1.0.2:
resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==}
dependencies:
wildcard: 1.1.2
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
@ -7546,11 +7901,14 @@ packages:
vueuc: 0.4.64
dev: false
/namespace-emitter@2.0.1:
resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==}
dev: false
/nanoid@3.3.8:
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/nanoid@5.0.9:
resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==}
@ -7584,6 +7942,10 @@ packages:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: true
/next-tick@1.1.0:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false
/nitropack@2.10.4(typescript@5.7.3):
resolution: {integrity: sha512-sJiG/MIQlZCVSw2cQrFG1H6mLeSqHlYfFerRjLKz69vUfdu0EL2l0WdOxlQbzJr3mMv/l4cOlCCLzVRzjzzF/g==}
engines: {node: ^16.11.0 || >=17.0.0}
@ -8713,6 +9075,10 @@ packages:
source-map-js: 1.2.1
dev: true
/preact@10.25.4:
resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==}
dev: false
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -8741,6 +9107,11 @@ packages:
engines: {node: ^14.13.1 || >=16.0.0}
dev: true
/prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
dev: false
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
@ -9174,6 +9545,12 @@ packages:
'@parcel/watcher': 2.5.1
dev: true
/scroll-into-view-if-needed@2.2.31:
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
dependencies:
compute-scroll-into-view: 1.0.20
dev: false
/scslre@0.3.0:
resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
engines: {node: ^14.0.0 || >=16.0.0}
@ -9384,10 +9761,32 @@ packages:
resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==}
dev: true
/slate-history@0.66.0(slate@0.72.8):
resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==}
peerDependencies:
slate: '>=0.65.3'
dependencies:
is-plain-object: 5.0.0
slate: 0.72.8
dev: false
/slate@0.72.8:
resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==}
dependencies:
immer: 9.0.21
is-plain-object: 5.0.0
tiny-warning: 1.0.3
dev: false
/smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
dev: true
/snabbdom@3.6.2:
resolution: {integrity: sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==}
engines: {node: '>=12.17.0'}
dev: false
/source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@ -9455,6 +9854,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/ssr-window@3.0.0:
resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==}
dev: false
/stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
dev: true
@ -9844,6 +10247,10 @@ packages:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
dev: true
/tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@ -9958,6 +10365,10 @@ packages:
mime-types: 2.1.35
dev: true
/type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
dev: false
/typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@ -10852,6 +11263,10 @@ packages:
isexe: 2.0.0
dev: true
/wildcard@1.1.2:
resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==}
dev: false
/word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}