Merge branch 'test'
|
@ -41,5 +41,6 @@
|
||||||
],
|
],
|
||||||
"interline-translate.knownPopularWordCount": 6000,
|
"interline-translate.knownPopularWordCount": 6000,
|
||||||
"iconify.annotations": true,
|
"iconify.annotations": true,
|
||||||
"iconify.inplace": true
|
"iconify.inplace": true,
|
||||||
|
"vue3snippets.enable-compile-vue-file-on-did-save-code": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,10 @@ We recommend using [VS Code](https://code.visualstudio.com/) with [Volar](https:
|
||||||
- [vitesse-nuxt-bridge](https://github.com/antfu/vitesse-nuxt-bridge) - Vitesse for Nuxt 2 with Bridge
|
- [vitesse-nuxt-bridge](https://github.com/antfu/vitesse-nuxt-bridge) - Vitesse for Nuxt 2 with Bridge
|
||||||
- [vitesse-webext](https://github.com/antfu/vitesse-webext) - WebExtension Vite starter template
|
- [vitesse-webext](https://github.com/antfu/vitesse-webext) - WebExtension Vite starter template
|
||||||
|
|
||||||
|
## rich text
|
||||||
|
|
||||||
|
- [wangEditor](https://www.wangeditor.com/v5/for-frame.html#%E5%AE%89%E8%A3%85-1) - Opinionated Astro Starter Template
|
||||||
|
|
||||||
## Try it now!
|
## Try it now!
|
||||||
|
|
||||||
### Online
|
### Online
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import type { ApiResponse, PaginationParams, PaginationResponse } from '~/types/api'
|
||||||
// api/common.ts
|
// api/common.ts
|
||||||
import request from '~/utils/request'
|
import request from '~/utils/request'
|
||||||
import type { ApiResponse, PaginationParams, PaginationResponse } from '~/types/api'
|
|
||||||
|
|
||||||
export const commonApi = {
|
export const commonApi = {
|
||||||
// 上传文件
|
// 上传文件
|
||||||
|
@ -9,13 +9,18 @@ export const commonApi = {
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
return request.post<ApiResponse<{ url: string }>>('/upload', formData, {
|
return request.post<ApiResponse<{ url: string }>>('/upload', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取配置信息
|
// 获取配置信息
|
||||||
getConfig() {
|
getConfig() {
|
||||||
return request.get<ApiResponse<Record<string, any>>>('/config')
|
return request.get<ApiResponse<Record<string, any>>>('/config')
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 获取用户字典
|
||||||
|
dictType(query: any) {
|
||||||
|
return request.get<ApiResponse<Record<string, any>>>(`/system/dict/data/type/${query.type}`)
|
||||||
|
},
|
||||||
}
|
}
|
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 279 B |
|
@ -10,17 +10,33 @@ declare module 'vue' {
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NBadge: typeof import('naive-ui')['NBadge']
|
NBadge: typeof import('naive-ui')['NBadge']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
|
NCarousel: typeof import('naive-ui')['NCarousel']
|
||||||
|
NCascader: typeof import('naive-ui')['NCascader']
|
||||||
|
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']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
|
NFor: typeof import('naive-ui')['NFor']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NFormTtem: typeof import('naive-ui')['NFormTtem']
|
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NInputgroup: typeof import('naive-ui')['NInputgroup']
|
|
||||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||||
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||||
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
|
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||||
|
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
|
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||||
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormItemRule } from "naive-ui";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const userInfo = userStore.userInfo;
|
||||||
|
const isVisible = ref(false);
|
||||||
|
|
||||||
|
const rules = ref({
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator(rule: FormItemRule, 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(rule: FormItemRule, 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,
|
||||||
|
});
|
||||||
|
const formRef = ref(null);
|
||||||
|
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)
|
||||||
|
// }
|
||||||
|
try {
|
||||||
|
const res = await request.post("/system/user/updateIdCard", ruleForm.value);
|
||||||
|
if (res.code === 200) {
|
||||||
|
await userStore.getUserInfo();
|
||||||
|
isVisible.value = false;
|
||||||
|
} else {
|
||||||
|
message.warning(res.msg);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCloseModel() {
|
||||||
|
isVisible.value = false;
|
||||||
|
ruleForm.value.name = "";
|
||||||
|
ruleForm.value.idCard = "";
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
isVisible,
|
||||||
|
});
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
// initFormData()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
v-model:show="isVisible"
|
||||||
|
:on-after-leave="onCloseModel"
|
||||||
|
preset="card"
|
||||||
|
style="width: 400px"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-form ref="formRef" :model="ruleForm" :rules="rules">
|
||||||
|
<n-form-item path="name" label="姓名">
|
||||||
|
<n-input
|
||||||
|
v-model:value="ruleForm.name"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
@keydown.enter.prevent
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item path="idCard" label="身份证号">
|
||||||
|
<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 text-[#fff] border-0 cursor-pointer"
|
||||||
|
@click="saveInfo"
|
||||||
|
>
|
||||||
|
实名认证
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form {
|
||||||
|
/* cursor: pointer; */
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item input {
|
||||||
|
width: 200px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,599 @@
|
||||||
|
<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?',
|
||||||
|
pictrue: '/imageComment/comment?',
|
||||||
|
model: '/ModelComment/comment?',
|
||||||
|
})
|
||||||
|
// 点赞
|
||||||
|
const likeList = ref({
|
||||||
|
workflow: '/WorkFlowComment/commentLike?',
|
||||||
|
pictrue: '/imageComment/commentLike?',
|
||||||
|
model:'ModelComment/commentLike?'
|
||||||
|
})
|
||||||
|
// 发送评论
|
||||||
|
const sendMessageList = ref({
|
||||||
|
workflow: '/WorkFlowComment/comment',
|
||||||
|
pictrue: '/imageComment/comment',
|
||||||
|
model: '/ModelComment/comment',
|
||||||
|
})
|
||||||
|
// 删除
|
||||||
|
const deleteList = ref({
|
||||||
|
workflow: '/WorkFlowComment/commentDelete?',
|
||||||
|
pictrue: '/imageComment/commentDelete?',
|
||||||
|
model: '/ModelComment/commentDelete?',
|
||||||
|
})
|
||||||
|
// 查条数
|
||||||
|
const commentNumUrl = ref({
|
||||||
|
workflow: '/WorkFlowComment/commentCount?workFlowId',
|
||||||
|
model: '/ModelComment/commentCount?modelId',
|
||||||
|
})
|
||||||
|
|
||||||
|
const commentCount = ref(0)
|
||||||
|
|
||||||
|
// 评论列表
|
||||||
|
const commentList = ref([])
|
||||||
|
async function getCommentList() {
|
||||||
|
try {
|
||||||
|
let url = ''
|
||||||
|
if(props.type === 'workflow'){
|
||||||
|
url = `${urlList.value[props.type]}commentId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||||
|
}else if(props.type === 'pictrue'){
|
||||||
|
url = `${urlList.value[props.type]}imageId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||||
|
}else{
|
||||||
|
url = `${urlList.value[props.type]}modelId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||||
|
}
|
||||||
|
const res = await request.get(url)
|
||||||
|
for (let i = 0; i < res.data.length; i++) {
|
||||||
|
res.data[i].isShowInput = false
|
||||||
|
res.data[i].isShowSend = false
|
||||||
|
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
|
||||||
|
if(props.type === 'pictrue'){
|
||||||
|
commentParams.value.modelImageId = props.detailsInfo.id
|
||||||
|
}else if(props.type === 'model'){
|
||||||
|
commentParams.value.modelId = props.detailsInfo.id
|
||||||
|
}
|
||||||
|
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('评论成功!')
|
||||||
|
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 = ''
|
||||||
|
if(props.type === 'pictrue'){
|
||||||
|
commentParams.value.modelImageId = props.detailsInfo.id
|
||||||
|
}else if(props.type === 'model'){
|
||||||
|
commentParams.value.modelId = props.detailsInfo.id
|
||||||
|
}
|
||||||
|
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('评论成功!')
|
||||||
|
publicWord.value = ''
|
||||||
|
getCommentList()
|
||||||
|
getCommentNum()
|
||||||
|
commentParamsInit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message.warning('评论不能为空!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询条数
|
||||||
|
async function getCommentNum() {
|
||||||
|
if(props.type !== 'pictrue'){
|
||||||
|
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:any) {
|
||||||
|
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:any) {
|
||||||
|
if (!item.isShowInput) {
|
||||||
|
item.word = ''
|
||||||
|
}
|
||||||
|
item.isShowInput = !item.isShowInput
|
||||||
|
}
|
||||||
|
// 删除评论
|
||||||
|
async function handleDel(item:any) {
|
||||||
|
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]" v-if="props.type !== 'pictrue'">
|
||||||
|
{{ commentCount }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center" v-if="props.type !== 'pictrue'">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</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: 100%;
|
||||||
|
height: 100%;
|
||||||
|
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: 100%;
|
||||||
|
height: 100%;
|
||||||
|
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: 100%;
|
||||||
|
height: 100%;
|
||||||
|
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>
|
|
@ -0,0 +1,180 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const userInfo = userStore.userInfo
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const ruleForm = ref({})
|
||||||
|
function onShowModel() {
|
||||||
|
ruleForm.value.nickName = userInfo.nickName
|
||||||
|
ruleForm.value.avatar = userInfo.avatar
|
||||||
|
ruleForm.value.brief = userInfo.brief
|
||||||
|
ruleForm.value.userId = userInfo.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = ref({
|
||||||
|
nickName: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ min: 3, max: 12, message: '长度在 3 到 12 个字符', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
// 文件输入引用
|
||||||
|
const pictureInput = ref<HTMLInputElement | null>(null)
|
||||||
|
function handlePictureInput() {
|
||||||
|
pictureInput.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片上传
|
||||||
|
async function handlePictureChange(event: Event) {
|
||||||
|
const files = (event.target as HTMLInputElement).files
|
||||||
|
if (!files || files.length === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (files.length > 1) {
|
||||||
|
message.error('只能选择 1 张图片')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'))
|
||||||
|
if (imageFiles.length === 0) {
|
||||||
|
message.error('请选择有效的图片文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pictureResultList = await uploadImagesInBatches(imageFiles)
|
||||||
|
ruleForm.value.avatar = pictureResultList[0].url
|
||||||
|
event.target.value = ''
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
message.error('图片上传失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveInfo() {
|
||||||
|
const res = await request.post('/system/user/updateUserInfo', ruleForm.value)
|
||||||
|
const data = await userStore.getUserInfo()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
isVisible,
|
||||||
|
})
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
// initFormData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
v-model:show="isVisible"
|
||||||
|
:on-after-leave="onCloseModel"
|
||||||
|
:on-after-enter="onShowModel"
|
||||||
|
preset="card"
|
||||||
|
style="width: 400px"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<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" alt="编辑">
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center mb-2">
|
||||||
|
<button class="cursor-pointer text-center text-base text-[#222] dark:text-[#fff] dark:border-[#AABEFF1A] border-0 bg-[#F1F2F8] hover:bg-[#ECEEF5] dark:bg-[#1C1D29] dark:hover:bg-[#2A2D3D] font-medium leading-[3rem] w-[116px] h-12 rounded-lg" @click="handlePictureInput">
|
||||||
|
更换头像
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
ref="pictureInput"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
class="hidden"
|
||||||
|
@change="handlePictureChange"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<n-form ref="formRef" :model="ruleForm" :rules="rules">
|
||||||
|
<n-form-item path="nickName" label="用户名">
|
||||||
|
<n-input v-model:value="ruleForm.nickName" placeholder="请输入用户名" @keydown.enter.prevent />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="简介" path="textareaValue">
|
||||||
|
<n-input
|
||||||
|
v-model:value="ruleForm.brief"
|
||||||
|
placeholder="请输入简介"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 3,
|
||||||
|
maxRows: 5,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<div class="flex justify-center items-center ">
|
||||||
|
<button class="w-1/2 flex justify-center mx-1 bg-[#f1f2f2] py-3 rounded-lg align-middle border-0 cursor-pointer" @click="onCloseModel">
|
||||||
|
暂不修改
|
||||||
|
</button>
|
||||||
|
<!-- <div class="w-1/2 flex justify-center mx-1 bg-[#f1f2f2] py-3 rounded-lg align-middle">
|
||||||
|
暂不修改
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<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]">
|
||||||
|
确定修改
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form {
|
||||||
|
/* cursor: pointer; */
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item input {
|
||||||
|
width: 200px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,18 +1,18 @@
|
||||||
<template>
|
|
||||||
<n-config-provider :theme="theme" :theme-overrides="themeOverrides">
|
|
||||||
<n-message-provider>
|
|
||||||
<slot></slot>
|
|
||||||
</n-message-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { darkTheme, NConfigProvider, NMessageProvider } from 'naive-ui';
|
import { darkTheme, NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||||
const theme = darkTheme
|
|
||||||
// 可以根据需要配置主题
|
// 可以根据需要配置主题
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
common: {
|
common: {
|
||||||
primaryColor: '#18a058'
|
primaryColor: '#18a058',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NConfigProvider :theme-overrides="themeOverrides">
|
||||||
|
<NMessageProvider>
|
||||||
|
<slot />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</template>
|
||||||
|
|
|
@ -0,0 +1,336 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 输入框搜索
|
||||||
|
import { headerRole } from "@/constants/index";
|
||||||
|
import {
|
||||||
|
Bell,
|
||||||
|
CirclePlus,
|
||||||
|
GraduationCap,
|
||||||
|
HardDriveUpload,
|
||||||
|
Image,
|
||||||
|
Monitor,
|
||||||
|
Workflow
|
||||||
|
} from "lucide-vue-next";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
|
||||||
|
// import { AtCircle } from '@vicons/ionicons5'
|
||||||
|
import { NIcon } from "naive-ui";
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
|
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);
|
||||||
|
// 执行搜索逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户下拉选项
|
||||||
|
const notificationOptions = [
|
||||||
|
{
|
||||||
|
label: "系统通知",
|
||||||
|
key: "system",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "互动消息",
|
||||||
|
key: "interaction",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
function renderIcon(icon: Component) {
|
||||||
|
return () => {
|
||||||
|
return h(NIcon, null, {
|
||||||
|
default: () => h(icon),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 发布下拉选项
|
||||||
|
const publishOptions = [
|
||||||
|
{
|
||||||
|
label: "模型",
|
||||||
|
key: "publish-model",
|
||||||
|
icon: renderIcon(HardDriveUpload),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "图片",
|
||||||
|
key: "picture",
|
||||||
|
icon: renderIcon(Image),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "工作流",
|
||||||
|
key: "publish-workflow",
|
||||||
|
icon: renderIcon(Workflow),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const userOptions = ref([
|
||||||
|
{
|
||||||
|
label: "我的模型",
|
||||||
|
key: "model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "我的作品",
|
||||||
|
key: "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "我的点赞",
|
||||||
|
key: "like",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "账号设置",
|
||||||
|
key: "userSettings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "退出登录",
|
||||||
|
key: "logout",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 用户下拉选项
|
||||||
|
async function handleUserSelect(key: string) {
|
||||||
|
if (key === "logout") {
|
||||||
|
try {
|
||||||
|
await request.post("/logout");
|
||||||
|
userStore.logout();
|
||||||
|
navigateTo("/model-square");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Logout failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发布下拉选项
|
||||||
|
async function handlePublishSelect(key: string) {
|
||||||
|
if (key === "picture") {
|
||||||
|
isShowPublishPicture.value = true;
|
||||||
|
if (PublishPictureRef.value) {
|
||||||
|
PublishPictureRef.value.isVisible = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
window.open(`${baseUrl}/${key}?type=add`, "_blank", "noopener,noreferrer");
|
||||||
|
// router.push({
|
||||||
|
// path: `/${key}`,
|
||||||
|
// query: {
|
||||||
|
// type: 'add',
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function closePublishImg() {
|
||||||
|
isShowPublishPicture.value = false;
|
||||||
|
if (PublishPictureRef.value) {
|
||||||
|
PublishPictureRef.value.isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogin() {
|
||||||
|
modalStore.showLoginModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgList = ref([]);
|
||||||
|
async function getAllMessage() {
|
||||||
|
try {
|
||||||
|
const res = await request.get("/advice/getAllMsg");
|
||||||
|
if (res.code == 200) {
|
||||||
|
msgList.value = res.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getAllMessage();
|
||||||
|
|
||||||
|
// 跳转到消息详情
|
||||||
|
function toDetail(){
|
||||||
|
const baseUrl = window.location.origin
|
||||||
|
window.open(`${baseUrl}/message`, '_blank', 'noopener,noreferrer')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-50 flex h-12 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-6">
|
||||||
|
<!-- Logo 区域调整 -->
|
||||||
|
<div class="flex min-w-[130px] items-center gap-3 pr-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<NuxtLink to="/" class="text-xl font-semibold tracking-tight no-underline">
|
||||||
|
<!-- <img src="/vite.png" alt="Logo" class="h-9 w-9" /> -->
|
||||||
|
魔创未来
|
||||||
|
</NuxtLink>
|
||||||
|
<!-- <span class="text-xl font-semibold tracking-tight">魔创未来</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <HeaderSearchInput /> -->
|
||||||
|
<HeaderSearchInput
|
||||||
|
v-if="hasItem(currentUseRoute, headerRole.inputSearch)"
|
||||||
|
v-model="searchText"
|
||||||
|
@search="onSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Actions -->
|
||||||
|
<NSpace align="center" :size="24">
|
||||||
|
<!-- PC Client -->
|
||||||
|
<NButton text class="header-btn-primary">
|
||||||
|
<Monitor class="h-4 w-4 mr-1" />
|
||||||
|
<span>PC客户端</span>
|
||||||
|
</NButton>
|
||||||
|
|
||||||
|
<!-- Tutorials -->
|
||||||
|
<NButton text class="header-btn">
|
||||||
|
<GraduationCap class="h-4 w-4 mr-1" />
|
||||||
|
<span>教程专区</span>
|
||||||
|
</NButton>
|
||||||
|
|
||||||
|
<NDropdown
|
||||||
|
:options="publishOptions"
|
||||||
|
trigger="hover"
|
||||||
|
:on-select="handlePublishSelect"
|
||||||
|
>
|
||||||
|
<NButton text class="header-btn">
|
||||||
|
<CirclePlus class="h-4 w-4 mr-1" />
|
||||||
|
<span>发布</span>
|
||||||
|
</NButton>
|
||||||
|
</NDropdown>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<!-- :options="notificationOptions" -->
|
||||||
|
<!-- <NDropdown trigger="click">
|
||||||
|
<NBadge :value="5" :max="99" processing>
|
||||||
|
<NButton text circle>
|
||||||
|
<Bell class="h-5 w-5 mr-1 relative" />
|
||||||
|
<div class="absolute top-2 right-1 border-solid border-2 border-[#f0f0f0] py-2">
|
||||||
|
<div class="border-b-solid border-b-2 border-b-[#f0f0f0]">
|
||||||
|
通知
|
||||||
|
</div>
|
||||||
|
<div v-for="(item,index) in 3">
|
||||||
|
1111
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NButton>
|
||||||
|
</NBadge>
|
||||||
|
</NDropdown> -->
|
||||||
|
<div
|
||||||
|
class="p-1 bg-[#f1f1f6] rounded-full flex items-center justify-center relative group"
|
||||||
|
>
|
||||||
|
<Bell class="h-5 w-5relative cursor-pointer" @mouseenter="getAllMessage" />
|
||||||
|
<div class="pt-4 absolute top-5 -right-4 hidden group-hover:block">
|
||||||
|
<div
|
||||||
|
class="border-solid border border-[#f0f0f0] py-2 w-[300px] rounded-lg bg-white"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="border-b-solid border-b border-b-[#f0f0f0] p-2 text-sm font-bold"
|
||||||
|
>
|
||||||
|
通知
|
||||||
|
</div>
|
||||||
|
<div class="p-2 max-h-[300px] overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in msgList"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center my-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<n-ellipsis
|
||||||
|
style="max-width: 250px"
|
||||||
|
:tooltip="false"
|
||||||
|
class="font-bold text-gray-600"
|
||||||
|
>
|
||||||
|
{{ item.content }}
|
||||||
|
</n-ellipsis>
|
||||||
|
<div class="text-[12px] text-gray-400">{{ item.createTime }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-4 flex h-[100%] items-center justify-center">
|
||||||
|
<div
|
||||||
|
class="w-2 h-2 bg-[#ea5049] rounded"
|
||||||
|
v-if="item.isRead === '0'"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-4 text-gray-500 text-sm text-center">
|
||||||
|
更多通知可点击查看全部~
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="bg-[#4c79ee] w-[270px] h-10 flex items-center justify-center rounded-lg text-white cursor-pointer" @click="toDetail">
|
||||||
|
查看全部
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- User -->
|
||||||
|
<div class="min-w-10">
|
||||||
|
<client-only>
|
||||||
|
<NDropdown
|
||||||
|
v-if="userStore.token"
|
||||||
|
:options="userOptions"
|
||||||
|
:on-select="handleUserSelect"
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<NAvatar
|
||||||
|
class="cursor-pointer w-10 h-10"
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
:src="userStore.userInfo.avatar"
|
||||||
|
/>
|
||||||
|
</NDropdown>
|
||||||
|
<div
|
||||||
|
v-if="!userStore.token"
|
||||||
|
class="flex text-white bg-[#197dff] rounded-[4px] px-4 py-2 text-xs cursor-pointer hover:bg-[#1a6eff]"
|
||||||
|
@click="handleLogin"
|
||||||
|
>
|
||||||
|
登录/注册
|
||||||
|
</div>
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
</NSpace>
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<Publish-picture
|
||||||
|
v-if="isShowPublishPicture"
|
||||||
|
ref="PublishPictureRef"
|
||||||
|
type="add"
|
||||||
|
:form-data="publishPicture"
|
||||||
|
@close-publish-img="closePublishImg"
|
||||||
|
/>
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header-btn-primary {
|
||||||
|
@apply border border-solid border-[#3162ff] px-1 py-1 text-[#3162ff] rounded-md hover:text-[#3162ff];
|
||||||
|
}
|
||||||
|
.header-btn {
|
||||||
|
@apply bg-[#f1f1f7] font-bold px-2 py-2 rounded flex items-center hover:text-black hover:bg-[#f1f1f7];
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!-- components/SearchInput.vue -->
|
||||||
|
<script setup>
|
||||||
|
import { CloseCircle } from '@vicons/ionicons5'
|
||||||
|
import {
|
||||||
|
Camera,
|
||||||
|
Search,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'search'])
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
if (props.modelValue) {
|
||||||
|
emit('search', props.modelValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clearInput() {
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative flex items-center bg-[#f1f2f7] rounded-md px-2 py-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:value="modelValue"
|
||||||
|
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||||
|
class="w-[280px] h-4 bg-[#f1f2f7] rounded-md border-0 outline-none text-gray-800 text-[12px] placeholder:text-gray-400"
|
||||||
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<div class="flex items-center w-5 mr-2">
|
||||||
|
<n-icon v-if="modelValue" size="20" class="cursor-pointer" @click="clearInput">
|
||||||
|
<CloseCircle />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Camera class="h-5 w-5 text-gray-400 mr-3 cursor-pointer" />
|
||||||
|
<Search class="h-5 w-5 text-gray-400 mr-3 cursor-pointer" @click="handleSearch" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -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>
|
|
@ -1,100 +1,101 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import phoneImg from "@/assets/img/phone.png";
|
import type { FormInst, FormRules } from 'naive-ui'
|
||||||
import wechatImg from "@/assets/img/wechat.png";
|
import phoneImg from '@/assets/img/phone.png'
|
||||||
import type { FormInst, FormRules } from "naive-ui";
|
import wechatImg from '@/assets/img/wechat.png'
|
||||||
import { createDiscreteApi } from 'naive-ui';
|
import { createDiscreteApi } from 'naive-ui'
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore()
|
||||||
const { message } = createDiscreteApi(['message'])
|
const { message } = createDiscreteApi(['message'])
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
showModal,
|
showModal,
|
||||||
});
|
})
|
||||||
|
const regex = /^1[3-9]\d{9}$/
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
phone: [
|
phone: [
|
||||||
{ required: true, message: "请输入手机号", trigger: "blur" },
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: "手机号格式不正确", trigger: "blur" },
|
{ pattern: regex, message: '手机号格式不正确', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
code: [
|
code: [
|
||||||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||||
{ len: 4, message: "验证码长度为4位", trigger: "blur" },
|
{ len: 4, message: '验证码长度为4位', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
};
|
}
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const isVisible = ref(false);
|
const isVisible = ref(false)
|
||||||
const formRef = ref<FormInst | null>(null)
|
const formRef = ref<FormInst | null>(null)
|
||||||
type LoginMode = "wechat" | "phone";
|
type LoginMode = 'wechat' | 'phone'
|
||||||
const currentLoginMode = ref<LoginMode>("phone");
|
const currentLoginMode = ref<LoginMode>('phone')
|
||||||
const codeButtonText = ref("获取验证码");
|
const codeButtonText = ref('获取验证码')
|
||||||
const codeButtonDisabled = ref(false);
|
const codeButtonDisabled = ref(false)
|
||||||
|
|
||||||
const timerCode: Ref<ReturnType<typeof setInterval> | null> = ref(null);
|
const timerCode: Ref<ReturnType<typeof setInterval> | null> = ref(null)
|
||||||
interface UserData {
|
interface UserData {
|
||||||
code:number,
|
code: number
|
||||||
phone:number,
|
phone: number
|
||||||
}
|
}
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
code: number;
|
code: number
|
||||||
msg: string;
|
msg: string
|
||||||
token: string;
|
token: string
|
||||||
}
|
}
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
|
|
||||||
});
|
})
|
||||||
interface LoginModeDescription {
|
interface LoginModeDescription {
|
||||||
title: string;
|
title: string
|
||||||
des: string;
|
des: string
|
||||||
src: string;
|
src: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginModeDescriptions = Record<LoginMode, LoginModeDescription>;
|
type LoginModeDescriptions = Record<LoginMode, LoginModeDescription>
|
||||||
// 登录模式配置
|
// 登录模式配置
|
||||||
const loginModeDescriptions: LoginModeDescriptions = {
|
const loginModeDescriptions: LoginModeDescriptions = {
|
||||||
phone: {
|
phone: {
|
||||||
title: "登录",
|
title: '登录',
|
||||||
des: "如未注册,验证后自动登录",
|
des: '如未注册,验证后自动登录',
|
||||||
src: wechatImg,
|
src: wechatImg,
|
||||||
},
|
},
|
||||||
wechat: {
|
wechat: {
|
||||||
title: "微信一键登录",
|
title: '微信一键登录',
|
||||||
des: "关注后自动登录",
|
des: '关注后自动登录',
|
||||||
src: phoneImg,
|
src: phoneImg,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
if (isVisible.value) return;
|
if (isVisible.value)
|
||||||
isVisible.value = true;
|
return
|
||||||
|
isVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCloseLogin() {
|
function onCloseLogin() {
|
||||||
formData.value = {}
|
formData.value = {}
|
||||||
isVisible.value = false;
|
isVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//切换登录方式
|
// 切换登录方式
|
||||||
function toggleMode() {
|
function toggleMode() {
|
||||||
return currentLoginMode.value === "phone" ? "wechat" : "phone";
|
return currentLoginMode.value === 'phone' ? 'wechat' : 'phone'
|
||||||
}
|
}
|
||||||
function setCurrentLoginMode() {
|
function setCurrentLoginMode() {
|
||||||
currentLoginMode.value = toggleMode();
|
currentLoginMode.value = toggleMode()
|
||||||
}
|
}
|
||||||
interface UserToken{
|
interface UserToken {
|
||||||
token:string
|
token: string
|
||||||
}
|
}
|
||||||
// 登录
|
// 登录
|
||||||
async function handleValidateClick(e: MouseEvent) {
|
async function handleValidateClick(e: MouseEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
formRef.value?.validate(async(errors:any) => {
|
formRef.value?.validate(async (errors: any) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
const res = await request.post<ApiResponse<UserData>>('/phoneLogin', {
|
const res = await request.post<ApiResponse<UserData>>('/phoneLogin', {
|
||||||
...formData.value
|
...formData.value,
|
||||||
})
|
})
|
||||||
userStore.setToken(res.token)
|
userStore.setToken(res.token)
|
||||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||||
token:res.token
|
token: res.token,
|
||||||
})
|
})
|
||||||
userStore.setUserInfo(res1.user)
|
userStore.setUserInfo(res1.user)
|
||||||
onCloseLogin()
|
onCloseLogin()
|
||||||
|
@ -106,46 +107,52 @@ async function handleValidateClick(e: MouseEvent) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
interface codeInterface{
|
interface codeInterface {
|
||||||
phone:number
|
phone: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
async function onGetCode() {
|
async function onGetCode() {
|
||||||
|
if (!regex.test(formData.value.phone))
|
||||||
|
return
|
||||||
try {
|
try {
|
||||||
const response = await request.get<codeInterface>("/getCode", {
|
const response = await request.get<codeInterface>('/getCode', {
|
||||||
params: {
|
params: {
|
||||||
phone:formData.value.phone
|
phone: formData.value.phone,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
codeButtonDisabled.value = true;
|
codeButtonDisabled.value = true
|
||||||
let countdown = 60;
|
let countdown = 60
|
||||||
codeButtonText.value = `${countdown}秒后重试`;
|
codeButtonText.value = `${countdown}秒后重试`
|
||||||
|
|
||||||
if (timerCode.value) clearInterval(timerCode.value);
|
if (timerCode.value)
|
||||||
|
clearInterval(timerCode.value)
|
||||||
|
|
||||||
timerCode.value = setInterval(() => {
|
timerCode.value = setInterval(() => {
|
||||||
countdown--;
|
countdown--
|
||||||
if (countdown <= 0) {
|
if (countdown <= 0) {
|
||||||
clearInterval(timerCode.value);
|
clearInterval(timerCode.value)
|
||||||
codeButtonDisabled.value = false;
|
codeButtonDisabled.value = false
|
||||||
codeButtonText.value = "获取验证码";
|
codeButtonText.value = '获取验证码'
|
||||||
} else {
|
|
||||||
codeButtonText.value = `${countdown}秒后重试`;
|
|
||||||
}
|
}
|
||||||
}, 1000);
|
else {
|
||||||
} else {
|
codeButtonText.value = `${countdown}秒后重试`
|
||||||
message.error(response.message || "获取验证码失败!");
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}, 1000)
|
||||||
console.error(err);
|
}
|
||||||
|
else {
|
||||||
|
message.error(response.message || '获取验证码失败!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onCloseModel = () =>{
|
function onCloseModel() {
|
||||||
formData.value = {}
|
formData.value = {}
|
||||||
if (timerCode.value) {
|
if (timerCode.value) {
|
||||||
clearInterval(timerCode.value);
|
clearInterval(timerCode.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,19 +160,18 @@ const onCloseModel = () =>{
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
formData.value = {}
|
formData.value = {}
|
||||||
if (timerCode.value) {
|
if (timerCode.value) {
|
||||||
clearInterval(timerCode.value);
|
clearInterval(timerCode.value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NModal
|
<NModal
|
||||||
:on-after-leave="onCloseModel"
|
|
||||||
v-model:show="isVisible"
|
v-model:show="isVisible"
|
||||||
|
:on-after-leave="onCloseModel"
|
||||||
preset="card"
|
preset="card"
|
||||||
style="width: 400px"
|
style="width: 400px"
|
||||||
:maskClosable="false"
|
:mask-closable="false"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<!-- 关闭按钮 -->
|
<!-- 关闭按钮 -->
|
||||||
|
@ -185,7 +191,7 @@ onUnmounted(() => {
|
||||||
<h2 class="text-center text-xl text-gray-800 font-bold">
|
<h2 class="text-center text-xl text-gray-800 font-bold">
|
||||||
{{ loginModeDescriptions[currentLoginMode].title }}
|
{{ loginModeDescriptions[currentLoginMode].title }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-center text-sm text-gray-500">
|
<p class="text-center text-sm text-gray-500 my-2">
|
||||||
{{ loginModeDescriptions[currentLoginMode].des }}
|
{{ loginModeDescriptions[currentLoginMode].des }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -210,7 +216,7 @@ onUnmounted(() => {
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="codeButtonDisabled"
|
:disabled="codeButtonDisabled"
|
||||||
ghost
|
ghost
|
||||||
@click="onGetCode"
|
@click="onGetCode($event)"
|
||||||
>
|
>
|
||||||
{{ codeButtonText }}
|
{{ codeButtonText }}
|
||||||
</n-button>
|
</n-button>
|
||||||
|
@ -239,17 +245,19 @@ onUnmounted(() => {
|
||||||
|
|
||||||
<!-- 其他登录方式 -->
|
<!-- 其他登录方式 -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-sm text-gray-400">其他登录方式</div>
|
<div class="text-sm text-gray-400">
|
||||||
|
其他登录方式
|
||||||
|
</div>
|
||||||
<div class="mt-2 flex items-center justify-center gap-6">
|
<div class="mt-2 flex items-center justify-center gap-6">
|
||||||
<div
|
<div
|
||||||
class="h-10 w-10 cursor-pointer rounded-full p-2 transition-all hover:bg-gray-100"
|
class="cursor-pointer rounded-full p-2 transition-all hover:bg-gray-100"
|
||||||
@click="setCurrentLoginMode"
|
@click="setCurrentLoginMode"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="loginModeDescriptions[currentLoginMode].src"
|
:src="loginModeDescriptions[currentLoginMode].src"
|
||||||
alt=""
|
alt=""
|
||||||
class="h-10 w-10"
|
class="h-10 w-10"
|
||||||
/>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap justify-center">
|
||||||
|
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
|
||||||
|
<div
|
||||||
|
v-for="item in dataList"
|
||||||
|
:key="item.id"
|
||||||
|
class="relative rounded-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- 图片 -->
|
||||||
|
<div class="relative h-[300px] overflow-hidden rounded-lg" @click="toDetail(item)">
|
||||||
|
<img
|
||||||
|
:src="item.surfaceUrl"
|
||||||
|
class="w-full h-full object-cover rounded-lg cursor-pointer ransform transition-transform duration-300 hover:scale-110"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 左上角标签 -->
|
||||||
|
<div
|
||||||
|
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
{{ item.type }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部数据统计 -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||||
|
{{ item.reals || 0 }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||||
|
/>
|
||||||
|
{{ item.numbers || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作者信息条 -->
|
||||||
|
<div class="mt-1 px-2 py-1">
|
||||||
|
<div>
|
||||||
|
{{ item.modelName }}
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
|
||||||
|
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="loadingTrigger"
|
||||||
|
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div v-if="loading">加载中...</div>
|
||||||
|
<div v-if="finished && dataList.length >= 20">没有更多数据了</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Download, Play } from "lucide-vue-next";
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
const total = ref(0); // 总条数
|
||||||
|
const loadingTrigger = ref(null);
|
||||||
|
const observer = ref<IntersectionObserver | null>(null);
|
||||||
|
const props = defineProps({
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const listParams = ref({
|
||||||
|
...props.params,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
function initPageNUm() {
|
||||||
|
listParams.value.pageNumber = 1;
|
||||||
|
finished.value = false; // 重置加载完成状态
|
||||||
|
listParams.value = Object.assign({}, listParams.value, props.params);
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataList = ref([]);
|
||||||
|
async function getDataList() {
|
||||||
|
if (loading.value || finished.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await request.post("/model/modelSquare", { ...listParams.value });
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 如果是第一页,直接赋值,否则追加数据
|
||||||
|
if (listParams.value.pageNumber === 1) {
|
||||||
|
dataList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
dataList.value = [...dataList.value, ...res.data.list];
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = res.data.total; // 假设接口返回了总条数
|
||||||
|
|
||||||
|
// 判断是否加载完所有数据
|
||||||
|
if (dataList.value.length >= total.value) {
|
||||||
|
finished.value = true;
|
||||||
|
}
|
||||||
|
// 自动增加页码
|
||||||
|
listParams.value.pageNumber++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
dataList.value = [];
|
||||||
|
finished.value = true;
|
||||||
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDataList();
|
||||||
|
|
||||||
|
|
||||||
|
// 跳转详情
|
||||||
|
function toDetail(item:any){
|
||||||
|
router.push(`/model-details/${item.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("scroll", topedRefresh);
|
||||||
|
observer.value = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !loading.value && !finished.value) {
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loadingTrigger.value) {
|
||||||
|
observer.value.observe(loadingTrigger.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("scroll", topedRefresh);
|
||||||
|
if (observer.value) {
|
||||||
|
observer.value.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function topedRefresh() {
|
||||||
|
if (import.meta.client) {
|
||||||
|
await nextTick();
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initPageNUm();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initPageNUm,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,317 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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('')
|
||||||
|
|
||||||
|
interface Member {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 活动列表
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
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="grid grid-cols-4 gap-1 mt-4 px-6">
|
||||||
|
<div
|
||||||
|
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">
|
||||||
|
{{ item.memberName }}
|
||||||
|
</div>
|
||||||
|
<div class="card-item">
|
||||||
|
<span class="text-[#814600] text-[20px] mr-2">¥</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]">
|
||||||
|
{{ 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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 mt-1 text-[#9d8c75] text-[12px]">
|
||||||
|
会员每月算力和加速特权按下月下发, 有效期31天, 到期重置
|
||||||
|
</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 class="bg-[#fefaee] text-center text-[#AF7F1A] text-[12px] py-1 rounded-b-lg">
|
||||||
|
需支付差价,计算规则为:专业版年包价格-剩余未下发基础版年包月数*基础版年包购买价格/12
|
||||||
|
</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>
|
|
@ -0,0 +1,407 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
CircleAlert,
|
||||||
|
Download,
|
||||||
|
EllipsisVertical,
|
||||||
|
Play
|
||||||
|
} from 'lucide-vue-next';
|
||||||
|
import { nextTick, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
currentType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
currentState: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义 emit
|
||||||
|
const emit = defineEmits(['topedRefresh'])
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 跳转详情
|
||||||
|
function toDetails() {
|
||||||
|
if (props.currentType === '0') {
|
||||||
|
router.push(`/model-details/${props.item.id}`)
|
||||||
|
}
|
||||||
|
else if (props.currentType === '1') {
|
||||||
|
// console.log('object', 111);
|
||||||
|
router.push(`/workflow-details/${props.item.id}`)
|
||||||
|
}else if(props.currentType === '2'){
|
||||||
|
onEditPicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图片详情进行编辑
|
||||||
|
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() // 阻止事件冒泡
|
||||||
|
if (key === 'top') {
|
||||||
|
handleTop()
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (props.currentType === '0') { // 模型
|
||||||
|
router.push({
|
||||||
|
path: `/publish-model`,
|
||||||
|
query: {
|
||||||
|
type: 'edit',
|
||||||
|
id: props.item.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 置顶
|
||||||
|
interface TopUrlType {
|
||||||
|
[key: string | number]: string
|
||||||
|
}
|
||||||
|
const topUrl = ref<TopUrlType>({
|
||||||
|
0: 'model',
|
||||||
|
1: 'WorkFlow',
|
||||||
|
2: 'image',
|
||||||
|
})
|
||||||
|
async function handleTop() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(
|
||||||
|
`/${topUrl.value[props.currentType]}/${props.item.id}/top?isTop=${!props.item
|
||||||
|
.isTop}`,
|
||||||
|
)
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 刷新列表
|
||||||
|
emit('topedRefresh')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
|
||||||
|
interface Response {
|
||||||
|
code: number
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(): Promise<void> {
|
||||||
|
const { currentType, item } = props
|
||||||
|
let url: string
|
||||||
|
|
||||||
|
switch (currentType) {
|
||||||
|
case '0':
|
||||||
|
url = `/model/delete?id=${item.id}`
|
||||||
|
break
|
||||||
|
case '1':
|
||||||
|
url = `/WorkFlow/deleteWorkFlow?id=${item.id}`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
url = `/image/delete?id=${item.id}`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res: Response = await request.get(url)
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 刷新列表
|
||||||
|
emit('topedRefresh')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// 统一处理错误
|
||||||
|
console.error('删除操作失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getFirstImagePath(imagePaths: string): string {
|
||||||
|
if (!imagePaths)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示图片的编辑
|
||||||
|
const isShowEditorPicture = ref(false)
|
||||||
|
interface EditUserInfoType {
|
||||||
|
isVisible: boolean
|
||||||
|
}
|
||||||
|
const editUserInfoRef = ref<EditUserInfoType | null>(null)
|
||||||
|
function onEditPicture() {
|
||||||
|
isShowEditorPicture.value = true
|
||||||
|
nextTick(()=>{
|
||||||
|
if(editUserInfoRef.value){
|
||||||
|
editUserInfoRef.value.isVisible = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function closeEditorPicture(){
|
||||||
|
isShowEditorPicture.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLike(type:number){
|
||||||
|
if (props.item.isLike === 1) {
|
||||||
|
props.item.isLike = 0;
|
||||||
|
props.item.likeNum -= 1;
|
||||||
|
} else {
|
||||||
|
props.item.isLike = 1;
|
||||||
|
props.item.likeNum += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="currentState === 'like' && currentType === '2'">
|
||||||
|
<div
|
||||||
|
class="h-80 rounded-2xl overflow-hidden cursor-pointer relative group"
|
||||||
|
@click="toDetails"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="w-full h-full object-cover block"
|
||||||
|
:src="getFirstImagePath(item.imagePaths)"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute w-full h-full top-0 left-0 flex justify-between px-4 py-4 box-border bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||||
|
>
|
||||||
|
<div class="flex items-center h-6">
|
||||||
|
<img
|
||||||
|
class="w-6 h-6 rounded-full"
|
||||||
|
:src="item.userAvatar"
|
||||||
|
alt="头像"
|
||||||
|
>
|
||||||
|
<span class="ml-2 h-5 text-gray-300 text-[12px]">{{ item.userName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center h-7 justify-center rounded-xl bg-[#fceceb] p-2">
|
||||||
|
<img
|
||||||
|
src="@/assets/img/heart.png"
|
||||||
|
class="w-3 h-3 mr-1"
|
||||||
|
alt="❤️"
|
||||||
|
>
|
||||||
|
<span class="text-xs text-[#000]">{{ item.likeNum }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div
|
||||||
|
class="h-80 rounded-2xl overflow-hidden cursor-pointer relative"
|
||||||
|
@click="toDetails"
|
||||||
|
>
|
||||||
|
<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="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"
|
||||||
|
>
|
||||||
|
置顶
|
||||||
|
</div>
|
||||||
|
<!-- 在发布中 auditStatus等于代表没有审批通过 -->
|
||||||
|
<div
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="currentState === 'mallProduct' && currentType === '0'"
|
||||||
|
class="text-white text-[12px] px-3 bg-[#000] bg-opacity-40 rounded-lg h-[20px] leading-relaxed"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ item.modelType }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="currentState === 'mallProduct' && currentType === '1'"
|
||||||
|
class="text-white text-[12px] px-3 bg-[#000] bg-opacity-40 rounded-lg h-[20px] leading-relaxed"
|
||||||
|
>
|
||||||
|
工作流
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="currentState === 'mallProduct'"
|
||||||
|
class="modelSelectByUserIdModel-mask h-1/3 absolute top-0 left-0 bg-gradient-to-b from-black/100 to-transparent px-4 py-4 text-white box-border flex justify-end"
|
||||||
|
>
|
||||||
|
<div class="menu-content">
|
||||||
|
<component
|
||||||
|
:is="EllipsisVertical"
|
||||||
|
class="h-[18px] w-[18px] text-white menu-icon"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="menu-group text-[#000000] text-[12px] bg-white rounded-lg text-center w-20 mt-2 hidden"
|
||||||
|
>
|
||||||
|
<div class="menu-item" @click="(event) => handleSelect(event, 'edit')">
|
||||||
|
编辑
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-item text-red-600"
|
||||||
|
@click="(event) => handleSelect(event, 'delete')"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</div>
|
||||||
|
<div class="menu-item" @click="(event) => handleSelect(event, 'top')">
|
||||||
|
{{ item.isTop === 1 ? "取消置顶" : "置顶" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="currentType !== '2'"
|
||||||
|
class="absolute bottom-0 left-0 px-4 py-2 text-white box-border flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||||
|
<span v-if="currentType === '0'">
|
||||||
|
{{ item.reals || 0 }}
|
||||||
|
</span>
|
||||||
|
<span v-if="currentType === '1'">
|
||||||
|
{{ item.useNumber || 0 }}
|
||||||
|
</span>
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||||
|
/>
|
||||||
|
<span v-if="currentType === '0'">
|
||||||
|
{{ item.numbers || 0 }}
|
||||||
|
</span>
|
||||||
|
<span v-if="currentType === '1'">
|
||||||
|
{{ item.downloadNumber || 0 }}
|
||||||
|
</span>
|
||||||
|
<!-- <component
|
||||||
|
:is="ImagePlay"
|
||||||
|
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||||
|
/>0 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="currentType !== '2'" class="mt-2 text-[12px] text-[#67787e]">
|
||||||
|
<div class="text-[#000] mb-1">
|
||||||
|
<span v-if="currentType === '0'">
|
||||||
|
{{ item.modelName }}
|
||||||
|
</span>
|
||||||
|
<span v-if="currentType === '1'">
|
||||||
|
{{ item.workflowName }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- <span>{{ item.userName }}</span> -->
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<img
|
||||||
|
class="block w-[20px] h-[20px] rounded-full mr-2"
|
||||||
|
:src="item.userAvatar"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<span>{{ item.userName }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<div v-if="isShowEditorPicture">
|
||||||
|
<PictureDetail @close-editor-picture="closeEditorPicture" ref="editUserInfoRef" :item="item" @update-like="updateLike"/>
|
||||||
|
</div>
|
||||||
|
<Publish-picture v-if="isShowPublishPicture" type="edit" ref="PublishPictureRef" :form-data="publishPictureData" @close-publish-img="closePublishImg" />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.modelSelectByUserIdModel {
|
||||||
|
position: absolute;
|
||||||
|
&:hover {
|
||||||
|
.modelSelectByUserIdModel-mask {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modelSelectByUserIdModel-mask {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
.menu-content {
|
||||||
|
width: 20px;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.menu-content:hover {
|
||||||
|
.menu-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-group {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 0;
|
||||||
|
.menu-item {
|
||||||
|
padding: 4px 0 4px 0;
|
||||||
|
margin: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
&:hover {
|
||||||
|
background-color: #eeeded;
|
||||||
|
// border: solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,190 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from "@/api/common";
|
||||||
|
import { CopyOutline, Heart } from "@vicons/ionicons5";
|
||||||
|
import { Download, PartyPopper } from "lucide-vue-next";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
import { ref } from "vue";
|
||||||
|
const emit = defineEmits(["updateLike"]);
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const userInfo = userStore.userInfo;
|
||||||
|
const message = useMessage();
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dataInfo = ref({});
|
||||||
|
async function getDetail() {
|
||||||
|
if (props.item && props.item.id) {
|
||||||
|
const res = await request.get(`/image/detail?id=${props.item.id}`);
|
||||||
|
if (res.code === 200) {
|
||||||
|
dataInfo.value = res.data;
|
||||||
|
getDictType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDetail();
|
||||||
|
const tagsList = ref([]);
|
||||||
|
// 获取图片标签
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "image_label" });
|
||||||
|
if (res.code === 200 && res.data.length > 0) {
|
||||||
|
for (let i = 0; i < res.data.length; i++) {
|
||||||
|
for (let j = 0; j < dataInfo.value.tags.length; j++) {
|
||||||
|
if (res.data[i].dictValue === dataInfo.value.tags[j]) {
|
||||||
|
tagsList.value.push(res.data[i].dictLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShowModel() {
|
||||||
|
// ruleForm.value.nickName = userInfo.nickName
|
||||||
|
// ruleForm.value.avatar = userInfo.avatar
|
||||||
|
// ruleForm.value.brief = userInfo.brief
|
||||||
|
// ruleForm.value.userId = userInfo.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVisible = ref(false);
|
||||||
|
function onCloseModel() {
|
||||||
|
isVisible.value = false;
|
||||||
|
}
|
||||||
|
const commentHeight = ref(200);
|
||||||
|
|
||||||
|
//点赞
|
||||||
|
async function onLike() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/image/imageLike?id=${props.item.id}`);
|
||||||
|
if (res.code === 200) {
|
||||||
|
// emit('updateLike')
|
||||||
|
if (dataInfo.value.isLike === 1) {
|
||||||
|
dataInfo.value.isLike = 0;
|
||||||
|
dataInfo.value.likeNum -= 1;
|
||||||
|
message.success("取消点赞成功");
|
||||||
|
} else {
|
||||||
|
dataInfo.value.isLike = 1;
|
||||||
|
dataInfo.value.likeNum += 1;
|
||||||
|
message.success("点赞成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
isVisible,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
v-model:show="isVisible"
|
||||||
|
:on-after-leave="onCloseModel"
|
||||||
|
:on-after-enter="onShowModel"
|
||||||
|
preset="card"
|
||||||
|
style="width: auto; border-radius: 10px; box-sizing: border-box"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<div class="p-2 w-[700px]" style="box-sizing: border-box">
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1 p-1">
|
||||||
|
<img class="w-full h-[300px]" :src="dataInfo.imagePaths" alt="" />
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<div
|
||||||
|
class="mr-2 w-40 px2 py-3 flex items-center justify-center text-[#8a4200] cursor-pointer rounded-lg bg-gradient-to-r from-[#ffe9c8] to-[#ffd264]"
|
||||||
|
>
|
||||||
|
<Download size="16" class="mr-1" />
|
||||||
|
下载无水印原图
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center bg-[#eceef4] px2 py-3 w-20 justify-center cursor-pointer rounded-lg"
|
||||||
|
>
|
||||||
|
<n-icon
|
||||||
|
class="mr-2"
|
||||||
|
size="20"
|
||||||
|
:color="dataInfo.isLike === 1 ? '#ff0000' : '#ccc'"
|
||||||
|
@click="onLike"
|
||||||
|
>
|
||||||
|
<Heart />
|
||||||
|
</n-icon>
|
||||||
|
{{ dataInfo.likeNum }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-[#f6f9fe] p-2 text-[12px] mt-2 rounded-lg">
|
||||||
|
作者添加了水印,成为会员后可下载无水印原图。
|
||||||
|
<span class="text-[#4a69ed] cursor-pointer">直接下载</span>带水印的图片
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 p-1">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<img class="w-10 h-10 rounded-full mr-2" :src="dataInfo.userAvatar" alt="" />
|
||||||
|
<div>
|
||||||
|
{{ dataInfo.userName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-[20px] mt-3">
|
||||||
|
{{ dataInfo.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-[14px] mt-2 text-gray-400">
|
||||||
|
{{ dataInfo.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-[14px] mt-2 text-gray-400">
|
||||||
|
{{ dataInfo.createTime }}
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<div
|
||||||
|
class="flex w-[100px] items-center bg-[#eceef4] px2 py-3 justify-center cursor-pointer rounded-lg mr-2"
|
||||||
|
>
|
||||||
|
<n-icon class="mr-2" size="20" color="#ccc">
|
||||||
|
<CopyOutline />
|
||||||
|
</n-icon>
|
||||||
|
复制全部
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex w-[200px] items-center px2 py-3 bg-[#416af6] text-white rounded-lg justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<PartyPopper size="16" class="mr-1" />一键生图
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="p-1 flex flex-wrap">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in tagsList"
|
||||||
|
:key="index"
|
||||||
|
class="text-[#5d79ba] bg-[#ecf2fe] p-2 text-[12px] font-bold mr-2 mt-2 w-auto rounded-lg"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div style="padding: 20px">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<BaseComment
|
||||||
|
v-if="dataInfo.id"
|
||||||
|
type="pictrue"
|
||||||
|
:height="commentHeight"
|
||||||
|
:details-info="dataInfo"
|
||||||
|
/>
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,210 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap justify-center">
|
||||||
|
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
|
||||||
|
<div
|
||||||
|
v-for="item in dataList"
|
||||||
|
:key="item.id"
|
||||||
|
class="relative rounded-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- 图片 -->
|
||||||
|
<div class="relative h-[300px] overflow-hidden rounded-lg group">
|
||||||
|
<img
|
||||||
|
:src="item.avatar"
|
||||||
|
class="w-full h-full object-cover rounded-lg cursor-pointer"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="px-2 py-1 absolute top-0 left-0 w-full h-full hidden group-hover:block bg-black bg-opacity-40 cursor-pointer"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between w-full">
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<div class="w-5 h-5 border border-white mr-2 rounded-full">
|
||||||
|
<img :src="item.avatar" class="w-full h-full rounded-full" alt="" />
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-white truncate">{{ item.nickName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 rounded-full bg-white flex items-center cursor-pointer">
|
||||||
|
<n-icon
|
||||||
|
class="mr-2"
|
||||||
|
size="20"
|
||||||
|
:color="item.isLike === 0 ? '#ccc' : '#ff0000'"
|
||||||
|
@click="onLike(item)"
|
||||||
|
>
|
||||||
|
<Heart />
|
||||||
|
</n-icon>
|
||||||
|
{{ item.likeNum }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 左上角标签 -->
|
||||||
|
<!-- <div
|
||||||
|
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
{{ item.type }}
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- 底部数据统计 -->
|
||||||
|
<!-- <div
|
||||||
|
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||||
|
{{ item.reals || 0 }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||||
|
/>
|
||||||
|
{{ item.numbers || 0 }}
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作者信息条 -->
|
||||||
|
<!-- <div class="mt-1 px-2 py-1">
|
||||||
|
<div>
|
||||||
|
{{ item.modelName }}
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
|
||||||
|
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="loadingTrigger"
|
||||||
|
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div v-if="loading">加载中...</div>
|
||||||
|
<div v-if="finished && dataList.length >= 20">没有更多数据了</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Heart } from "@vicons/ionicons5";
|
||||||
|
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
|
const loading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
const total = ref(0); // 总条数
|
||||||
|
const loadingTrigger = ref(null);
|
||||||
|
const observer = ref<IntersectionObserver | null>(null);
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const listParams = ref({
|
||||||
|
...props.params,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
function initPageNUm() {
|
||||||
|
listParams.value.pageNumber = 1;
|
||||||
|
finished.value = false; // 重置加载完成状态
|
||||||
|
listParams.value = Object.assign({}, listParams.value, props.params);
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataList = ref([]);
|
||||||
|
async function getDataList() {
|
||||||
|
if (loading.value || finished.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await request.get("/image/imageList", { ...listParams.value });
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 如果是第一页,直接赋值,否则追加数据
|
||||||
|
if (listParams.value.pageNumber === 1) {
|
||||||
|
dataList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
dataList.value = [...dataList.value, ...res.data.list];
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = res.data.total; // 假设接口返回了总条数
|
||||||
|
|
||||||
|
// 判断是否加载完所有数据
|
||||||
|
if (dataList.value.length >= total.value) {
|
||||||
|
finished.value = true;
|
||||||
|
}
|
||||||
|
// 自动增加页码
|
||||||
|
listParams.value.pageNumber++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
dataList.value = [];
|
||||||
|
finished.value = true;
|
||||||
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDataList();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("scroll", topedRefresh);
|
||||||
|
observer.value = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !loading.value && !finished.value) {
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loadingTrigger.value) {
|
||||||
|
observer.value.observe(loadingTrigger.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("scroll", topedRefresh);
|
||||||
|
if (observer.value) {
|
||||||
|
observer.value.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function topedRefresh() {
|
||||||
|
if (import.meta.client) {
|
||||||
|
await nextTick();
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initPageNUm();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onLike(item: any) {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/image/imageLike?id=${item.id}`);
|
||||||
|
if (res.code === 200) {
|
||||||
|
if (item.isLike === 1) {
|
||||||
|
item.isLike = 0;
|
||||||
|
item.likeNum -= 1;
|
||||||
|
message.success("取消点赞成功");
|
||||||
|
} else {
|
||||||
|
item.isLike = 1;
|
||||||
|
item.likeNum += 1;
|
||||||
|
message.success("点赞成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
initPageNUm,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,188 @@
|
||||||
|
<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: () => ({}),
|
||||||
|
},
|
||||||
|
type:{
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
debugger
|
||||||
|
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>
|
|
@ -0,0 +1,206 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: 'Scroll List',
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [1, 2, 3, 4, 5, 6, 7],
|
||||||
|
},
|
||||||
|
initialIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'item-click'])
|
||||||
|
|
||||||
|
const containerRef = ref(null)
|
||||||
|
const listRef = ref(null)
|
||||||
|
const itemRefs = ref([])
|
||||||
|
const currentIndex = ref(props.initialIndex)
|
||||||
|
|
||||||
|
// 居中滚动函数
|
||||||
|
function scrollToCenter(index) {
|
||||||
|
const container = containerRef.value
|
||||||
|
const item = itemRefs.value[index]
|
||||||
|
|
||||||
|
if (!container || !item)
|
||||||
|
return
|
||||||
|
|
||||||
|
const containerWidth = container.offsetWidth
|
||||||
|
const itemWidth = item.offsetWidth
|
||||||
|
const itemLeft = item.offsetLeft
|
||||||
|
const scrollLeft = itemLeft - (containerWidth / 2) + (itemWidth / 2)
|
||||||
|
|
||||||
|
listRef.value.scrollTo({
|
||||||
|
left: scrollLeft,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleItemClick(index) {
|
||||||
|
currentIndex.value = index
|
||||||
|
scrollToCenter(index)
|
||||||
|
emit('item-click', {
|
||||||
|
index,
|
||||||
|
item: props.items[index],
|
||||||
|
})
|
||||||
|
emit('change', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpUp() {
|
||||||
|
if (currentIndex.value > 0) {
|
||||||
|
currentIndex.value--
|
||||||
|
scrollToCenter(currentIndex.value)
|
||||||
|
emit('change', currentIndex.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpDown() {
|
||||||
|
if (currentIndex.value < props.items.length - 1) {
|
||||||
|
currentIndex.value++
|
||||||
|
scrollToCenter(currentIndex.value)
|
||||||
|
emit('change', currentIndex.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
jumpUp,
|
||||||
|
jumpDown,
|
||||||
|
currentIndex,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="scroll-list-container">
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
<div ref="containerRef" class="wrap">
|
||||||
|
<div ref="listRef" class="list">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:id="`item${index + 1}`"
|
||||||
|
:key="index" ref="itemRefs"
|
||||||
|
class="item"
|
||||||
|
:class="[{ active: currentIndex === index }]"
|
||||||
|
@click="handleItemClick(index)"
|
||||||
|
>
|
||||||
|
<slot name="item" :item="item" :index="index">
|
||||||
|
{{ item }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<button @click="jumpUp">
|
||||||
|
上一个
|
||||||
|
</button>
|
||||||
|
<button @click="jumpDown">
|
||||||
|
下一个
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.scroll-list-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
width: 400px;
|
||||||
|
outline: 4px solid #666;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
/* 隐藏滚动条但保持功能 */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为 Webkit 浏览器隐藏滚动条 */
|
||||||
|
.list::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: royalblue;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #f44336;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 5px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #4caf50;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加两侧渐变遮罩效果 */
|
||||||
|
.wrap::before,
|
||||||
|
.wrap::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 30px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .wrap::before {
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(to right, rgba(255, 255, 255, 0.9), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap::after {
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(to left, rgba(255, 255, 255, 0.9), transparent);
|
||||||
|
} */
|
||||||
|
</style>
|
|
@ -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>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script setup>
|
||||||
|
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: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 引入 css
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const mode = 'default'
|
||||||
|
// 编辑器实例,必须用 shallowRef
|
||||||
|
const editorRef = shallowRef()
|
||||||
|
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 },
|
||||||
|
)
|
||||||
|
// 内容 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(() => {
|
||||||
|
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>
|
||||||
|
<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>
|
|
@ -3,20 +3,20 @@
|
||||||
// import { getUUid, uuidLogin } from '@api/login'
|
// import { getUUid, uuidLogin } from '@api/login'
|
||||||
// import { useStore } from '@store/index'
|
// import { useStore } from '@store/index'
|
||||||
// import { IosRefresh } from '@vicons/ionicons5';
|
// import { IosRefresh } from '@vicons/ionicons5';
|
||||||
import { RotateCcw } from 'lucide-vue-next';
|
import { RotateCcw } from 'lucide-vue-next'
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'login-success', data: any): void
|
(event: 'login-success', data: any): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
interface UserToken{
|
interface UserToken {
|
||||||
token:string
|
token: string
|
||||||
}
|
}
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
code: number;
|
code: number
|
||||||
msg: string;
|
msg: string
|
||||||
token: string;
|
token: string
|
||||||
}
|
}
|
||||||
const qrUrl = ref<string>('')
|
const qrUrl = ref<string>('')
|
||||||
const qrSize = ref(174)
|
const qrSize = ref(174)
|
||||||
|
@ -40,8 +40,8 @@ async function onGetUUid() {
|
||||||
qrUrl.value = codeUrl
|
qrUrl.value = codeUrl
|
||||||
let counter = 1
|
let counter = 1
|
||||||
pollingTimer && clearTimeout(pollingTimer)
|
pollingTimer && clearTimeout(pollingTimer)
|
||||||
pollingTimer = setInterval(async() => {
|
pollingTimer = setInterval(async () => {
|
||||||
await request.get('/wx/uuid/login',{uuid}).then(async(res)=>{
|
await request.get('/wx/uuid/login', { uuid }).then(async (res) => {
|
||||||
counter++
|
counter++
|
||||||
if (counter === 59) {
|
if (counter === 59) {
|
||||||
clearTimeout(pollingTimer)
|
clearTimeout(pollingTimer)
|
||||||
|
@ -52,25 +52,12 @@ async function onGetUUid() {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
userStore.setToken(res.token)
|
userStore.setToken(res.token)
|
||||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||||
token:res.token
|
token: res.token,
|
||||||
})
|
})
|
||||||
userStore.setUserInfo(res1.user)
|
userStore.setUserInfo(res1.user)
|
||||||
window.location.href = '/'
|
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((err: any) => {
|
}).catch(() => {
|
||||||
console.log(err)
|
|
||||||
clearTimeout(pollingTimer)
|
clearTimeout(pollingTimer)
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
@ -100,14 +87,14 @@ onBeforeUnmount(() => {
|
||||||
<div class="w-[280px] px-5 text-center relative">
|
<div class="w-[280px] px-5 text-center relative">
|
||||||
<div v-if="bindTimeout" class="absolute left-[41px] h-full w-[230px] top-0 z-[9999] flex items-center justify-center flex-col cursor-pointer text-white bg-black/40" @click="onGetUUid">
|
<div v-if="bindTimeout" class="absolute left-[41px] h-full w-[230px] top-0 z-[9999] flex items-center justify-center flex-col cursor-pointer text-white bg-black/40" @click="onGetUUid">
|
||||||
刷新二维码
|
刷新二维码
|
||||||
<rotate-ccw/>
|
<RotateCcw />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="qrUrl" class="relative w-full">
|
<div v-if="qrUrl" class="relative w-full">
|
||||||
<component
|
<!-- <component
|
||||||
is="RotateCcw"
|
is="RotateCcw"
|
||||||
class="h-[18px] w-[18px] color-white"
|
class="h-[18px] w-[18px] color-white"
|
||||||
/>
|
/> -->
|
||||||
<n-qr-code :value="qrUrl" :size="qrSize" />
|
<n-qr-code style="padding: 0;" class="p-0" :value="qrUrl" :size="qrSize" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="p-5 text-gray-400">
|
<div v-else class="p-5 text-gray-400">
|
||||||
加载中...
|
加载中...
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap justify-center">
|
||||||
|
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
|
||||||
|
<div
|
||||||
|
v-for="item in dataList"
|
||||||
|
:key="item.id"
|
||||||
|
class="relative rounded-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- 图片 -->
|
||||||
|
<div class="relative h-[300px] overflow-hidden rounded-lg" @click="toDetail(item)">
|
||||||
|
<img
|
||||||
|
:src="item.coverPath"
|
||||||
|
class="w-full h-full object-cover rounded-lg cursor-pointer ransform transition-transform duration-300 hover:scale-110"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 左上角标签 -->
|
||||||
|
<div
|
||||||
|
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
{{ item.type }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部数据统计 -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 left-0 right-0 flex items-center gap-2 p-2 text-xs text-white"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||||
|
{{ item.useNumber || 0 }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||||
|
/>
|
||||||
|
{{ item.downloadNumber || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作者信息条 -->
|
||||||
|
<div class="mt-1 px-2 py-1">
|
||||||
|
<div>
|
||||||
|
{{ item.workflowNam }}
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<img :src="item.avatar" class="w-5 h-5 rounded-full mr-2" alt="" />
|
||||||
|
<span class="text-sm text-gray-500 truncate">{{ item.nickName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="loadingTrigger"
|
||||||
|
class="h-20 w-[1000px] text-center text-gray-500 flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div v-if="loading">加载中...</div>
|
||||||
|
<div v-if="finished && dataList.length >= 20">没有更多数据了</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Download, Play } from "lucide-vue-next";
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
const total = ref(0); // 总条数
|
||||||
|
const loadingTrigger = ref(null);
|
||||||
|
const observer = ref<IntersectionObserver | null>(null);
|
||||||
|
const props = defineProps({
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const listParams = ref({
|
||||||
|
...props.params,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
function initPageNUm() {
|
||||||
|
listParams.value.pageNumber = 1;
|
||||||
|
finished.value = false; // 重置加载完成状态
|
||||||
|
listParams.value = Object.assign({}, listParams.value, props.params);
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataList = ref([]);
|
||||||
|
async function getDataList() {
|
||||||
|
if (loading.value || finished.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await request.post("/WorkFlow/workFlowList", { ...listParams.value });
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 如果是第一页,直接赋值,否则追加数据
|
||||||
|
if (listParams.value.pageNumber === 1) {
|
||||||
|
dataList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
dataList.value = [...dataList.value, ...res.data.list];
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = res.data.total; // 假设接口返回了总条数
|
||||||
|
|
||||||
|
// 判断是否加载完所有数据
|
||||||
|
if (dataList.value.length >= total.value) {
|
||||||
|
finished.value = true;
|
||||||
|
}
|
||||||
|
// 自动增加页码
|
||||||
|
listParams.value.pageNumber++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
dataList.value = [];
|
||||||
|
finished.value = true;
|
||||||
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDataList();
|
||||||
|
|
||||||
|
// 跳转详情
|
||||||
|
function toDetail(item:any){
|
||||||
|
router.push(`/workflow-details/${item.id}`)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("scroll", topedRefresh);
|
||||||
|
observer.value = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !loading.value && !finished.value) {
|
||||||
|
getDataList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loadingTrigger.value) {
|
||||||
|
observer.value.observe(loadingTrigger.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("scroll", topedRefresh);
|
||||||
|
if (observer.value) {
|
||||||
|
observer.value.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function topedRefresh() {
|
||||||
|
if (import.meta.client) {
|
||||||
|
await nextTick();
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initPageNUm();
|
||||||
|
}
|
||||||
|
|
||||||
|
const workFlowCategoryList = ref([]);
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "work_flow_type_child" });
|
||||||
|
if (res.code === 200) {
|
||||||
|
workFlowCategoryList.value = res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initPageNUm,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in dataList"
|
||||||
|
:key="index"
|
||||||
|
@click="toDetail(item)"
|
||||||
|
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-base ellipsis w-[650px]">{{ item.content }}</div>
|
||||||
|
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.isRead === 0"
|
||||||
|
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
dataList: {
|
||||||
|
type: Object,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function toDetail(item){
|
||||||
|
try{
|
||||||
|
const res = await request.get(`/advice/read?adviceId=${item.id}`)
|
||||||
|
debugger
|
||||||
|
|
||||||
|
}catch(err){
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in dataList"
|
||||||
|
:key="index"
|
||||||
|
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-base ellipsis w-[600px]">{{ item.content }}</div>
|
||||||
|
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10">
|
||||||
|
<img class="w-full h-full rounded-lg" :src="item.userAvatar" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.isRead === 0"
|
||||||
|
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
dataList: {
|
||||||
|
type: Object,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="item in 15"
|
||||||
|
class="bg-white h-20 rounded-lg p-3 mb-2 flex flex-col justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<div class="text-base">你师傅说房东说短发女生的方式对佛呢</div>
|
||||||
|
<div class="text-[12px] text-gray-400 mt-1">2010-22-12 18:32:11</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in dataList" :key="index"
|
||||||
|
@click="toDetail(item)"
|
||||||
|
class="bg-white h-20 rounded-lg p-3 mb-2 flex items-center justify-center cursor-pointer relative"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-base ellipsis w-[600px]">{{ item.content }}</div>
|
||||||
|
<div class="text-[12px] text-gray-400 mt-1">{{ item.createTime }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-10 h-10 ">
|
||||||
|
<img class="w-full h-full rounded-lg" :src="item.userAvatar" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.isRead === 0"
|
||||||
|
class="w-2 h-2 rounded-full bg-[#ec7768] absolute top-2 right-2"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<n-empty v-if="dataList.length === 0" size="large" description="暂无数据!">
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataList: {
|
||||||
|
type: Object,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
async function toDetail(item){
|
||||||
|
try{
|
||||||
|
debugger
|
||||||
|
const res = await request.get(`/advice/read?adviceId=${item.id}`)
|
||||||
|
if(res.code === 200){
|
||||||
|
// 0模型 1工作流 2图片
|
||||||
|
if(item.productType === 0){
|
||||||
|
router.push(`/model-details/${item.id}`)
|
||||||
|
}else if(item.productType === 1){
|
||||||
|
router.push(`/workflow-details/${item.id}`)
|
||||||
|
}else{
|
||||||
|
// onEditPicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(err){
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ellipsis{
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,285 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from '@/api/common';
|
||||||
|
import type { FormInst } from 'naive-ui';
|
||||||
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue', 'createModelsNext'])
|
||||||
|
const localForm = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const options: any[] = [
|
||||||
|
|
||||||
|
]
|
||||||
|
watch(
|
||||||
|
() => localForm.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
const formRef = ref<FormInst | null>(null)
|
||||||
|
const rules = {
|
||||||
|
'modelProduct.modelName': {
|
||||||
|
required: true,
|
||||||
|
message: '',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
'modelProduct.modelType': {
|
||||||
|
required: true,
|
||||||
|
message: '',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelChildCategory = ref([]) // 垂类二级
|
||||||
|
const model_category = ref([]) // 模型类型
|
||||||
|
const categoryList = ref([])
|
||||||
|
const work_flow_functions = ref([])
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const [res1, res2, res3, res4] = await Promise.all([
|
||||||
|
commonApi.dictType({ type: 'model_part_category' }),
|
||||||
|
commonApi.dictType({ type: 'model_child_category' }),
|
||||||
|
commonApi.dictType({ type: 'model_category' }),
|
||||||
|
commonApi.dictType({ type: 'work_flow_functions' }),
|
||||||
|
])
|
||||||
|
|
||||||
|
// modelPartCategory.value = res1.data
|
||||||
|
modelChildCategory.value = res2.data
|
||||||
|
model_category.value = res3.data
|
||||||
|
work_flow_functions.value = res4.data
|
||||||
|
categoryList.value = res1.data
|
||||||
|
// 遍历第一个数据数组,检查是否有子项
|
||||||
|
categoryList.value.forEach((item: any) => {
|
||||||
|
// 给每个对象添加一个children属性,初始化为空数组
|
||||||
|
item.children = []
|
||||||
|
// 遍历第二个数据数组
|
||||||
|
modelChildCategory.value.forEach((child) => {
|
||||||
|
// 检查parentId是否与dictValue相等
|
||||||
|
if (child.partId === item.dictCode) {
|
||||||
|
// 将符合条件的子项放入children数组中
|
||||||
|
item.children.push(child)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType()
|
||||||
|
|
||||||
|
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 originalBtnList = ref([
|
||||||
|
{
|
||||||
|
label: '原创',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '转载',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
function nextStep() {
|
||||||
|
formRef.value?.validate((errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
emit('createModelsNext')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('error', errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleIsOriginal(value: number) {
|
||||||
|
localForm.value.modelProduct.isOriginal = value
|
||||||
|
// if (value === 0) {
|
||||||
|
// localForm.value.modelProduct.originalAuthorName = ''
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
function handleCategoryUpdateValue(value) {
|
||||||
|
// 如果需要,可以在这里处理值的格式
|
||||||
|
// if (value) {
|
||||||
|
// // 确保值的格式正确
|
||||||
|
// localForm.value.modelProduct.category = value.includes('-') ? value : `${value}-0`
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
function changeModelType(item){
|
||||||
|
debugger
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="bg-gray-100 p-4 rounded-lg">
|
||||||
|
<n-form
|
||||||
|
ref="formRef"
|
||||||
|
:label-width="80"
|
||||||
|
:model="localForm"
|
||||||
|
:rules="rules"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<n-form-item label="模型名称" path="modelProduct.modelName">
|
||||||
|
<n-input v-model:value="localForm.modelProduct.modelName" placeholder="输入模型名" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="模型类型" path="modelProduct.modelType">
|
||||||
|
<n-select
|
||||||
|
v-model:value="localForm.modelProduct.modelType"
|
||||||
|
label-field="dictLabel"
|
||||||
|
@update:value="changeModelType"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择模型类型"
|
||||||
|
:options="model_category"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<div>
|
||||||
|
内容类别
|
||||||
|
</div>
|
||||||
|
<div class="-mb-5 text-gray-400 text-[12px]">
|
||||||
|
填写类别可让模型获得更精准的流量, 平台也有权基于标准修改你的类别标签
|
||||||
|
</div>
|
||||||
|
<n-form-item path="category">
|
||||||
|
<n-cascader
|
||||||
|
v-model:value="localForm.modelProduct.category"
|
||||||
|
placeholder="垂类"
|
||||||
|
:options="categoryList"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
check-strategy="child"
|
||||||
|
@update:value="handleCategoryUpdateValue"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item path="functions" class="-mt-12" v-if="localForm.modelProduct.modelType !== '0'">
|
||||||
|
<n-select
|
||||||
|
v-model:value="localForm.modelProduct.functions"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="功能"
|
||||||
|
:options="work_flow_functions"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
标签
|
||||||
|
</div>
|
||||||
|
<div class="-mb-5 text-gray-400 text-[12px]">
|
||||||
|
添加标签将自动推荐给可能感兴趣的人
|
||||||
|
</div>
|
||||||
|
<n-form-item path="category">
|
||||||
|
<!-- <n-select
|
||||||
|
v-model:value="localForm.modelProduct.tags"
|
||||||
|
:options="options"
|
||||||
|
filterable
|
||||||
|
tag
|
||||||
|
multiple
|
||||||
|
placeholder="请选择或输入标签"
|
||||||
|
@update:value="handleUpdate"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
v-model:value="localForm.modelProduct.tagsList"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
tag
|
||||||
|
placeholder="输入,按回车确认"
|
||||||
|
:show-arrow="false"
|
||||||
|
:show="false"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<div>
|
||||||
|
参与活动
|
||||||
|
</div>
|
||||||
|
<div class="-mb-5 text-gray-400 text-[12px]">
|
||||||
|
参与特定活动或比赛,下拉选择
|
||||||
|
</div>
|
||||||
|
<n-form-item path="activityId">
|
||||||
|
<n-select
|
||||||
|
v-model:value="localForm.modelProduct.activityId"
|
||||||
|
label-field="activityName"
|
||||||
|
value-field="id"
|
||||||
|
placeholder="请选择参与哪个活动"
|
||||||
|
:options="activityList"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<div>
|
||||||
|
原创内容
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center mt-5 bg-white h-12 rounded-lg border border-gray-100">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in originalBtnList"
|
||||||
|
:key="index"
|
||||||
|
:style="{
|
||||||
|
backgroundColor:
|
||||||
|
localForm.modelProduct.isOriginal === item.value
|
||||||
|
? 'rgba(49, 98, 255, 0.1)'
|
||||||
|
: '#fff',
|
||||||
|
color: localForm.modelProduct.isOriginal === item.value ? '#3162ff' : '#000',
|
||||||
|
}" class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer" @click="handleIsOriginal(item.value)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="localForm.modelProduct.isOriginal === 1" class="text-[12px]">
|
||||||
|
<div class="my-3">
|
||||||
|
魔创未来原创模型加密保护计划
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-400">
|
||||||
|
原创模型加密保护计划是魔创未来推出的,为维护创作者权益、保障平台健康发展,通过技术手段遏制爬取、盗版、侵权等不法行为,保证创作者的劳动成果得到合理回报,进而激励更多优秀原创模型的诞生。
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-400">
|
||||||
|
谁能加入? 所有发布原创模型的作者,均可以加入本计划
|
||||||
|
</div>
|
||||||
|
<div class="my-3 text-gray-400">
|
||||||
|
加入后有何好处? 1.模型加密能力 2.流量扶持 3.创作激励
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-400">
|
||||||
|
详情查看 <a href="" class="text-[#3162ff] underline">魔创未来原创模型加密保护计划</a>
|
||||||
|
</div>
|
||||||
|
<div class="my-3 ">
|
||||||
|
原创声明
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-400">
|
||||||
|
本人声明并承诺模型是由本人原创,相关的权利和义务由本人承担。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<n-form-item path="modelProduct.originalAuthorName" size="large">
|
||||||
|
<n-input v-model:value="localForm.modelProduct.originalAuthorName" placeholder="输入原创作者" />
|
||||||
|
</n-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full justify-center">
|
||||||
|
<div class="flex justify-center items-center mt-5 text-white w-[200px] h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="nextStep">
|
||||||
|
下一步
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,433 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from "@/api/common";
|
||||||
|
import { uploadImagesInBatches } from "@/utils/uploadImg.ts";
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import { Asterisk, Trash } from "lucide-vue-next";
|
||||||
|
import type { FormInst } from "naive-ui";
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 可接受的文件类型
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "nextStep", "prevStep"]);
|
||||||
|
const acceptTypes =
|
||||||
|
".safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft";
|
||||||
|
const modelVersionItem = {
|
||||||
|
delFlag: '0', // 0代表存在 2代表删除
|
||||||
|
versionName: "", // 版本名称
|
||||||
|
modelVersionType: null, // 基础模型
|
||||||
|
versionDescription: "", // 版本描述
|
||||||
|
filePath: "", // 文件路径
|
||||||
|
fileName: "", //
|
||||||
|
sampleImagePaths:[], // 第三部的图片路径最多20张,切割
|
||||||
|
triggerWords: "", // 触发词
|
||||||
|
isPublic: 1, // 权限是否公
|
||||||
|
isOnlineUse:1, //在线使用
|
||||||
|
allowFusion: 1, // 是否允许融合
|
||||||
|
isFree: 0, // 0免费
|
||||||
|
allowDownloadImage: 1, // 允许下载生图
|
||||||
|
allowSoftwareUse: 1, // 允许在软件旗下使用
|
||||||
|
allowCommercialUse: 1, // 是否允许商用
|
||||||
|
allowUsage: 1, // 允许模型转售或者融合出售
|
||||||
|
isExclusiveModel: 1, // 是否为独家模型这个字段
|
||||||
|
hideImageGenInfo:0, //隐藏图片生成信息
|
||||||
|
|
||||||
|
};
|
||||||
|
const isPublicList = [
|
||||||
|
{
|
||||||
|
label: "公开",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "仅自己可见",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
defineExpose({
|
||||||
|
addVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const formRefs = ref<(FormInst | null)[]>([]);
|
||||||
|
function setFormRef(el: FormInst | null, index: number) {
|
||||||
|
if (el) {
|
||||||
|
formRefs.value[index] = el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rules = {
|
||||||
|
versionName: {
|
||||||
|
required: true,
|
||||||
|
message: "",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
modelVersionType: {
|
||||||
|
required: true,
|
||||||
|
message: "",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function addVersion() {
|
||||||
|
const param = cloneDeep(modelVersionItem)
|
||||||
|
localForm.value.modelVersionList.unshift(param);
|
||||||
|
}
|
||||||
|
const originalBtnList = ref([
|
||||||
|
{
|
||||||
|
label: "免费",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "会员下载",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
function handleIsFree(index: number, value: number) {
|
||||||
|
localForm.value.modelVersionList[index].isFree = value;
|
||||||
|
}
|
||||||
|
async function nextStep() {
|
||||||
|
for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
|
||||||
|
if (
|
||||||
|
localForm.value.modelVersionList[i].delFlag === "0" &&
|
||||||
|
localForm.value.modelVersionList[i].fileName === ""
|
||||||
|
) {
|
||||||
|
return message.error("请上传文件");
|
||||||
|
}
|
||||||
|
const regex = /[\u4E00-\u9FA5]/; // 匹配汉字的正则表达式
|
||||||
|
if (
|
||||||
|
localForm.value.modelVersionList[i].delFlag === "0" &&
|
||||||
|
!regex.test(localForm.value.modelVersionList[i].versionDescription)
|
||||||
|
) {
|
||||||
|
return message.error("请用中文填写版本介绍");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const promises = formRefs.value
|
||||||
|
.filter((form): form is FormInst => form !== null)
|
||||||
|
.map((form) => form.validate());
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
emit("nextStep");
|
||||||
|
} catch (errors) {
|
||||||
|
console.error("部分表单验证失败:", errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
const uploadFileIndex = ref(0);
|
||||||
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
|
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 uploadImagesInBatches(files);
|
||||||
|
localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url;
|
||||||
|
localForm.value.modelVersionList[uploadFileIndex.value].fileName = res[0].fileName;
|
||||||
|
}
|
||||||
|
target.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevStep() {
|
||||||
|
emit("prevStep");
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseModelTypeList = ref([]);
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "mode_type" });
|
||||||
|
if (res.code === 200) {
|
||||||
|
baseModelTypeList.value = res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType();
|
||||||
|
|
||||||
|
function computedDelFlag() {
|
||||||
|
return localForm.value.modelVersionList.filter(item => item.delFlag === '0')
|
||||||
|
}
|
||||||
|
function onDelete(index: number) {
|
||||||
|
if (computedDelFlag().length === 1)
|
||||||
|
return
|
||||||
|
localForm.value.modelVersionList[index].delFlag = '2'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<template
|
||||||
|
v-for="(item, index) in localForm.modelVersionList"
|
||||||
|
:key="index"
|
||||||
|
class="bg-gray-100 p-4 mt-4 rounded-lg"
|
||||||
|
>
|
||||||
|
<div v-if="item.delFlag === '0'" class="bg-gray-100 p-4 rounded-lg mt-4 relative">
|
||||||
|
<div class="absolute -right-10 top-4 cursor-pointer">
|
||||||
|
<Trash class="cursor-pointer" @click="onDelete(index)" />
|
||||||
|
</div>
|
||||||
|
<n-form
|
||||||
|
:ref="(el) => setFormRef(el, index)"
|
||||||
|
:label-width="80"
|
||||||
|
:model="localForm.modelVersionList[index]"
|
||||||
|
:rules="rules"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<n-form-item label="版本名称" path="versionName">
|
||||||
|
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="基础模型" path="modelVersionType">
|
||||||
|
<n-select
|
||||||
|
v-model:value="item.modelVersionType"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择基础模型"
|
||||||
|
:options="baseModelTypeList"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<div class="flex">
|
||||||
|
上传文件 <Asterisk :size="10" color="#ff0000" class="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
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">
|
||||||
|
.safetensors/.ckpt/.pt/.bin/.pth/.zip/.json/.flow/.lightflow/.yaml/.yml/.onnx/.gguf/.sft
|
||||||
|
</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.versionDescription" />
|
||||||
|
</client-only>
|
||||||
|
</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 v-if="localForm.modelProduct.modelType === '0'">
|
||||||
|
<n-form-item label="采样方法" path="modelVersionType">
|
||||||
|
<n-select
|
||||||
|
v-model:value="item.modelVersionType"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择采样方法"
|
||||||
|
:options="baseModelTypeList"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<div>
|
||||||
|
<n-select v-model:value="item.modelVersionType" multiple :options="baseModelTypeList" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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="text-gray-400 text-[12px] my-4">许可范围</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.isOnlineUse"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
disabled="true"
|
||||||
|
label="允许在魔创未来在线使用"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.allowDownloadImage"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
label="允许下载生图"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.allowSoftwareUse"
|
||||||
|
:checked-value="1"
|
||||||
|
disabled="true"
|
||||||
|
:unchecked-value="0"
|
||||||
|
label="允许在魔创未来旗下其他产品在线使用"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.allowFusion"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
label="允许进行融合"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-gray-400 text-[12px] my-4">商用许可范围</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.allowCommercialUse"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
label="生成图片可出售或用于商业目的"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-[50%] mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.allowUsage"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
label="允许模型转售或融合后出售"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="text-gray-400 text-[12px] my-4">独家设置</div>
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.isExclusiveModel"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
/>
|
||||||
|
<div class="ml-3 text-[12px] text-gray-500">
|
||||||
|
此版本为魔创未来独家模型 *获取更多流量:独家模型规则
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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="prevStep"
|
||||||
|
>
|
||||||
|
上一步
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer"
|
||||||
|
@click="nextStep"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
|
@ -0,0 +1,197 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import { Asterisk } from 'lucide-vue-next';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue', 'preStep'])
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const { type } = route.query
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const localForm = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
function preStep() {
|
||||||
|
emit('preStep')
|
||||||
|
}
|
||||||
|
const showSuccessModal = ref(false)
|
||||||
|
|
||||||
|
async function handlePublish() {
|
||||||
|
for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
|
||||||
|
if (localForm.value.modelVersionList[i].delFlag === '0' && localForm.value.modelVersionList[i].sampleImagePaths.length === 0) {
|
||||||
|
return message.error('请上传图片')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const param = cloneDeep(localForm.value)
|
||||||
|
if (param.modelProduct.tagsList.length !== 0) {
|
||||||
|
param.modelProduct.tags = JSON.stringify(param.modelProduct.tagsList)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
param.modelProduct.type = '[]'
|
||||||
|
}
|
||||||
|
for (let i = 0; i < param.modelVersionList.length; i++) {
|
||||||
|
if (param.modelVersionList[i].sampleImagePaths.length !== 0) {
|
||||||
|
param.modelVersionList[i].sampleImagePaths = param.modelVersionList[i].sampleImagePaths.join(',')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
param.modelVersionList[i].sampleImagePaths = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'add') {
|
||||||
|
try {
|
||||||
|
const res = await request.post('/model/insert', param)
|
||||||
|
if (res.code === 200) {
|
||||||
|
showSuccessModal.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
const res = await request.post('/model/update', param)
|
||||||
|
if (res.code === 200) {
|
||||||
|
showSuccessModal.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => localForm.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
const fileInput = ref<HTMLInputElement | null>(null)
|
||||||
|
const uploadFileIndex = ref(0)
|
||||||
|
|
||||||
|
function triggerFileInput(index: number) {
|
||||||
|
(fileInput.value as HTMLInputElement)?.click()
|
||||||
|
uploadFileIndex.value = index
|
||||||
|
}
|
||||||
|
async function handleFileChange(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const files = target.files
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
const sum = localForm.value.modelVersionList[uploadFileIndex.value].sampleImagePaths.length + files.length
|
||||||
|
if (sum >= 20)
|
||||||
|
return message.error('最多20张')
|
||||||
|
const res = await uploadImagesInBatches(files)
|
||||||
|
const urlList = res.map(item => item.url)
|
||||||
|
localForm.value.modelVersionList[uploadFileIndex.value].sampleImagePaths.push(...urlList)
|
||||||
|
}
|
||||||
|
target.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPositiveClick() {
|
||||||
|
showSuccessModal.value = false
|
||||||
|
router.push('/personal-center')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto mt-10">
|
||||||
|
<div class="w-[700px] mx-auto items-start rounded-lg">
|
||||||
|
<template v-for="(item, index) in localForm.modelVersionList" :key="index">
|
||||||
|
<div v-if="item.delFlag === '0'" class="w-full bg-gray-100 p-4 rounded-lg mt-2">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
版本名: <span>{{ item.versionName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-checkbox
|
||||||
|
v-model:checked="item.hideImageGenInfo"
|
||||||
|
:checked-value="1" :unchecked-value="0"
|
||||||
|
/>
|
||||||
|
隐藏图片生成信息
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center mt-4">
|
||||||
|
<div class="flex">
|
||||||
|
添加版本示例图片
|
||||||
|
<Asterisk :size="10" color="#ff0000" class="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div class="text-[12px] text-gray-400">
|
||||||
|
最多20张图片, 图片不超过30M
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white">
|
||||||
|
<div class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center" @click="triggerFileInput(index)">
|
||||||
|
上传文件
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
点击上传文件
|
||||||
|
</div>
|
||||||
|
<div class="text-[#999999] text-xs">
|
||||||
|
请勿上传裸露、暴力、血腥或其他包含非法信息图片
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-2.5 mt-4 w-full">
|
||||||
|
<div v-for="(subItem, subIndex) in item.sampleImagePaths" :key="subIndex">
|
||||||
|
<img class="w-full h-[200px] object-cover rounded-lg" :src="subItem" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center mt-5">
|
||||||
|
<div class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer" @click="preStep">
|
||||||
|
上一步
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer" @click="handlePublish">
|
||||||
|
发布
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref="fileInput"
|
||||||
|
type="file"
|
||||||
|
class="hidden"
|
||||||
|
accept="image/*"
|
||||||
|
multiple
|
||||||
|
@change="handleFileChange"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- 成功之后的弹框 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showSuccessModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
preset="dialog"
|
||||||
|
title="发布成功!"
|
||||||
|
content="工作流发布成功, 可至个人中心查看状态"
|
||||||
|
positive-text="确认"
|
||||||
|
@positive-click="onPositiveClick"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,222 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { uploadFileBatches } from '@/utils/uploadImg.ts';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import { Asterisk, Trash } from 'lucide-vue-next';
|
||||||
|
import type { FormInst } from 'naive-ui';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
// 可接受的文件类型
|
||||||
|
const props = defineProps({
|
||||||
|
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() {
|
||||||
|
const param = cloneDeep(modelVersionItem)
|
||||||
|
localForm.value.workFlowVersionList.unshift(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
|
@ -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) => {
|
||||||
|
// 检查parentId是否与dictValue相等
|
||||||
|
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>
|
|
@ -0,0 +1,201 @@
|
||||||
|
<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 = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(param.modelProduct.workFlow === 0){
|
||||||
|
param.modelProduct.originalAuthorName = ''
|
||||||
|
}
|
||||||
|
// 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>
|
|
@ -1,2 +1,22 @@
|
||||||
|
|
||||||
export const appName = '魔创未来'
|
export const appName = '魔创未来'
|
||||||
export const appDescription = '魔创未来'
|
export const appDescription = '魔创未来'
|
||||||
|
export const authRoutes = ['/personal-center', '/publish-model', '/publish-workflow']
|
||||||
|
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','/picture-square', '/work-square'],
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
Bell, Binary, Code2, Crown, GraduationCap, Image, LayoutGrid,
|
Binary,
|
||||||
Lightbulb, Maximize, Monitor, Plus, Search, User, Workflow
|
Code2,
|
||||||
|
Crown,
|
||||||
|
Image,
|
||||||
|
LayoutGrid,
|
||||||
|
Lightbulb,
|
||||||
|
Maximize,
|
||||||
|
User,
|
||||||
|
Workflow
|
||||||
} from 'lucide-vue-next';
|
} from 'lucide-vue-next';
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
const isClient = ref(false)
|
const menuStore = useMenuStore()
|
||||||
const modalStore = useModalStore()
|
|
||||||
// import ../assets/img/default-avatar.png
|
|
||||||
import defaultAvatar from '../assets/img/default-avatar.png'
|
|
||||||
// 路径到图标的映射
|
// 路径到图标的映射
|
||||||
const iconMap: any = {
|
const iconMap: any = {
|
||||||
'/model-square': LayoutGrid,
|
'/model-square': LayoutGrid,
|
||||||
'/works-inspire': Lightbulb,
|
'/picture-square':Lightbulb,
|
||||||
'/workspace': Lightbulb,
|
'/work-square':Workflow,
|
||||||
'/web-ui': Image,
|
'/web-ui': Image,
|
||||||
'/comfy-ui': Workflow,
|
'/comfy-ui': Workflow,
|
||||||
'/training-lora': Binary,
|
'/training-lora': Binary,
|
||||||
|
@ -20,187 +25,71 @@ const iconMap: any = {
|
||||||
'/api-platform': Code2,
|
'/api-platform': Code2,
|
||||||
'/creator-center': Code2,
|
'/creator-center': Code2,
|
||||||
'/personal-center': User,
|
'/personal-center': User,
|
||||||
'/member-center': Crown
|
'/member-center': Crown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
import { useMenuStore } from '~/stores/menu';
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const menuStore = useMenuStore()
|
|
||||||
|
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
// 监听路由变化
|
// 监听路由变化
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
(path) => {
|
(path) => {
|
||||||
menuStore.setActiveMenu(path)
|
menuStore.setActiveMenu(path)
|
||||||
},
|
},
|
||||||
{ immediate: true } // 这样一进入页面就会执行一次
|
{ immediate: true }, // 这样一进入页面就会执行一次
|
||||||
)
|
)
|
||||||
onMounted(() => {
|
|
||||||
isClient.value = true
|
|
||||||
})
|
|
||||||
// 更新菜单项中的图标
|
// 更新菜单项中的图标
|
||||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
LucideIcon: iconMap[item.path], // 添加 Lucide 图标组件
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 消息通知下拉选项
|
function handleSide(event: Event, path: string) {
|
||||||
const notificationOptions = [
|
if (path === '/member-center') {
|
||||||
{
|
event.preventDefault() // 阻止默认行为
|
||||||
label: '系统通知',
|
event.stopPropagation() // 阻止事件冒泡
|
||||||
key: 'system'
|
const baseUrl = window.location.origin
|
||||||
},
|
window.open(`${baseUrl}/member-center`, '_blank', 'noopener,noreferrer')
|
||||||
{
|
// 确保当前路由不变
|
||||||
label: '互动消息',
|
// nextTick(() => {
|
||||||
key: 'interaction'
|
// debugger
|
||||||
|
// if (route.path !== window.location.pathname) {
|
||||||
|
// navigateTo(route.path, { replace: true })
|
||||||
|
// }
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
]
|
else {
|
||||||
|
menuStore.setActiveMenu(path)
|
||||||
// 用户下拉选项
|
|
||||||
const userOptions = ref([
|
|
||||||
{
|
|
||||||
label: '我的模型',
|
|
||||||
key: 'model'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '我的作品',
|
|
||||||
key: 'project'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '我的点赞',
|
|
||||||
key: 'like'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '账号设置',
|
|
||||||
key: 'userSettings'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '退出登录',
|
|
||||||
key: 'logout'
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
|
||||||
const handleUserSelect = async(key: string) => {
|
|
||||||
if (key === 'logout') {
|
|
||||||
try {
|
|
||||||
await request.post('/logout')
|
|
||||||
userStore.logout()
|
|
||||||
window.location.href = '/'
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout failed:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
|
||||||
modalStore.showLoginModal()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="sticky top-0 z-50 flex h-14 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80">
|
<Header />
|
||||||
<div class="flex items-center gap-6">
|
|
||||||
<!-- Logo 区域调整 -->
|
|
||||||
<div class="flex min-w-[220px] items-center gap-3 pr-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<!-- <img src="/vite.png" alt="Logo" class="h-9 w-9" /> -->
|
|
||||||
<span class="text-xl font-semibold tracking-tight">魔创未来</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search -->
|
|
||||||
<NInput
|
|
||||||
round
|
|
||||||
clearable
|
|
||||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
|
||||||
class="w-[480px]"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<Search class="h-4 w-4 text-gray-400" />
|
|
||||||
</template>
|
|
||||||
</NInput>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right Actions -->
|
|
||||||
<NSpace align="center" :size="24">
|
|
||||||
<!-- PC Client -->
|
|
||||||
<NButton text class="flex items-center gap-1.5">
|
|
||||||
<Monitor class="h-4 w-4" />
|
|
||||||
<span>PC客户端</span>
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<!-- Tutorials -->
|
|
||||||
<NButton text class="flex items-center gap-1.5">
|
|
||||||
<GraduationCap class="h-4 w-4" />
|
|
||||||
<span>教程专区</span>
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<!-- VIP -->
|
|
||||||
<NuxtLink to="/member-center" target="_blank" class="inline-block">
|
|
||||||
<NButton text type="warning" class="flex items-center gap-1.5">
|
|
||||||
<Crown class="h-4 w-4" />
|
|
||||||
<span>会员中心</span>
|
|
||||||
</NButton>
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<!-- Create -->
|
|
||||||
<NButton type="primary" round class="flex items-center gap-1.5">
|
|
||||||
<Plus class="h-4 w-4" />
|
|
||||||
<span>发布</span>
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<!-- Notifications -->
|
|
||||||
<NDropdown :options="notificationOptions" trigger="click">
|
|
||||||
<NBadge :value="5" :max="99" processing>
|
|
||||||
<NButton text circle>
|
|
||||||
<Bell class="h-5 w-5" />
|
|
||||||
</NButton>
|
|
||||||
</NBadge>
|
|
||||||
</NDropdown>
|
|
||||||
|
|
||||||
<!-- User -->
|
|
||||||
<NDropdown v-if="isClient && userStore.token" :options="userOptions" :on-select="handleUserSelect" trigger="hover" >
|
|
||||||
<NAvatar
|
|
||||||
class="cursor-pointer w-10 h-10"
|
|
||||||
round
|
|
||||||
size="small"
|
|
||||||
:src="userStore.userInfo && userStore.userInfo.avatar ? userStore.userInfo.avatar : defaultAvatar"
|
|
||||||
/>
|
|
||||||
</NDropdown>
|
|
||||||
<div v-else>
|
|
||||||
<div @click="handleLogin" class="text-white bg-[#197dff] rounded-[4px] px-4 py-2 text-xs cursor-pointer hover:bg-[#1a6eff]">登录/注册</div>
|
|
||||||
</div>
|
|
||||||
</NSpace>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="flex flex-1 overflow-hidden">
|
<div class="flex flex-1 overflow-hidden">
|
||||||
<!-- Sidebar -->
|
<!-- 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">
|
<div class="space-y-1 px-2">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-for="item in menuStore.menuItems"
|
v-for="item in menuStore.menuItems"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
:to="item.path"
|
:to="item.path"
|
||||||
class="flex items-center gap-3 rounded-lg px-4 py-2.5 text-[15px] font-medium no-underline transition-colors"
|
class="flex items-center gap-3 rounded-lg px-4 py-4 text-[15px] font-medium no-underline transition-colors"
|
||||||
:class="[
|
:class="[
|
||||||
menuStore.activeMenu === item.path
|
menuStore.activeMenu === item.path
|
||||||
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'
|
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'
|
||||||
: 'text-gray-600 hover:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-dark-700/50'
|
: 'text-gray-600 hover:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-dark-700/50',
|
||||||
]"
|
]"
|
||||||
@click="menuStore.setActiveMenu(item.path)"
|
@click="(event: Event) => handleSide(event, item.path)"
|
||||||
>
|
>
|
||||||
<!-- 只显示 Lucide 图标 -->
|
<!-- 只显示 Lucide 图标 -->
|
||||||
<component
|
<component
|
||||||
:is="item.LucideIcon"
|
:is="item.LucideIcon"
|
||||||
class="h-[18px] w-[18px]"
|
class="h-[14px] w-[14px]"
|
||||||
:class="menuStore.activeMenu === item.path
|
:class="menuStore.activeMenu === item.path
|
||||||
? 'text-blue-600 dark:text-blue-400'
|
? 'text-blue-600 dark:text-blue-400'
|
||||||
: 'text-gray-500 dark:text-gray-400'"
|
: 'text-gray-500 dark:text-gray-400'"
|
||||||
|
@ -212,15 +101,13 @@ const handleLogin = () => {
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Page Content -->
|
<!-- Page Content -->
|
||||||
<main class="flex-1 overflow-auto p-6">
|
<main class="flex-1 overflow-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 登录框组件 -->
|
<!-- 登录框组件 -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,232 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
|
||||||
Bell, Binary, Code2, Crown, GraduationCap, Image, LayoutGrid,
|
|
||||||
Lightbulb, Maximize, Monitor, Plus, Search, User, Workflow
|
|
||||||
} from 'lucide-vue-next';
|
|
||||||
import {
|
|
||||||
NAvatar,
|
|
||||||
NBadge, NButton, NDropdown, NInput, NSpace
|
|
||||||
} from 'naive-ui';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const userStore = useUserStore()
|
|
||||||
userStore.checkLoginStatus()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 路径到图标的映射
|
|
||||||
const iconMap: any = {
|
|
||||||
'/model-square': LayoutGrid,
|
|
||||||
'/works-inspire': Lightbulb,
|
|
||||||
'/workspace': Lightbulb,
|
|
||||||
'/web-ui': Image,
|
|
||||||
'/comfy-ui': Workflow,
|
|
||||||
'/training-lora': Binary,
|
|
||||||
'/high-availability': Maximize,
|
|
||||||
'/api-platform': Code2,
|
|
||||||
'/creator-center': Code2,
|
|
||||||
'/personal-center': User,
|
|
||||||
'/member-center': Crown
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
import { useMenuStore } from '~/stores/menu';
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const menuStore = useMenuStore()
|
|
||||||
|
|
||||||
// 监听路由变化
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
(path) => {
|
|
||||||
menuStore.setActiveMenu(path)
|
|
||||||
},
|
|
||||||
{ immediate: true } // 这样一进入页面就会执行一次
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 更新菜单项中的图标
|
|
||||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
|
||||||
...item,
|
|
||||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 消息通知下拉选项
|
|
||||||
const notificationOptions = [
|
|
||||||
{
|
|
||||||
label: '系统通知',
|
|
||||||
key: 'system'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '互动消息',
|
|
||||||
key: 'interaction'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const publisOptions = [
|
|
||||||
{
|
|
||||||
label: '模型',
|
|
||||||
key: 'model'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '图片',
|
|
||||||
key: 'picture'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label:'工作流',
|
|
||||||
key:'workFlow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
// 用户下拉选项
|
|
||||||
const userOptions = [
|
|
||||||
{
|
|
||||||
label: '个人中心',
|
|
||||||
key: 'profile'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '创作中心',
|
|
||||||
key: 'creator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'divider',
|
|
||||||
key: 'd1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '退出登录',
|
|
||||||
key: 'logout'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
<div>
|
||||||
<!-- Header -->
|
<Header />
|
||||||
<header class="sticky top-0 z-50 flex h-14 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80">
|
<main class="flex-1 overflow-auto">
|
||||||
<div class="flex items-center gap-6">
|
|
||||||
<!-- Logo 区域调整 -->
|
|
||||||
<div class="flex min-w-[220px] items-center gap-3 pr-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<img src="/vite.png" alt="Logo" class="h-9 w-9" />
|
|
||||||
<span class="text-xl font-semibold tracking-tight">LibLib AI</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search -->
|
|
||||||
<NInput
|
|
||||||
round
|
|
||||||
clearable
|
|
||||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
|
||||||
class="w-[480px]"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<Search class="h-4 w-4 text-gray-400" />
|
|
||||||
</template>
|
|
||||||
</NInput>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right Actions -->
|
|
||||||
<NSpace align="center" :size="24">
|
|
||||||
<!-- PC Client -->
|
|
||||||
<NButton text class="flex items-center gap-1.5">
|
|
||||||
<Monitor class="h-4 w-4" />
|
|
||||||
<span>PC客户端</span>
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<!-- Tutorials -->
|
|
||||||
<NButton text class="flex items-center gap-1.5">
|
|
||||||
<GraduationCap class="h-4 w-4" />
|
|
||||||
<span>教程专区</span>
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<!-- VIP -->
|
|
||||||
<NuxtLink to="/member-center" target="_blank" class="inline-block">
|
|
||||||
<NButton text type="warning" class="flex items-center gap-1.5">
|
|
||||||
<Crown class="h-4 w-4" />
|
|
||||||
<span>会员中心</span>
|
|
||||||
</NButton>
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<!-- Create -->
|
|
||||||
<NDropdown :options="publisOptions" trigger="click">
|
|
||||||
<NButton class="flex items-center gap-1.5">
|
|
||||||
<Plus class="h-4 w-4" />
|
|
||||||
<span>发布</span>
|
|
||||||
</NButton>
|
|
||||||
</NDropdown>
|
|
||||||
<!-- Notifications -->
|
|
||||||
<NDropdown :options="notificationOptions" trigger="click">
|
|
||||||
<NBadge :value="5" :max="99" processing>
|
|
||||||
<NButton text circle>
|
|
||||||
<Bell class="h-5 w-5" />
|
|
||||||
</NButton>
|
|
||||||
</NBadge>
|
|
||||||
</NDropdown>
|
|
||||||
|
|
||||||
<!-- User -->
|
|
||||||
<NDropdown :options="userOptions" trigger="click">
|
|
||||||
<NAvatar
|
|
||||||
round
|
|
||||||
size="small"
|
|
||||||
src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
|
|
||||||
/>
|
|
||||||
</NDropdown>
|
|
||||||
</NSpace>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div class="flex flex-1 overflow-hidden">
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Page Content -->
|
|
||||||
<main class="flex-1 overflow-auto p-6">
|
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 登录框组件 -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
|
||||||
--primary: rgb(59, 130, 246);
|
|
||||||
--primary-hover: rgb(37, 99, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义滚动条 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: #e5e7eb;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #d1d5db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark ::-webkit-scrollbar-thumb {
|
|
||||||
background: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark ::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #4b5563;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -1,137 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
|
||||||
NInput,
|
|
||||||
NButton,
|
|
||||||
NSpace,
|
|
||||||
NDropdown,
|
|
||||||
NAvatar,
|
|
||||||
NBadge
|
|
||||||
} from 'naive-ui'
|
|
||||||
import {
|
|
||||||
Search,
|
|
||||||
Monitor,
|
|
||||||
GraduationCap,
|
|
||||||
Crown,
|
|
||||||
Plus,
|
|
||||||
Bell,
|
|
||||||
LayoutGrid,
|
|
||||||
Lightbulb,
|
|
||||||
Image,
|
|
||||||
Workflow,
|
|
||||||
Binary,
|
|
||||||
Maximize,
|
|
||||||
Code2,
|
|
||||||
PenTool,
|
|
||||||
User,
|
|
||||||
CreditCard
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
|
|
||||||
// 路径到图标的映射
|
|
||||||
const iconMap: any = {
|
|
||||||
'/model-square': LayoutGrid,
|
|
||||||
'/works-inspire': Lightbulb,
|
|
||||||
'/workspace': Lightbulb,
|
|
||||||
'/web-ui': Image,
|
|
||||||
'/comfy-ui': Workflow,
|
|
||||||
'/training-lora': Binary,
|
|
||||||
'/high-availability': Maximize,
|
|
||||||
'/api-platform': Code2,
|
|
||||||
'/creator-center': Code2,
|
|
||||||
'/personal-center': User,
|
|
||||||
'/member-center': Crown
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
import { useMenuStore } from '~/stores/menu'
|
|
||||||
|
|
||||||
const menuStore = useMenuStore()
|
|
||||||
|
|
||||||
// 更新菜单项中的图标
|
|
||||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
|
||||||
...item,
|
|
||||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 消息通知下拉选项
|
|
||||||
const notificationOptions = [
|
|
||||||
{
|
|
||||||
label: '系统通知',
|
|
||||||
key: 'system'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '互动消息',
|
|
||||||
key: 'interaction'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 用户下拉选项
|
|
||||||
const userOptions = [
|
|
||||||
{
|
|
||||||
label: '个人中心',
|
|
||||||
key: 'profile'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '创作中心',
|
|
||||||
key: 'creator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'divider',
|
|
||||||
key: 'd1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '退出登录',
|
|
||||||
key: 'logout'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
||||||
|
|
||||||
demo
|
|
||||||
<main class="flex-1 overflow-auto p-6">
|
<main class="flex-1 overflow-auto p-6">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
demo
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
|
||||||
--primary: rgb(59, 130, 246);
|
|
||||||
--primary-hover: rgb(37, 99, 235);
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义滚动条 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: #e5e7eb;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #d1d5db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark ::-webkit-scrollbar-thumb {
|
|
||||||
background: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark ::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #4b5563;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -1,5 +1,8 @@
|
||||||
// middleware/auth.ts
|
// middleware/auth.ts
|
||||||
|
import { authRoutes, verifyBlankRoute } from '@/constants/index'
|
||||||
|
// import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// const router = useRouter()
|
||||||
/**
|
/**
|
||||||
* 全局认证中间件
|
* 全局认证中间件
|
||||||
* 用于处理需要登录才能访问的路由
|
* 用于处理需要登录才能访问的路由
|
||||||
|
@ -8,21 +11,19 @@
|
||||||
* @param to - 目标路由对象
|
* @param to - 目标路由对象
|
||||||
* @returns void | NavigationResult - 如果需要重定向则返回导航结果
|
* @returns void | NavigationResult - 如果需要重定向则返回导航结果
|
||||||
*/
|
*/
|
||||||
export default defineNuxtRouteMiddleware((to) => {
|
export default defineNuxtRouteMiddleware((to, from) => {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const modalStore = useModalStore()
|
const modalStore = useModalStore()
|
||||||
|
const menuStore = useMenuStore()
|
||||||
// 需要登录权限的路由列表
|
if (verifyBlankRoute.includes(to.path) && !verifyBlankRoute.includes(from.path)) {
|
||||||
const authRoutes = [
|
return abortNavigation()
|
||||||
'/member-center',
|
}
|
||||||
// 可以继续添加其他需要登录的路由
|
|
||||||
]
|
|
||||||
|
|
||||||
// 如果是需要登录的路由,且用户未登录
|
// 如果是需要登录的路由,且用户未登录
|
||||||
if (authRoutes.includes(to.path) && !userStore.isLoggedIn) {
|
if (authRoutes.includes(to.path) && !userStore.isLoggedIn) {
|
||||||
// 显示登录模态框
|
// 显示登录模态框
|
||||||
modalStore.showLoginModal()
|
modalStore.showLoginModal()
|
||||||
// 重定向到模型广场页面
|
menuStore.setActiveMenu(from.path)
|
||||||
return navigateTo('/model-square')
|
// return abortNavigation()
|
||||||
|
// return navigateTo('/model-square')
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NInput, NDataTable, useMessage } from 'naive-ui'
|
import { NButton, NDataTable, NInput, useMessage } from 'naive-ui'
|
||||||
import { ref, onMounted } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import request from '~/utils/request'
|
import request from '~/utils/request'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
@ -34,23 +34,23 @@ const userData = ref<UserData[]>([])
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: 'id'
|
key: 'id',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '姓名',
|
||||||
key: 'name'
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '邮箱',
|
title: '邮箱',
|
||||||
key: 'email'
|
key: 'email',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
render: (row: UserData) => {
|
render: (row: UserData) => {
|
||||||
return row.status === 1 ? '激活' : '禁用'
|
return row.status === 1 ? '激活' : '禁用'
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
|
@ -59,19 +59,22 @@ async function fetchUserList() {
|
||||||
try {
|
try {
|
||||||
const response = await request.get<ApiResponse<UserData[]>>('/api/users', {
|
const response = await request.get<ApiResponse<UserData[]>>('/api/users', {
|
||||||
params: {
|
params: {
|
||||||
keyword: searchText.value
|
keyword: searchText.value,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
// userData.value = response.data
|
// userData.value = response.data
|
||||||
message.success('数据加载成功')
|
message.success('数据加载成功')
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message.error(response.message || '获取数据失败')
|
message.error(response.message || '获取数据失败')
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
}
|
||||||
|
catch (error: any) {
|
||||||
message.error(error.message || '请求出错')
|
message.error(error.message || '请求出错')
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,16 +90,18 @@ async function handleAddUser() {
|
||||||
const response = await request.post<ApiResponse<UserData>>('/api/users', {
|
const response = await request.post<ApiResponse<UserData>>('/api/users', {
|
||||||
name: '测试用户',
|
name: '测试用户',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
status: 1
|
status: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
message.success('添加成功')
|
message.success('添加成功')
|
||||||
fetchUserList() // 刷新列表
|
fetchUserList() // 刷新列表
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message.error(response.message || '添加失败')
|
message.error(response.message || '添加失败')
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
}
|
||||||
|
catch (error: any) {
|
||||||
message.error(error.message || '请求出错')
|
message.error(error.message || '请求出错')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,21 +115,21 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="mb-4 flex gap-4 items-center">
|
<div class="mb-4 flex gap-4 items-center">
|
||||||
<n-input
|
<NInput
|
||||||
v-model:value="searchText"
|
v-model:value="searchText"
|
||||||
placeholder="请输入搜索关键词"
|
placeholder="请输入搜索关键词"
|
||||||
class="w-64"
|
class="w-64"
|
||||||
@keyup.enter="handleSearch"
|
@keyup.enter="handleSearch"
|
||||||
/>
|
/>
|
||||||
<n-button type="primary" :loading="loading" @click="handleSearch">
|
<NButton type="primary" :loading="loading" @click="handleSearch">
|
||||||
搜索
|
搜索
|
||||||
</n-button>
|
</NButton>
|
||||||
<n-button type="info" @click="handleAddUser">
|
<NButton type="info" @click="handleAddUser">
|
||||||
添加用户
|
添加用户
|
||||||
</n-button>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-data-table
|
<NDataTable
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="userData"
|
:data="userData"
|
||||||
|
|
|
@ -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>
|
|
@ -32,11 +32,11 @@ const online = useOnline()
|
||||||
</template>
|
</template>
|
||||||
</ClientOnly> -->
|
</ClientOnly> -->
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<n-button type="info">测试按钮-3-3</n-button>
|
<NButton type="info">
|
||||||
<n-input placeholder="请输入" />
|
测试按钮-3-3
|
||||||
|
</NButton>
|
||||||
|
<NInput placeholder="请输入" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,55 +1,497 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMessage } from 'naive-ui';
|
import { CheckmarkCircleSharp, Close } from '@vicons/ionicons5'
|
||||||
|
import { NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'default',
|
layout: 'header',
|
||||||
})
|
})
|
||||||
|
|
||||||
const message = useMessage()
|
// 订单
|
||||||
|
const needPayment = ref([{
|
||||||
// 定义数据接口
|
amount: '',
|
||||||
interface UserData {
|
productId: '',
|
||||||
id: number
|
promotionId: '',
|
||||||
name: string
|
type: 'member',
|
||||||
email: string
|
}])
|
||||||
status: number
|
const userStore = useUserStore()
|
||||||
|
const userInfo = userStore.userInfo as UserInfoType
|
||||||
|
// 显示支付弹框
|
||||||
|
interface Payment {
|
||||||
|
isVisible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义响应接口
|
// 是否是会员
|
||||||
interface ApiResponse<T> {
|
const isMember = ref(false)
|
||||||
code: number
|
async function getIsMember() {
|
||||||
data: T
|
try {
|
||||||
message: string
|
const res = await request.get('/member/isMember')
|
||||||
|
if (res.code === 200) {
|
||||||
|
isMember.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIsMember()
|
||||||
|
|
||||||
|
// 获取会员等级及权益列表
|
||||||
|
const MemberBenefitList = ref([])
|
||||||
|
async function getMemberBenefitList() {
|
||||||
|
try {
|
||||||
|
const res = await request.get('/memberLevel/getMemberBenefitList')
|
||||||
|
if (res.code === 200) {
|
||||||
|
MemberBenefitList.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getMemberBenefitList()
|
||||||
|
|
||||||
|
// 获取会员等级列表
|
||||||
|
interface memberLevel {
|
||||||
|
memberName: string
|
||||||
|
unitPrice: number
|
||||||
|
originalPrice: number
|
||||||
|
subscriptionPeriod: number
|
||||||
|
}
|
||||||
|
const memberLevelList = ref<memberLevel[]>([])
|
||||||
|
async function getMemberLevelList() {
|
||||||
|
try {
|
||||||
|
const res = await request.get('/memberLevel/list')
|
||||||
|
if (res.code === 200) {
|
||||||
|
memberLevelList.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<div>
|
||||||
83475982345897234957420435365
|
<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 my-3">
|
||||||
|
<!-- 右上角订阅管理 -->
|
||||||
|
<div class="text-right">
|
||||||
|
<button class="text-[#8b8685] text-sm bg-inherit border-none cursor-pointer">
|
||||||
|
订阅管理 >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户头像和信息 -->
|
||||||
|
<div class="flex flex-col items-center mt-2">
|
||||||
|
<client-only>
|
||||||
|
<img :src="userInfo.avatar" alt="头像" class="w-20 h-20 rounded-full mb-3">
|
||||||
|
<span class="text-white text-sm mb-2">{{ userInfo.nickName }}</span>
|
||||||
|
</client-only>
|
||||||
|
<div class="flex items-center text-[#8b8685] text-xs">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-4 w-4 mr-1"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
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(1)"
|
||||||
|
>
|
||||||
|
开通会员
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 兑换会员链接 -->
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<button class="text-[#8b8685] text-sm bg-inherit border-none cursor-pointer">
|
||||||
|
兑换会员
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧内容 -->
|
||||||
|
<div class="flex-1 space-y-4">
|
||||||
|
<!-- 余额信息卡片 -->
|
||||||
|
<div class="bg-[#2b2421] rounded-lg p-6 mt-3">
|
||||||
|
<div class="grid grid-cols-3 gap-8">
|
||||||
|
<!-- 算力余额 -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-[#c3986b] mb-4">
|
||||||
|
算力余额
|
||||||
|
</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" @click="showIntPayment">
|
||||||
|
充值 >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="text-white bg-inherit border-none cursor-pointer p-0" @click="toIntDetail">
|
||||||
|
算力明细 >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 生图加速余额 -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-[#c3986b] mb-4">
|
||||||
|
生图加速余额
|
||||||
|
</h3>
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<span class="text-[#c3986b] mr-2">0次</span>
|
||||||
|
<button class="text-[#c3986b] bg-inherit border-none cursor-pointer">
|
||||||
|
充值 >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="text-white bg-inherit border-none cursor-pointer p-0">
|
||||||
|
加速明细 >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 训练加速余额 -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-[#c3986b] mb-4">
|
||||||
|
训练加速余额
|
||||||
|
</h3>
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<span class="text-[#c3986b]">0次</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 存储空间卡片 -->
|
||||||
|
<div class="bg-[#2b2421] rounded-lg p-6">
|
||||||
|
<div class="flex items-center gap-6 mb-6">
|
||||||
|
<span class="text-[#c3986b] text-lg">我的存储空间</span>
|
||||||
|
<button class="text-[#8b8685] bg-inherit border-none cursor-pointer p-0">
|
||||||
|
管理图库
|
||||||
|
</button>
|
||||||
|
<button class="text-[#8b8685] bg-inherit border-none cursor-pointer p-0">
|
||||||
|
管理训练
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="relative">
|
||||||
|
<div class="h-1 bg-[#3a322e] rounded-full">
|
||||||
|
<div class="absolute right-0 -top-6 text-[#8b8685] text-sm">
|
||||||
|
0G/3G
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图例 -->
|
||||||
|
<div class="flex gap-4 mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-2 h-2 bg-blue-500 rounded-full mr-2" />
|
||||||
|
<span class="text-[#8b8685] text-sm">生图</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-2 h-2 bg-purple-500 rounded-full mr-2" />
|
||||||
|
<span class="text-[#8b8685] text-sm">训练</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-gradient-to-b from-[#fdf6e3] to-[#ffffff] w-full flex justify-center py-8"
|
||||||
|
>
|
||||||
|
<div class="w-[1145px]">
|
||||||
|
<div class="grid grid-cols-5 gap-1">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in memberLevelList"
|
||||||
|
:key="index"
|
||||||
|
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 }}
|
||||||
|
</div>
|
||||||
|
<div class="card-item">
|
||||||
|
<span class="text-[#814600] text-[20px] mr-2">¥</span>
|
||||||
|
<span class="text-[#814600] text-[40px] mr-2">{{ item.unitPrice }}</span>
|
||||||
|
<span class="text-gray-400 text-[20px] line-through">¥{{ item.originalPrice }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-item text-[#e08909] w-7/10 text-center text-[12px]">
|
||||||
|
{{ item.subscriptionPeriod }}
|
||||||
|
</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" @click="showPayment(item)"
|
||||||
|
>
|
||||||
|
立即开通
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="py-8">
|
||||||
|
<div class="flex justify-center mb-4">
|
||||||
|
<div class="text-xl font-bold">
|
||||||
|
会员权益
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <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> -->
|
||||||
|
<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="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="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 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="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="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="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 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="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>
|
||||||
|
<div class="text-[#999] text-[12px]">
|
||||||
|
<div class="mt-1">
|
||||||
|
会员每月算力和加速特权按月下发,有效期31天,到期重置。会员模型下载次数上限为每月200次
|
||||||
|
</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
发票或团队/企业定制需求,请点击立即咨询联系我们,企业合作需求也可直接联系xxxx
|
||||||
|
</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
更多问题可见帮助中心
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.p-4 {
|
.card-item {
|
||||||
padding: 1rem;
|
@apply flex items-center justify-center h-1/4;
|
||||||
}
|
}
|
||||||
.mb-4 {
|
.member-item {
|
||||||
margin-bottom: 1rem;
|
@apply w-1/4 h-12 flex justify-center items-center;
|
||||||
}
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.gap-4 {
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.w-64 {
|
|
||||||
width: 16rem;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,272 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AttentionMsg from "@/components/message/AttentionMsg.vue";
|
||||||
|
import LikeMsg from "@/components/message/LikeMsg.vue";
|
||||||
|
import OfficialMsg from "@/components/message/OfficialMsg.vue";
|
||||||
|
import ReplyMsg from "@/components/message/ReplyMsg.vue";
|
||||||
|
import { Check, Heart, MessageSquareMore, UserPlus, Volume2 } from "lucide-vue-next";
|
||||||
|
|
||||||
|
const currentMsgType = ref("reply");
|
||||||
|
const MsgTypeList = ref([
|
||||||
|
// {
|
||||||
|
// label:'官方通知',
|
||||||
|
// value:'official'
|
||||||
|
// icon:'Volume2'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: "回复我的",
|
||||||
|
value: "reply",
|
||||||
|
icon: "MessageSquareMore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "收到的赞",
|
||||||
|
value: "like",
|
||||||
|
icon: "Heart",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "关注我的",
|
||||||
|
value: "attention",
|
||||||
|
icon: "UserPlus",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const iconMap: any = {
|
||||||
|
'official':Volume2,
|
||||||
|
'reply': MessageSquareMore,
|
||||||
|
'like':Heart,
|
||||||
|
'attention':UserPlus,
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgTypeList.value = MsgTypeList.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
LucideIcon: iconMap[item.value], // 添加 Lucide 图标组件
|
||||||
|
}))
|
||||||
|
|
||||||
|
const urlList = ref({
|
||||||
|
official: "",
|
||||||
|
reply: "/advice/getCommentMsg",
|
||||||
|
like: "/advice/getLikeMsg",
|
||||||
|
attention: "/advice/getAttentionMsg",
|
||||||
|
});
|
||||||
|
const allList = ref({
|
||||||
|
reply: [],
|
||||||
|
like: [],
|
||||||
|
attention: [],
|
||||||
|
});
|
||||||
|
// const officialList = ref([])
|
||||||
|
// const replyList = ref([])
|
||||||
|
// const likeList = ref([])
|
||||||
|
// const attentionList = ref([])
|
||||||
|
|
||||||
|
// const officialParamsType = ref('')
|
||||||
|
// const officialParamsTypeList = ref([
|
||||||
|
// {
|
||||||
|
// label:'全部通知',
|
||||||
|
// value:'4'
|
||||||
|
// }
|
||||||
|
// ])
|
||||||
|
const replyParamsType = ref("3");
|
||||||
|
const replyParamsTypeList = ref([
|
||||||
|
{
|
||||||
|
label: "全部通知",
|
||||||
|
value: "3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "模型评论",
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "图片评论",
|
||||||
|
value: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "工作流评论",
|
||||||
|
value: "1",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const likeParamsType = ref("4");
|
||||||
|
const likeParamsTypeList = ref([
|
||||||
|
{
|
||||||
|
label: "全部通知",
|
||||||
|
value: "4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "模型点赞",
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "图片点赞",
|
||||||
|
value: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "工作流点赞",
|
||||||
|
value: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "评论点赞",
|
||||||
|
value: "3",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function getList() {
|
||||||
|
const url = urlList.value[currentMsgType.value];
|
||||||
|
let productType = "";
|
||||||
|
if (currentMsgType.value === "reply") {
|
||||||
|
productType = replyParamsType.value;
|
||||||
|
} else if (currentMsgType.value === "like") {
|
||||||
|
productType = likeParamsType.value;
|
||||||
|
} else {
|
||||||
|
productType = "";
|
||||||
|
}
|
||||||
|
const res = await request.get(url, { productType });
|
||||||
|
if (res.code === 200) {
|
||||||
|
allList.value[currentMsgType.value] = res.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getList();
|
||||||
|
|
||||||
|
function changeType(item: any) {
|
||||||
|
currentMsgType.value = item.value;
|
||||||
|
likeParamsType.value = "4";
|
||||||
|
replyParamsType.value = "3";
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
function handleSelect(item: any, type: string) {
|
||||||
|
if (type === "like") {
|
||||||
|
likeParamsType.value = item.value;
|
||||||
|
} else {
|
||||||
|
replyParamsType.value = item.value;
|
||||||
|
}
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
// 一键已读
|
||||||
|
async function onAllRead() {
|
||||||
|
try {
|
||||||
|
const res = await request.get("/advice/readAll");
|
||||||
|
if (res.code === 200) {
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
definePageMeta({
|
||||||
|
layout: "default",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-[#f0f1f5] w-full flex items-center justify-center"
|
||||||
|
:style="{ height: `calc(100vh - 48px)` }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card w-[1036px] min-h-[500px] m-auto rounded-lg flex border border-solid border-gray-200"
|
||||||
|
:style="{ height: `calc(100vh - 100px)` }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-[287px] bg-white p-3 box-content rounded-l-lg text-gray-600 flex flex-col justify-between"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="p-3 text-xl">通知</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="msg-item"
|
||||||
|
:style="{ background: item.value === currentMsgType ? '#f5f6fa' : '' }"
|
||||||
|
v-for="(item, index) in MsgTypeList"
|
||||||
|
:key="index"
|
||||||
|
@click="changeType(item)"
|
||||||
|
>
|
||||||
|
<!-- <Volume2 size="18" /> -->
|
||||||
|
<!-- <MessageSquareMore size="18" /> -->
|
||||||
|
<!-- <Heart size="18" /> -->
|
||||||
|
<!-- <UserPlus
|
||||||
|
size="18"
|
||||||
|
:style="{ color: item.value === currentMsgType ? '#3541ec' : '' }"
|
||||||
|
class="mr-2"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<component
|
||||||
|
:is="item.LucideIcon"
|
||||||
|
size="18"
|
||||||
|
:style="{ color: item.value === currentMsgType ? '#3541ec' : '' }"
|
||||||
|
class="mr-2"
|
||||||
|
/>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex py-2 cursor-pointer justify-center">
|
||||||
|
<div class="flex justify-center items-center cursor-pointer" @click="onAllRead">
|
||||||
|
<Check size="14" class="mr-1" />
|
||||||
|
一键已读
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-content p-3 flex-1">
|
||||||
|
<div class="w-full h-10 flex justify-end items-center cursor-pointer relative">
|
||||||
|
<div class="group py-2">
|
||||||
|
<template v-if="currentMsgType === 'reply' || currentMsgType === 'like'">
|
||||||
|
全部通知
|
||||||
|
<n-icon size="14" class="ml-1">
|
||||||
|
<ChevronDown />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="currentMsgType === 'reply'"
|
||||||
|
class="absolute right-0 top-[30px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in replyParamsTypeList"
|
||||||
|
:style="{
|
||||||
|
color: item.value === replyParamsType ? '#3a75f6' : '',
|
||||||
|
}"
|
||||||
|
:key="index"
|
||||||
|
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||||
|
@click="(event) => handleSelect(item, 'reply')"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="currentMsgType === 'like'"
|
||||||
|
class="absolute right-0 top-[30px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in likeParamsTypeList"
|
||||||
|
:style="{
|
||||||
|
color: item.value === likeParamsType ? '#3a75f6' : '',
|
||||||
|
}"
|
||||||
|
:key="index"
|
||||||
|
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||||
|
@click="(event) => handleSelect(item, 'like')"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 overflow-y-auto" :style="{ height: `calc(100% - 48px)` }">
|
||||||
|
<OfficialMsg v-if="currentMsgType === 'official'" />
|
||||||
|
<ReplyMsg v-if="currentMsgType === 'reply'" :data-list="allList.reply" />
|
||||||
|
<LikeMsg v-if="currentMsgType === 'like'" :data-list="allList.like" />
|
||||||
|
<AttentionMsg
|
||||||
|
v-if="currentMsgType === 'attention'"
|
||||||
|
:data-list="allList.attention"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.msg-item {
|
||||||
|
@apply h-14 flex items-center mb-2 py-3 pl-6 rounded-lg cursor-pointer;
|
||||||
|
@apply hover:bg-[#f5f6fa];
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
@apply rounded-lg overflow-hidden transition-all duration-300;
|
||||||
|
@apply [box-shadow:0_0_20px_0_rgba(0,0,0,0.05)];
|
||||||
|
@apply hover:bg-[#f5f6fa] hover:[box-shadow:0_0_20px_0_rgba(0,0,0,0.1)];
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,462 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Heart, PersonAddOutline } from '@vicons/ionicons5'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircleUser,
|
||||||
|
Download,
|
||||||
|
EllipsisVertical, Play,
|
||||||
|
SquarePlus
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// 用于版本tabs当前选中的选项卡
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'default',
|
||||||
|
})
|
||||||
|
// const 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 {
|
||||||
|
const res = await request.get(`/model/selectModelById?id=${id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
detailsInfo.value = res.data
|
||||||
|
// detailsInfo.value.styleList =JSON.parse(res.data.styleList)
|
||||||
|
// // 1翻译
|
||||||
|
try {
|
||||||
|
const res1 = await request.get(`/ModelVersion/finbyid?id=${res.data.id}`)// 获取版本
|
||||||
|
if (res1.code === 200 && res1.data.length > 0) {
|
||||||
|
versionByWorkInfo.value = res1.data
|
||||||
|
versionByWorkInfo.value.forEach((item) => {
|
||||||
|
item.sampleImagePaths = item.sampleImagePaths.split(',')
|
||||||
|
})
|
||||||
|
nextTick(() => {
|
||||||
|
activeTab.value = versionByWorkInfo.value[0].id
|
||||||
|
})
|
||||||
|
// const commentRes = await request.get(`/WorkFlowComment/comment/${res.data.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 获取当前作品的用户信息
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/system/user/selectUserById?id=${detailsInfo.value.userId}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
currentUserInfo.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
getAttention()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getInfo()
|
||||||
|
|
||||||
|
// 获取用户点赞/粉丝/关注数量
|
||||||
|
interface SelectUserInfo {
|
||||||
|
attention: number
|
||||||
|
bean: number
|
||||||
|
imageLikeNum:number
|
||||||
|
modelDownLoadNum:number
|
||||||
|
modelLikeNum:number
|
||||||
|
modelRunNum:number
|
||||||
|
}
|
||||||
|
const selectUserInfo = ref<SelectUserInfo>({
|
||||||
|
attention: 0,
|
||||||
|
bean: 0,
|
||||||
|
imageLikeNum:0,
|
||||||
|
modelDownLoadNum:0,
|
||||||
|
modelLikeNum:0,
|
||||||
|
modelRunNum:0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取点赞粉丝等的数量
|
||||||
|
async function getAttention() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/attention/selectUserInfo?userId=${detailsInfo.value.userId}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
selectUserInfo.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 举报/编辑/删除
|
||||||
|
const isDelete = ref(false)
|
||||||
|
async function handleSelect(event: Event, type: string) {
|
||||||
|
event.stopPropagation() // 阻止事件冒泡
|
||||||
|
if (type === 'report') {
|
||||||
|
await request.get(`/WorkFlow/report?id=${id}`) // 举报
|
||||||
|
}
|
||||||
|
else if (type === 'edit') {
|
||||||
|
router.push({
|
||||||
|
path: `/publish-model`,
|
||||||
|
query: {
|
||||||
|
type: 'edit',
|
||||||
|
id: detailsInfo.value.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isDelete.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
async function onDelete() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/model/delete?id=${detailsInfo.value.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('删除成功')
|
||||||
|
isDelete.value = false
|
||||||
|
router.push('/personal-center')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关注用户/取消关注
|
||||||
|
async function onChangeAttention() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/attention/addAttention?userId=${detailsInfo.value.userId}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
if (res.data) {
|
||||||
|
detailsInfo.value.isAttention = 1
|
||||||
|
message.success('关注成功')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
detailsInfo.value.isAttention = 0
|
||||||
|
message.success('取消关注成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点赞
|
||||||
|
async function onLike() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/ModelComment/modelLike?modelId=${detailsInfo.value.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
detailsInfo.value.isLike === 0 ? detailsInfo.value.isLike = 1 : detailsInfo.value.isLike = 0
|
||||||
|
if (detailsInfo.value.isLike === 0) {
|
||||||
|
message.success('取消点赞成功')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message.success('点赞成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-[1125px] p-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="text-[26px] font-bold mr-4">
|
||||||
|
{{ detailsInfo.modelName }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-black menu-icon m-1" />
|
||||||
|
<span> {{ detailsInfo.reals || 0 }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
在线生成数
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full mx-4">
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-black menu-icon m-1"
|
||||||
|
/>
|
||||||
|
<span> {{ detailsInfo.numbers || 0 }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
下载数
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||||
|
<img src="@/assets/img/heart.png" class="w-[14px] h-[14px] mr-1" alt="">
|
||||||
|
<span>{{ detailsInfo.likeNum || 0}} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
点赞数
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mt-3 mb-5">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in detailsInfo.styleList"
|
||||||
|
:key="index"
|
||||||
|
class="text-[12px] bg-[#ebf2fe] p-1 px-2 text-[#557abf] mr-4 rounded-md"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-1">
|
||||||
|
<div class="w-2/3">
|
||||||
|
<div class="w-full">
|
||||||
|
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||||
|
<n-tab-pane
|
||||||
|
v-for="(item, index) in versionByWorkInfo"
|
||||||
|
:key="index"
|
||||||
|
:name="item.id"
|
||||||
|
:tab="item.versionName"
|
||||||
|
>
|
||||||
|
<!-- 显示最后一步上传图片的图片 -->
|
||||||
|
<div class="grid grid-cols-2 gap-2.5 box-border">
|
||||||
|
<img
|
||||||
|
v-for="(subItem, subIndex) in item.sampleImagePaths"
|
||||||
|
:key="subIndex"
|
||||||
|
:src="subItem"
|
||||||
|
class="w-full h-[300px]"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="detailsInfo.original === 1" class="font-bold text-[20px] my-6">
|
||||||
|
转载自作者: {{ detailsInfo.authorName }}
|
||||||
|
</div>
|
||||||
|
<!-- 富文本中输入的文字 图片 -->
|
||||||
|
<div class="w-full mt-2">
|
||||||
|
<div v-html="item.versionDescription" />
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div style="padding: 20px">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<BaseComment v-if="detailsInfo.id" type="model" :height="commentHeight" :details-info="detailsInfo" />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/3 mt-3">
|
||||||
|
<div
|
||||||
|
class="flex justify-between text-[#a3a1a1] text-[12px] items-center -ml-60"
|
||||||
|
>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div v-if="detailsInfo.createTime" class="mr-2">
|
||||||
|
首发时间{{ detailsInfo.createTime }}
|
||||||
|
</div>
|
||||||
|
<div v-if="detailsInfo.updateTime">
|
||||||
|
更新时间:{{ detailsInfo.updateTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center relative">
|
||||||
|
<n-icon size="20" :color="detailsInfo.isLike === 0 ? '#ccc' : '#ff0000'" @click="onLike">
|
||||||
|
<Heart class="cursor-pointer" />
|
||||||
|
</n-icon>
|
||||||
|
<div class="group relative">
|
||||||
|
<component
|
||||||
|
:is="EllipsisVertical"
|
||||||
|
class="h-[18px] w-[48px] text-[#557abf] cursor-pointer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-[10px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
|
||||||
|
>
|
||||||
|
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'report')">
|
||||||
|
举报
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||||
|
@click="(event) => handleSelect(event, 'edit')"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</div>
|
||||||
|
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'delete')">
|
||||||
|
删除
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center mt-10 p-2 bg-[#f3f5f9] w-full rounded-md h-[80px] box-border"
|
||||||
|
>
|
||||||
|
<div class="w-[70px] h-[70px] rounded-full overflow-hidden mr-4">
|
||||||
|
<client-only>
|
||||||
|
<NAvatar
|
||||||
|
class="w-full h-full mr-2 block"
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
:src="currentUserInfo.avatar"
|
||||||
|
/>
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex-1">
|
||||||
|
<client-only>
|
||||||
|
<div class="text[20px] font-bold">
|
||||||
|
{{ currentUserInfo.nickName }}
|
||||||
|
</div>
|
||||||
|
</client-only>
|
||||||
|
<!-- 0代表原创 1代表转载 -->
|
||||||
|
<div v-if="detailsInfo.original === 0" class="text-[14px]">
|
||||||
|
原创作者
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex justify-end">
|
||||||
|
<div class="flex items-center font-bold px-1 justify-center w-24 h-10 rounded-full text-[#426af7] border-2 border-[#426af7] border-solid cursor-pointer" @click="onChangeAttention">
|
||||||
|
<n-icon v-if="detailsInfo.isAttention === 0" size="20" class="mr-2">
|
||||||
|
<PersonAddOutline />
|
||||||
|
</n-icon>
|
||||||
|
{{ detailsInfo.isAttention === 1 ? '已关注' : '关注' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center text-[#969798]">
|
||||||
|
<component
|
||||||
|
:is="CircleUser"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.bean }} </span>
|
||||||
|
<!-- <component
|
||||||
|
:is="HardDriveUpload"
|
||||||
|
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> 2 </span> -->
|
||||||
|
<component
|
||||||
|
:is="Play"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.modelRunNum }} </span>
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.modelDownloadNum }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 不支持的bg #b1b2b2 -->
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-center mt-4 w-full text-white bg-[#3c7af6] w-full rounded-md h-[50px] cursor-pointer"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="Play"
|
||||||
|
class="h-[20px] w-[20px] text-white menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-1"> 立即生图</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-y-2">
|
||||||
|
<div class="flex flex-1 items-center justify-center bg-[#eceef4] h-[50px] mt-4 mr-1 rounded-md">
|
||||||
|
<component
|
||||||
|
:is="SquarePlus"
|
||||||
|
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-1">加入模型课</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-1 items-center bg-gradient-to-r from-[#ffe9c8] to-[#ffd264] justify-center ml-1 mt-4 text-black bg-[#eceef4] w-full rounded-md h-[50px] cursor-pointer"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-1"> 下载 (122.22MB) </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div style="background: linear-gradient(135deg,#3cc9ff, #8fa6ff, 41%, #d8b4ff 74%,#326bff)" class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#fff] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]">
|
||||||
|
<component :is="Download" class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]" />
|
||||||
|
<span class="mr-1">
|
||||||
|
下载客户端
|
||||||
|
</span>
|
||||||
|
</div> -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="isDelete"
|
||||||
|
:mask-closable="false"
|
||||||
|
preset="dialog"
|
||||||
|
title="提示!"
|
||||||
|
content="确定要将模型删除? 模型删除后无法找回"
|
||||||
|
negative-text="取消"
|
||||||
|
positive-text="确认"
|
||||||
|
@negative-click="onDelete"
|
||||||
|
@positive-click="onDelete"
|
||||||
|
/>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.header-num {
|
||||||
|
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||||
|
}
|
||||||
|
.header-tag {
|
||||||
|
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||||
|
display: flex;
|
||||||
|
align-self: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab,
|
||||||
|
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab {
|
||||||
|
color: #949494;
|
||||||
|
}
|
||||||
|
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab--active,
|
||||||
|
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab--active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-tabs .n-tabs-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #173eff 0%, #1b7dff 100%);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition:
|
||||||
|
left 0.2s var(--n-bezier),
|
||||||
|
max-width 0.2s var(--n-bezier),
|
||||||
|
opacity 0.3s var(--n-bezier),
|
||||||
|
background-color 0.3s var(--n-bezier);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,246 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
import { commonApi } from "@/api/common";
|
||||||
layout: 'default',
|
import { ArrowBack, ArrowForward } from '@vicons/ionicons5';
|
||||||
})
|
import { ref } from "vue";
|
||||||
</script>
|
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "default",
|
||||||
|
});
|
||||||
|
const params = ref({
|
||||||
|
name: null,
|
||||||
|
order: null,
|
||||||
|
type: null
|
||||||
|
})
|
||||||
|
// const hotNameList = ref([
|
||||||
|
// '哪吒',
|
||||||
|
// '电商',
|
||||||
|
// '产品',
|
||||||
|
// '海报',
|
||||||
|
// '国潮',
|
||||||
|
// '写真',
|
||||||
|
// '推文',
|
||||||
|
// 'XL',
|
||||||
|
// ])
|
||||||
|
const listRef = ref(null);
|
||||||
|
// function handleSearch() {
|
||||||
|
// onSearch()
|
||||||
|
// }
|
||||||
|
const modelCategoryList = ref([]);
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "model_child_category" });
|
||||||
|
if (res.code === 200) {
|
||||||
|
modelCategoryList.value = res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType();
|
||||||
|
|
||||||
|
//执行搜索的时候
|
||||||
|
function onSearch() {
|
||||||
|
if (listRef.value) {
|
||||||
|
listRef.value?.initPageNUm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 切换类型
|
||||||
|
function changeCategory(item:any){
|
||||||
|
params.value.type = item.dictValue
|
||||||
|
onSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// function onHot(name:string){
|
||||||
|
// params.value.name = name
|
||||||
|
// onSearch()
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<h1 class="text-2xl font-bold">模型广场</h1>
|
<div class="header flex h-[150px] border-b border-b-[#ebebeb]">
|
||||||
<!-- 这里添加模型广场的具体内容 -->
|
<div class="flex-1 py-2">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<div class="relative flex items-center w-full">
|
||||||
|
<!-- 搜索输入框 -->
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="w-full py-3 px-6 text-lg text-gray-600 placeholder-gray-400 bg-white border-2 border-blue-500 rounded-full focus:outline-none focus:border-blue-600"
|
||||||
|
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||||
|
@keyup.enter="onSearch"
|
||||||
|
v-model="params.name"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 相机图标 -->
|
||||||
|
<button class="absolute right-24 p-2 mr-2 text-gray-400 hover:text-gray-600">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 搜索按钮 -->
|
||||||
|
<button
|
||||||
|
class="absolute right-0 px-8 py-3 mr-2 text-white bg-blue-500 rounded-full hover:bg-blue-600 focus:outline-none"
|
||||||
|
@click="onSearch"
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="flex p-3">
|
||||||
|
<div v-for="(item, index) in hotNameList" @click="onHot(item)" :key="index" class="mr-3 mb-2 cursor-pointer">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="w-[500px] ml-4">
|
||||||
|
<!-- <img
|
||||||
|
class="h-[130px] w-full rounded-lg"
|
||||||
|
src="https://img.zcool.cn/tubelocation/e41767ac6706cd0109100099e04b.jpg?imageMogr2/format/webp"
|
||||||
|
alt=""
|
||||||
|
/> -->
|
||||||
|
<n-carousel show-arrow autoplay>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
|
||||||
|
/>
|
||||||
|
<template #arrow="{ prev, next }">
|
||||||
|
<div class="custom-arrow">
|
||||||
|
<button type="button" class="custom-arrow--left" @click="prev">
|
||||||
|
<n-icon><ArrowBack /></n-icon>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="custom-arrow--right" @click="next">
|
||||||
|
<n-icon><ArrowForward /></n-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #dots="{ total, currentIndex, to }">
|
||||||
|
<ul class="custom-dots">
|
||||||
|
<li
|
||||||
|
v-for="index of total"
|
||||||
|
:key="index"
|
||||||
|
:class="{ ['is-active']: currentIndex === index - 1 }"
|
||||||
|
@click="to(index - 1)"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</n-carousel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in modelCategoryList"
|
||||||
|
:key="index"
|
||||||
|
:style="{color: item.dictValue === params.type ? '#000' : '#6f6f6f'}"
|
||||||
|
@click="changeCategory(item)"
|
||||||
|
class="pt-4 mr-4 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ item.dictLabel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<ModelList ref="listRef" :params="params"></ModelList>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.grid > div {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.grid > div:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.carousel-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.custom-arrow {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 25px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 0 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
transition:
|
||||||
|
width 0.3s,
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li.is-active {
|
||||||
|
width: 40px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,568 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from '@/api/common'
|
||||||
|
import EditUserInfo from '@/components/EditUserInfo.vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { formatDate } from '@/utils/index.ts'
|
||||||
|
import { NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const finished = ref(false)
|
||||||
|
const total = ref(0) // 总条数
|
||||||
|
const loadingTrigger = ref(null)
|
||||||
|
|
||||||
|
const observer = ref<IntersectionObserver | null>(null)
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'default',
|
||||||
|
})
|
||||||
|
interface UserInfo {
|
||||||
|
nickName?: string // 使用 ? 表示 nickName 是可选的
|
||||||
|
avatar?: string
|
||||||
|
name?: string
|
||||||
|
brief?: string
|
||||||
|
}
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const userInfo: UserInfo = userStore.userInfo
|
||||||
|
|
||||||
|
// 当前是发布还是点赞?
|
||||||
|
const currentState = ref('mallProduct')
|
||||||
|
// 当前是模型还是工作流还是图片?
|
||||||
|
const currentType = ref('0')
|
||||||
|
|
||||||
|
const orderOptions = ref([
|
||||||
|
{
|
||||||
|
dictLabel: '最新',
|
||||||
|
dictValue: 'create_time',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dictLabel: '最热',
|
||||||
|
dictValue: 'like_num',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const stateList = ref([
|
||||||
|
{ id: 'mallProduct', title: '发布' },
|
||||||
|
{ id: 'like', title: '点赞' },
|
||||||
|
])
|
||||||
|
const typeList = ref([
|
||||||
|
{ id: '0', title: '模型' },
|
||||||
|
{ id: '1', title: '工作流' },
|
||||||
|
{ id: '2', title: '图片' },
|
||||||
|
])
|
||||||
|
|
||||||
|
// 发布的form查询条件
|
||||||
|
const publishParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
status: '0',
|
||||||
|
orderByColumn: 'create_time',
|
||||||
|
date: null,
|
||||||
|
endTime: '',
|
||||||
|
startTime: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
function initPublishParams() {
|
||||||
|
publishParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
status: '0',
|
||||||
|
orderByColumn: 'create_time',
|
||||||
|
date: null,
|
||||||
|
endTime: '',
|
||||||
|
startTime: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点赞form的查询条件
|
||||||
|
const likesParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
orderByColumn: 'create_time',
|
||||||
|
})
|
||||||
|
|
||||||
|
function initLikesParams() {
|
||||||
|
likesParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
orderByColumn: 'create_time',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlList = ref({
|
||||||
|
mallProduct: {
|
||||||
|
0: '/model/selectByUserIdModel',
|
||||||
|
1: '/model/selectByUserIdWorkFlow',
|
||||||
|
2: '/model/selectByUserIdImage',
|
||||||
|
},
|
||||||
|
like: {
|
||||||
|
0: '/model/likeModel',
|
||||||
|
1: '/model/likeWorkFlow',
|
||||||
|
2: '/model/likeImage',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取数据字典
|
||||||
|
|
||||||
|
const statusOptions = ref([])
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({
|
||||||
|
type: 'mall_product_status',
|
||||||
|
})
|
||||||
|
if (res.code === 200 && res.data.length > 0) {
|
||||||
|
statusOptions.value = res.data
|
||||||
|
publishParams.value.status = res.data[0].dictValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType()
|
||||||
|
|
||||||
|
// 编辑用户信息
|
||||||
|
interface EditUserInfoType {
|
||||||
|
isVisible: boolean
|
||||||
|
}
|
||||||
|
const editUserInfoRef = ref<EditUserInfoType | null>(null)
|
||||||
|
function onEditInfo() {
|
||||||
|
if (editUserInfoRef.value) {
|
||||||
|
editUserInfoRef.value.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实名认证
|
||||||
|
interface AuthComponentType {
|
||||||
|
isVisible: boolean
|
||||||
|
}
|
||||||
|
const authenticationRef = ref<AuthComponentType | null>(null)
|
||||||
|
function onAuth() {
|
||||||
|
if (authenticationRef.value) {
|
||||||
|
authenticationRef.value.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initChangeParams() {
|
||||||
|
if (currentState.value === 'mallProduct') {
|
||||||
|
initPublishParams()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
initLikesParams()
|
||||||
|
}
|
||||||
|
finished.value = false // 重置加载完成状态
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
// 切换发布/点赞
|
||||||
|
function changeTabs(id: string) {
|
||||||
|
currentState.value = id
|
||||||
|
currentType.value = '0'
|
||||||
|
initChangeParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换模型/工作流/图片
|
||||||
|
function changeType(id: string) {
|
||||||
|
currentType.value = id
|
||||||
|
initChangeParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPageNUm() {
|
||||||
|
if (currentState.value === 'mallProduct') {
|
||||||
|
publishParams.value.pageNum = 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
likesParams.value.pageNum = 1
|
||||||
|
}
|
||||||
|
finished.value = false // 重置加载完成状态
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换select全部状态/已发布/
|
||||||
|
function changeStatus(value: string) {
|
||||||
|
publishParams.value.status = value
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换发布的最热/最新
|
||||||
|
function changeOrder(value: string) {
|
||||||
|
publishParams.value.orderByColumn = value
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换点赞的最热/最新
|
||||||
|
function changeLikeOrder(value: string) {
|
||||||
|
likesParams.value.orderByColumn = value
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换日期
|
||||||
|
async function changeDate(value: string[]) {
|
||||||
|
publishParams.value.startTime = `${await formatDate(value[0] as string)} 00:00:00`
|
||||||
|
publishParams.value.endTime = `${await formatDate(value[1] as string)} 23:59:59`
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户点赞/粉丝/关注数量
|
||||||
|
interface SelectUserInfo {
|
||||||
|
likeCount: number
|
||||||
|
bean: number
|
||||||
|
download: number
|
||||||
|
attention: number
|
||||||
|
}
|
||||||
|
const selectUserInfo = ref<SelectUserInfo>({
|
||||||
|
likeCount: 0,
|
||||||
|
bean: 0,
|
||||||
|
download: 0,
|
||||||
|
attention: 0,
|
||||||
|
})
|
||||||
|
async function getAttention() {
|
||||||
|
try {
|
||||||
|
const res = await request.get('/attention/selectUserInfo')
|
||||||
|
if (res.code === 200) {
|
||||||
|
selectUserInfo.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getAttention()
|
||||||
|
|
||||||
|
// Banner 样式
|
||||||
|
const bannerStyle = {
|
||||||
|
backgroundImage:
|
||||||
|
'url(\'https://liblibai-web-static.liblib.cloud/liblibai_v4_online/static/_next/static/images/defaultBgImg.381282c0f2b01780c83d8fe6dc0aa90a.png\')',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义响应接口
|
||||||
|
interface ApiResponse<T> {
|
||||||
|
code: number
|
||||||
|
rows: T[]
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
// 定义数据接口
|
||||||
|
interface UserData {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
status: number
|
||||||
|
}
|
||||||
|
// 查询发布模型接口
|
||||||
|
const dataList = ref([])
|
||||||
|
async function getList() {
|
||||||
|
if (loading.value || finished.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
let params = {}
|
||||||
|
if (currentState.value === 'mallProduct') {
|
||||||
|
params = publishParams.value
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params = likesParams.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = urlList.value[currentState.value][currentType.value]
|
||||||
|
try {
|
||||||
|
const res = await request.post<ApiResponse<UserData>>(url, params)
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 如果是第一页,直接赋值,否则追加数据
|
||||||
|
if (params.pageNum === 1) {
|
||||||
|
dataList.value = res.rows
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dataList.value = [...dataList.value, ...res.rows]
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = res.total // 假设接口返回了总条数
|
||||||
|
|
||||||
|
// 判断是否加载完所有数据
|
||||||
|
if (dataList.value.length >= total.value) {
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动增加页码
|
||||||
|
if (currentState.value === 'mallProduct') {
|
||||||
|
publishParams.value.pageNum++
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
likesParams.value.pageNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
dataList.value = []
|
||||||
|
finished.value = true
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('scroll', topedRefresh)
|
||||||
|
observer.value = new IntersectionObserver(([entry]) => {
|
||||||
|
if (entry.isIntersecting && !loading.value && !finished.value) {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
threshold: 0.1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (loadingTrigger.value) {
|
||||||
|
observer.value.observe(loadingTrigger.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('scroll', topedRefresh)
|
||||||
|
if (observer.value) {
|
||||||
|
observer.value.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function topedRefresh() {
|
||||||
|
if (import.meta.client) {
|
||||||
|
await nextTick()
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关注/粉丝列表模态框
|
||||||
|
const currentAttentionType = ref<string>('attention')
|
||||||
|
const attentionIsVisible = ref<boolean>(false)
|
||||||
|
// function onShowAttentionModel(type: string) {
|
||||||
|
// attentionIsVisible.value = true
|
||||||
|
// }
|
||||||
|
function onCloseAttentionModel() {
|
||||||
|
attentionIsVisible.value = false
|
||||||
|
}
|
||||||
|
function onClearDate(){
|
||||||
|
publishParams.value.startTime = '',
|
||||||
|
publishParams.value.endTime = '',
|
||||||
|
initPageNUm()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto container">
|
||||||
|
<!-- Banner Section -->
|
||||||
|
<div class="banner-content h-32 bg-blue bg-cover bg-center" :style="bannerStyle" />
|
||||||
|
|
||||||
|
<!-- User Info Section -->
|
||||||
|
<div class="info-content mt-[-50px] p-5">
|
||||||
|
<div class="edit-info-content flex items-center">
|
||||||
|
<!-- Avatar -->
|
||||||
|
|
||||||
|
<div class="mc-head mr-5 h-20 w-20 rounded-full bg-white shadow-lg flex items-center justify-center">
|
||||||
|
<div class="mc-head-inner h-18 w-18 m-1 rounded-full bg-blue-200">
|
||||||
|
<client-only>
|
||||||
|
<img
|
||||||
|
class="head-img m-1 h-16 w-16 rounded-full bg-white"
|
||||||
|
:src="userStore.userInfo.avatar"
|
||||||
|
alt="User Avatar"
|
||||||
|
>
|
||||||
|
</client-only>
|
||||||
|
<!-- {{ userStore.userInfo.avatar }} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Info Button -->
|
||||||
|
<div
|
||||||
|
class="edit-info mr-2 cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
|
||||||
|
@click="onEditInfo()"
|
||||||
|
>
|
||||||
|
编辑资料
|
||||||
|
</div>
|
||||||
|
<!-- Real Name Verification -->
|
||||||
|
<div
|
||||||
|
v-if="userInfo.name"
|
||||||
|
class="edit-info rounded-full bg-white px-5 py-2 shadow-md"
|
||||||
|
>
|
||||||
|
已经实名
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="edit-info cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
|
||||||
|
@click="onAuth()"
|
||||||
|
>
|
||||||
|
去实名
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Details -->
|
||||||
|
<div class="user-info mt-4">
|
||||||
|
<div v-if="userStore.userInfo" class="nickname text-2xl font-bold">
|
||||||
|
{{ userStore.userInfo.nickName }}
|
||||||
|
<!-- {{ userInfo.nickName }} -->
|
||||||
|
</div>
|
||||||
|
<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 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 cursor-pointer" @click="onShowAttentionModel('attention')">
|
||||||
|
<span class="production-state-number font-bold">{{
|
||||||
|
selectUserInfo.attention ? selectUserInfo.attention : 0
|
||||||
|
}}</span>
|
||||||
|
关注
|
||||||
|
</div>
|
||||||
|
<div class="production-state-item mr-5">
|
||||||
|
<span class="production-state-number font-bold">{{
|
||||||
|
selectUserInfo.download ? selectUserInfo.download : 0
|
||||||
|
}}</span>
|
||||||
|
作品被使用次数
|
||||||
|
</div>
|
||||||
|
<div class="production-state-item">
|
||||||
|
<span class="production-state-number font-bold">{{
|
||||||
|
selectUserInfo.likeCount ? selectUserInfo.likeCount : 0
|
||||||
|
}}</span>
|
||||||
|
作品被点赞次数
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mc-tabs flex px-5 pb-3" style="border-bottom: 1px solid #e0e0e0">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in stateList"
|
||||||
|
:key="index"
|
||||||
|
class="mc-tabs-btn mr-2 cursor-pointer rounded-full px-5 py-1"
|
||||||
|
:class="{
|
||||||
|
'bg-black text-white': currentState === item.id,
|
||||||
|
'bg-white text-black': currentState !== item.id,
|
||||||
|
}"
|
||||||
|
@click="changeTabs(item.id)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="select-content mt-4 flex items-center justify-between px-5">
|
||||||
|
<div class="flex items-center rounded-full bg-gray-100">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in typeList"
|
||||||
|
:key="index"
|
||||||
|
class="m-1 mr-2 cursor-pointer rounded-full px-4 py-1 text-sm"
|
||||||
|
:class="{
|
||||||
|
'bg-white': item.id === currentType,
|
||||||
|
}"
|
||||||
|
@click="changeType(item.id)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Published Works Filters -->
|
||||||
|
<div v-if="currentState === 'mallProduct'" class="flex items-center">
|
||||||
|
<div>
|
||||||
|
<n-select
|
||||||
|
v-model:value="publishParams.status"
|
||||||
|
:options="statusOptions"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="width: 180px;"
|
||||||
|
class="mr-2"
|
||||||
|
@update:value="changeStatus"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-select
|
||||||
|
v-model:value="publishParams.orderByColumn"
|
||||||
|
:options="orderOptions"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择"
|
||||||
|
class="mr-2 w-28"
|
||||||
|
@update:value="changeOrder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-date-picker
|
||||||
|
v-model:value="publishParams.date"
|
||||||
|
type="daterange"
|
||||||
|
style="width: 230px;"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
value-format="yyyy.MM.dd"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:on-clear="onClearDate"
|
||||||
|
clearable
|
||||||
|
@update:value="changeDate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Liked Works Filters -->
|
||||||
|
<div v-if="currentState === 'like'" class="flex items-center">
|
||||||
|
<NSelect
|
||||||
|
v-model:value="likesParams.orderByColumn"
|
||||||
|
:options="orderOptions"
|
||||||
|
label-field="dictLabel"
|
||||||
|
value-field="dictValue"
|
||||||
|
placeholder="请选择"
|
||||||
|
class="w-32"
|
||||||
|
@update:value="changeLikeOrder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<Authentication ref="authenticationRef" />
|
||||||
|
<EditUserInfo ref="editUserInfoRef" />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
<!-- Dialog Components -->
|
||||||
|
|
||||||
|
<div class="login-content my-4 grid grid-cols-4 gap-4 px-5">
|
||||||
|
<PersonalCenterCard
|
||||||
|
v-for="(item, index) in dataList"
|
||||||
|
:key="index"
|
||||||
|
:item="item"
|
||||||
|
:current-type="currentType"
|
||||||
|
:current-state="currentState"
|
||||||
|
@toped-refresh="topedRefresh"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div ref="loadingTrigger" class="h-10">
|
||||||
|
<div v-if="loading" class="text-center text-gray-500">
|
||||||
|
加载中...
|
||||||
|
</div>
|
||||||
|
<div v-if="finished" class="text-center text-gray-500">
|
||||||
|
没有更多数据了
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.edit-info {
|
||||||
|
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,189 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from "@/api/common";
|
||||||
|
import { ArrowBack, ArrowForward } from "@vicons/ionicons5";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "default",
|
||||||
|
});
|
||||||
|
const params = ref({
|
||||||
|
name: null,
|
||||||
|
order: null,
|
||||||
|
type: null,
|
||||||
|
});
|
||||||
|
const listRef = ref(null);
|
||||||
|
// function handleSearch() {
|
||||||
|
// onSearch()
|
||||||
|
// }
|
||||||
|
|
||||||
|
const modelCategoryList = ref([]);
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "image_label" });
|
||||||
|
if (res.code === 200) {
|
||||||
|
modelCategoryList.value = res.data;
|
||||||
|
modelCategoryList.value.unshift({
|
||||||
|
dictValue: null,
|
||||||
|
dictLabel: "全部",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType();
|
||||||
|
|
||||||
|
//执行搜索的时候
|
||||||
|
function onSearch() {
|
||||||
|
if (listRef.value) {
|
||||||
|
listRef.value?.initPageNUm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 切换类型
|
||||||
|
function changeCategory(item: any) {
|
||||||
|
params.value.type = item.dictValue;
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="w-full">
|
||||||
|
<n-carousel show-arrow autoplay>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
|
||||||
|
/>
|
||||||
|
<template #arrow="{ prev, next }">
|
||||||
|
<div class="custom-arrow">
|
||||||
|
<button type="button" class="custom-arrow--left" @click="prev">
|
||||||
|
<n-icon><ArrowBack /></n-icon>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="custom-arrow--right" @click="next">
|
||||||
|
<n-icon><ArrowForward /></n-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #dots="{ total, currentIndex, to }">
|
||||||
|
<ul class="custom-dots">
|
||||||
|
<li
|
||||||
|
v-for="index of total"
|
||||||
|
:key="index"
|
||||||
|
:class="{ ['is-active']: currentIndex === index - 1 }"
|
||||||
|
@click="to(index - 1)"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</n-carousel>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mt-4">
|
||||||
|
<div class="text-[20px] mr-4">作品灵感</div>
|
||||||
|
<HeaderSearchInput v-model="params.name" @search="onSearch" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap mt-2">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in modelCategoryList"
|
||||||
|
:key="index"
|
||||||
|
:style="{
|
||||||
|
color: item.dictValue === params.type ? '#fff' : '#000',
|
||||||
|
background: item.dictValue === params.type ? '#000' : '#fff',
|
||||||
|
}"
|
||||||
|
@click="changeCategory(item)"
|
||||||
|
class="px-2 py-1 rounded-full mr-4 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ item.dictLabel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<PictureList ref="listRef" :params="params" />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.grid > div {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.grid > div:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.carousel-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: 10px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 25px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 0 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
transition: width 0.3s, background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li.is-active {
|
||||||
|
width: 40px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,166 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CreateModels from "@/components/publishModel/CreateModels.vue";
|
||||||
|
import EditVersion from "@/components/publishModel/EditVersion.vue";
|
||||||
|
import UploadImg from "@/components/publishModel/UploadImg.vue";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { type, id } = route.query;
|
||||||
|
const step2Ref = ref(null);
|
||||||
|
const currentStep = ref(1);
|
||||||
|
const formData = ref({
|
||||||
|
modelProduct: {},
|
||||||
|
modelVersionList: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initFormData() {
|
||||||
|
if (type === "add") {
|
||||||
|
formData.value = {
|
||||||
|
modelProduct: {
|
||||||
|
modelName: "",
|
||||||
|
modelType: null, // ,,模型类型
|
||||||
|
category: null, // 垂类分类
|
||||||
|
functions: null, // '模型功能',
|
||||||
|
tags: '', // 标签(最多三个,切割)string
|
||||||
|
tagsList:[],
|
||||||
|
activityId: null, // 参与活动string
|
||||||
|
isOriginal: 1, // 几代表原创
|
||||||
|
originalAuthorName: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
modelVersionList: [
|
||||||
|
{
|
||||||
|
delFlag: "0", // 0代表存在 2代表删除
|
||||||
|
versionName: "", // 版本名称
|
||||||
|
modelVersionType: null, // 基础模型
|
||||||
|
filePath: "", // 文件路径
|
||||||
|
fileName: "", // 文档里没有 ,表里没有
|
||||||
|
versionDescription: "", // 版本描述
|
||||||
|
|
||||||
|
sampleImagePaths: [], // 第三部的图片路径最多20张,切割
|
||||||
|
triggerWords: "", // 触发词
|
||||||
|
isPublic: 1, // 权限是否公开权限 1公开 0自见
|
||||||
|
isFree: 0, // 是否免费
|
||||||
|
|
||||||
|
isOnlineUse: 1, // 是否允许在线使用
|
||||||
|
allowDownloadImage: 1, // 允许下载生图
|
||||||
|
allowSoftwareUse: 1, // 允许在软件旗下使用
|
||||||
|
allowFusion: 1, // 允许进行融合
|
||||||
|
allowCommercialUse: 1, // 是否允许商用
|
||||||
|
// 允许模型转售或者融合手出售字段没找到?
|
||||||
|
isExclusiveModel: 0, // 是否为独家模型这个字段
|
||||||
|
hideImageGenInfo:0, //隐藏图片生成信息
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/model/finbyid?id=${id}`);
|
||||||
|
formData.value = res.data;
|
||||||
|
if(res.data.modelProduct.modelType && res.data.modelProduct.modelType != 0){
|
||||||
|
formData.value.modelProduct.modelType = res.data.modelProduct.modelType.toString();
|
||||||
|
}
|
||||||
|
if(res.data.modelProduct.activityId && res.data.modelProduct.activityId != 0){
|
||||||
|
formData.value.modelProduct.activityId = Number(res.data.modelProduct.activityId);
|
||||||
|
}
|
||||||
|
if(formData.value.modelProduct.tags){
|
||||||
|
formData.value.modelProduct.tagsList = JSON.parse(formData.value.modelProduct.tags);
|
||||||
|
}else{
|
||||||
|
formData.value.modelProduct.tagsList = []
|
||||||
|
}
|
||||||
|
if(formData.value.modelVersionList.length > 0){
|
||||||
|
for(let i = 0; i < formData.value.modelVersionList.length; i ++){
|
||||||
|
formData.value.modelVersionList[i].sampleImagePaths = formData.value.modelVersionList[i].sampleImagePaths.split(',')
|
||||||
|
formData.value.modelVersionList[i].modelVersionType = formData.value.modelVersionList[i].modelVersionType.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (formData.value.workFlow.type) {
|
||||||
|
// formData.value.workFlow.typeList = formData.value.workFlow.type.split(',')
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// formData.value.workFlow.typeList = []
|
||||||
|
// }
|
||||||
|
// if (formData.value.workFlow.activityParticipation) {
|
||||||
|
// formData.value.workFlow.activityParticipation = Number(formData.value.workFlow.activityParticipation)
|
||||||
|
// }
|
||||||
|
// for (let i = 0; i < formData.value.workFlowVersionList.length; i++) {
|
||||||
|
// if (formData.value.workFlowVersionList[i].imagePaths) {
|
||||||
|
// formData.value.workFlowVersionList[i].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(',')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initFormData();
|
||||||
|
|
||||||
|
const timeLineList = ref([
|
||||||
|
{
|
||||||
|
name: "创建模型",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "编辑版本",
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "上传图片",
|
||||||
|
index: 3,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
function nextStep() {
|
||||||
|
currentStep.value += 1;
|
||||||
|
}
|
||||||
|
function prevStep() {
|
||||||
|
currentStep.value -= 1;
|
||||||
|
}
|
||||||
|
function handleAddVersion() {
|
||||||
|
if (step2Ref.value) {
|
||||||
|
step2Ref.value?.addVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto py-6">
|
||||||
|
<div class="container mx-auto w-[700px]">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<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>
|
||||||
|
<div class="form-container">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<div v-if="currentStep === 1" class="first-step">
|
||||||
|
<CreateModels v-model="formData" @create-models-next="nextStep" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currentStep === 2" class="second-step">
|
||||||
|
<EditVersion
|
||||||
|
ref="step2Ref"
|
||||||
|
v-model="formData"
|
||||||
|
@prev-step="prevStep"
|
||||||
|
@next-step="nextStep"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="currentStep === 3" class="third-step">
|
||||||
|
<UploadImg v-model="formData" @pre-step="prevStep" />
|
||||||
|
</div>
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,135 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import EditVersion from "@/components/publishWorkFlow/EditVersion.vue";
|
||||||
|
import EditWorkFlow from "@/components/publishWorkFlow/EditWorkFlow.vue";
|
||||||
|
import UploadImg from "@/components/publishWorkFlow/UploadImg.vue";
|
||||||
|
import { CirclePlus } from "lucide-vue-next";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (formData.value.workFlowVersionList.length > 0) {
|
||||||
|
for (let i = 0; i < formData.value.workFlowVersionList.length; i++) {
|
||||||
|
if (formData.value.workFlowVersionList[i].imagePaths) {
|
||||||
|
formData.value.workFlowVersionList[
|
||||||
|
i
|
||||||
|
].imagePaths = formData.value.workFlowVersionList[i].imagePaths.split(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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>
|
|
@ -1,48 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
const route = useRoute<'publishDetails-id'>()
|
|
||||||
const user = useUserStore()
|
|
||||||
const name = route.params.id
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
user.setNewName(route.params.id as string)
|
|
||||||
})
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'home',
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div i-twemoji:waving-hand inline-block animate-shake-x animate-duration-5000 text-4xl />
|
|
||||||
<h3 text-2xl font-500>
|
|
||||||
Hi,
|
|
||||||
</h3>
|
|
||||||
<div text-xl>
|
|
||||||
{{ name }}!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-if="user.otherNames.length">
|
|
||||||
<div my-4 text-sm>
|
|
||||||
<span op-50>Also as known as:</span>
|
|
||||||
<ul>
|
|
||||||
<li v-for="otherName in user.otherNames" :key="otherName">
|
|
||||||
<router-link :to="`/hi/${otherName}`" replace>
|
|
||||||
{{ otherName }}
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<NuxtLink
|
|
||||||
class="m-3 text-sm btn"
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { commonApi } from "@/api/common";
|
||||||
|
import { ArrowBack, ArrowForward } from "@vicons/ionicons5";
|
||||||
|
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "default",
|
||||||
|
});
|
||||||
|
const params = ref({
|
||||||
|
name: null,
|
||||||
|
order: null,
|
||||||
|
type: null,
|
||||||
|
});
|
||||||
|
const listRef = ref(null);
|
||||||
|
// function handleSearch() {
|
||||||
|
// onSearch()
|
||||||
|
// }
|
||||||
|
const workFlowCategoryList = ref([]);
|
||||||
|
async function getDictType() {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.dictType({ type: "work_flow_type_child" });
|
||||||
|
if (res.code === 200) {
|
||||||
|
workFlowCategoryList.value = res.data;
|
||||||
|
workFlowCategoryList.value.unshift({
|
||||||
|
dictValue: null,
|
||||||
|
dictLabel: "全部",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDictType();
|
||||||
|
|
||||||
|
//执行搜索的时候
|
||||||
|
function onSearch() {
|
||||||
|
if (listRef.value) {
|
||||||
|
listRef.value?.initPageNUm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 切换类型
|
||||||
|
function changeCategory(item: any) {
|
||||||
|
params.value.type = item.dictValue;
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="w-full">
|
||||||
|
<n-carousel show-arrow autoplay>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel3.jpeg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="carousel-img"
|
||||||
|
src="https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel4.jpeg"
|
||||||
|
/>
|
||||||
|
<template #arrow="{ prev, next }">
|
||||||
|
<div class="custom-arrow">
|
||||||
|
<button type="button" class="custom-arrow--left" @click="prev">
|
||||||
|
<n-icon><ArrowBack /></n-icon>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="custom-arrow--right" @click="next">
|
||||||
|
<n-icon><ArrowForward /></n-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #dots="{ total, currentIndex, to }">
|
||||||
|
<ul class="custom-dots">
|
||||||
|
<li
|
||||||
|
v-for="index of total"
|
||||||
|
:key="index"
|
||||||
|
:class="{ ['is-active']: currentIndex === index - 1 }"
|
||||||
|
@click="to(index - 1)"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</n-carousel>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mt-4">
|
||||||
|
<div class="text-[20px] mr-4">工作流</div>
|
||||||
|
<HeaderSearchInput v-model="params.name" @search="onSearch" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap mt-2">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in workFlowCategoryList"
|
||||||
|
:key="index"
|
||||||
|
:style="{
|
||||||
|
color: item.dictValue === params.type ? '#fff' : '#000',
|
||||||
|
background: item.dictValue === params.type ? '#000' : '#fff',
|
||||||
|
}"
|
||||||
|
@click="changeCategory(item)"
|
||||||
|
class="px-2 py-1 rounded-full mr-4 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ item.dictLabel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<WorkFlowList ref="listRef" :params="params" />
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.grid > div {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.grid > div:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.carousel-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: 10px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 25px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-arrow button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 0 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
transition: width 0.3s, background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dots li.is-active {
|
||||||
|
width: 40px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,458 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Heart, PersonAddOutline } from '@vicons/ionicons5'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircleUser,
|
||||||
|
Download,
|
||||||
|
EllipsisVertical, 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 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 {
|
||||||
|
const res = await request.get(`/WorkFlow/selectWorkFlowById?id=${id}&type=1`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
detailsInfo.value = res.data
|
||||||
|
// 1翻译
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
getAttention()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getInfo()
|
||||||
|
|
||||||
|
// 获取用户点赞/粉丝/关注数量
|
||||||
|
interface SelectUserInfo {
|
||||||
|
likeCount: number
|
||||||
|
bean: number
|
||||||
|
download: number
|
||||||
|
attention: number
|
||||||
|
}
|
||||||
|
const selectUserInfo = ref<SelectUserInfo>({
|
||||||
|
likeCount: 0,
|
||||||
|
bean: 0,
|
||||||
|
download: 0,
|
||||||
|
attention: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取点赞粉丝等的数量
|
||||||
|
async function getAttention() {
|
||||||
|
try {
|
||||||
|
const res = await request.get(`/attention/selectUserInfo?userId=${detailsInfo.value.userId}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
selectUserInfo.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 举报/编辑/删除
|
||||||
|
const isDelete = ref(false)
|
||||||
|
async function handleSelect(event: Event, type: string) {
|
||||||
|
event.stopPropagation() // 阻止事件冒泡
|
||||||
|
if (type === 'report') {
|
||||||
|
await request.get(`/WorkFlow/report?id=${id}`) // 举报
|
||||||
|
}
|
||||||
|
else if (type === 'edit') {
|
||||||
|
router.push({
|
||||||
|
path: `/publish-workflow`,
|
||||||
|
query: {
|
||||||
|
type: 'edit',
|
||||||
|
id: detailsInfo.value.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isDelete.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
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>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-[1125px] p-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="text-[26px] font-bold mr-4">
|
||||||
|
{{ detailsInfo.workflowName }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||||
|
<component :is="Play" class="h-[14px] w-[14px] text-black menu-icon m-1" />
|
||||||
|
<span> {{ detailsInfo.useNumber }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
在线生成数
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full mx-4">
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[14px] w-[14px] text-black menu-icon m-1"
|
||||||
|
/>
|
||||||
|
<span> {{ detailsInfo.downloadNumber }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
下载数
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||||
|
<img src="@/assets/img/heart.png" class="w-[14px] h-[14px] mr-1" alt="">
|
||||||
|
<span> <!-- {{ item.numbers }} -->{{ detailsInfo.likeCount }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
点赞数
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mt-3 mb-5">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in detailsInfo.typeList"
|
||||||
|
:key="index"
|
||||||
|
class="text-[12px] bg-[#ebf2fe] p-1 px-2 text-[#557abf] mr-4 rounded-md"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full gap-1">
|
||||||
|
<div class="w-2/3">
|
||||||
|
<div class="w-full">
|
||||||
|
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||||
|
<n-tab-pane
|
||||||
|
v-for="(item, index) in versionByWorkInfo"
|
||||||
|
:key="index"
|
||||||
|
:name="item.id"
|
||||||
|
:tab="item.versionName"
|
||||||
|
>
|
||||||
|
<!-- 显示最后一步上传图片的图片 -->
|
||||||
|
<div class="grid grid-cols-2 gap-2.5 box-border">
|
||||||
|
<img
|
||||||
|
v-for="(subItem, subIndex) in item.imagePathsList"
|
||||||
|
:key="subIndex"
|
||||||
|
:src="subItem"
|
||||||
|
class="w-full h-[300px]"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="detailsInfo.original === 1" class="font-bold text-[20px] my-6">
|
||||||
|
转载自作者: {{ detailsInfo.authorName }}
|
||||||
|
</div>
|
||||||
|
<!-- 富文本中输入的文字 图片 -->
|
||||||
|
<div class="w-full">
|
||||||
|
<div v-html="item.versionDescription" />
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div style="padding: 20px">
|
||||||
|
<NConfigProvider>
|
||||||
|
<NMessageProvider>
|
||||||
|
<BaseComment v-if="detailsInfo.id" type="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="flex justify-end">
|
||||||
|
<div v-if="detailsInfo.createTime" class="mr-2">
|
||||||
|
首发时间{{ detailsInfo.createTime }}
|
||||||
|
</div>
|
||||||
|
<div v-if="detailsInfo.updateTime">
|
||||||
|
更新时间:{{ detailsInfo.updateTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center relative">
|
||||||
|
<!-- <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"
|
||||||
|
class="h-[18px] w-[48px] text-[#557abf] cursor-pointer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-[10px] hidden group-hover:block text-[#000000] text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
|
||||||
|
>
|
||||||
|
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'report')">
|
||||||
|
举报
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||||
|
@click="(event) => handleSelect(event, 'edit')"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</div>
|
||||||
|
<div class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg" @click="(event) => handleSelect(event, 'delete')">
|
||||||
|
删除
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center mt-10 p-2 bg-[#f3f5f9] w-full rounded-md h-[80px] box-border"
|
||||||
|
>
|
||||||
|
<div class="w-[70px] h-[70px] rounded-full overflow-hidden mr-4">
|
||||||
|
<!-- <img
|
||||||
|
src="@/assets/img/default-avatar.png"
|
||||||
|
class="w-full h-full mr-2 block"
|
||||||
|
alt=""
|
||||||
|
> -->
|
||||||
|
<client-only>
|
||||||
|
<NAvatar
|
||||||
|
class="w-full h-full mr-2 block"
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
:src="currentUserInfo.avatar"
|
||||||
|
/>
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex-1">
|
||||||
|
<client-only>
|
||||||
|
<div class="text[20px] font-bold">
|
||||||
|
{{ currentUserInfo.nickName }}
|
||||||
|
</div>
|
||||||
|
</client-only>
|
||||||
|
<!-- 0代表原创 1代表转载 -->
|
||||||
|
<div v-if="detailsInfo.original === 0" class="text-[14px]">
|
||||||
|
原创作者
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex justify-end">
|
||||||
|
<div class="flex items-center font-bold px-1 justify-center w-24 h-10 rounded-full text-[#426af7] border-2 border-[#426af7] border-solid cursor-pointer" @click="onChangeAttention">
|
||||||
|
<n-icon v-if="detailsInfo.isAttention === 0" size="20" class="mr-2">
|
||||||
|
<PersonAddOutline />
|
||||||
|
</n-icon>
|
||||||
|
{{ detailsInfo.isAttention === 1 ? '已关注' : '关注' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center text-[#969798]">
|
||||||
|
<component
|
||||||
|
:is="CircleUser"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.bean }} </span>
|
||||||
|
<!-- <component
|
||||||
|
:is="HardDriveUpload"
|
||||||
|
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> 2 </span> -->
|
||||||
|
<component
|
||||||
|
:is="Play"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.modelRunNum }} </span>
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-2"> {{ selectUserInfo.modelDownloadNum }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 不支持的bg #b1b2b2 -->
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-center mt-4 w-full h-14 text-white bg-[#3c7af6] w-full rounded-md h-[80px] cursor-pointer"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="Play"
|
||||||
|
class="h-[20px] w-[20px] text-white menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-1"> 立即生图 </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#eceef4] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="Download"
|
||||||
|
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
|
||||||
|
/>
|
||||||
|
<span class="mr-1"> 下载 (122.22MB) </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div style="background: linear-gradient(135deg,#3cc9ff, #8fa6ff, 41%, #d8b4ff 74%,#326bff)" class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#fff] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]">
|
||||||
|
<component :is="Download" class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]" />
|
||||||
|
<span class="mr-1">
|
||||||
|
下载客户端
|
||||||
|
</span>
|
||||||
|
</div> -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="isDelete"
|
||||||
|
:mask-closable="false"
|
||||||
|
preset="dialog"
|
||||||
|
title="提示!"
|
||||||
|
content="确定要将工作流删除? 工作流删除后无法找回"
|
||||||
|
negative-text="取消"
|
||||||
|
positive-text="确认"
|
||||||
|
@negative-click="onDelete"
|
||||||
|
@positive-click="onDelete"
|
||||||
|
/>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.header-num {
|
||||||
|
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||||
|
}
|
||||||
|
.header-tag {
|
||||||
|
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||||
|
display: flex;
|
||||||
|
align-self: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab,
|
||||||
|
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab {
|
||||||
|
color: #949494;
|
||||||
|
}
|
||||||
|
.n-tabs.n-tabs--line-type .n-tabs-tab.n-tabs-tab--active,
|
||||||
|
.n-tabs.n-tabs--bar-type .n-tabs-tab.n-tabs-tab--active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-tabs .n-tabs-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #173eff 0%, #1b7dff 100%);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition:
|
||||||
|
left 0.2s var(--n-bezier),
|
||||||
|
max-width 0.2s var(--n-bezier),
|
||||||
|
opacity 0.3s var(--n-bezier),
|
||||||
|
background-color 0.3s var(--n-bezier);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,20 +1,21 @@
|
||||||
import { setup } from '@css-render/vue3-ssr'
|
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from '#app'
|
||||||
|
import { setup } from '@css-render/vue3-ssr'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
if (process.server) {
|
if (import.meta.server) {
|
||||||
const { collect } = setup(nuxtApp.vueApp)
|
const { collect } = setup(nuxtApp.vueApp)
|
||||||
const originalRenderMeta = nuxtApp.ssrContext?.renderMeta
|
const originalRenderMeta = nuxtApp.ssrContext?.renderMeta
|
||||||
nuxtApp.ssrContext!.renderMeta = () => {
|
nuxtApp.ssrContext!.renderMeta = () => {
|
||||||
if (!originalRenderMeta) {
|
if (!originalRenderMeta) {
|
||||||
return {
|
return {
|
||||||
headTags: collect()
|
headTags: collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const originalMeta = originalRenderMeta()
|
const originalMeta = originalRenderMeta()
|
||||||
if ('headTags' in originalMeta) {
|
if ('headTags' in originalMeta) {
|
||||||
originalMeta.headTags += collect()
|
originalMeta.headTags += collect()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
originalMeta.headTags = collect()
|
originalMeta.headTags = collect()
|
||||||
}
|
}
|
||||||
return originalMeta
|
return originalMeta
|
||||||
|
@ -30,11 +31,10 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||||
const match = styleString.match(/<style cssr-id="([^"]*)">([\s\S]*)/)
|
const match = styleString.match(/<style cssr-id="([^"]*)">([\s\S]*)/)
|
||||||
if (match) {
|
if (match) {
|
||||||
const id = match[1]
|
const id = match[1]
|
||||||
return { 'cssr-id': id, children: match[2] }
|
return { 'cssr-id': id, 'children': match[2] }
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
|
@ -0,0 +1,6 @@
|
||||||
|
// plugins/pinia-persist.ts
|
||||||
|
import { createPersistedState } from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.$pinia.use(createPersistedState)
|
||||||
|
})
|
|
@ -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)
|
||||||
|
})
|
|
@ -2,11 +2,12 @@ import { defineStore } from 'pinia'
|
||||||
|
|
||||||
export const useMenuStore = defineStore('menu', () => {
|
export const useMenuStore = defineStore('menu', () => {
|
||||||
const activeMenu = ref('/model-square')
|
const activeMenu = ref('/model-square')
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ path: '/model-square', icon: 'i-carbon-gallery', label: '模型广场' },
|
{ path: '/model-square', icon: 'i-carbon-gallery', label: '模型广场' },
|
||||||
|
{ path: '/picture-square', icon: 'i-carbon-light-filled', label: '作品灵感' },
|
||||||
// { path: '/works-inspire', icon: 'i-carbon-light-filled', label: '作品灵感' },
|
// { path: '/works-inspire', icon: 'i-carbon-light-filled', label: '作品灵感' },
|
||||||
// { path: '/workspace', icon: 'i-carbon-workspace', label: '工作台' },
|
// { path: '/workspace', icon: 'i-carbon-workspace', label: '工作流' },
|
||||||
|
{ path: '/work-square', icon: 'i-carbon-workspace', label: '工作流' },
|
||||||
// { path: '/web-ui', icon: 'i-carbon-image-search', label: '在线生图' },
|
// { path: '/web-ui', icon: 'i-carbon-image-search', label: '在线生图' },
|
||||||
// { path: '/comfy-ui', icon: 'i-carbon-image-search', label: '在线工作流' },
|
// { path: '/comfy-ui', icon: 'i-carbon-image-search', label: '在线工作流' },
|
||||||
// { path: '/training-lora', icon: 'i-carbon-machine-learning', label: '训练LoRA' },
|
// { path: '/training-lora', icon: 'i-carbon-machine-learning', label: '训练LoRA' },
|
||||||
|
@ -26,4 +27,24 @@ export const useMenuStore = defineStore('menu', () => {
|
||||||
menuItems,
|
menuItems,
|
||||||
setActiveMenu,
|
setActiveMenu,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
persist: {
|
||||||
|
key: 'mc-menu-store',
|
||||||
|
// paths: ['isLoggedIn', 'mymcname', 'token', 'userInfo'], // 只持久化 token
|
||||||
|
storage: import.meta.client ? localStorage : undefined,
|
||||||
|
},
|
||||||
|
// persist: {
|
||||||
|
// storage: {
|
||||||
|
// getItem(key) {
|
||||||
|
// return window.localStorage.getItem(key)
|
||||||
|
// },
|
||||||
|
// setItem(key, value) {
|
||||||
|
// window.localStorage.setItem(key, value)
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// serializer: {
|
||||||
|
// deserialize: parse,
|
||||||
|
// serialize: stringify,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
})
|
})
|
|
@ -8,15 +8,14 @@ export const useModalStore = defineStore('modal', () => {
|
||||||
|
|
||||||
function setLoginModal(modalRef: any) {
|
function setLoginModal(modalRef: any) {
|
||||||
loginModalRef.value = modalRef
|
loginModalRef.value = modalRef
|
||||||
console.log('Modal ref set:', modalRef)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoginModal() {
|
function showLoginModal() {
|
||||||
console.log('Showing login modal, ref:', loginModalRef.value)
|
|
||||||
if (loginModalRef.value?.showModal) {
|
if (loginModalRef.value?.showModal) {
|
||||||
loginModalRef.value.showModal()
|
loginModalRef.value.showModal()
|
||||||
isModalVisible.value = true
|
isModalVisible.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
console.warn('Login modal not initialized')
|
console.warn('Login modal not initialized')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +32,6 @@ export const useModalStore = defineStore('modal', () => {
|
||||||
isModalVisible,
|
isModalVisible,
|
||||||
setLoginModal,
|
setLoginModal,
|
||||||
showLoginModal,
|
showLoginModal,
|
||||||
hideLoginModal
|
hideLoginModal,
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,31 +1,48 @@
|
||||||
|
import defaultAvatar from '@/assets/img/default-avatar.png'
|
||||||
// stores/user.ts
|
// stores/user.ts
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
parse,
|
parse,
|
||||||
stringify,
|
stringify,
|
||||||
} from 'zipson'
|
} from 'zipson'
|
||||||
|
|
||||||
|
export interface UserInfoType {
|
||||||
|
nickName: string
|
||||||
|
avatar: string
|
||||||
|
brief: string
|
||||||
|
userId: number | string
|
||||||
|
}
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const isLoggedIn = ref(false)
|
const isLoggedIn = ref(false)
|
||||||
const token = ref('')
|
const token = ref('')
|
||||||
const userInfo = ref({})
|
// const userInfo = ref<UserInfoType>({})
|
||||||
|
const userInfo = ref<UserInfoType | null>(null)
|
||||||
function setToken(userToken: string) {
|
function setToken(userToken: string) {
|
||||||
isLoggedIn.value = true
|
isLoggedIn.value = true
|
||||||
token.value = userToken
|
token.value = userToken
|
||||||
localStorage.setItem('token', userToken)
|
// localStorage.setItem('token', userToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUserInfo(info: any) {
|
function setUserInfo(info: any) {
|
||||||
userInfo.value = info
|
userInfo.value = info
|
||||||
}
|
}
|
||||||
|
async function getUserInfo() {
|
||||||
|
const res = await request.get('/system/user/selectUserById', {
|
||||||
|
token: token.value,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
res.data.avatar = res.data.avatar || defaultAvatar
|
||||||
|
setUserInfo(res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 登出
|
// 登出
|
||||||
function logout() {
|
function logout() {
|
||||||
isLoggedIn.value = false
|
isLoggedIn.value = false
|
||||||
token.value = ''
|
token.value = ''
|
||||||
localStorage.removeItem('token')
|
userInfo.value = {} as UserInfoType
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
|
@ -36,7 +53,6 @@ export const useUserStore = defineStore('user', () => {
|
||||||
token.value = savedToken
|
token.value = savedToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
token,
|
token,
|
||||||
|
@ -45,22 +61,28 @@ export const useUserStore = defineStore('user', () => {
|
||||||
setToken,
|
setToken,
|
||||||
setUserInfo,
|
setUserInfo,
|
||||||
userInfo,
|
userInfo,
|
||||||
|
getUserInfo,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
persist: {
|
persist: {
|
||||||
storage: {
|
key: 'mc-user-store',
|
||||||
getItem(key) {
|
// paths: ['isLoggedIn', 'mymcname', 'token', 'userInfo'], // 只持久化 token
|
||||||
return window.localStorage.getItem(key)
|
storage: import.meta.client ? localStorage : undefined,
|
||||||
},
|
|
||||||
setItem(key, value) {
|
|
||||||
window.localStorage.setItem(key, value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
serializer: {
|
|
||||||
deserialize: parse,
|
|
||||||
serialize: stringify,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
// persist: {
|
||||||
|
// storage: {
|
||||||
|
// getItem(key) {
|
||||||
|
// return window.localStorage.getItem(key)
|
||||||
|
// },
|
||||||
|
// setItem(key, value) {
|
||||||
|
// window.localStorage.setItem(key, value)
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// serializer: {
|
||||||
|
// deserialize: parse,
|
||||||
|
// serialize: stringify,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
})
|
})
|
||||||
|
|
||||||
// import { defineStore } from 'pinia'
|
// import { defineStore } from 'pinia'
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export async function formatDate(timestamp: string) {
|
||||||
|
const date = new Date(timestamp)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { createDiscreteApi } from 'naive-ui'
|
import { createDiscreteApi } from 'naive-ui'
|
||||||
|
|
||||||
const { message, loadingBar } = createDiscreteApi(['message', 'loadingBar'])
|
const { message, loadingBar } = createDiscreteApi(['message', 'loadingBar'])
|
||||||
|
|
||||||
// 定义响应数据接口
|
// 定义响应数据接口
|
||||||
|
@ -28,9 +29,8 @@ class RequestHttp {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const isToken = (config.headers || {}).isToken === false
|
const isToken = (config.headers || {}).isToken === false
|
||||||
if (userStore.token && !isToken) {
|
if (userStore.token && !isToken) {
|
||||||
config.headers['Authorization'] = 'Bearer ' + userStore.token // 让每个请求携带自定义token 请根据实际情况自行修改
|
config.headers.Authorization = `Bearer ${userStore.token}` // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开启 loading
|
// 开启 loading
|
||||||
if (config.loading) {
|
if (config.loading) {
|
||||||
loadingBar.start()
|
loadingBar.start()
|
||||||
|
@ -39,14 +39,13 @@ class RequestHttp {
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
this.instance.interceptors.response.use(
|
this.instance.interceptors.response.use(
|
||||||
(response: AxiosResponse) => {
|
async (response: AxiosResponse) => {
|
||||||
const { data, config } = response
|
const { data, config } = response
|
||||||
|
|
||||||
// 关闭 loading
|
// 关闭 loading
|
||||||
if (config.loading) {
|
if (config.loading) {
|
||||||
loadingBar.finish()
|
loadingBar.finish()
|
||||||
|
@ -54,7 +53,24 @@ class RequestHttp {
|
||||||
|
|
||||||
// 处理业务状态码
|
// 处理业务状态码
|
||||||
if (data.code !== 200) {
|
if (data.code !== 200) {
|
||||||
message.error(data.message || '请求失败')
|
this.handleError(data.code)
|
||||||
|
// token过期以后,需要重新登录
|
||||||
|
if (data.code === 401) {
|
||||||
|
// const modalStore = useModalStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
// useUser.logout()
|
||||||
|
// modalStore.showLoginModal()
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
await request.post('/logout')
|
||||||
|
userStore.logout()
|
||||||
|
navigateTo('/model-square')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// message.error(data.message || '请求失败')
|
||||||
return Promise.reject(data)
|
return Promise.reject(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,12 +83,13 @@ class RequestHttp {
|
||||||
// 处理错误
|
// 处理错误
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
this.handleError(error.response.status)
|
this.handleError(error.response.status)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message.error('网络连接异常')
|
message.error('网络连接异常')
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +100,7 @@ class RequestHttp {
|
||||||
message.error('请求参数错误')
|
message.error('请求参数错误')
|
||||||
break
|
break
|
||||||
case 401:
|
case 401:
|
||||||
message.error('未登录或登录已过期')
|
// message.error('未登录或登录已过期')
|
||||||
break
|
break
|
||||||
case 403:
|
case 403:
|
||||||
message.error('没有权限')
|
message.error('没有权限')
|
||||||
|
@ -91,9 +108,9 @@ class RequestHttp {
|
||||||
case 404:
|
case 404:
|
||||||
message.error('请求的资源不存在')
|
message.error('请求的资源不存在')
|
||||||
break
|
break
|
||||||
case 500:
|
// case 500:
|
||||||
message.error('服务器错误')
|
// message.error('服务器错误')
|
||||||
break
|
// break
|
||||||
default:
|
default:
|
||||||
message.error('未知错误')
|
message.error('未知错误')
|
||||||
}
|
}
|
||||||
|
@ -104,14 +121,14 @@ class RequestHttp {
|
||||||
public get<T = any>(
|
public get<T = any>(
|
||||||
url: string,
|
url: string,
|
||||||
data?: Record<string, any>,
|
data?: Record<string, any>,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {},
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
// 如果 data 中包含 params,则使用 params 中的值
|
// 如果 data 中包含 params,则使用 params 中的值
|
||||||
const params = data?.params || data
|
const params = data?.params || data
|
||||||
|
|
||||||
return this.instance.get(url, {
|
return this.instance.get(url, {
|
||||||
...options,
|
...options,
|
||||||
params // 使用解构后的参数
|
params, // 使用解构后的参数
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,17 +136,22 @@ class RequestHttp {
|
||||||
public post<T = any>(
|
public post<T = any>(
|
||||||
url: string,
|
url: string,
|
||||||
data?: Record<string, any>,
|
data?: Record<string, any>,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {},
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
return this.instance.post(url, data, options)
|
return this.instance.post(url, data, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT 请求
|
// PUT 请求
|
||||||
|
// 发送PUT请求,返回Promise<ApiResponse<T>>
|
||||||
public put<T = any>(
|
public put<T = any>(
|
||||||
|
// 请求的URL
|
||||||
url: string,
|
url: string,
|
||||||
|
// 请求的数据
|
||||||
data?: Record<string, any>,
|
data?: Record<string, any>,
|
||||||
options: RequestOptions = {}
|
// 请求的配置
|
||||||
|
options: RequestOptions = {},
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
|
// 发送PUT请求
|
||||||
return this.instance.put(url, data, options)
|
return this.instance.put(url, data, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +159,7 @@ class RequestHttp {
|
||||||
public delete<T = any>(
|
public delete<T = any>(
|
||||||
url: string,
|
url: string,
|
||||||
params?: Record<string, any>,
|
params?: Record<string, any>,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {},
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
return this.instance.delete(url, { params, ...options })
|
return this.instance.delete(url, { params, ...options })
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import type { ApiResponse } from '~/types/api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分批次上传图片
|
||||||
|
* @param {File[]} files - 需要上传的图片文件数组
|
||||||
|
* @param {string} url - 上传接口的 URL
|
||||||
|
* @param {number} batchSize - 每批次上传的文件数量(默认为 3)
|
||||||
|
* @returns {Promise<{ success: boolean, message: string, data: any[] }>} - 返回上传结果
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function uploadImagesInBatches(files: File[], 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/imgUpload', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// const res = await mallProductFile(formData)
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
uploadResults.push(res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`图片上传失败: ${file.name}`, error)
|
||||||
|
uploadResults.push({ success: false, error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 279 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 860 B |
|
@ -0,0 +1,9 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.btn {
|
||||||
|
@apply px-4 py-2 rounded-lg font-medium transition-colors;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// // 全局变量
|
||||||
|
// $primary-color: #3eaf7c;
|
||||||
|
// $text-color: #2c3e50;
|
||||||
|
|
||||||
|
// :root {
|
||||||
|
// --primary: #{$primary-color};
|
||||||
|
// --text: #{$text-color};
|
||||||
|
// }
|
|
@ -0,0 +1,26 @@
|
||||||
|
// // Import Tailwind
|
||||||
|
// @use 'tailwind';
|
||||||
|
// @use 'variables';
|
||||||
|
|
||||||
|
// // 你的其他全局样式
|
||||||
|
// // Variables
|
||||||
|
// $primary-color: #3eaf7c;
|
||||||
|
// $text-color: #2c3e50;
|
||||||
|
|
||||||
|
// // Global styles
|
||||||
|
// :root {
|
||||||
|
// --primary: #{$primary-color};
|
||||||
|
// --text: #{$text-color};
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Custom SCSS
|
||||||
|
// body {
|
||||||
|
// font-family: 'Inter', sans-serif;
|
||||||
|
// color: var(--text);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // You can organize your SCSS with partials
|
||||||
|
// // @import 'components/buttons';
|
||||||
|
// // @import 'components/forms';
|
||||||
|
// // @import 'layouts/header';
|
||||||
|
// // @import 'layouts/footer';
|
|
@ -5,7 +5,6 @@ import nuxt from './.nuxt/eslint.config.mjs'
|
||||||
export default nuxt(
|
export default nuxt(
|
||||||
antfu(
|
antfu(
|
||||||
{
|
{
|
||||||
unocss: true,
|
|
||||||
formatters: true,
|
formatters: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
import { pwa } from './app/config/pwa'
|
import { pwa } from './app/config/pwa'
|
||||||
import { appDescription } from './app/constants/index'
|
import { appDescription } from './app/constants/index'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
modules: [
|
modules: [
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@unocss/nuxt',
|
|
||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
'@nuxtjs/color-mode',
|
'@nuxtjs/color-mode',
|
||||||
'@vite-pwa/nuxt',
|
'@vite-pwa/nuxt',
|
||||||
'@nuxt/eslint',
|
'@nuxt/eslint',
|
||||||
'nuxtjs-naive-ui',
|
'nuxtjs-naive-ui',
|
||||||
'@pinia-plugin-persistedstate/nuxt',
|
'@pinia-plugin-persistedstate/nuxt',
|
||||||
],
|
],
|
||||||
|
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
|
||||||
devtools: {
|
devtools: { enabled: true },
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
|
@ -36,56 +39,60 @@ export default defineNuxtConfig({
|
||||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// css: [
|
css: ['assets/scss/main.scss'],
|
||||||
// '@unocss/reset/tailwind.css',
|
|
||||||
// ],
|
|
||||||
|
|
||||||
colorMode: {
|
colorMode: { classSuffix: '' },
|
||||||
classSuffix: '',
|
|
||||||
|
alias: {
|
||||||
|
'@styles': './assets/styles',
|
||||||
|
'assets': './assets',
|
||||||
|
'@assets': './assets',
|
||||||
|
'public': './public',
|
||||||
|
'@': resolve(__dirname, './app'),
|
||||||
|
'~': resolve(__dirname, './app'),
|
||||||
|
'@constants': resolve(__dirname, './app/constants'),
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
transpile:
|
transpile: process.env.NODE_ENV === 'production'
|
||||||
process.env.NODE_ENV === 'production'
|
|
||||||
? ['naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer']
|
? ['naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer']
|
||||||
: ['@juggle/resize-observer'],
|
: ['@juggle/resize-observer'],
|
||||||
},
|
},
|
||||||
|
|
||||||
routeRules: {
|
routeRules: {
|
||||||
'/': { redirect: '/model-square' },
|
'/': { redirect: '/model-square' },
|
||||||
|
'/personal-center': { ssr: true },
|
||||||
|
'/model-square': { ssr: true },
|
||||||
|
'/member-center': { ssr: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
future: {
|
future: { compatibilityVersion: 4 },
|
||||||
compatibilityVersion: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
experimental: {
|
experimental: {
|
||||||
// when using generate, payload js assets included in sw precache manifest
|
|
||||||
// but missing on offline, disabling extraction it until fixed
|
|
||||||
payloadExtraction: false,
|
payloadExtraction: false,
|
||||||
renderJsonPayloads: true,
|
renderJsonPayloads: true,
|
||||||
typedPages: true,
|
typedPages: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-08-14',
|
compatibilityDate: '2025-01-23',
|
||||||
|
|
||||||
nitro: {
|
nitro: {
|
||||||
devProxy: {
|
devProxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
// 192.168.1.69 海洋
|
// target: 'http://1.13.246.108:8080', // 线上
|
||||||
// 192.168.2.22 代
|
// target: 'http://192.168.2.10:8080', // 代
|
||||||
target: `http://192.168.2.22:8080`,
|
target: 'http://192.168.1.69:8080', // 嗨
|
||||||
|
// target: 'https://2d1a399f.r27.cpolar.top', // 嗨
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
prependPath: true,
|
prependPath: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
esbuild: {
|
esbuild: {
|
||||||
options: {
|
options: { target: 'esnext' },
|
||||||
target: 'esnext',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
prerender: {
|
prerender: {
|
||||||
crawlLinks: false,
|
crawlLinks: false,
|
||||||
|
@ -93,6 +100,7 @@ export default defineNuxtConfig({
|
||||||
ignore: ['/hi'],
|
ignore: ['/hi'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
define: {
|
define: {
|
||||||
'process.env.DEBUG': false,
|
'process.env.DEBUG': false,
|
||||||
|
@ -114,7 +122,13 @@ export default defineNuxtConfig({
|
||||||
resolvers: [NaiveUiResolver()],
|
resolvers: [NaiveUiResolver()],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
// 避免 vite 热更新时出现警告
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: '@use "@assets/scss/_variables.scss";', // 使用 @import 而不是 @use
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// optimizeDeps: {
|
// optimizeDeps: {
|
||||||
// include: ['date-fns-tz/esm/formatInTimeZone']
|
// include: ['date-fns-tz/esm/formatInTimeZone']
|
||||||
// }
|
// }
|
||||||
|
@ -129,5 +143,16 @@ export default defineNuxtConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pinia: {
|
||||||
|
autoImports: ['defineStore', 'storeToRefs'],
|
||||||
|
},
|
||||||
|
|
||||||
pwa,
|
pwa,
|
||||||
|
|
||||||
|
tailwindcss: {
|
||||||
|
cssPath: 'assets/scss/_tailwind.scss',
|
||||||
|
configPath: 'tailwind.config.js',
|
||||||
|
exposeConfig: false,
|
||||||
|
viewer: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
26
package.json
|
@ -13,6 +13,19 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"typecheck": "vue-tsc --noEmit"
|
"typecheck": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
"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",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"lucide-vue-next": "^0.471.0",
|
||||||
|
"naive-ui": "^2.41.0",
|
||||||
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
|
"wangeditor": "^4.7.15",
|
||||||
|
"zipson": "^0.2.12"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^3.12.1",
|
"@antfu/eslint-config": "^3.12.1",
|
||||||
"@css-render/vue3-ssr": "^0.15.14",
|
"@css-render/vue3-ssr": "^0.15.14",
|
||||||
|
@ -25,8 +38,6 @@
|
||||||
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
|
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
|
||||||
"@pinia/nuxt": "^0.9.0",
|
"@pinia/nuxt": "^0.9.0",
|
||||||
"@types/node": "^22.10.6",
|
"@types/node": "^22.10.6",
|
||||||
"@unocss/eslint-config": "^0.65.3",
|
|
||||||
"@unocss/nuxt": "^0.65.3",
|
|
||||||
"@vite-pwa/nuxt": "^0.10.6",
|
"@vite-pwa/nuxt": "^0.10.6",
|
||||||
"@vueuse/nuxt": "^12.2.0",
|
"@vueuse/nuxt": "^12.2.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
@ -38,6 +49,8 @@
|
||||||
"nuxtjs-naive-ui": "^1.0.2",
|
"nuxtjs-naive-ui": "^1.0.2",
|
||||||
"pinia": "^2.3.0",
|
"pinia": "^2.3.0",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
|
"sass": "^1.83.4",
|
||||||
|
"sass-loader": "^16.0.4",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"unplugin-auto-import": "^19.0.0",
|
"unplugin-auto-import": "^19.0.0",
|
||||||
|
@ -49,14 +62,5 @@
|
||||||
"unplugin": "^2.1.0",
|
"unplugin": "^2.1.0",
|
||||||
"vite": "^6.0.6",
|
"vite": "^6.0.6",
|
||||||
"vite-plugin-inspect": "^0.10.6"
|
"vite-plugin-inspect": "^0.10.6"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@unocss/reset": "^65.4.0",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"date-fns-tz": "^3.2.0",
|
|
||||||
"lucide-vue-next": "^0.471.0",
|
|
||||||
"naive-ui": "^2.41.0",
|
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
|
||||||
"zipson": "^0.2.12"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3220
pnpm-lock.yaml
|
@ -1,45 +1,24 @@
|
||||||
// tailwind.config.js
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./components/**/*.{js,vue,ts}",
|
'./components/**/*.{js,vue,ts}',
|
||||||
"./layouts/**/*.vue",
|
'./layouts/**/*.vue',
|
||||||
"./pages/**/*.vue",
|
'./pages/**/*.vue',
|
||||||
"./plugins/**/*.{js,ts}",
|
'./plugins/**/*.{js,ts}',
|
||||||
"./app.vue",
|
'./app.vue',
|
||||||
"./error.vue",
|
'./error.vue',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
// 自定义颜色
|
|
||||||
colors: {
|
colors: {
|
||||||
primary: {
|
// 自定义颜色
|
||||||
50: '#f0f9ff',
|
primary: '#3eaf7c',
|
||||||
100: '#e0f2fe',
|
|
||||||
200: '#bae6fd',
|
|
||||||
300: '#7dd3fc',
|
|
||||||
400: '#38bdf8',
|
|
||||||
500: '#0ea5e9',
|
|
||||||
600: '#0284c7',
|
|
||||||
700: '#0369a1',
|
|
||||||
800: '#075985',
|
|
||||||
900: '#0c4a6e',
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
// 自定义字体
|
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
|
// 自定义字体
|
||||||
sans: ['Inter var', 'sans-serif'],
|
sans: ['Inter var', 'sans-serif'],
|
||||||
},
|
},
|
||||||
// 自定义断点
|
|
||||||
screens: {
|
|
||||||
'xs': '475px',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
// 禁用预加载(可选)
|
|
||||||
future: {
|
|
||||||
removeDeprecatedGapUtilities: true,
|
|
||||||
purgeLayersByDefault: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
},
|
||||||
"types": [
|
"types": [
|
||||||
"naive-ui/volar"
|
"naive-ui/volar"
|
||||||
]
|
]
|
||||||
|
|