update
parent
5db887bff3
commit
5fb4a9df0c
4
.env.dev
4
.env.dev
|
@ -1,2 +1,2 @@
|
|||
NODE_ENV = 'development'
|
||||
VITE_NUXT_ENV = 'http://1.13.246.108:8080/'
|
||||
NODE_ENV = 'dev'
|
||||
VITE_NUXT_ENV = 'http://www.mc158c.com'
|
2
.env.pro
2
.env.pro
|
@ -1,2 +1,2 @@
|
|||
NODE_ENV = 'production'
|
||||
VITE_NUXT_ENV = 'http://113.45.190.154:8080/'
|
||||
VITE_NUXT_ENV = 'http://www.mc158c.com'
|
|
@ -0,0 +1,4 @@
|
|||
# 生产环境配置
|
||||
VITE_APP_TITLE=魔创未来
|
||||
VITE_NUXT_ENVL=http://113.45.9.111
|
||||
VITE_APP_ENV=production
|
|
@ -82,3 +82,5 @@ npx degit antfu/vitesse-nuxt my-nuxt-app
|
|||
cd my-nuxt-app
|
||||
pnpm i # If you don't have pnpm installed, run: npm install -g pnpm
|
||||
```
|
||||
|
||||
<!-- box-shadow: 0 4px 14px 0 rgba(0, 0, 0, .1); -->
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -7,6 +7,7 @@ export {}
|
|||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
NAffix: typeof import('naive-ui')['NAffix']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
|
@ -21,17 +22,21 @@ declare module 'vue' {
|
|||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NImageGroup: typeof import('naive-ui')['NImageGroup']
|
||||
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NPagination: typeof import('naive-ui')['NPagination']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NProgress: typeof import('naive-ui')['NProgress']
|
||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
// import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui'
|
||||
import {
|
||||
ThumbsUp
|
||||
} from 'lucide-vue-next';
|
||||
ThumbsUp,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
|
@ -52,7 +52,7 @@ const urlList = ref({
|
|||
const likeList = ref({
|
||||
workflow: '/WorkFlowComment/commentLike?',
|
||||
pictrue: '/imageComment/commentLike?',
|
||||
model:'ModelComment/commentLike?'
|
||||
model: 'ModelComment/commentLike?',
|
||||
})
|
||||
// 发送评论
|
||||
const sendMessageList = ref({
|
||||
|
@ -79,11 +79,13 @@ const commentList = ref([])
|
|||
async function getCommentList() {
|
||||
try {
|
||||
let url = ''
|
||||
if(props.type === 'workflow'){
|
||||
if (props.type === 'workflow') {
|
||||
url = `${urlList.value[props.type]}commentId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||
}else if(props.type === 'pictrue'){
|
||||
}
|
||||
else if (props.type === 'pictrue') {
|
||||
url = `${urlList.value[props.type]}imageId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
url = `${urlList.value[props.type]}modelId=${props.detailsInfo.id}&sortType=${sortType.value}`
|
||||
}
|
||||
const res = await request.get(url)
|
||||
|
@ -115,16 +117,17 @@ function handleBlur(ele) {
|
|||
}
|
||||
}
|
||||
// 发送评论
|
||||
async function sendMessage(ele, index) {
|
||||
async function sendMessage(ele: any, index: number) {
|
||||
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'){
|
||||
if (props.type === 'pictrue') {
|
||||
commentParams.value.modelImageId = props.detailsInfo.id
|
||||
}else if(props.type === 'model'){
|
||||
}
|
||||
else if (props.type === 'model') {
|
||||
commentParams.value.modelId = props.detailsInfo.id
|
||||
}
|
||||
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
|
||||
|
@ -149,9 +152,10 @@ async function sendMessage(ele, index) {
|
|||
commentParams.value.parentId = ''
|
||||
commentParams.value.content = publicWord.value
|
||||
commentParams.value.replyUserId = ''
|
||||
if(props.type === 'pictrue'){
|
||||
if (props.type === 'pictrue') {
|
||||
commentParams.value.modelImageId = props.detailsInfo.id
|
||||
}else if(props.type === 'model'){
|
||||
}
|
||||
else if (props.type === 'model') {
|
||||
commentParams.value.modelId = props.detailsInfo.id
|
||||
}
|
||||
const res = await request.post(sendMessageList.value[props.type], commentParams.value)
|
||||
|
@ -173,9 +177,9 @@ async function sendMessage(ele, index) {
|
|||
}
|
||||
}
|
||||
|
||||
//查询条数
|
||||
// 查询条数
|
||||
async function getCommentNum() {
|
||||
if(props.type !== 'pictrue'){
|
||||
if (props.type !== 'pictrue') {
|
||||
try {
|
||||
const res = await request.get(`${commentNumUrl.value[props.type]}=${props.detailsInfo.id}`)
|
||||
if (res.code === 200) {
|
||||
|
@ -190,7 +194,7 @@ async function getCommentNum() {
|
|||
getCommentNum()
|
||||
|
||||
// 点赞/取消点赞
|
||||
async function handleFocus(item:any) {
|
||||
async function handleFocus(item: any) {
|
||||
await request.get(`${likeList.value[props.type]}commentId=${item.commentId}`)
|
||||
if (item.isLike === 0) {
|
||||
item.isLike = 1
|
||||
|
@ -205,14 +209,14 @@ async function handleFocus(item:any) {
|
|||
}
|
||||
|
||||
// 显示回复框
|
||||
function handleMessage(item:any) {
|
||||
function handleMessage(item: any) {
|
||||
if (!item.isShowInput) {
|
||||
item.word = ''
|
||||
}
|
||||
item.isShowInput = !item.isShowInput
|
||||
}
|
||||
// 删除评论
|
||||
async function handleDel(item:any) {
|
||||
async function handleDel(item: any) {
|
||||
try {
|
||||
const res = await request.get(`${deleteList.value[props.type]}commentId=${item.commentId}`)
|
||||
if (res.code === 200) {
|
||||
|
@ -239,11 +243,11 @@ function changeType(type: string) {
|
|||
<div class="left text-[20px] mr-2">
|
||||
讨论
|
||||
</div>
|
||||
<div class="text-[#999]" v-if="props.type !== 'pictrue'">
|
||||
<div v-if="props.type !== 'pictrue'" class="text-[#999]">
|
||||
{{ commentCount }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" v-if="props.type !== 'pictrue'">
|
||||
<div v-if="props.type !== 'pictrue'" class="flex items-center">
|
||||
<div class="cursor-pointer" :class="sortType === 0 ? '' : 'text-[#999]'" @click="changeType(0)">
|
||||
最热
|
||||
</div>|
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import { commonApi } from '@/api/common'
|
||||
|
||||
import { uploadImagesInBatches } from '@/utils/uploadImg.ts'
|
||||
import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NRadio, NRadioGroup, NSelect, NSpace, NTextarea, useMessage } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:show', value: boolean): void
|
||||
(e: 'success'): void
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const loading = ref(false)
|
||||
const formValue = ref({
|
||||
imageUrl: '',
|
||||
communityName: '',
|
||||
communityTag: null,
|
||||
type: '1',
|
||||
price: 0,
|
||||
validityDay: null,
|
||||
description: '', // 添加描述字段
|
||||
})
|
||||
|
||||
const rules = {
|
||||
imageUrl: {
|
||||
required: true,
|
||||
message: '请上传封面',
|
||||
trigger: 'change',
|
||||
},
|
||||
communityName: {
|
||||
required: true,
|
||||
message: '请输入星球名称',
|
||||
},
|
||||
communityTag: {
|
||||
required: true,
|
||||
message: '请选择星球标签',
|
||||
},
|
||||
validityDay: {
|
||||
required: true,
|
||||
message: '请选择有效期',
|
||||
},
|
||||
}
|
||||
|
||||
// 获取标签列表
|
||||
const tagList = ref<{ dictLabel: string, dictValue: string }[]>([])
|
||||
async function getDictType() {
|
||||
try {
|
||||
const res = await commonApi.dictType({ type: 'community_tag' })
|
||||
if (res.code === 200)
|
||||
tagList.value = res.data
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
getDictType()
|
||||
|
||||
const validTimeOptions = [
|
||||
{ label: '1年', value: '1' },
|
||||
{ label: '2年', value: '2' },
|
||||
{ label: '3年', value: '3' },
|
||||
]
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.show,
|
||||
set: (value: boolean) => emit('update:show', value),
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
const params = {
|
||||
...formValue.value,
|
||||
price: formValue.value.type === '1' ? formValue.value.price : 0,
|
||||
}
|
||||
const res = await request.post('/community/addCommunity', params)
|
||||
if (res.code === 200) {
|
||||
message.success('创建成功')
|
||||
emit('success')
|
||||
emit('refresh')
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:show', false)
|
||||
formValue.value = {
|
||||
name: '',
|
||||
tags: [],
|
||||
type: '',
|
||||
price: 0,
|
||||
validTime: 0,
|
||||
description: '', // 重置描述字段
|
||||
}
|
||||
}
|
||||
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)
|
||||
formValue.value.imageUrl = pictureResultList[0].url
|
||||
;(event.target as HTMLInputElement).value = ''
|
||||
}
|
||||
catch (error: any) {
|
||||
message.error('图片上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="modelValue"
|
||||
preset="dialog"
|
||||
title="创建星球"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
style="width: 420px"
|
||||
:style="{
|
||||
'width': '420px',
|
||||
'--n-title-text-align': 'center',
|
||||
}"
|
||||
@close="handleClose"
|
||||
>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
label-placement="top"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
size="large"
|
||||
>
|
||||
<!-- 上传封面 -->
|
||||
<div class="flex justify-center">
|
||||
<NFormItem path="imageUrl" style="margin-bottom: 0">
|
||||
<div
|
||||
class="w-[120px] h-[120px] bg-[#f7f8fa] rounded-lg flex items-center justify-center cursor-pointer mb-6 overflow-hidden"
|
||||
@click="handlePictureInput"
|
||||
>
|
||||
<img
|
||||
v-if="formValue.imageUrl"
|
||||
:src="formValue.imageUrl"
|
||||
class="w-full h-full object-cover"
|
||||
alt="封面"
|
||||
>
|
||||
<span v-else>上传封面</span>
|
||||
</div>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<!-- 星球名称 -->
|
||||
<NFormItem label="星球名称" path="communityName">
|
||||
<NInput v-model:value="formValue.communityName" placeholder="请输入星球名称" />
|
||||
</NFormItem>
|
||||
|
||||
<!-- 星球标签 -->
|
||||
<NFormItem label="星球标签" path="communityTag">
|
||||
<NSelect
|
||||
v-model:value="formValue.communityTag"
|
||||
:options="tagList.map(item => ({ label: item.dictLabel, value: item.dictValue }))"
|
||||
placeholder="请选择星球标签"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 描述 -->
|
||||
<!-- <NFormItem label="描述" path="description">
|
||||
<NTextarea
|
||||
v-model:value="formValue.description"
|
||||
placeholder="请输入星球描述"
|
||||
:autosize="{
|
||||
minRows: 3,
|
||||
maxRows: 5,
|
||||
}"
|
||||
/>
|
||||
</NFormItem> -->
|
||||
|
||||
<NFormItem label="描述" path="description">
|
||||
<NInput
|
||||
v-model:value="formValue.description"
|
||||
placeholder="请输入星球描述"
|
||||
type="textarea"
|
||||
:autosize="{
|
||||
minRows: 3,
|
||||
maxRows: 5,
|
||||
}"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 星球类型 -->
|
||||
<NFormItem label="星球类型" path="type">
|
||||
<NRadioGroup v-model:value="formValue.type" name="type">
|
||||
<NSpace>
|
||||
<NRadio value="1">
|
||||
付费星球
|
||||
</NRadio>
|
||||
<NRadio value="0">
|
||||
免费星球
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 设置加入费用 -->
|
||||
<NFormItem v-if="formValue.type === '1'" label="设置加入费用" path="price">
|
||||
<NInputNumber
|
||||
v-model:value="formValue.price"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
:step="1"
|
||||
placeholder="请输入加入费用"
|
||||
>
|
||||
<template #suffix>
|
||||
积分
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 设置成员加入有效期 -->
|
||||
<NFormItem label="设置成员加入有效期" path="validityDay">
|
||||
<NSelect
|
||||
v-model:value="formValue.validityDay"
|
||||
:options="validTimeOptions"
|
||||
placeholder="请选择有效期"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #action>
|
||||
<NSpace>
|
||||
<NButton @click="handleClose">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">
|
||||
创建
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</template>
|
|
@ -0,0 +1,149 @@
|
|||
# 创建新文件
|
||||
<script setup lang="ts">
|
||||
import { Download } from 'lucide-vue-next'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
interface Props {
|
||||
communityId: number | string
|
||||
tenantId: number | string
|
||||
show?: boolean
|
||||
}
|
||||
|
||||
interface FileItem {
|
||||
fileName: string
|
||||
fileSize: string
|
||||
uploadTime: string
|
||||
uploadBy: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
'update:show': [value: boolean]
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const fileList = ref<FileItem[]>([])
|
||||
const currentFile = ref<FileItem | null>(null)
|
||||
|
||||
// 获取文件列表
|
||||
async function getFileList() {
|
||||
try {
|
||||
const res = await request.post('/communityFile/list', {
|
||||
tenantId: props.tenantId,
|
||||
communityId: props.communityId,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
fileList.value = res.rows
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
message.error('获取文件列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件下载
|
||||
async function handleDownload(file: FileItem) {
|
||||
if (!file.url) {
|
||||
message.error('文件链接不存在')
|
||||
return
|
||||
}
|
||||
|
||||
currentFile.value = file
|
||||
loading.value = true
|
||||
try {
|
||||
const link = document.createElement('a')
|
||||
link.href = file.url
|
||||
link.download = file.fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
message.success('下载成功')
|
||||
}
|
||||
catch (error) {
|
||||
message.error('下载失败')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
currentFile.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取文件列表
|
||||
onMounted(() => {
|
||||
if (props.show) {
|
||||
getFileList()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 show 变化,当显示弹框时获取文件列表
|
||||
watch(() => props.show, (newVal) => {
|
||||
if (newVal) {
|
||||
getFileList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
:show="props.show"
|
||||
:mask-closable="true"
|
||||
preset="dialog"
|
||||
class="w-[600px]"
|
||||
:show-icon="false"
|
||||
title="文件下载"
|
||||
@update:show="emit('update:show', $event)"
|
||||
>
|
||||
<div class="py-4">
|
||||
<div v-if="fileList.length > 0" class="space-y-3">
|
||||
<div
|
||||
v-for="file in fileList"
|
||||
:key="file.fileName"
|
||||
class="flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-base font-medium mb-1 truncate">
|
||||
{{ file.fileName }}
|
||||
</div>
|
||||
<div class="text-gray-500 text-sm">
|
||||
{{ file.fileSize }} · {{ file.uploadBy }} · {{ file.uploadTime }}
|
||||
</div>
|
||||
</div>
|
||||
<n-button
|
||||
type="primary"
|
||||
class="ml-4 !px-6"
|
||||
:loading="currentFile?.fileName === file.fileName && loading"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handleDownload(file)"
|
||||
>
|
||||
<template #icon>
|
||||
<Download class="w-4 h-4 mr-1" />
|
||||
</template>
|
||||
下载
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-500 py-8">
|
||||
暂无文件
|
||||
</div>
|
||||
</div>
|
||||
<template #action>
|
||||
<div class="flex justify-end">
|
||||
<n-button
|
||||
size="large"
|
||||
class="!px-8"
|
||||
@click="emit('update:show', false)"
|
||||
>
|
||||
关闭
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
|
@ -0,0 +1,320 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import { commonApi } from '@/api/common'
|
||||
import { uploadImagesInBatches } from '@/utils/uploadImg'
|
||||
import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NRadio, NRadioGroup, NSelect, NSpace, useMessage } from 'naive-ui'
|
||||
import { computed, defineEmits, ref, watchEffect } from 'vue'
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
communityId: string | number
|
||||
tenantId: string | number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
communityId: '',
|
||||
tenantId: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:show', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const message = useMessage()
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const loading = ref(false)
|
||||
const formValue = ref({
|
||||
imageUrl: '',
|
||||
communityName: '',
|
||||
communityTag: null,
|
||||
type: '1',
|
||||
price: 0,
|
||||
validityDay: null,
|
||||
description: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
imageUrl: {
|
||||
required: true,
|
||||
message: '请上传封面',
|
||||
trigger: 'change',
|
||||
},
|
||||
communityName: {
|
||||
required: true,
|
||||
message: '请输入星球名称',
|
||||
},
|
||||
communityTag: {
|
||||
required: true,
|
||||
message: '请选择星球标签',
|
||||
},
|
||||
validityDay: {
|
||||
required: true,
|
||||
message: '请选择有效期',
|
||||
},
|
||||
}
|
||||
|
||||
// 获取标签列表
|
||||
const tagList = ref<{ dictLabel: string, dictValue: string }[]>([])
|
||||
async function getDictType() {
|
||||
try {
|
||||
const res = await commonApi.dictType({ type: 'community_tag' })
|
||||
if (res.code === 200)
|
||||
tagList.value = res.data
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
getDictType()
|
||||
|
||||
// 获取星球详情
|
||||
async function getCommunityDetail() {
|
||||
try {
|
||||
const res = await request.get(`/community/detail?communityId=${props.communityId}&tenantId=${props.tenantId}`)
|
||||
if (res.code === 200) {
|
||||
const { imageUrl, communityName, communityTag, type, price, validityDay, description, id } = res.data
|
||||
formValue.value = {
|
||||
imageUrl,
|
||||
communityName,
|
||||
communityTag: communityTag.toString(),
|
||||
type: type.toString(),
|
||||
price,
|
||||
id,
|
||||
validityDay,
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
message.error('获取星球详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 id 变化,获取详情
|
||||
watchEffect(() => {
|
||||
if (props.show && props.communityId && props.tenantId)
|
||||
getCommunityDetail()
|
||||
})
|
||||
|
||||
const validTimeOptions = [
|
||||
{ label: '1年', value: 1 },
|
||||
{ label: '2年', value: 2 },
|
||||
{ label: '3年', value: 3 },
|
||||
]
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.show,
|
||||
set: (value: boolean) => emit('update:show', value),
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
const params = {
|
||||
...formValue.value,
|
||||
price: formValue.value.type === '1' ? formValue.value.price : 0,
|
||||
// communityId: props.communityId,
|
||||
// tenantId: props.tenantId,
|
||||
}
|
||||
const res = await request.post('/community/edit', params)
|
||||
if (res.code === 200) {
|
||||
message.success('更新成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:show', false)
|
||||
formValue.value = {
|
||||
imageUrl: '',
|
||||
communityName: '',
|
||||
communityTag: null,
|
||||
type: '1',
|
||||
price: 0,
|
||||
validityDay: null,
|
||||
description: '',
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
formValue.value.imageUrl = pictureResultList[0].url
|
||||
;(event.target as HTMLInputElement).value = ''
|
||||
}
|
||||
catch (error: any) {
|
||||
message.error('图片上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="modelValue"
|
||||
preset="dialog"
|
||||
title="编辑星球"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
style="width: 420px"
|
||||
:style="{
|
||||
'width': '420px',
|
||||
'--n-title-text-align': 'center',
|
||||
}"
|
||||
@close="handleClose"
|
||||
>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
label-placement="top"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
size="large"
|
||||
class="edit-planet-form"
|
||||
>
|
||||
<!-- 上传封面 -->
|
||||
<div class="flex justify-center">
|
||||
<NFormItem path="imageUrl" style="margin-bottom: 0">
|
||||
<div
|
||||
class="w-[120px] h-[120px] bg-[#f7f8fa] rounded-lg flex items-center justify-center cursor-pointer mb-3 overflow-hidden"
|
||||
@click="handlePictureInput"
|
||||
>
|
||||
<img
|
||||
v-if="formValue.imageUrl"
|
||||
:src="formValue.imageUrl"
|
||||
class="w-full h-full object-cover"
|
||||
alt="封面"
|
||||
>
|
||||
<span v-else>上传封面</span>
|
||||
</div>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<!-- 星球名称 -->
|
||||
<NFormItem label="星球名称" path="communityName">
|
||||
<NInput v-model:value="formValue.communityName" placeholder="请输入星球名称" />
|
||||
</NFormItem>
|
||||
|
||||
<!-- 星球标签 -->
|
||||
<NFormItem label="星球标签" path="communityTag">
|
||||
<NSelect
|
||||
v-model:value="formValue.communityTag"
|
||||
:options="tagList.map(item => ({ label: item.dictLabel, value: item.dictValue }))"
|
||||
placeholder="请选择星球标签"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 描述 -->
|
||||
<NFormItem label="描述" path="description">
|
||||
<NInput
|
||||
v-model:value="formValue.description"
|
||||
placeholder="请输入星球描述"
|
||||
type="textarea"
|
||||
:autosize="{
|
||||
minRows: 3,
|
||||
maxRows: 5,
|
||||
}"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 星球类型 -->
|
||||
<NFormItem label="星球类型" path="type">
|
||||
<NRadioGroup v-model:value="formValue.type" name="type">
|
||||
<NSpace>
|
||||
<NRadio value="1">
|
||||
付费星球
|
||||
</NRadio>
|
||||
<NRadio value="0">
|
||||
免费星球
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 设置加入费用 -->
|
||||
<NFormItem v-if="formValue.type === '1'" label="设置加入费用" path="price">
|
||||
<NInputNumber
|
||||
v-model:value="formValue.price"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
:step="1"
|
||||
placeholder="请输入加入费用"
|
||||
>
|
||||
<template #suffix>
|
||||
积分
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
|
||||
<!-- 设置成员加入有效期 -->
|
||||
<NFormItem label="设置成员加入有效期" path="validityDay">
|
||||
<NSelect
|
||||
v-model:value="formValue.validityDay"
|
||||
:options="validTimeOptions"
|
||||
placeholder="请选择有效期"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #action>
|
||||
<NSpace>
|
||||
<NButton @click="handleClose">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">
|
||||
保存
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.edit-planet-form :deep(.n-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.edit-planet-form :deep(.n-form-item:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { ref } from 'vue';
|
||||
import { uploadImagesInBatches } from '../utils/uploadImg.ts';
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo
|
||||
const message = useMessage()
|
||||
|
@ -54,26 +55,26 @@ async function handlePictureChange(event: Event) {
|
|||
}
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
async function saveInfo() {
|
||||
formRef.value?.validate(async(errors:any) => {
|
||||
formRef.value?.validate(async (errors: any) => {
|
||||
if (!errors) {
|
||||
const res1 = await request.post('/system/user/updateUserInfo', ruleForm.value)
|
||||
if(res1.code === 200){
|
||||
const res = await request.get('/system/user/selectUserById')
|
||||
if (res.code === 200) {
|
||||
message.success('修改成功!')
|
||||
userStore.setUserInfo(res.data)
|
||||
if (res1.code === 200) {
|
||||
await userStore.getUserInfo()
|
||||
onCloseModel()
|
||||
}
|
||||
// const res = await request.get('/system/user/selectUserById')
|
||||
// if (res.code === 200) {
|
||||
// message.success('修改成功!')
|
||||
// userStore.setUserInfo(res.data)
|
||||
// onCloseModel()
|
||||
// }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
watch(
|
||||
() => userStore.userInfo, // 监听 userInfo
|
||||
(newUserInfo:any) => {
|
||||
(newUserInfo: any) => {
|
||||
if (newUserInfo) {
|
||||
ruleForm.value.nickName = newUserInfo.nickName
|
||||
ruleForm.value.avatar = newUserInfo.avatar
|
||||
|
|
|
@ -1,141 +1,147 @@
|
|||
<script setup lang="ts">
|
||||
// 输入框搜索
|
||||
import { headerRole } from "@/constants/index";
|
||||
import { headerRole } from '@/constants/index'
|
||||
import {
|
||||
Bell,
|
||||
CirclePlus,
|
||||
GraduationCap,
|
||||
HardDriveUpload,
|
||||
Image,
|
||||
Monitor,
|
||||
Workflow
|
||||
} from "lucide-vue-next";
|
||||
import { NConfigProvider, NMessageProvider } from "naive-ui";
|
||||
Bell,
|
||||
CirclePlus,
|
||||
GraduationCap,
|
||||
HardDriveUpload,
|
||||
Image,
|
||||
Monitor,
|
||||
Workflow,
|
||||
} from 'lucide-vue-next'
|
||||
import { NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||
|
||||
// import { AtCircle } from '@vicons/ionicons5'
|
||||
import { NIcon } from "naive-ui";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const modalStore = useModalStore();
|
||||
const currentUseRoute = ref("");
|
||||
const isShowPublishPicture = ref<boolean>(false);
|
||||
const PublishPictureRef = ref<Payment | null>(null);
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const modalStore = useModalStore()
|
||||
const currentUseRoute = ref('')
|
||||
const isShowPublishPicture = ref<boolean>(false)
|
||||
const PublishPictureRef = ref<Payment | null>(null)
|
||||
const publishPicture = ref({
|
||||
title: "",
|
||||
title: '',
|
||||
tags: [],
|
||||
description: "",
|
||||
description: '',
|
||||
imagePaths: [],
|
||||
});
|
||||
})
|
||||
watch(
|
||||
() => route.path, // 监听 route.path 的变化
|
||||
(newPath) => {
|
||||
currentUseRoute.value = newPath;
|
||||
(newPath: any) => {
|
||||
currentUseRoute.value = newPath
|
||||
},
|
||||
{ immediate: true } // 立即执行一次
|
||||
);
|
||||
{ immediate: true }, // 立即执行一次
|
||||
)
|
||||
function hasItem(path: string, list: any) {
|
||||
return !list.includes(path);
|
||||
return !list.includes(path)
|
||||
}
|
||||
const searchText = ref("");
|
||||
const searchText = ref('')
|
||||
function onSearch(value: any) {
|
||||
console.log("搜索:", value);
|
||||
console.log('搜索:', value)
|
||||
// 执行搜索逻辑
|
||||
}
|
||||
function initSearch(){
|
||||
console.log( 'initSearch');
|
||||
}
|
||||
|
||||
// 用户下拉选项
|
||||
const notificationOptions = [
|
||||
{
|
||||
label: "系统通知",
|
||||
key: "system",
|
||||
},
|
||||
{
|
||||
label: "互动消息",
|
||||
key: "interaction",
|
||||
},
|
||||
];
|
||||
// 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",
|
||||
label: '模型',
|
||||
key: 'publish-model',
|
||||
icon: renderIcon(HardDriveUpload),
|
||||
},
|
||||
{
|
||||
label: "图片",
|
||||
key: "picture",
|
||||
label: '图片',
|
||||
key: 'picture',
|
||||
icon: renderIcon(Image),
|
||||
},
|
||||
{
|
||||
label: "工作流",
|
||||
key: "publish-workflow",
|
||||
label: '工作流',
|
||||
key: 'publish-workflow',
|
||||
icon: renderIcon(Workflow),
|
||||
},
|
||||
];
|
||||
]
|
||||
const userOptions = ref([
|
||||
{
|
||||
label: "我的模型",
|
||||
key: "0",
|
||||
label: '我的模型',
|
||||
key: '0',
|
||||
},
|
||||
{
|
||||
label: "我的作品",
|
||||
key: "2",
|
||||
label: '我的作品',
|
||||
key: '2',
|
||||
},
|
||||
{
|
||||
label: "我的点赞",
|
||||
key: "like",
|
||||
label: '我的点赞',
|
||||
key: 'like',
|
||||
},
|
||||
// {
|
||||
// label: "账号设置",
|
||||
// key: "userSettings",
|
||||
// },
|
||||
{
|
||||
label: "退出登录",
|
||||
key: "logout",
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
// 用户下拉选项
|
||||
async function handleUserSelect(key: string) {
|
||||
if (key === "logout") {
|
||||
if (key === 'logout') {
|
||||
try {
|
||||
await request.post("/logout");
|
||||
userStore.logout();
|
||||
navigateTo("/model-square");
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
navigateTo('/model-square')
|
||||
}
|
||||
}else if(key === "like"){
|
||||
catch (error) {
|
||||
console.error('Logout failed:', error)
|
||||
}
|
||||
}
|
||||
else if (key === 'like') {
|
||||
router.push(`/personal-center?type=${key}`)
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
router.push(`/personal-center?status=${key}`)
|
||||
}
|
||||
}
|
||||
// 发布下拉选项
|
||||
async function handlePublishSelect(key: string) {
|
||||
if (!userStore?.userInfo.name)
|
||||
return
|
||||
if (!userStore.isLoggedIn) {
|
||||
modalStore.showLoginModal();
|
||||
} else {
|
||||
if (key === "picture") {
|
||||
isShowPublishPicture.value = true;
|
||||
if (PublishPictureRef.value) {
|
||||
PublishPictureRef.value.isVisible = true;
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
} else {
|
||||
const baseUrl = window.location.origin;
|
||||
window.open(`${baseUrl}/${key}?type=add`, "_blank", "noopener,noreferrer");
|
||||
else {
|
||||
if (key === 'picture') {
|
||||
isShowPublishPicture.value = true
|
||||
if (PublishPictureRef.value) {
|
||||
PublishPictureRef.value.isVisible = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
const baseUrl = window.location.origin
|
||||
debugger
|
||||
window.open(`${baseUrl}/${key}?type=add`, '_blank', 'noopener,noreferrer')
|
||||
// router.push({
|
||||
// path: `/${key}`,
|
||||
// query: {
|
||||
|
@ -146,37 +152,37 @@ async function handlePublishSelect(key: string) {
|
|||
}
|
||||
}
|
||||
function closePublishImg() {
|
||||
isShowPublishPicture.value = false;
|
||||
isShowPublishPicture.value = false
|
||||
if (PublishPictureRef.value) {
|
||||
PublishPictureRef.value.isVisible = false;
|
||||
PublishPictureRef.value.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
modalStore.showLoginModal();
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
|
||||
const msgList = ref([]);
|
||||
const isRead = ref('0')
|
||||
const msgList = ref([])
|
||||
async function getAllMessage() {
|
||||
try {
|
||||
const res = await request.get("/advice/getAllMsg");
|
||||
if (res.code == 200) {
|
||||
msgList.value = res.data;
|
||||
const res = await request.get('/advice/getAllMsg')
|
||||
if (res.code === 200) {
|
||||
msgList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
// getAllMessage();
|
||||
|
||||
// 跳转到消息详情
|
||||
function toDetail() {
|
||||
const baseUrl = window.location.origin;
|
||||
window.open(`${baseUrl}/message`, "_blank", "noopener,noreferrer");
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/message`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
onMounted(() => {});
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -188,9 +194,17 @@ onMounted(() => {});
|
|||
<!-- Logo 区域调整 -->
|
||||
<div class="flex min-w-[130px] items-center gap-3 pr-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center cursor-pointer" @click="handleTabClick('/')">
|
||||
<img
|
||||
src="@/assets/img/logo.png"
|
||||
alt="魔创未来"
|
||||
class="h-8 w-8"
|
||||
>
|
||||
<span class="text-[#328AFE] text-xl font-bold ml-2">魔创未来</span>
|
||||
</div>
|
||||
<NuxtLink to="/" class="text-xl font-semibold tracking-tight no-underline">
|
||||
<!-- <img src="/vite.png" alt="Logo" class="h-9 w-9" /> -->
|
||||
魔创未来
|
||||
<!-- 魔创未来 -->
|
||||
<!-- <img
|
||||
src="https://liblibai-web-static.liblib.cloud/liblibai_v4_online/static/_next/static/images/icon-logo.e3ce24f316fb81dbde1cafc3bf956080.svg"
|
||||
alt=""
|
||||
|
@ -287,13 +301,15 @@ onMounted(() => {});
|
|||
>
|
||||
{{ item.content }}
|
||||
</n-ellipsis>
|
||||
<div class="text-[12px] text-gray-400">{{ item.createTime }}</div>
|
||||
<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>
|
||||
class="w-2 h-2 bg-[#ea5049] rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import phoneImg from '@/assets/img/phone.png'
|
||||
import wechatImg from '@/assets/img/wechat.png'
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
|
@ -19,7 +19,7 @@ const rules: FormRules = {
|
|||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{ len: 4, message: '验证码长度为4位', trigger: 'blur' },
|
||||
{ len: 6, message: '验证码长度为6位', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
// 响应式状态
|
||||
|
@ -93,13 +93,19 @@ async function handleValidateClick(e: MouseEvent) {
|
|||
const res = await request.post<ApiResponse<UserData>>('/phoneLogin', {
|
||||
...formData.value,
|
||||
})
|
||||
userStore.setToken(res.token)
|
||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
token: res.token,
|
||||
})
|
||||
userStore.setUserInfo(res1.user)
|
||||
await userStore.setToken(res.token)
|
||||
await userStore.getUserInfo()
|
||||
// const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
// token: res.token,
|
||||
// })
|
||||
// await userStore.getUserInfo()
|
||||
// const res1 = await request.get('/system/user/selectUserById', {
|
||||
// token: res.token,
|
||||
// })
|
||||
// debugger
|
||||
// userStore.setUserInfo(res1.user)
|
||||
onCloseLogin()
|
||||
window.location.reload();
|
||||
window.location.reload()
|
||||
// window.location.href = '/'
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
FileChartColumnIncreasing,
|
||||
} from 'lucide-vue-next'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import DownPlanetFile from './DownPlanetFile.vue'
|
||||
import QuestionPublish from './QuestionPublish.vue'
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
interface Props {
|
||||
communityId: string | number
|
||||
tenantId: string | number
|
||||
}
|
||||
const planetInfo = ref({
|
||||
imageUrl: '',
|
||||
communityName: '',
|
||||
description: '',
|
||||
createDay: 0,
|
||||
publishNum: 0,
|
||||
})
|
||||
|
||||
const defaultCover = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU4IiBoZWlnaHQ9IjI1OCIgdmlld0JveD0iMCAwIDI1OCAyNTgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjI1OCIgaGVpZ2h0PSIyNTgiIGZpbGw9IiNmMmYzZjUiLz48cGF0aCBkPSJNMTI5IDg2QzExMC43NzQgODYgOTYgMTAwLjc3NCA5NiAxMTlDOTYgMTM3LjIyNiAxMTAuNzc0IDE1MiAxMjkgMTUyQzE0Ny4yMjYgMTUyIDE2MiAxMzcuMjI2IDE2MiAxMTlDMTYyIDEwMC43NzQgMTQ3LjIyNiA4NiAxMjkgODZaTTg0LjU3MTQgMTUyQzc1LjE0MjkgMTUyIDY3LjQyODYgMTU5LjcxNCA2Ny40Mjg2IDE2OVYxNzQuNzE0QzY3LjQyODYgMTc0LjcxNCA4NC41NzE0IDE5MiAxMjkgMTkyQzE3My40MjkgMTkyIDE5MC41NzEgMTc0LjcxNCAxOTAuNTcxIDE3NC43MTRWMTY5QzE5MC41NzEgMTU5LjcxNCAxODIuODU3IDE1MiAxNzMuNDI5IDE1MkMxNjQuNTcxIDE1MiAxMjkgMTUyIDg0LjU3MTQgMTUyWiIgZmlsbD0iI2U1ZTZlYiIvPjwvc3ZnPg=='
|
||||
|
||||
// 获取星球详情
|
||||
async function getCommunityDetail() {
|
||||
try {
|
||||
const res = await request.get(`/community/detail?communityId=${props.communityId}&tenantId=${props.tenantId}`)
|
||||
if (res.code === 200) {
|
||||
const { imageUrl, communityName, communityTag, type, price, validityDay, description, id } = res.data
|
||||
planetInfo.value = {
|
||||
imageUrl,
|
||||
communityName,
|
||||
communityTag: communityTag.toString(),
|
||||
type: type.toString(),
|
||||
price,
|
||||
id,
|
||||
validityDay,
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
// 获取用户信息
|
||||
const currentUserInfo = ref({})
|
||||
async function getUserInfo() {
|
||||
try {
|
||||
const res = await request.get(
|
||||
`/system/user/selectUserById?id=${props.tenantId}`,
|
||||
)
|
||||
if (res.code === 200) {
|
||||
currentUserInfo.value = res.data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
getUserInfo()
|
||||
|
||||
// 获取是否关注
|
||||
const isSelectAttention = ref(null)
|
||||
async function getSelectAttention() {
|
||||
try {
|
||||
const res = await request.get(
|
||||
`/attention/selectAttention?userId=${props.tenantId}`,
|
||||
)
|
||||
if (res.code === 200) {
|
||||
isSelectAttention.value = res.data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
getSelectAttention()
|
||||
|
||||
// 关注/取消关注
|
||||
async function onAddAttention() {
|
||||
try {
|
||||
const res = await request.get(
|
||||
`/attention/addAttention?userId=${props.tenantId}`,
|
||||
)
|
||||
if (res.code === 200) {
|
||||
isSelectAttention.value = res.data
|
||||
if (res.data) {
|
||||
message.success('关注成功')
|
||||
}
|
||||
else {
|
||||
message.success('取消关注成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加控制弹窗显示的变量
|
||||
const showQuestionModal = ref(false)
|
||||
|
||||
// 处理显示提问弹窗
|
||||
function handleShowQuestion() {
|
||||
showQuestionModal.value = true
|
||||
}
|
||||
|
||||
const showFileModal = ref(false)
|
||||
|
||||
function toFileList() {
|
||||
router.push({
|
||||
path: '/planetAllFile',
|
||||
query: {
|
||||
communityId: props.communityId,
|
||||
tenantId: props.tenantId,
|
||||
},
|
||||
})
|
||||
}
|
||||
// 监听 id 变化,获取详情
|
||||
watchEffect(() => {
|
||||
if (props.communityId && props.tenantId)
|
||||
getCommunityDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white rounded-lg w-[290px] p-4 h-fit">
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- 左侧图片 -->
|
||||
<div class="w-[258px] h-[258px] rounded-lg relative bg-[#f2f3f5] overflow-hidden">
|
||||
<img
|
||||
:src="planetInfo.imageUrl || defaultCover"
|
||||
class="w-full h-full object-cover flex-shrink-0 rounded-lg"
|
||||
:alt="planetInfo.communityName"
|
||||
@error="$event.target.src = defaultCover"
|
||||
>
|
||||
<div class="flex justify-between text-xs items-center absolute left-0 bottom-0 w-full p-2">
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 rounded-full bg-[#f2f3f5] overflow-hidden mr-2 flex-shrink-0">
|
||||
<img
|
||||
:src="currentUserInfo.avatar"
|
||||
:alt="currentUserInfo.nickName"
|
||||
class="w-full h-full object-cover"
|
||||
@error="$event.target.src = defaultAvatar"
|
||||
>
|
||||
</div>
|
||||
<div class="text-white">
|
||||
{{ currentUserInfo.nickName }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="Number(userStore.userInfo.userId) !== Number(tenantId)" class="bg-[#1e80ff] text-white py-1 px-2 rounded-lg cursor-pointer" @click="onAddAttention">
|
||||
{{ isSelectAttention ? '已关注' : '关注' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧信息 -->
|
||||
<div class="flex flex-col justify-between flex-1">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-xl font-bold">
|
||||
{{ planetInfo.communityName }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-8 text-xs text-[#878d95] mt-1 mb-4">
|
||||
<div>创建{{ planetInfo.validityDay }}天</div>
|
||||
</div>
|
||||
<div class="text-sm text-[#4a5563] line-clamp-3">
|
||||
{{ planetInfo.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center bg-[#F4F9FF] px-2 py-3 rounded-lg mt-4 text-sm text-gray-800 cursor-pointer"
|
||||
@click="toFileList"
|
||||
>
|
||||
<FileChartColumnIncreasing class="w-4 h-4 mr-2" />
|
||||
全部文件
|
||||
</div>
|
||||
<!-- 在线咨询 -->
|
||||
<div class="mt-6">
|
||||
<button
|
||||
class="w-full bg-[#1e80ff] hover:bg-[#3b8fff] text-white rounded-lg py-3 mt-4"
|
||||
@click="handleShowQuestion"
|
||||
>
|
||||
我要提问
|
||||
</button>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showQuestionModal"
|
||||
:style="{ width: '640px' }"
|
||||
preset="card"
|
||||
:mask-closable="false"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<QuestionPublish
|
||||
:community-id="communityId"
|
||||
:tenant-id="tenantId"
|
||||
@success="showQuestionModal = false"
|
||||
/>
|
||||
</n-modal>
|
||||
</div>
|
||||
|
||||
<DownPlanetFile
|
||||
v-model:show="showFileModal"
|
||||
:community-id="props.communityId"
|
||||
:tenant-id="props.tenantId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,667 @@
|
|||
<script setup lang="ts">
|
||||
// import { NConfigProvider, NInfiniteScroll, NInput } from 'naive-ui'
|
||||
import {
|
||||
EllipsisVertical,
|
||||
Star,
|
||||
ThumbsUp,
|
||||
} from 'lucide-vue-next'
|
||||
import { watch } from 'vue'
|
||||
|
||||
interface LikeParams {
|
||||
id: string | number
|
||||
commentId?: string | number
|
||||
communityId?: string | number
|
||||
tenantId?: string | number
|
||||
publishId?: string | number
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
isMy: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 800,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
publishListParams: {
|
||||
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.publishListParams.id,
|
||||
})
|
||||
function commentParamsInit() {
|
||||
commentParams.value.content = ''
|
||||
commentParams.value.replyUserId = ''
|
||||
commentParams.value.parentId = ''
|
||||
}
|
||||
|
||||
const isShowSend = ref(false)
|
||||
const publicWord = ref('')
|
||||
|
||||
// 评论列表
|
||||
const commentList = ref([])
|
||||
async function getCommentList() {
|
||||
try {
|
||||
const url = props.publishListParams.isMy === 1 ? '/personHome/getPersonHomeList' : '/publish/publishList'
|
||||
const res = await request.post(url, props.publishListParams)
|
||||
if (res.code === 200) {
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
res.rows[i].isShowInput = false
|
||||
res.rows[i].isShowSend = false
|
||||
if (res.rows[i].contentList && res.rows[i].contentList.length > 0) {
|
||||
for (let j = 0; j < res.rows[i].contentList.length; j++) {
|
||||
res.rows[i].contentList[j].isShowInput = false
|
||||
res.rows[i].contentList[j].isShowSend = false
|
||||
}
|
||||
}
|
||||
}
|
||||
commentList.value = res.rows
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
// 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()
|
||||
|
||||
// 监听 publishListParams 变化
|
||||
watch(
|
||||
() => props.publishListParams,
|
||||
() => {
|
||||
if (props.publishListParams.type !== 999) {
|
||||
if (props.publishListParams.communityId && props.publishListParams.tenantId) {
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
function handleBlur(ele) {
|
||||
// 默认评价
|
||||
if (!ele) {
|
||||
isShowSend.value = !!publicWord.value
|
||||
}
|
||||
else {
|
||||
ele.isShowSend = !!ele.word
|
||||
}
|
||||
}
|
||||
// 发送评论
|
||||
async function sendMessage(type: string, ele: any, index: number, subIndex?: number) {
|
||||
if (ele && ele.userId) {
|
||||
try {
|
||||
if (ele.word) {
|
||||
commentParams.value.content = ele.word
|
||||
commentParams.value.publishId = commentList.value[index].id
|
||||
if (type === 'sub') {
|
||||
commentParams.value.parentId = commentList.value[index].commentList[subIndex].id
|
||||
}
|
||||
else {
|
||||
commentParams.value.parentId = commentList.value[index].id
|
||||
}
|
||||
commentParams.value.communityId = props.publishListParams.communityId
|
||||
commentParams.value.tenantId = props.publishListParams.tenantId
|
||||
const res = await request.post(`/publishComment/save`, commentParams.value)
|
||||
if (res.code === 200) {
|
||||
ele.word = ''
|
||||
message.success('评论成功!')
|
||||
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(type: string, item: any) {
|
||||
const params: LikeParams = { id: '' }
|
||||
if (type === 'parent') {
|
||||
params.communityId = item.item.communityId
|
||||
params.publishId = item.item.id
|
||||
params.tenantId = item.item.tenantId
|
||||
}
|
||||
else {
|
||||
const { id, communityId, tenantId } = item.sub
|
||||
params.commentId = id
|
||||
params.communityId = communityId
|
||||
params.tenantId = tenantId
|
||||
params.publishId = item.item.id
|
||||
}
|
||||
|
||||
try {
|
||||
const url = type === 'sub' ? '/publishComment/like' : '/PublishLike/like'
|
||||
const res = await request.post(url, params)
|
||||
if (res.code === 200) {
|
||||
// message.success('删除成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// const { id, communityId, tenantId, operatorId } = item
|
||||
// const res = await request.post(`PublishLike/like`, { commentId: id, communityId, tenantId, operatorId })
|
||||
// if (res.code === 200) {
|
||||
// if (item.isLike === 0) {
|
||||
// item.isLike = 1
|
||||
// item.likeNum++
|
||||
// message.success('点赞成功!')
|
||||
// }
|
||||
// else {
|
||||
// item.isLike = 0
|
||||
// item.likeNum--
|
||||
// message.success('取消点赞成功!')
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// 收藏/取消收藏handleCollect
|
||||
async function handleCollect(item: any) {
|
||||
const params = {
|
||||
publishId: item.id,
|
||||
communityId: item.communityId,
|
||||
tenantId: item.tenantId,
|
||||
}
|
||||
try {
|
||||
const res = await request.post('/publish/collect', params)
|
||||
if (res.code === 200) {
|
||||
// message.success('删除成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示回复框
|
||||
function handleMessage(item: any) {
|
||||
if (!item.isShowInput) {
|
||||
item.word = ''
|
||||
}
|
||||
item.isShowInput = !item.isShowInput
|
||||
}
|
||||
// 删除评论
|
||||
async function handleDel(type: string, item: any) {
|
||||
const params = {}
|
||||
if (type === 'parent') {
|
||||
params.publishId = item.item.id
|
||||
params.communityId = item.item.communityId
|
||||
}
|
||||
else {
|
||||
const { id, communityId, tenantId } = item.sub
|
||||
params.id = id
|
||||
params.communityId = communityId
|
||||
params.tenantId = tenantId
|
||||
params.publishId = item.item.id
|
||||
}
|
||||
try {
|
||||
const url = type === 'sub' ? '/publishComment/delete' : '/publish/remove'
|
||||
const res = await request.post(url, params)
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelect(event: any, type: string, item: any) {
|
||||
event.stopPropagation() // 阻止事件冒泡
|
||||
if (type === 'choiceness') {
|
||||
try {
|
||||
const res = await request.get(`publish/elite?publishId=${item.id}&communityId=${item.communityId}`)
|
||||
if (res.code === 200) {
|
||||
message.success(item.isElite === 1 ? '取消精选成功!' : '精选成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
else if (type === 'complaint') {
|
||||
console.log('投诉')
|
||||
}
|
||||
else if (type === 'delete') {
|
||||
console.log('删除')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white py-4 rounded min-h-[320px]">
|
||||
<div
|
||||
v-for="(item, index) in commentList"
|
||||
:key="index"
|
||||
class="px-4"
|
||||
>
|
||||
<div class="nav-wrap">
|
||||
<div class="left-img">
|
||||
<img :src="item.avatar">
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="name flex justify-between items-center relative">
|
||||
<span>{{ item.userName }}</span>
|
||||
<img
|
||||
v-if="item.isElite === 1"
|
||||
class="cursor-pointer absolute right-0 top-[4px] w-10 h-10"
|
||||
src="@/assets/img/elite.png"
|
||||
alt=""
|
||||
@click="showActivityList"
|
||||
>
|
||||
<div class="relative group">
|
||||
<span>
|
||||
<EllipsisVertical size="16" class="cursor-pointer rotate-90" />
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="absolute right-0 top-[4px] hidden group-hover:block text-gray-500 text-[12px] bg-white rounded-lg text-center px-2 py-2 w-20 mt-2 shadow-lg z-10"
|
||||
>
|
||||
<!-- v-if="props.publishListParams.type === 0" -->
|
||||
<div
|
||||
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||
@click="(event) => handleSelect(event, 'choiceness', item)"
|
||||
>
|
||||
{{ item.isElite === 1 ? '取消精选' : '精选' }}
|
||||
</div>
|
||||
<!-- <template v-if="userStore?.userInfo?.userId === detailsInfo.userId"> -->
|
||||
<div
|
||||
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||
@click="(event) => handleSelect(event, 'complaint', item)"
|
||||
>
|
||||
投诉
|
||||
</div>
|
||||
<!-- <div
|
||||
v-if="props.publishListParams.type === 0"
|
||||
class="menu-item hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||
@click="(event) => handleSelect(event, 'delete')"
|
||||
>
|
||||
删除
|
||||
</div> -->
|
||||
<!-- </template> -->
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<ThumbsUp size="16" class="mr-1" :color="item.isLike !== 0 ? '#ff0000' : '#000000'" @click="handleFocus('parent', { item })" />
|
||||
<span style="vertical-align: middle">{{ item.likeNum }}</span>
|
||||
</div>
|
||||
<div class="icon-wrap">
|
||||
<Star size="16" class="mr-1" :color="item.isCollect !== 0 ? '#ff0000' : '#000000'" @click="handleCollect(item)" />
|
||||
</div>
|
||||
|
||||
<span class="cursor-pointer m-r16" @click="handleMessage(item)">回复</span>
|
||||
|
||||
<span class="cursor-pointer" @click="handleDel('parent', { 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('parent', item, index)">
|
||||
<span v-if="item.isShowSend">发送</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child-wrap">
|
||||
<div v-for="(ele, subIndex) in item.commentList" :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 v-if="ele.replyUserName">
|
||||
<span>回复</span>
|
||||
<span class="u-name">@{{ ele.replyUserName }}</span>
|
||||
</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('sub', { sub: ele, item })" />
|
||||
<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('sub', { item, sub: 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('sub', ele, index, subIndex)">
|
||||
<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: 20px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,186 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// import { useRoute } from 'vue-router'
|
||||
// const menuStore = useMenuStore()
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// const route = useRoute()
|
||||
// const currentPath = ref('/planet/')
|
||||
const currentPath = ref(route.path)
|
||||
if (route.path === '/planet') {
|
||||
currentPath.value = '/planet/'
|
||||
}
|
||||
const searchQuery = ref('')
|
||||
const tabList = ref([
|
||||
{
|
||||
name: '首页',
|
||||
path: '/planet/',
|
||||
},
|
||||
{
|
||||
name: '我的星球',
|
||||
path: '/my-planet',
|
||||
},
|
||||
])
|
||||
|
||||
const showMessage = ref(false)
|
||||
|
||||
// 显示登录框
|
||||
function handleLogin() {
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
navigateTo('/model-square')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Logout failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function handleTabClick(path: string) {
|
||||
router.push(path)
|
||||
currentPath.value = path
|
||||
|
||||
// menuStore.setActiveMenu(path)
|
||||
// if (path === '/my-planet') {
|
||||
// navigateTo('/my-planet')
|
||||
// }
|
||||
}
|
||||
|
||||
function openPersonalPage() {
|
||||
const url = `${window.location.origin}${router.resolve('/myPlanetDetail').href}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function openEarningsPage() {
|
||||
const url = `${window.location.origin}${router.resolve('/planet-earnings').href}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleShowMessage() {
|
||||
showMessage.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="w-full h-16 bg-white border-b border-gray-100">
|
||||
<div class="max-w-[1920px] mx-auto">
|
||||
<div class="flex items-center h-16 px-10">
|
||||
<!-- Logo部分 -->
|
||||
<div class="flex items-center cursor-pointer" @click="handleTabClick('/')">
|
||||
<img
|
||||
src="@/assets/img/logo.png"
|
||||
alt="魔创未来"
|
||||
class="h-8 w-8"
|
||||
>
|
||||
<span class="text-[#328AFE] text-xl font-bold ml-2">魔创未来</span>
|
||||
</div>
|
||||
|
||||
<!-- 导航链接 -->
|
||||
<nav class="flex items-center ml-20">
|
||||
<NuxtLink
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
to="/"
|
||||
class="text-xl font-bold px-5 transition-colors"
|
||||
:class="{ 'text-[#192029]': currentPath === item.path, 'text-[#878D95]': currentPath !== item.path }"
|
||||
@click="handleTabClick(item.path)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</NuxtLink>
|
||||
<!-- <NuxtLink
|
||||
to="/"
|
||||
class="text-xl font-bold px-5 transition-colors text-[#878D95]"
|
||||
>
|
||||
魔创未来
|
||||
</NuxtLink> -->
|
||||
</nav>
|
||||
|
||||
<!-- 右侧功能区 -->
|
||||
<div class="flex items-center ml-auto space-x-4">
|
||||
<!-- 搜索框 -->
|
||||
<div class="relative flex items-center w-[360px]">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="搜索星球、用户或内容"
|
||||
class="w-full h-10 pl-4 pr-16 bg-[#F4F6F9] rounded-lg text-sm placeholder-[#878D95] outline-none"
|
||||
>
|
||||
<button class="absolute right-3 flex items-center space-x-1 text-[#4A5563]">
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.333 12.667A5.333 5.333 0 1 0 7.333 2a5.333 5.333 0 0 0 0 10.667zM14 14l-2.9-2.9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<span class="text-sm font-bold">搜索</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 通知铃铛 -->
|
||||
<button class="relative p-2 hover:bg-gray-50 rounded-lg" @click="handleShowMessage">
|
||||
<svg class="w-6 h-6 text-[#4A5563]" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z" fill="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<PlanetMessage v-if="showMessage" @close="showMessage = false" />
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<client-only v-if="!userStore.token">
|
||||
<button class="h-10 px-8 rounded-lg bg-gradient-to-l from-[#3BEDFF] to-[#328AFE] text-white text-sm font-bold hover:opacity-90 transition-opacity" @click="handleLogin">
|
||||
登录
|
||||
</button>
|
||||
</client-only>
|
||||
<div v-else class="relative group">
|
||||
<NAvatar
|
||||
class="cursor-pointer w-10 h-10"
|
||||
round
|
||||
size="small"
|
||||
:src="userStore?.userInfo?.avatar"
|
||||
/>
|
||||
<div class="p-2 absolute hidden group-hover:block z-50 top-11 right-0 bg-white border border-[#E5E5E5] rounded-lg w-60">
|
||||
<div class="flex items-center gap-2 bg-[rgba(50,138,254,0.05)] opacity-0.5 p-2 mb-2">
|
||||
<div class="w-10 h-10">
|
||||
<img class="w-full h-full rounded-full" :src="userStore?.userInfo?.avatar" alt="">
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="text-base font-bold">
|
||||
{{ userStore.userInfo.nickName }}
|
||||
</div>
|
||||
<div class="text-xs text-[#878D95]">
|
||||
欢迎来到魔创星球
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between text-sm text-[#4a5563]">
|
||||
<div class="flex-1 text-center py-2 cursor-pointer" @click="openPersonalPage">
|
||||
个人主页
|
||||
</div>
|
||||
<div class="flex-1 text-center py-2 cursor-pointer" @click="openEarningsPage">
|
||||
收益明细
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-[#4a5563] flex justify-center items-center my-4 cursor-pointer" @click="handleLogout">
|
||||
推出登录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 如果需要自定义样式可以在这里添加 */
|
||||
</style>
|
|
@ -0,0 +1,216 @@
|
|||
<script setup lang="ts">
|
||||
import request from '@/utils/request'
|
||||
import { Bell, MessageCircle, ShieldQuestion, ThumbsUp, X } from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface MessageItem {
|
||||
id: number
|
||||
name: string
|
||||
type: number
|
||||
icon: any
|
||||
}
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const messageList = ref<MessageItem[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '星球通知',
|
||||
type: 0,
|
||||
icon: Bell,
|
||||
},
|
||||
// {
|
||||
// id: 2,
|
||||
// name: '等我回答',
|
||||
// type: 2,
|
||||
// icon: ShieldQuestion,
|
||||
// },
|
||||
{
|
||||
id: 3,
|
||||
name: '回复我的',
|
||||
type: 1,
|
||||
icon: MessageCircle,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '收藏/赞',
|
||||
type: 3,
|
||||
icon: ThumbsUp,
|
||||
},
|
||||
])
|
||||
|
||||
const messageData = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// 获取消息列表
|
||||
const params = ref({
|
||||
adviceType: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
async function changeType(type: number) {
|
||||
params.value.adviceType = type
|
||||
params.value.pageNum = 1
|
||||
initGetList()
|
||||
}
|
||||
|
||||
// async function getMessageList() {
|
||||
// loading.value = true
|
||||
// try {
|
||||
// const res = await request.post('/communityAdvice/adviceList', params.value)
|
||||
// if (res.code === 200) {
|
||||
// messageData.value = res.rows || []
|
||||
// }
|
||||
// }
|
||||
// catch (error) {
|
||||
// console.error(error)
|
||||
// }
|
||||
// finally {
|
||||
// loading.value = false
|
||||
// }
|
||||
// }
|
||||
|
||||
// 获取列表
|
||||
const listFinish = ref(false)
|
||||
function initGetList() {
|
||||
listFinish.value = false
|
||||
params.value.pageNum = 1
|
||||
getMessageList()
|
||||
}
|
||||
async function getMessageList() {
|
||||
try {
|
||||
if (listFinish.value || loading.value) // 添加loading检查,避免重复请求
|
||||
return
|
||||
loading.value = params.value.pageNum === 1 // 只在第一页显示loading
|
||||
const res = await request.post('/communityAdvice/adviceList', params.value)
|
||||
|
||||
if (res.code === 200) {
|
||||
if (params.value.pageNum === 1) {
|
||||
messageData.value = res.rows
|
||||
}
|
||||
else {
|
||||
messageData.value = [...messageData.value, ...res.rows]
|
||||
}
|
||||
|
||||
if (messageData.value.length >= res.total) {
|
||||
listFinish.value = true
|
||||
}
|
||||
params.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
getMessageList()
|
||||
// 初始化加载第一个类型的消息
|
||||
onMounted(() => {
|
||||
// getMessageList(1)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div class="bg-white w-[800px] rounded-lg overflow-hidden">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center justify-between px-6 h-14 border-b border-[#eee]">
|
||||
<div class="text-lg font-medium">
|
||||
通知
|
||||
</div>
|
||||
<div
|
||||
class="i-lucide:x w-5 h-5 text-[#86909c] cursor-pointer hover:text-[#1e80ff]"
|
||||
@click="handleClose"
|
||||
>
|
||||
<X />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<div class="flex h-[600px]">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="w-[200px] border-r border-[#eee] py-4">
|
||||
<div
|
||||
v-for="item in messageList"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-2 px-6 h-12 cursor-pointer my-4 hover:bg-[#f2f3f5]"
|
||||
:class="{ 'text-[#1e80ff] bg-[#f2f3f5]': params.adviceType === item.type }"
|
||||
@click="changeType(item.type)"
|
||||
>
|
||||
<component :is="item.icon" class="w-5 h-5" />
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧消息列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<n-spin :show="loading">
|
||||
<template v-if="messageData.length > 0">
|
||||
<n-infinite-scroll style="height: calc(100vh - 200px)" :distance="10" @load="getMessageList">
|
||||
<div
|
||||
v-for="item in messageData"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-3 p-4 border-b border-[#eee] hover:bg-[#f7f8fa]"
|
||||
>
|
||||
<img :src="item.sendUserAvatar" class="w-12 h-12 rounded-full">
|
||||
<div v-if="params.adviceType === 0" class="flex-1">
|
||||
<div class="overflow-hidden">
|
||||
<p class="line-clamp-1 text-[#1f2329]">
|
||||
{{ item.content }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-gray-500 text-sm mt-2">
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="params.adviceType === 1" class="flex-1 flex flex-col justify-between">
|
||||
<div>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="text-[#1e80ff]">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
|
||||
<div class="text-[#86909c] text-xs">
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="params.adviceType === 3" class="flex-1">
|
||||
<div class="overflow-hidden">
|
||||
<p class="line-clamp-1 text-[#1f2329]">
|
||||
{{ item.content }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-gray-500 text-sm mt-2">
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-infinite-scroll>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex items-center justify-center min-h-[200px] text-[#86909c]">
|
||||
暂无消息
|
||||
</div>
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-spin-container) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,704 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ThumbsUp,
|
||||
} from 'lucide-vue-next'
|
||||
import { watch } from 'vue'
|
||||
|
||||
interface LikeParams {
|
||||
id: string | number
|
||||
communityId?: string | number
|
||||
tenantId?: string | number
|
||||
publishId?: string | number
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: Number,
|
||||
default: 800,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
publishListParams: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
// const $props = defineProps(['headUrl', 'dataList', 'height'])
|
||||
|
||||
const userStore = useUserStore()
|
||||
const message = useMessage()
|
||||
const showQuestionModal = ref(false)
|
||||
const questionItem = ref({})
|
||||
function handleShowQuestion(item: any) {
|
||||
questionItem.value = item
|
||||
showQuestionModal.value = true
|
||||
}
|
||||
const questionContent = ref('')
|
||||
// 发布评论
|
||||
const commentParams = ref({
|
||||
content: '',
|
||||
parentId: '',
|
||||
replyUserId: '',
|
||||
userId: userStore?.userInfo?.userId,
|
||||
workFlowId: props.publishListParams.id,
|
||||
})
|
||||
function commentParamsInit() {
|
||||
commentParams.value.content = ''
|
||||
commentParams.value.replyUserId = ''
|
||||
commentParams.value.parentId = ''
|
||||
}
|
||||
|
||||
const isShowSend = ref(false)
|
||||
const publicWord = ref('')
|
||||
// 评论列表
|
||||
const commentList = ref([])
|
||||
async function getCommentList() {
|
||||
try {
|
||||
const res = await request.post(`/question/list`, props.publishListParams)
|
||||
if (res.code === 200) {
|
||||
// for (let i = 0; i < res.rows.length; i++) {
|
||||
// res.rows[i].isShowInput = false
|
||||
// res.rows[i].isShowSend = false
|
||||
// if (res.rows[i].contentList && res.rows[i].contentList.length > 0) {
|
||||
// for (let j = 0; j < res.rows[i].contentList.length; j++) {
|
||||
// res.rows[i].contentList[j].isShowInput = false
|
||||
// res.rows[i].contentList[j].isShowSend = false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
commentList.value = res.rows
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
// 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()
|
||||
|
||||
// 监听 publishListParams 变化
|
||||
watch(
|
||||
() => props.publishListParams,
|
||||
() => {
|
||||
if (props.publishListParams.type !== 999) {
|
||||
getCommentList()
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
function handleBlur(ele) {
|
||||
// 默认评价
|
||||
if (!ele) {
|
||||
isShowSend.value = !!publicWord.value
|
||||
}
|
||||
else {
|
||||
ele.isShowSend = !!ele.word
|
||||
}
|
||||
}
|
||||
// 发送评论
|
||||
async function sendMessage(type: string, ele: any, index: number, subIndex?: number) {
|
||||
if (ele && ele.userId) {
|
||||
try {
|
||||
if (ele.word) {
|
||||
commentParams.value.content = ele.word
|
||||
commentParams.value.publishId = commentList.value[index].id
|
||||
if (type === 'sub') {
|
||||
commentParams.value.parentId = commentList.value[index].commentList[subIndex].id
|
||||
}
|
||||
else {
|
||||
commentParams.value.parentId = commentList.value[index].id
|
||||
}
|
||||
commentParams.value.communityId = props.publishListParams.communityId
|
||||
commentParams.value.tenantId = props.publishListParams.tenantId
|
||||
const res = await request.post(`/publishComment/save`, commentParams.value)
|
||||
if (res.code === 200) {
|
||||
ele.word = ''
|
||||
message.success('评论成功!')
|
||||
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(type: string, item: any) {
|
||||
const params: LikeParams = { id: '' }
|
||||
if (type === 'parent') {
|
||||
params.id = item.item.id
|
||||
}
|
||||
else {
|
||||
const { id, communityId, tenantId } = item.sub
|
||||
params.id = id
|
||||
params.communityId = communityId
|
||||
params.tenantId = tenantId
|
||||
params.publishId = item.item.id
|
||||
}
|
||||
|
||||
try {
|
||||
const url = type === 'sub' ? '/publishComment/like' : '/PublishLike/like'
|
||||
const res = await request.post(url, params)
|
||||
if (res.code === 200) {
|
||||
// message.success('删除成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// const { id, communityId, tenantId, operatorId } = item
|
||||
// const res = await request.post(`PublishLike/like`, { commentId: id, communityId, tenantId, operatorId })
|
||||
// if (res.code === 200) {
|
||||
// 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(type: string, item: any) {
|
||||
const params = {}
|
||||
if (type === 'parent') {
|
||||
params.publishId = item.item.id
|
||||
params.communityId = item.item.communityId
|
||||
}
|
||||
else {
|
||||
const { id, communityId, tenantId } = item.sub
|
||||
params.id = id
|
||||
params.communityId = communityId
|
||||
params.tenantId = tenantId
|
||||
params.publishId = item.item.id
|
||||
}
|
||||
try {
|
||||
const url = type === 'sub' ? '/publishComment/delete' : '/publish/remove'
|
||||
const res = await request.post(url, params)
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功!')
|
||||
getCommentList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 发布回答
|
||||
async function handlePublish() {
|
||||
const params = {
|
||||
content: questionContent.value,
|
||||
questionId: questionItem.value.id,
|
||||
communityId: questionItem.value.communityId,
|
||||
tenantId: questionItem.value.tenantId,
|
||||
}
|
||||
try {
|
||||
const res = await request.post('questionComment/comment', params)
|
||||
if (res.code === 200) {
|
||||
message.success('回答成功!')
|
||||
getCommentList()
|
||||
showQuestionModal.value = false
|
||||
questionContent.value = ''
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white py-4 rounded min-h-[320px]">
|
||||
<!-- <div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center">
|
||||
<div class="left text-[20px] mr-2">
|
||||
讨论
|
||||
</div>
|
||||
<div v-if="props.type !== 'pictrue'" class="text-[#999]">
|
||||
{{ commentCount }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.type !== 'pictrue'" class="flex items-center">
|
||||
<div class="cursor-pointer" :class="sortType === 0 ? '' : 'text-[#999]'" @click="changeType(0)">
|
||||
最热
|
||||
</div>|
|
||||
<div class="cursor-pointer" :class="sortType === 1 ? '' : 'text-[#999]'" @click="changeType(1)">
|
||||
最新
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <div class="header-wrap">
|
||||
<div v-if="userStore?.userInfo?.avatar" class="left">
|
||||
<img
|
||||
alt="avatar"
|
||||
:src="userStore?.userInfo?.avatar"
|
||||
>
|
||||
</div>
|
||||
<div class="input-wrap">
|
||||
<NConfigProvider inline-theme-disabled style="width:100%">
|
||||
<NInput
|
||||
v-model:value="publicWord"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 1 }"
|
||||
placeholder="善语结善缘,恶言伤人心~"
|
||||
class="text"
|
||||
@focus="isShowSend = true"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
</NConfigProvider>
|
||||
|
||||
<div class="send-btn" :class="{ active: publicWord }" @click="sendMessage">
|
||||
<span v-if="isShowSend">发送</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div
|
||||
v-for="(item, index) in commentList"
|
||||
:key="index"
|
||||
class="px-4"
|
||||
>
|
||||
<div class="nav-wrap">
|
||||
<div class="left-img">
|
||||
<img :src="item.questionUserAvatar">
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="name">
|
||||
{{ item.questionUserName }}
|
||||
</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">
|
||||
<ThumbsUp size="16" class="mr-1" :color="item.isLike !== 0 ? '#ff0000' : '#000000'" @click="handleFocus('parent', { 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('parent', { item })">删除</span>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-if="item.questionUrl" class="flex flex-wrap gap-2">
|
||||
<img v-for="(ele, index) in item.questionUrl.split(',')" :key="index" class="w-16 h-16 rounded-md" :src="ele">
|
||||
</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('parent', item, index)">
|
||||
<span v-if="item.isShowSend">发送</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="child-wrap">
|
||||
<div v-for="(ele, subIndex) in item.commentList" :key="subIndex" class="child-wrap-item">
|
||||
<div class="left-img">
|
||||
<img :src="ele.avatar">
|
||||
</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 v-if="ele.replyUserName">
|
||||
<span>回复</span>
|
||||
<span class="u-name">@{{ ele.replyUserName }}</span>
|
||||
</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('sub', { sub: ele, item })" />
|
||||
<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('sub', { item, sub: 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('sub', ele, index, subIndex)">
|
||||
<span v-if="ele.isShowSend">发送</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-center text-sm my-4">
|
||||
<div v-if="item.amount" class="cursor-pointer" @click="handleShowQuestion(item)">
|
||||
<span class="text-blue-500">回答该问题可获得</span>
|
||||
<span class="text-[#ffa820]">{{ item.amount }}积分</span>
|
||||
</div>
|
||||
<div v-else class="cursor-pointer" @click="handleShowQuestion(item)">
|
||||
<span class="text-blue-500">回答该问题</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-more">
|
||||
暂时没有更多评论
|
||||
</div>
|
||||
<n-modal
|
||||
v-model:show="showQuestionModal"
|
||||
:style="{ width: '640px' }"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
class="custom-modal"
|
||||
:show-icon="false"
|
||||
>
|
||||
<div class="bg-white rounded-lg px-6">
|
||||
<div class="flex items-center justify-between pb-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center">
|
||||
回答<img class="w-4 h-4 rounded-full mx-2" :src="questionItem.questionUserAvatar">
|
||||
</div>
|
||||
<span>{{ questionItem.questionUserName }}</span>
|
||||
<span>发起的咨询</span>
|
||||
</div>
|
||||
</div>
|
||||
<n-form
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item path="content" :show-label="false">
|
||||
<n-input
|
||||
v-model:value="questionContent"
|
||||
type="textarea"
|
||||
placeholder="请输入提问内容"
|
||||
:required="true"
|
||||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||
class="rounded-lg"
|
||||
/>
|
||||
</n-form-item>
|
||||
<div class="flex items-center justify-between my-4">
|
||||
<div class="flex items-center justify-end gap-8 w-full">
|
||||
<n-button
|
||||
type="primary"
|
||||
class="!px-8 rounded"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handlePublish"
|
||||
>
|
||||
回答
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-modal>
|
||||
</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: 20px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,217 @@
|
|||
<script setup lang="ts">
|
||||
import { CloseOutline } from '@vicons/ionicons5'
|
||||
import { FolderPlus, Image, ImagePlus } from 'lucide-vue-next'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
interface Props {
|
||||
communityId: number | string
|
||||
tenantId: number | string
|
||||
}
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
content: '',
|
||||
fileUrl: '',
|
||||
fileName: '',
|
||||
imageUrl: '',
|
||||
})
|
||||
|
||||
// 图片上传相关
|
||||
const fileUrl = ref<string[]>([])
|
||||
const fileName = ref<string[]>([])
|
||||
const imageUrl = ref<string[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 表单ref
|
||||
const formRef = ref()
|
||||
|
||||
// 处理发布提问
|
||||
async function handlePublish() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
const params = {
|
||||
...formData.value,
|
||||
communityId: props.communityId,
|
||||
tenantId: props.tenantId,
|
||||
fileUrl: fileUrl.value.join(','),
|
||||
fileName: fileName.value.join(','),
|
||||
imageUrl: imageUrl.value.join(','),
|
||||
}
|
||||
|
||||
const res = await request.post('/publish/publish', params)
|
||||
if (res.code === 200) {
|
||||
message.success('发布成功')
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
content: '',
|
||||
fileUrl: '',
|
||||
fileName: '',
|
||||
imageUrl: '',
|
||||
}
|
||||
fileUrl.value = []
|
||||
fileName.value = []
|
||||
imageUrl.value = [] // 清空图片URL列表
|
||||
emit('success') // 触发成功事件
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 当前上传类型
|
||||
const currentUploadType = ref<string | null>(null)
|
||||
|
||||
const acceptTypes = computed(() => {
|
||||
return currentUploadType.value === 'img'
|
||||
? 'image/*'
|
||||
: 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain,application/zip'
|
||||
})
|
||||
|
||||
// 图片上传input
|
||||
const pictureInput = ref<HTMLInputElement | null>(null)
|
||||
function handlePictureInput(type: string) {
|
||||
currentUploadType.value = type
|
||||
nextTick(() => {
|
||||
pictureInput.value?.click()
|
||||
})
|
||||
}
|
||||
// 处理图片上传
|
||||
async function handlePictureChange(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files
|
||||
if (!files || files.length === 0)
|
||||
return message.error('请选择有效的文件')
|
||||
|
||||
try {
|
||||
const fileList = Array.from(files)
|
||||
const isImageUpload = currentUploadType.value === 'img'
|
||||
|
||||
// 根据上传类型验证文件
|
||||
const validFiles = fileList.filter(file =>
|
||||
isImageUpload ? file.type.startsWith('image/') : !file.type.startsWith('image/'),
|
||||
)
|
||||
|
||||
if (validFiles.length === 0)
|
||||
return message.error(`请选择${isImageUpload ? '图片' : '文档'}文件`)
|
||||
|
||||
const uploadResults = await uploadImagesInBatches(validFiles)
|
||||
|
||||
// 处理上传结果
|
||||
if (isImageUpload) {
|
||||
imageUrl.value.push(...uploadResults.map((item: any) => item.url))
|
||||
}
|
||||
else {
|
||||
fileUrl.value.push(...uploadResults.map((item: any) => item.url))
|
||||
fileName.value.push(...uploadResults.map((item: any) => item.fileName))
|
||||
}
|
||||
|
||||
pictureInput.value!.value = '' // 清空input值,允许重复选择相同文件
|
||||
}
|
||||
catch (error) {
|
||||
message.error(currentUploadType.value === 'img' ? '图片上传失败' : '文件上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white rounded-lg px-6">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item path="content" :show-label="false">
|
||||
<n-input
|
||||
v-model:value="formData.content"
|
||||
type="textarea"
|
||||
placeholder="点击发表主题"
|
||||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||
class="rounded-lg"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<div v-if="fileName.length > 0" class="mt-4 flex flex-wrap gap-3 mb-2">
|
||||
<div
|
||||
v-for="(item, index) in fileName"
|
||||
:key="index"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
{{ item }}
|
||||
<n-icon color="#3f7ef7" class="cursor-pointer flex items-center justify-center w-6 h-6">
|
||||
<CloseOutline class="text-[20px]" size="20" @click="fileName.splice(index, 1)" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 上传的图片预览 -->
|
||||
<div v-if="imageUrl.length > 0" class="mt-4 flex flex-wrap gap-3 mb-2">
|
||||
<div
|
||||
v-for="(item, index) in imageUrl"
|
||||
:key="index"
|
||||
class="relative group"
|
||||
>
|
||||
<img
|
||||
:src="item"
|
||||
class="w-[125px] h-[125px] rounded"
|
||||
>
|
||||
<n-icon color="#fff" class="absolute top-1 right-1 p-1 z-40 cursor-pointer w-6 h-6">
|
||||
<CloseOutline class="text-[20px]" size="20" @click="questionUrlList.splice(index, 1)" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <n-form-item v-if="formData.type === 1" path="amount" class="mb-3"> -->
|
||||
|
||||
<!-- </n-form-item> -->
|
||||
<div class="flex items-center justify-between my-2">
|
||||
<div class="flex">
|
||||
<div class="border border-gray-300 rounded p-1 cursor-pointer hover:bg-gray-50 transition-colors mr-6" @click="handlePictureInput('img')">
|
||||
<ImagePlus class="w-5 h-5" />
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded p-1 cursor-pointer hover:bg-gray-50 transition-colors" @click="handlePictureInput('file')">
|
||||
<FolderPlus class="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-8">
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
class="!px-8 rounded"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handlePublish"
|
||||
>
|
||||
发布
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
:accept="acceptTypes"
|
||||
class="hidden"
|
||||
multiple="true"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,212 @@
|
|||
<script setup lang="ts">
|
||||
import { CloseOutline } from '@vicons/ionicons5'
|
||||
import { Image } from 'lucide-vue-next'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
interface Props {
|
||||
communityId: number | string
|
||||
tenantId: number | string
|
||||
}
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
type: 0, // 提问类型:0免费,1付费
|
||||
amount: 10, // 付费金额
|
||||
content: '', // 提问内容
|
||||
isAnonymous: 0, // 是否匿名 1匿名 0不匿名
|
||||
questionUrl: '', // 提问图片
|
||||
})
|
||||
|
||||
// 图片上传相关
|
||||
const fileList = ref<string[]>([])
|
||||
const questionUrlList = ref<string[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
content: {
|
||||
required: true,
|
||||
message: '请输入提问内容',
|
||||
// trigger: ['blur'],
|
||||
},
|
||||
amount: {
|
||||
required: (formData: any) => formData.type === 1,
|
||||
message: '请输入付费金额',
|
||||
// trigger: ['blur', 'change'],
|
||||
type: 'number',
|
||||
},
|
||||
}
|
||||
|
||||
// 表单ref
|
||||
const formRef = ref()
|
||||
|
||||
// 处理发布提问
|
||||
async function handlePublish() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
const params = {
|
||||
...formData.value,
|
||||
communityId: props.communityId,
|
||||
tenantId: props.tenantId,
|
||||
questionUserId: userStore.userInfo.userId,
|
||||
questionUrl: questionUrlList.value.join(','), // 将图片URL数组转为逗号分隔的字符串
|
||||
}
|
||||
|
||||
const res = await request.post('/question/addQuestion', params)
|
||||
if (res.code === 200) {
|
||||
message.success('提问成功')
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
type: 0,
|
||||
amount: undefined,
|
||||
content: '',
|
||||
isAnonymous: 0,
|
||||
questionUrl: '',
|
||||
}
|
||||
fileList.value = []
|
||||
questionUrlList.value = [] // 清空图片URL列表
|
||||
emit('success') // 触发成功事件
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'))
|
||||
if (imageFiles.length === 0) {
|
||||
message.error('请选择有效的图片文件')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const pictureResultList = await uploadImagesInBatches(imageFiles)
|
||||
questionUrlList.value.push(...pictureResultList.map((item: any) => item.url))
|
||||
pictureInput.value!.value = ''
|
||||
}
|
||||
catch (error: any) {
|
||||
message.error('图片上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white rounded-lg px-6">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item path="content" :show-label="false">
|
||||
<n-input
|
||||
v-model:value="formData.content"
|
||||
type="textarea"
|
||||
placeholder="请输入提问内容"
|
||||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||
class="rounded-lg"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 上传的图片预览 -->
|
||||
<div v-if="questionUrlList.length > 0" class="mt-4 flex flex-wrap gap-3 mb-2">
|
||||
<div
|
||||
v-for="(url, index) in questionUrlList"
|
||||
:key="index"
|
||||
class="relative group"
|
||||
>
|
||||
<img
|
||||
:src="url"
|
||||
class="w-[125px] h-[125px] rounded"
|
||||
>
|
||||
<n-icon color="#fff" class="absolute top-1 right-1 p-1 z-40 cursor-pointer w-6 h-6">
|
||||
<CloseOutline class="text-[20px]" size="20" @click="questionUrlList.splice(index, 1)" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <n-form-item v-if="formData.type === 1" path="amount" class="mb-3"> -->
|
||||
<n-input-number
|
||||
v-if="formData.type === 1"
|
||||
v-model:value="formData.amount"
|
||||
:min="10"
|
||||
:max="999.9"
|
||||
:precision="1"
|
||||
placeholder="请输入付费金额"
|
||||
class="w-[240px] my-5"
|
||||
>
|
||||
<template #prefix>
|
||||
¥
|
||||
</template>
|
||||
</n-input-number>
|
||||
<!-- </n-form-item> -->
|
||||
<div class="flex items-center justify-between my-4">
|
||||
<div class="border border-gray-300 rounded p-1 cursor-pointer hover:bg-gray-50 transition-colors" @click="handlePictureInput">
|
||||
<Image class="w-6 h-6 size-6" />
|
||||
</div>
|
||||
<div class="flex items-center gap-8">
|
||||
<n-radio-group v-model:value="formData.type">
|
||||
<n-radio :value="0">
|
||||
免费
|
||||
</n-radio>
|
||||
<n-radio :value="1">
|
||||
付费
|
||||
</n-radio>
|
||||
</n-radio-group>
|
||||
|
||||
<n-checkbox v-model:checked="formData.isAnonymous" unchecked-value="0" checked-value="1">
|
||||
匿名提问
|
||||
</n-checkbox>
|
||||
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
class="!px-8 rounded"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handlePublish"
|
||||
>
|
||||
提问
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -3,8 +3,8 @@
|
|||
// import { getUUid, uuidLogin } from '@api/login'
|
||||
// import { useStore } from '@store/index'
|
||||
// import { IosRefresh } from '@vicons/ionicons5';
|
||||
import { RotateCcw } from 'lucide-vue-next';
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { RotateCcw } from 'lucide-vue-next'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(event: 'login-success', data: any): void
|
||||
|
@ -50,14 +50,18 @@ async function onGetUUid() {
|
|||
if (res.status === 1) {
|
||||
clearTimeout(pollingTimer)
|
||||
const userStore = useUserStore()
|
||||
userStore.setToken(res.token)
|
||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
token: res.token,
|
||||
})
|
||||
userStore.setUserInfo(res1.user)
|
||||
await userStore.setToken(res.token)
|
||||
await userStore.getUserInfo()
|
||||
// const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
// token: res.token,
|
||||
// })
|
||||
// const res1 = await request.get('/system/user/selectUserById', {
|
||||
// token: token.value,
|
||||
// })
|
||||
// userStore.setUserInfo(res1.user)
|
||||
// window.location.href = '/'
|
||||
// 刷新页面
|
||||
window.location.reload();
|
||||
window.location.reload()
|
||||
}
|
||||
}).catch(() => {
|
||||
clearTimeout(pollingTimer)
|
||||
|
@ -87,7 +91,6 @@ onBeforeUnmount(() => {
|
|||
|
||||
<template>
|
||||
<div class="w-[280px] px-5 text-center relative">
|
||||
|
||||
<div v-if="qrUrl" class="relative w-full">
|
||||
<div v-if="bindTimeout" class="absolute py-3 left-0 w-full h-full top-0 z-[9999] flex items-center justify-center flex-col cursor-pointer text-white bg-black/40" @click="onGetUUid">
|
||||
刷新二维码
|
||||
|
|
|
@ -1,3 +1,124 @@
|
|||
<script setup lang="ts">
|
||||
import { Download, Play } from 'lucide-vue-next'
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
// import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['workFlowTotal'])
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// 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 listParams = ref({
|
||||
...props.params,
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
name: modalStore.searchQuery,
|
||||
isCollect: 1,
|
||||
})
|
||||
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++
|
||||
emit('workFlowTotal', total.value)
|
||||
}
|
||||
}
|
||||
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}`)
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/workflow-details/${item.id}`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap w-full">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 gap-4 p-4">
|
||||
|
@ -12,14 +133,14 @@
|
|||
:src="item.coverPath"
|
||||
class="w-full h-full object-cover rounded-lg cursor-pointer ransform transition-transform duration-300 hover:scale-110"
|
||||
alt=""
|
||||
/>
|
||||
>
|
||||
|
||||
<!-- 左上角标签 -->
|
||||
<div
|
||||
<!-- <div
|
||||
class="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded"
|
||||
>
|
||||
{{ item.type }}
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 底部数据统计 -->
|
||||
<div
|
||||
|
@ -47,143 +168,27 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="flex mt-1">
|
||||
<img :src="item.avatar" class="w-4 h-4 rounded-full mr-2" alt="" />
|
||||
<img :src="item.avatar" class="w-4 h-4 rounded-full mr-2" alt="">
|
||||
<span class="text-xs text-gray-500 truncate">{{ item.nickName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex justify-center" v-if="!loading && dataList.length === 0">
|
||||
<Empty></Empty>
|
||||
<div v-if="!loading && dataList.length === 0" class="w-full flex justify-center">
|
||||
<Empty />
|
||||
</div>
|
||||
<div
|
||||
ref="loadingTrigger"
|
||||
class="h-20 w-full text-gray-500 flex justify-center items-center"
|
||||
>
|
||||
<div v-if="loading">加载中...</div>
|
||||
<div v-if="finished && dataList.length >= 20">没有更多数据了</div>
|
||||
<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 emit = defineEmits(['workFlowTotal'])
|
||||
const modalStore = useModalStore()
|
||||
|
||||
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,
|
||||
name:modalStore.searchQuery,
|
||||
isCollect:1,
|
||||
});
|
||||
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++;
|
||||
emit('workFlowTotal', total.value)
|
||||
}
|
||||
} 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}`)
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/workflow-details/${item.id}`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
|
@ -1,49 +1,9 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<div class="fileUpload">
|
||||
<div class="item-upload">
|
||||
<!-- <span v-if="required" class="required">*</span> -->
|
||||
<!-- <n-button type="success" @click="triggerFileSelect"> 文件选择 </n-button> -->
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInputRef"
|
||||
class="file2"
|
||||
style="display: none"
|
||||
@change="selectFile"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div v-if="showFileName" class="item-info">
|
||||
{{ fileName }}
|
||||
</div>
|
||||
<div v-else class="item-info">支持{{ getAcceptType() }}文件格式</div>
|
||||
<div v-if="progress > 0" class="item-cancel">
|
||||
<n-button type="success" style="margin-top: 5px" @click="cancelUploadInfo">
|
||||
X
|
||||
</n-button>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="item-process" v-if="progress > 0">
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="progress"
|
||||
indicator-placement="inside"
|
||||
processing
|
||||
/>
|
||||
</div>
|
||||
<!-- <div v-if="requiredValid" class="item-valid">请上传文件</div> -->
|
||||
<!-- <div class="resume-upload">
|
||||
<n-button type="primary" @click="resumeUpload">
|
||||
继续上传
|
||||
</n-button>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { MessageReactive, useMessage } from "naive-ui";
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import { onMounted, ref } from "vue";
|
||||
const messageReactive: MessageReactive | null = null;
|
||||
import type { MessageReactive } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import SparkMD5 from 'spark-md5'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
// const { calculateHash } = useFileHash();
|
||||
// const { calculateHash, isCalculating } = useFileHash()
|
||||
// debugger
|
||||
|
@ -51,7 +11,7 @@ const messageReactive: MessageReactive | null = null;
|
|||
const props = defineProps({
|
||||
accept: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ["doc", "docx", "pdf", "safetensors", "ckpt"],
|
||||
default: () => ['doc', 'docx', 'pdf', 'safetensors', 'ckpt'],
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
|
@ -71,69 +31,82 @@ const props = defineProps({
|
|||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "model",
|
||||
default: 'model',
|
||||
},
|
||||
});
|
||||
|
||||
})
|
||||
// 定义上传完成后发送的事件
|
||||
const emit = defineEmits<{
|
||||
'upload-success': [
|
||||
{
|
||||
objectKey: string
|
||||
objectUrl: string
|
||||
fileName: string
|
||||
currentFileSize: number
|
||||
hashCode: number
|
||||
},
|
||||
]
|
||||
}>()
|
||||
const messageReactive: MessageReactive | null = null
|
||||
// 响应式状态
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||
const file = ref<File | null>(null);
|
||||
const fileName = ref("");
|
||||
const fileSize = ref(0);
|
||||
const progress = ref(0);
|
||||
const objectKey = ref("");
|
||||
const chunkSize = ref(5 * 1024 * 1024);
|
||||
const totalChunks = ref(0);
|
||||
const currentChunk = ref(1);
|
||||
const uploadId = ref("");
|
||||
const partArr = ref([]);
|
||||
const showFileName = ref(false);
|
||||
const bucketName = ref("");
|
||||
const objectKeyName = ref("");
|
||||
const requiredValid = ref(false);
|
||||
const hashCode = ref("");
|
||||
const message = useMessage();
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
const file = ref<File | null>(null)
|
||||
const fileName = ref('')
|
||||
const fileSize = ref(0)
|
||||
const progress = ref(0)
|
||||
const objectKey = ref('')
|
||||
const chunkSize = ref(5 * 1024 * 1024)
|
||||
const totalChunks = ref(0)
|
||||
const currentChunk = ref(1)
|
||||
const uploadId = ref('')
|
||||
const partArr = ref([])
|
||||
const showFileName = ref(false)
|
||||
const bucketName = ref('')
|
||||
const objectKeyName = ref('')
|
||||
const requiredValid = ref(false)
|
||||
const hashCode = ref('')
|
||||
const message = useMessage()
|
||||
const currentFileSize = ref(0)
|
||||
|
||||
// 添加新的状态来跟踪上传状态
|
||||
const uploadStatus = ref<'idle' | 'uploading' | 'paused' | 'completed'>('idle');
|
||||
const uploadedChunks = ref<Set<number>>(new Set());
|
||||
const retryCount = ref<number>(0);
|
||||
const maxRetries = 5; // 最大重试次数
|
||||
const uploadStatus = ref<'idle' | 'uploading' | 'paused' | 'completed'>('idle')
|
||||
const uploadedChunks = ref<Set<number>>(new Set())
|
||||
const retryCount = ref<number>(0)
|
||||
const maxRetries = 5 // 最大重试次数
|
||||
|
||||
// 触发文件选择
|
||||
const triggerFileSelect = () => {
|
||||
fileInputRef.value?.click();
|
||||
};
|
||||
function triggerFileSelect() {
|
||||
fileInputRef.value?.click()
|
||||
}
|
||||
|
||||
// 获取接受的文件类型
|
||||
const getAcceptType = () => {
|
||||
return props.accept.join(",");
|
||||
};
|
||||
function getAcceptType() {
|
||||
return props.accept.join(',')
|
||||
}
|
||||
|
||||
// 添加计算hash的函数
|
||||
const calculateFileHash = (file: File): Promise<string> => {
|
||||
function calculateFileHash(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
const reader = new FileReader()
|
||||
reader.readAsArrayBuffer(file)
|
||||
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const buffer = event.target?.result as ArrayBuffer;
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
spark.append(buffer);
|
||||
const hash = spark.end();
|
||||
resolve(hash);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
const buffer = event.target?.result as ArrayBuffer
|
||||
const spark = new SparkMD5.ArrayBuffer()
|
||||
spark.append(buffer)
|
||||
const hash = spark.end()
|
||||
resolve(hash)
|
||||
}
|
||||
catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('Failed to read file'));
|
||||
};
|
||||
});
|
||||
};
|
||||
reject(new Error('Failed to read file'))
|
||||
}
|
||||
})
|
||||
}
|
||||
// const calculateFileHash = (file: File): Promise<string> => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const reader = new FileReader();
|
||||
|
@ -166,81 +139,85 @@ const calculateFileHash = (file: File): Promise<string> => {
|
|||
// };
|
||||
|
||||
// 初始化参数
|
||||
const initParam = () => {
|
||||
file.value = null;
|
||||
objectKey.value = "";
|
||||
totalChunks.value = 0;
|
||||
currentChunk.value = 1;
|
||||
uploadId.value = "";
|
||||
partArr.value = [];
|
||||
requiredValid.value = false;
|
||||
uploadStatus.value = 'idle';
|
||||
uploadedChunks.value.clear();
|
||||
retryCount.value = 0;
|
||||
};
|
||||
function initParam() {
|
||||
file.value = null
|
||||
objectKey.value = ''
|
||||
totalChunks.value = 0
|
||||
currentChunk.value = 1
|
||||
uploadId.value = ''
|
||||
partArr.value = []
|
||||
requiredValid.value = false
|
||||
uploadStatus.value = 'idle'
|
||||
uploadedChunks.value.clear()
|
||||
retryCount.value = 0
|
||||
}
|
||||
|
||||
function getOssDefaultPath(name:any) {
|
||||
const timestamp = Date.now();
|
||||
const now = new Date();
|
||||
const year = now.getFullYear(); // 获取当前年份
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0"); // 获取当前月份,确保两位
|
||||
const day = String(now.getDate()).padStart(2, "0"); // 获取当前日期,确保两位
|
||||
function getOssDefaultPath(name: any) {
|
||||
const timestamp = Date.now()
|
||||
const now = new Date()
|
||||
const year = now.getFullYear() // 获取当前年份
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0') // 获取当前月份,确保两位
|
||||
const day = String(now.getDate()).padStart(2, '0') // 获取当前日期,确保两位
|
||||
|
||||
return `${year}/${month}/${day}/${timestamp}/${name}`; // 拼接路径
|
||||
return `${year}/${month}/${day}/${timestamp}/${name}` // 拼接路径
|
||||
}
|
||||
// 选择文件处理
|
||||
const selectFile = async (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.files?.length) return;
|
||||
async function selectFile(event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
if (!target.files?.length)
|
||||
return
|
||||
|
||||
file.value = target.files[0];
|
||||
target.value = "";
|
||||
file.value = target.files[0]
|
||||
target.value = ''
|
||||
|
||||
if (file.value?.size) {
|
||||
currentFileSize.value = file.value?.size
|
||||
// 文件大小检查
|
||||
const calculatedFileSize = Math.round((file.value.size / 1024 / 1024) * 100) / 100;
|
||||
const calculatedFileSize = Math.round((file.value.size / 1024 / 1024) * 100) / 100
|
||||
if (calculatedFileSize > props.fileSize) {
|
||||
message.warning(`文件大小超过上限${props.fileSize}MB`);
|
||||
return;
|
||||
message.warning(`文件大小超过上限${props.fileSize}MB`)
|
||||
return
|
||||
}
|
||||
fileSize.value = calculatedFileSize;
|
||||
fileSize.value = calculatedFileSize
|
||||
|
||||
// 文件类型检查
|
||||
const selectedFileName = file.value.name;
|
||||
const fileType = selectedFileName.split(".").pop()?.toLowerCase();
|
||||
const selectedFileName = file.value.name
|
||||
const fileType = selectedFileName.split('.').pop()?.toLowerCase()
|
||||
if (!fileType || !props.accept.includes(fileType)) {
|
||||
message.warning("文件类型不支持");
|
||||
return;
|
||||
message.warning('文件类型不支持')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果存在未完成的上传,询问是否继续
|
||||
if (uploadStatus.value === 'paused' && file.value) {
|
||||
const shouldResume = window.confirm('检测到未完成的上传,是否继续上传?');
|
||||
const shouldResume = window.confirm('检测到未完成的上传,是否继续上传?')
|
||||
if (shouldResume) {
|
||||
await resumeUpload();
|
||||
return;
|
||||
} else {
|
||||
await resumeUpload()
|
||||
return
|
||||
}
|
||||
else {
|
||||
// 用户选择重新上传,清除之前的上传状态
|
||||
initParam();
|
||||
initParam()
|
||||
}
|
||||
}
|
||||
|
||||
// 继续上传流程
|
||||
progress.value = 0;
|
||||
fileName.value = file.value.name;
|
||||
showFileName.value = true;
|
||||
progress.value = 0
|
||||
fileName.value = file.value.name
|
||||
showFileName.value = true
|
||||
|
||||
// 计算文件hash
|
||||
if (props.verifyHash) {
|
||||
message.info("正在校验文件hash...");
|
||||
message.info('正在校验文件hash...')
|
||||
try {
|
||||
hashCode.value = await calculateFileHash(file.value);
|
||||
} catch (err) {
|
||||
hashCode.value = await calculateFileHash(file.value)
|
||||
}
|
||||
catch (err) {
|
||||
message.warning(err)
|
||||
hashCode.value = ''
|
||||
}finally{
|
||||
}
|
||||
finally {
|
||||
// messageReactive.destroy();
|
||||
// messageReactive = null;
|
||||
}
|
||||
|
@ -250,14 +227,15 @@ const selectFile = async (event: Event) => {
|
|||
try {
|
||||
const hashRes = await request.get(
|
||||
`/file/selectHash?hashCode=${hashCode.value}&type=${
|
||||
props.type === "model" ? 0 : 1
|
||||
}`
|
||||
);
|
||||
props.type === 'model' ? 0 : 1
|
||||
}`,
|
||||
)
|
||||
if (hashRes.data !== 1) {
|
||||
message.warning("文件已存在");
|
||||
return;
|
||||
message.warning('文件已存在')
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
if (messageReactive) {
|
||||
// messageReactive.destroy();
|
||||
// messageReactive = null;
|
||||
|
@ -267,7 +245,7 @@ const selectFile = async (event: Event) => {
|
|||
|
||||
// 校验名称
|
||||
if (props.verifyName) {
|
||||
message.info("正在校验文件名...");
|
||||
message.info('正在校验文件名...')
|
||||
// 检查文件是否已存在
|
||||
// 上传文件前先校验是否存在 存在为0 不存在为1
|
||||
try {
|
||||
|
@ -277,15 +255,16 @@ const selectFile = async (event: Event) => {
|
|||
const res = await request.post(
|
||||
`/file/selectFile`,
|
||||
{
|
||||
type:props.type,
|
||||
name:fileName.value
|
||||
}
|
||||
);
|
||||
type: props.type,
|
||||
name: fileName.value,
|
||||
},
|
||||
)
|
||||
if (res.data !== 1) {
|
||||
message.warning("文件名已存在");
|
||||
return;
|
||||
message.warning('文件名已存在')
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
if (messageReactive) {
|
||||
// messageReactive.destroy();
|
||||
// messageReactive = null;
|
||||
|
@ -293,17 +272,18 @@ const selectFile = async (event: Event) => {
|
|||
}
|
||||
}
|
||||
|
||||
totalChunks.value = Math.ceil(file.value.size / chunkSize.value);
|
||||
objectKey.value = `${getOssDefaultPath(fileName.value)}`;
|
||||
totalChunks.value = Math.ceil(file.value.size / chunkSize.value)
|
||||
objectKey.value = `${getOssDefaultPath(fileName.value)}`
|
||||
// 获取上传ID
|
||||
const res = await request.get(`/file/getUploadId?objectKey=${objectKey.value}`);
|
||||
uploadId.value = res.data;
|
||||
progress.value = 1;
|
||||
uploadFile();
|
||||
} catch (error) {
|
||||
console.error("文件处理失败:", error);
|
||||
message.error("文件处理失败");
|
||||
initParam();
|
||||
const res = await request.get(`/file/getUploadId?objectKey=${objectKey.value}`)
|
||||
uploadId.value = res.data
|
||||
progress.value = 1
|
||||
uploadFile()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('文件处理失败:', error)
|
||||
message.error('文件处理失败')
|
||||
initParam()
|
||||
}
|
||||
}
|
||||
// const target = event.target as HTMLInputElement;
|
||||
|
@ -342,178 +322,210 @@ const selectFile = async (event: Event) => {
|
|||
// message.error("获取上传ID失败");
|
||||
// }
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
const uploadFile = async () => {
|
||||
if (!file.value) return;
|
||||
async function uploadFile() {
|
||||
if (!file.value)
|
||||
return
|
||||
|
||||
while (currentChunk.value <= totalChunks.value) {
|
||||
// 如果这个分片已经上传成功,跳过
|
||||
if (uploadedChunks.value.has(currentChunk.value)) {
|
||||
currentChunk.value++;
|
||||
continue;
|
||||
currentChunk.value++
|
||||
continue
|
||||
}
|
||||
|
||||
const index = currentChunk.value - 1;
|
||||
const start = index * chunkSize.value;
|
||||
const end = Math.min((index + 1) * chunkSize.value, file.value.size);
|
||||
const index = currentChunk.value - 1
|
||||
const start = index * chunkSize.value
|
||||
const end = Math.min((index + 1) * chunkSize.value, file.value.size)
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file.value.slice(start, end));
|
||||
formData.append("chunk", String(currentChunk.value));
|
||||
formData.append("objectKey", objectKey.value);
|
||||
formData.append("uploadId", uploadId.value);
|
||||
const formData = new FormData()
|
||||
formData.append('file', file.value.slice(start, end))
|
||||
formData.append('chunk', String(currentChunk.value))
|
||||
formData.append('objectKey', objectKey.value)
|
||||
formData.append('uploadId', uploadId.value)
|
||||
|
||||
try {
|
||||
uploadStatus.value = 'uploading';
|
||||
const res = await request.post("/file/chunk", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
uploadStatus.value = 'uploading'
|
||||
const res = await request.post('/file/chunk', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
// 添加上传进度监听
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const percentCompleted = Math.round(
|
||||
((currentChunk.value - 1) / totalChunks.value) * 100 +
|
||||
(progressEvent.loaded / progressEvent.total!) * (100 / totalChunks.value)
|
||||
);
|
||||
progress.value = percentCompleted;
|
||||
}
|
||||
});
|
||||
((currentChunk.value - 1) / totalChunks.value) * 100
|
||||
+ (progressEvent.loaded / progressEvent.total!) * (100 / totalChunks.value),
|
||||
)
|
||||
progress.value = percentCompleted
|
||||
},
|
||||
})
|
||||
|
||||
// 记录成功上传的分片
|
||||
uploadedChunks.value.add(currentChunk.value);
|
||||
partArr.value.push(res.data);
|
||||
currentChunk.value++;
|
||||
retryCount.value = 0; // 重置重试计数
|
||||
uploadedChunks.value.add(currentChunk.value)
|
||||
partArr.value.push(res.data)
|
||||
currentChunk.value++
|
||||
retryCount.value = 0 // 重置重试计数
|
||||
|
||||
progress.value = Math.floor((currentChunk.value / totalChunks.value) * 100);
|
||||
} catch (error) {
|
||||
console.error("切片上传失败:", error);
|
||||
progress.value = Math.floor((currentChunk.value / totalChunks.value) * 100)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('切片上传失败:', error)
|
||||
|
||||
// 重试逻辑
|
||||
if (retryCount.value < maxRetries) {
|
||||
retryCount.value++;
|
||||
message.warning(`第 ${currentChunk.value} 个分片上传失败,正在进行第 ${retryCount.value} 次重试...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试
|
||||
continue; // 重试当前分片
|
||||
retryCount.value++
|
||||
message.warning(`第 ${currentChunk.value} 个分片上传失败,正在进行第 ${retryCount.value} 次重试...`)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒后重试
|
||||
continue // 重试当前分片
|
||||
}
|
||||
|
||||
// 超过最大重试次数
|
||||
uploadStatus.value = 'paused';
|
||||
message.error(`上传失败,已暂停。您可以稍后继续上传。`);
|
||||
return;
|
||||
uploadStatus.value = 'paused'
|
||||
message.error(`上传失败,已暂停。您可以稍后继续上传。`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 所有分片上传完成
|
||||
if (currentChunk.value > totalChunks.value) {
|
||||
progress.value = 99;
|
||||
uploadStatus.value = 'completed';
|
||||
await complete();
|
||||
progress.value = 99
|
||||
uploadStatus.value = 'completed'
|
||||
await complete()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 添加继续上传方法
|
||||
const resumeUpload = async () => {
|
||||
async function resumeUpload() {
|
||||
if (uploadStatus.value === 'paused' && file.value) {
|
||||
retryCount.value = 0; // 重置重试计数
|
||||
message.info('正在继续上传...');
|
||||
await uploadFile();
|
||||
retryCount.value = 0 // 重置重试计数
|
||||
message.info('正在继续上传...')
|
||||
await uploadFile()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 完成上传
|
||||
const complete = async () => {
|
||||
async function complete() {
|
||||
try {
|
||||
const res = await request.post(
|
||||
`/file/completeUpload?objectKey=${objectKey.value}&uploadId=${uploadId.value}`,
|
||||
partArr.value
|
||||
);
|
||||
partArr.value,
|
||||
)
|
||||
|
||||
bucketName.value = res.data.bucketName;
|
||||
objectKeyName.value = res.data.objectKey;
|
||||
progress.value = 100;
|
||||
bucketName.value = res.data.bucketName
|
||||
objectKeyName.value = res.data.objectKey
|
||||
progress.value = 100
|
||||
|
||||
// 发送上传成功事件
|
||||
emit("upload-success", {
|
||||
emit('upload-success', {
|
||||
objectKey: objectKeyName.value,
|
||||
objectUrl: res.data.objectUrl, // 假设后端返回了文件路径
|
||||
fileName: fileName.value,
|
||||
currentFileSize: currentFileSize.value,
|
||||
hashCode: hashCode.value,
|
||||
});
|
||||
})
|
||||
|
||||
initParam();
|
||||
} catch (error) {
|
||||
console.error("文件上传失败:", error);
|
||||
message.warning("文件上传失败");
|
||||
initParam()
|
||||
}
|
||||
};
|
||||
|
||||
// 定义上传完成后发送的事件
|
||||
const emit = defineEmits<{
|
||||
"upload-success": [
|
||||
{
|
||||
objectKey: string;
|
||||
objectUrl: string;
|
||||
fileName: string;
|
||||
currentFileSize: number;
|
||||
hashCode: number;
|
||||
catch (error) {
|
||||
console.error('文件上传失败:', error)
|
||||
message.warning('文件上传失败')
|
||||
}
|
||||
];
|
||||
}>();
|
||||
}
|
||||
|
||||
// 取消上传
|
||||
const cancelUploadInfo = async () => {
|
||||
async function cancelUploadInfo() {
|
||||
if (uploadStatus.value !== 'completed') {
|
||||
try {
|
||||
await request.post("/file/cancelUpload", {
|
||||
await request.post('/file/cancelUpload', {
|
||||
objectKey: objectKey.value,
|
||||
uploadId: uploadId.value,
|
||||
});
|
||||
uploadStatus.value = 'idle';
|
||||
uploadedChunks.value.clear();
|
||||
progress.value = 0;
|
||||
showFileName.value = false;
|
||||
initParam();
|
||||
} catch (error) {
|
||||
message.error("取消上传失败");
|
||||
})
|
||||
uploadStatus.value = 'idle'
|
||||
uploadedChunks.value.clear()
|
||||
progress.value = 0
|
||||
showFileName.value = false
|
||||
initParam()
|
||||
}
|
||||
catch (error) {
|
||||
message.error('取消上传失败')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 校验是否已上传
|
||||
const validRequired = () => {
|
||||
function validRequired() {
|
||||
if (bucketName.value && objectKeyName.value) {
|
||||
requiredValid.value = false;
|
||||
return true;
|
||||
requiredValid.value = false
|
||||
return true
|
||||
}
|
||||
requiredValid.value = true;
|
||||
return false;
|
||||
};
|
||||
requiredValid.value = true
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取上传信息
|
||||
const getUploadInfo = () => {
|
||||
function getUploadInfo() {
|
||||
return {
|
||||
bucketName: bucketName.value,
|
||||
fileName: objectKeyName.value,
|
||||
fileSize: fileSize.value,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
progress.value = 0;
|
||||
});
|
||||
progress.value = 0
|
||||
})
|
||||
|
||||
// 明确暴露方法
|
||||
defineExpose({
|
||||
triggerFileSelect,
|
||||
validRequired,
|
||||
getUploadInfo,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<template>
|
||||
<div class="upload-container">
|
||||
<div class="fileUpload">
|
||||
<div class="item-upload">
|
||||
<!-- <span v-if="required" class="required">*</span> -->
|
||||
<!-- <n-button type="success" @click="triggerFileSelect"> 文件选择 </n-button> -->
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
type="file"
|
||||
class="file2"
|
||||
style="display: none"
|
||||
@change="selectFile"
|
||||
>
|
||||
</div>
|
||||
<!-- <div v-if="showFileName" class="item-info">
|
||||
{{ fileName }}
|
||||
</div>
|
||||
<div v-else class="item-info">支持{{ getAcceptType() }}文件格式</div>
|
||||
<div v-if="progress > 0" class="item-cancel">
|
||||
<n-button type="success" style="margin-top: 5px" @click="cancelUploadInfo">
|
||||
X
|
||||
</n-button>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-if="progress > 0" class="item-process">
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="progress"
|
||||
indicator-placement="inside"
|
||||
processing
|
||||
/>
|
||||
</div>
|
||||
<!-- <div v-if="requiredValid" class="item-valid">请上传文件</div> -->
|
||||
<!-- <div class="resume-upload">
|
||||
<n-button type="primary" @click="resumeUpload">
|
||||
继续上传
|
||||
</n-button>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-container {
|
||||
.fileUpload {
|
||||
display: flex;
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
<script setup lang="ts">
|
||||
import { EllipsisVertical, User } from 'lucide-vue-next'
|
||||
import { defineEmits, defineProps } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import EditPlanet from './EditPlanet.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
item: PlanetItem
|
||||
type: 'myCreate' | 'myJoin' | 'all'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', id: number): void
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const isDelete = ref(false)
|
||||
interface PlanetItem {
|
||||
id: number
|
||||
imageUrl: string
|
||||
publishNum: number
|
||||
communityName: string
|
||||
avatar: string
|
||||
nickName: string
|
||||
createDay: number
|
||||
description: string
|
||||
price: number | null
|
||||
userType: number
|
||||
}
|
||||
|
||||
const showEditPlanet = ref(false)
|
||||
|
||||
const menuItems = computed(() => {
|
||||
const items = [
|
||||
{
|
||||
label: '分享星球',
|
||||
action: () => {
|
||||
const url = window.location.href
|
||||
navigator.clipboard.writeText(url)
|
||||
message.success('链接已复制到剪贴板')
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '退出星球',
|
||||
action: async () => {
|
||||
isDelete.value = true
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (props.type === 'myCreate') {
|
||||
items.splice(1, 0, {
|
||||
label: '星球设置',
|
||||
action: () => {
|
||||
showEditPlanet.value = true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (props.item.userType !== 0) {
|
||||
items.splice(1, 0, {
|
||||
label: '成员管理',
|
||||
action: () => window.open(`/planetMember?communityId=${props.item.id}&tenantId=${props.item.userId}`, '_blank'),
|
||||
})
|
||||
}
|
||||
return items
|
||||
})
|
||||
|
||||
async function onDelete() {
|
||||
try {
|
||||
const res = await request.post('/community/quit', { communityId: props.item.id, tenantId: props.item.userId })
|
||||
if (res.code === 200) {
|
||||
message.success('退出成功!')
|
||||
emit('refresh')
|
||||
isDelete.value = false
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
if (error.code !== 12202) {
|
||||
message.error('退出失败,请重新退出!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showDropdown = ref(false)
|
||||
let timeoutId: number | null = null
|
||||
|
||||
function showMenu() {
|
||||
if (timeoutId)
|
||||
clearTimeout(timeoutId)
|
||||
showDropdown.value = true
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
timeoutId = setTimeout(() => {
|
||||
showDropdown.value = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function handleMenuClick(e: Event) {
|
||||
e.stopPropagation() // 防止触发 goDetail
|
||||
}
|
||||
function handleSuccess() {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
function goDetail(item: any) {
|
||||
if (item.isJoin === 1) {
|
||||
router.push(`/public-planet-detail?communityId=${item.id}&tenantId=${item.tenantId}`)
|
||||
}
|
||||
else {
|
||||
router.push(`/planet-detail?communityId=${item.id}&tenantId=${item.tenantId}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-white rounded-lg shadow-sm flex p-4 cursor-pointer hover:shadow-[0_4px_14px_0_rgba(0,0,0,0.1)]" @click="goDetail(item)">
|
||||
<div class="w-[161px] h-[161px] relative rounded-lg">
|
||||
<img class="w-full h-full object-cover rounded-lg" :src="item.imageUrl" alt="">
|
||||
<span class="absolute bottom-2 left-2 text-white text-xs">{{ item.publishNum || 0 }}人已经加入</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-4 flex-1 justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-base font-bold flex-1 truncate">
|
||||
{{ item.communityName }}
|
||||
</div>
|
||||
<div v-if="props.type === 'myCreate'" class="relative" @click="handleMenuClick">
|
||||
<div
|
||||
class="cursor-pointer p-1 hover:bg-gray-100 rounded-full"
|
||||
@mouseenter="showMenu"
|
||||
@mouseleave="hideMenu"
|
||||
>
|
||||
<User class="w-4 h-4" />
|
||||
</div>
|
||||
<!-- 下拉菜单 -->
|
||||
<div
|
||||
v-if="showDropdown"
|
||||
class="absolute right-0 top-full mt-1 bg-white border border-[#e5e6eb] rounded-lg py-2 min-w-[120px] z-999 shadow-[0_4px_14px_0_rgba(0,0,0,0.1)]"
|
||||
@mouseenter="showMenu"
|
||||
@mouseleave="hideMenu"
|
||||
>
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.label"
|
||||
class="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer"
|
||||
@click="item.action"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.type === 'myJoin'" class="relative" @click="handleMenuClick">
|
||||
<div
|
||||
class="cursor-pointer p-1 hover:bg-gray-100 rounded-full"
|
||||
@mouseenter="showMenu"
|
||||
@mouseleave="hideMenu"
|
||||
>
|
||||
<EllipsisVertical class="w-6 h-4 rotate-90" />
|
||||
</div>
|
||||
<!-- 下拉菜单 -->
|
||||
<div
|
||||
v-if="showDropdown"
|
||||
class="absolute right-0 top-full mt-1 bg-white border border-[#e5e6eb] rounded-lg py-2 min-w-[120px] z-10 shadow-[0_4px_14px_0_rgba(0,0,0,0.1)]"
|
||||
@mouseenter="showMenu"
|
||||
@mouseleave="hideMenu"
|
||||
>
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.label"
|
||||
class="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer"
|
||||
@click="item.action"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 text-xs text-[#878d95]">
|
||||
<img class="w-[20px] h-[20px] rounded-full" :src="item.avatar" alt="">
|
||||
<div>
|
||||
{{ item.nickName }}
|
||||
</div>
|
||||
<div>创建{{ item.createDay || 0 }}天</div>
|
||||
</div>
|
||||
<div class="text-sm text-[#4a5563] line-clamp-3">
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[#fc4141]">
|
||||
<div v-if="item.price">
|
||||
<span class="text-base font-bold mr-1">
|
||||
{{ item.price }}
|
||||
</span>
|
||||
<span class="text-xs">
|
||||
金币
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
免费
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<n-modal
|
||||
v-model:show="isDelete"
|
||||
:mask-closable="false"
|
||||
preset="dialog"
|
||||
title="提示!"
|
||||
content="退出后将无法查看/操作该星球的内容,是否确认退出该星球"
|
||||
negative-text="取消"
|
||||
positive-text="确认退出"
|
||||
@negative-click="isDelete = false"
|
||||
@positive-click="onDelete"
|
||||
/>
|
||||
<EditPlanet
|
||||
v-model:show="showEditPlanet"
|
||||
:community-id="props.item.id"
|
||||
:tenant-id="props.item.userId"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { commonApi } from "@/api/common";
|
||||
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();
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import { commonApi } from '@/api/common'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { Asterisk, Trash } from 'lucide-vue-next'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
// 可接受的文件类型
|
||||
const props = defineProps({
|
||||
|
@ -13,39 +11,43 @@ const props = defineProps({
|
|||
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 emit = defineEmits(['update:modelValue', 'nextStep', 'prevStep'])
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const acceptTypes
|
||||
= '.safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft'
|
||||
const acceptTypesList = [
|
||||
"safetensors",
|
||||
"ckpt",
|
||||
"pt",
|
||||
"bin",
|
||||
"pth",
|
||||
"zip",
|
||||
"json",
|
||||
"flow",
|
||||
"lightflow",
|
||||
"yaml",
|
||||
"yml",
|
||||
"onnx",
|
||||
"gguf",
|
||||
"sft",
|
||||
];
|
||||
'safetensors',
|
||||
'ckpt',
|
||||
'pt',
|
||||
'bin',
|
||||
'pth',
|
||||
'zip',
|
||||
'json',
|
||||
'flow',
|
||||
'lightflow',
|
||||
'yaml',
|
||||
'yml',
|
||||
'onnx',
|
||||
'gguf',
|
||||
'sft',
|
||||
]
|
||||
const modelVersionItem = {
|
||||
objectKey: null,
|
||||
isEncrypt: 0, //0不加密
|
||||
delFlag: "0", // 0代表存在 2代表删除
|
||||
versionName: "", // 版本名称
|
||||
isEncrypt: 0, // 0不加密
|
||||
delFlag: '0', // 0代表存在 2代表删除
|
||||
versionName: '', // 版本名称
|
||||
modelVersionType: null, // 基础模型
|
||||
versionDescription: "", // 版本描述
|
||||
filePath: "", // 文件路径
|
||||
fileName: "", //
|
||||
versionDescription: '', // 版本描述
|
||||
filePath: '', // 文件路径
|
||||
fileName: '', //
|
||||
sampleImagePaths: [], // 第三部的图片路径最多20张,切割
|
||||
triggerWords: "", // 触发词
|
||||
triggerWords: '', // 触发词
|
||||
isPublic: null, // 权限是否公
|
||||
isOnlineUse: 1, //在线使用
|
||||
isOnlineUse: 1, // 在线使用
|
||||
allowFusion: 1, // 是否允许融合
|
||||
isFree: 0, // 0免费
|
||||
allowDownloadImage: 1, // 允许下载生图
|
||||
|
@ -53,94 +55,95 @@ const modelVersionItem = {
|
|||
allowCommercialUse: 1, // 是否允许商用
|
||||
allowUsage: 1, // 允许模型转售或者融合出售
|
||||
isExclusiveModel: 1, // 是否为独家模型这个字段
|
||||
hideImageGenInfo: 0, //隐藏图片生成信息
|
||||
hideImageGenInfo: 0, // 隐藏图片生成信息
|
||||
id: null,
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addVersion,
|
||||
});
|
||||
const loading = ref(false);
|
||||
})
|
||||
const loading = ref(false)
|
||||
const localForm = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit("update:modelValue", value);
|
||||
emit('update:modelValue', value)
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
watch(
|
||||
() => localForm.value,
|
||||
(newVal) => {
|
||||
emit("update:modelValue", newVal);
|
||||
emit('update:modelValue', newVal)
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
const formRefs = ref<(FormInst | null)[]>([]);
|
||||
const formRefs = ref<(FormInst | null)[]>([])
|
||||
function setFormRef(el: FormInst | null, index: number) {
|
||||
if (el) {
|
||||
formRefs.value[index] = el;
|
||||
formRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
const rules = {
|
||||
versionName: {
|
||||
required: true,
|
||||
message: "",
|
||||
trigger: "blur",
|
||||
message: '',
|
||||
trigger: 'blur',
|
||||
},
|
||||
modelVersionType: {
|
||||
required: true,
|
||||
message: "",
|
||||
trigger: "blur",
|
||||
message: '',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
}
|
||||
function addVersion() {
|
||||
const param = cloneDeep(modelVersionItem);
|
||||
localForm.value.modelVersionList.unshift(param);
|
||||
const param = cloneDeep(modelVersionItem)
|
||||
localForm.value.modelVersionList.unshift(param)
|
||||
}
|
||||
const originalBtnList = ref([
|
||||
{
|
||||
label: "免费",
|
||||
label: '免费',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: "会员下载",
|
||||
label: '会员下载',
|
||||
value: 0,
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
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 === ""
|
||||
localForm.value.modelVersionList[i].delFlag === '0'
|
||||
&& localForm.value.modelVersionList[i].fileName === ''
|
||||
) {
|
||||
return message.error("请上传文件");
|
||||
return message.error('请上传文件')
|
||||
}
|
||||
const regex = /[\u4E00-\u9FA5]/; // 匹配汉字的正则表达式
|
||||
const regex = /[\u4E00-\u9FA5]/ // 匹配汉字的正则表达式
|
||||
if (
|
||||
localForm.value.modelVersionList[i].delFlag === "0" &&
|
||||
!regex.test(localForm.value.modelVersionList[i].versionDescription)
|
||||
localForm.value.modelVersionList[i].delFlag === '0'
|
||||
&& !regex.test(localForm.value.modelVersionList[i].versionDescription)
|
||||
) {
|
||||
return message.error("请用中文填写版本介绍");
|
||||
return message.error('请用中文填写版本介绍')
|
||||
}
|
||||
}
|
||||
try {
|
||||
const promises = formRefs.value
|
||||
.filter((form): form is FormInst => form !== null)
|
||||
.map((form) => form.validate());
|
||||
.map(form => form.validate())
|
||||
|
||||
await Promise.all(promises);
|
||||
emit("nextStep");
|
||||
} catch (errors) {
|
||||
console.error("部分表单验证失败:", errors);
|
||||
await Promise.all(promises)
|
||||
emit('nextStep')
|
||||
}
|
||||
catch (errors) {
|
||||
console.error('部分表单验证失败:', errors)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取上文件的信息
|
||||
const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null);
|
||||
const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
|
||||
// const getFileInfo = () => {
|
||||
// if (uploadRef.value[0].validRequired()) {
|
||||
// const fileInfo = uploadRef.value[0].getUploadInfo()
|
||||
|
@ -148,56 +151,58 @@ const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null);
|
|||
// }
|
||||
// }
|
||||
|
||||
const handleUploadSuccess = (fileInfo: {
|
||||
objectKey: string;
|
||||
objectUrl: string;
|
||||
fileName: string;
|
||||
currentFileSize: number;
|
||||
hashCode:string
|
||||
}) => {
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].filePath = fileInfo.objectUrl;
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].objectKey = fileInfo.objectKey;
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileName = fileInfo.fileName;
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileSize = fileInfo.currentFileSize;
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileHash = fileInfo.hashCode;
|
||||
function handleUploadSuccess(fileInfo: {
|
||||
objectKey: string
|
||||
objectUrl: string
|
||||
fileName: string
|
||||
currentFileSize: number
|
||||
hashCode: string
|
||||
}) {
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].filePath = fileInfo.objectUrl
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].objectKey = fileInfo.objectKey
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileName = fileInfo.fileName
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileSize = fileInfo.currentFileSize
|
||||
localForm.value.modelVersionList[uploadFileIndex.value].fileHash = fileInfo.hashCode
|
||||
// message.success('文件上传成功')
|
||||
// 这里可以处理文件上传成功后的逻辑
|
||||
// 比如更新表单数据等
|
||||
};
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
const uploadFileIndex = ref(0);
|
||||
const uploadFileIndex = ref(0)
|
||||
async function triggerFileInput(index: number) {
|
||||
uploadRef.value[0].triggerFileSelect()
|
||||
uploadFileIndex.value = index;
|
||||
uploadFileIndex.value = index
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
emit("prevStep");
|
||||
emit('prevStep')
|
||||
}
|
||||
|
||||
const baseModelTypeList = ref([]);
|
||||
const baseModelTypeList = ref([])
|
||||
async function getDictType() {
|
||||
try {
|
||||
const res = await commonApi.dictType({ type: "mode_version_type" });
|
||||
const res = await commonApi.dictType({ type: 'mode_version_type' })
|
||||
if (res.code === 200) {
|
||||
baseModelTypeList.value = res.data;
|
||||
baseModelTypeList.value = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
getDictType();
|
||||
getDictType()
|
||||
|
||||
function computedDelFlag() {
|
||||
return localForm.value.modelVersionList.filter((item) => item.delFlag === "0");
|
||||
return localForm.value.modelVersionList.filter(item => item.delFlag === '0')
|
||||
}
|
||||
function onDelete(index: number) {
|
||||
if (computedDelFlag().length === 1) return;
|
||||
localForm.value.modelVersionList[index].delFlag = "2";
|
||||
if (computedDelFlag().length === 1)
|
||||
return
|
||||
localForm.value.modelVersionList[index].delFlag = '2'
|
||||
}
|
||||
|
||||
function handledeleteFile(item:any){
|
||||
function handledeleteFile(item: any) {
|
||||
item.filePath = ''
|
||||
item.fileKey = ''
|
||||
item.fileName = ''
|
||||
|
@ -280,7 +285,9 @@ function handledeleteFile(item:any){
|
|||
>
|
||||
上传文件
|
||||
</div>
|
||||
<div class="my-3">点击上传文件</div>
|
||||
<div class="my-3">
|
||||
点击上传文件
|
||||
</div>
|
||||
<div class="text-[#999999] text-xs">
|
||||
.safetensors/.ckpt/.pt/.bin/.pth/.zip/.json/.flow/.lightflow/.yaml/.yml/.onnx/.gguf/.sft
|
||||
</div>
|
||||
|
@ -291,10 +298,10 @@ function handledeleteFile(item:any){
|
|||
:verify-name="true"
|
||||
type="model"
|
||||
:accept="acceptTypesList"
|
||||
@upload-success="handleUploadSuccess"
|
||||
:required="true"
|
||||
:file-size="30000"
|
||||
></fileUpload>
|
||||
@upload-success="handleUploadSuccess"
|
||||
/>
|
||||
</div>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
@ -306,8 +313,12 @@ function handledeleteFile(item:any){
|
|||
<WangEditor v-model="item.versionDescription" />
|
||||
</client-only>
|
||||
</div>
|
||||
<div class="mt-6">触发词</div>
|
||||
<div class="-mb-5 text-gray-400 text-[12px]">请输入您用来训练的单词</div>
|
||||
<div class="mt-6">
|
||||
触发词
|
||||
</div>
|
||||
<div class="-mb-5 text-gray-400 text-[12px]">
|
||||
请输入您用来训练的单词
|
||||
</div>
|
||||
<n-form-item path="triggerWords">
|
||||
<n-input v-model:value="item.triggerWords" placeholder="例如: 1boy" />
|
||||
</n-form-item>
|
||||
|
@ -328,7 +339,9 @@ function handledeleteFile(item:any){
|
|||
</div> -->
|
||||
</n-form>
|
||||
|
||||
<div class="text-gray-400 text-[12px] my-4">许可范围</div>
|
||||
<div class="text-gray-400 text-[12px] my-4">
|
||||
许可范围
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-[50%] mb-2">
|
||||
<n-checkbox
|
||||
|
@ -366,7 +379,9 @@ function handledeleteFile(item:any){
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-400 text-[12px] my-4">商用许可范围</div>
|
||||
<div class="text-gray-400 text-[12px] my-4">
|
||||
商用许可范围
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-[50%] mb-2">
|
||||
<n-checkbox
|
||||
|
@ -387,7 +402,9 @@ function handledeleteFile(item:any){
|
|||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="text-gray-400 text-[12px] my-4">独家设置</div>
|
||||
<div class="text-gray-400 text-[12px] my-4">
|
||||
独家设置
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<n-checkbox
|
||||
v-model:checked="item.isExclusiveModel"
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
export const appName = '魔创未来'
|
||||
export const appDescription = '魔创未来'
|
||||
export const authRoutes = ['/personal-center']
|
||||
export const verifyBlankRoute = ['/member-center']
|
||||
export const verifyBlankRoute = ['/member-center', '/planet']
|
||||
export const isOriginalList = [{
|
||||
label: '原创',
|
||||
value: 0,
|
||||
|
@ -18,7 +17,7 @@ export const isPublicList = [{
|
|||
value: 2,
|
||||
}]
|
||||
export const headerRole = { // 包括就隐藏
|
||||
inputSearch: ['/model-square']
|
||||
inputSearch: ['/model-square'],
|
||||
}
|
||||
// export const searchType = { // 包括就隐藏
|
||||
// 'picture-square':'image',
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
Binary,
|
||||
Code2,
|
||||
Crown,
|
||||
Earth,
|
||||
Image,
|
||||
LayoutGrid,
|
||||
Lightbulb,
|
||||
Maximize,
|
||||
Network,
|
||||
User,
|
||||
Workflow
|
||||
} from "lucide-vue-next";
|
||||
import { useRouter } from "vue-router";
|
||||
const userStore = useUserStore();
|
||||
Binary,
|
||||
Code2,
|
||||
Crown,
|
||||
Earth,
|
||||
Image,
|
||||
LayoutGrid,
|
||||
Lightbulb,
|
||||
Maximize,
|
||||
Network,
|
||||
User,
|
||||
Workflow,
|
||||
} from 'lucide-vue-next'
|
||||
// import { useRouter } from 'vue-router'
|
||||
|
||||
const userStore = useUserStore()
|
||||
// definePageMeta({
|
||||
// middleware:[
|
||||
// function (to, from ){
|
||||
|
@ -23,102 +24,103 @@ const userStore = useUserStore();
|
|||
// }
|
||||
// ]
|
||||
// })
|
||||
const modalStore = useModalStore();
|
||||
const modalStore = useModalStore()
|
||||
|
||||
const menuStore = useMenuStore();
|
||||
const router = useRouter();
|
||||
const menuStore = useMenuStore()
|
||||
// const router = useRouter()
|
||||
// 路径到图标的映射
|
||||
const iconMap: any = {
|
||||
"/model-square": LayoutGrid,
|
||||
"/picture-square": Lightbulb,
|
||||
"/work-square": Workflow,
|
||||
"/web-ui": Image,
|
||||
"/comfy-ui": Workflow,
|
||||
"/training-lora": Binary,
|
||||
"/high-availability": Maximize,
|
||||
"/api-platform": Code2,
|
||||
"/creator-center": Code2,
|
||||
"/personal-center": User,
|
||||
"/member-center": Crown,
|
||||
};
|
||||
const route = useRoute();
|
||||
'/model-square': LayoutGrid,
|
||||
'/picture-square': Lightbulb,
|
||||
'/work-square': Workflow,
|
||||
'/web-ui': Image,
|
||||
'/comfy-ui': Workflow,
|
||||
'/training-lora': Binary,
|
||||
'/high-availability': Maximize,
|
||||
'/api-platform': Code2,
|
||||
'/creator-center': Code2,
|
||||
'/personal-center': User,
|
||||
'/member-center': Crown,
|
||||
}
|
||||
const route = useRoute()
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
menuStore.setActiveMenu(path);
|
||||
menuStore.setActiveMenu(path)
|
||||
},
|
||||
{ immediate: true } // 这样一进入页面就会执行一次
|
||||
);
|
||||
{ immediate: true }, // 这样一进入页面就会执行一次
|
||||
)
|
||||
|
||||
const menuItems1 = ref({
|
||||
title: "探索",
|
||||
title: '探索',
|
||||
list: [
|
||||
{
|
||||
icon: LayoutGrid,
|
||||
text: "模型广场",
|
||||
route: "/model-square",
|
||||
text: '模型广场',
|
||||
route: '/model-square',
|
||||
},
|
||||
{
|
||||
icon: Lightbulb,
|
||||
text: "作品灵感",
|
||||
route: "/picture-square",
|
||||
text: '作品灵感',
|
||||
route: '/picture-square',
|
||||
},
|
||||
{
|
||||
icon: Workflow,
|
||||
text: "工作流",
|
||||
route: "/work-square",
|
||||
text: '工作流',
|
||||
route: '/work-square',
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
const menuItems2 = ref({
|
||||
title: "创作",
|
||||
title: '创作',
|
||||
list: [
|
||||
{
|
||||
icon: Network,
|
||||
text: "在线工作流",
|
||||
route: "",
|
||||
desc: "Comfy UI",
|
||||
text: '在线工作流',
|
||||
route: '',
|
||||
desc: 'Comfy UI',
|
||||
},
|
||||
{
|
||||
icon: Earth,
|
||||
text: "魔创星球",
|
||||
route: "",
|
||||
text: '魔创星球',
|
||||
route: '/planet',
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
const menuItems3 = ref({
|
||||
title: "其他",
|
||||
title: '其他',
|
||||
list: [
|
||||
{
|
||||
icon: User,
|
||||
text: "个人中心",
|
||||
route: "/personal-center",
|
||||
text: '个人中心',
|
||||
route: '/personal-center',
|
||||
},
|
||||
{
|
||||
icon: Crown,
|
||||
text: "会员中心",
|
||||
route: "/member-center",
|
||||
text: '会员中心',
|
||||
route: '/member-center',
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
|
||||
// 更新菜单项中的图标
|
||||
menuStore.menuItems = menuStore.menuItems.map((item: any) => ({
|
||||
...item,
|
||||
LucideIcon: iconMap[item.path], // 添加 Lucide 图标组件
|
||||
}));
|
||||
}))
|
||||
|
||||
function handleSide(event: Event, path: string) {
|
||||
if (path === "/member-center") {
|
||||
if (path === '/member-center') {
|
||||
if (!userStore.isLoggedIn) {
|
||||
modalStore.showLoginModal();
|
||||
} else {
|
||||
event.preventDefault(); // 阻止默认行为
|
||||
event.stopPropagation(); // 阻止事件冒泡
|
||||
const baseUrl = window.location.origin;
|
||||
window.open(`${baseUrl}/member-center`, "_blank", "noopener,noreferrer");
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
else {
|
||||
event.preventDefault() // 阻止默认行为
|
||||
event.stopPropagation() // 阻止事件冒泡
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/member-center`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
// 确保当前路由不变
|
||||
// nextTick(() => {
|
||||
|
@ -126,29 +128,32 @@ function handleSide(event: Event, path: string) {
|
|||
// navigateTo(route.path, { replace: true })
|
||||
// }
|
||||
// })
|
||||
} else if (path === "/personal-center" && !userStore.isLoggedIn) {
|
||||
modalStore.showLoginModal();
|
||||
} else {
|
||||
menuStore.setActiveMenu(path);
|
||||
}
|
||||
else if (path === '/personal-center' && !userStore.isLoggedIn) {
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
else {
|
||||
menuStore.setActiveMenu(path)
|
||||
}
|
||||
}
|
||||
|
||||
const appList = ref([]);
|
||||
const appList = ref([])
|
||||
async function getAppList() {
|
||||
try {
|
||||
const res = await request.get(`/app/list`);
|
||||
const res = await request.get(`/app/list`)
|
||||
if (res.code === 200) {
|
||||
appList.value = res.data;
|
||||
appList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getAppList();
|
||||
getAppList()
|
||||
|
||||
function toUs(url: string) {
|
||||
const baseUrl = window.location.origin;
|
||||
window.open(`${baseUrl}/us/${url}`, "_blank", "noopener,noreferrer");
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/us/${url}`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -160,7 +165,7 @@ function toUs(url: string) {
|
|||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<nav
|
||||
class="w-[230px] border-r border-gray-100 bg-gray-50/50 dark:border-dark-700 dark:bg-dark-800/50 overflow-y-auto"
|
||||
class="w-[230px] border-r border-gray-100 bg-gray-50/50 dark:border-dark-700 dark:bg-dark-800/50 overflow-y-auto scrollbar-hide [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
<div class="space-y-1 px-2">
|
||||
<div class="text-[#000] p-4">
|
||||
|
@ -270,36 +275,55 @@ function toUs(url: string) {
|
|||
<div class="px-4">
|
||||
<div class="text-xs text-gray-500 flex flex-wrap gap-2">
|
||||
<div
|
||||
class="cursor-pointer relative group"
|
||||
v-for="(item, index) in appList"
|
||||
:key="index"
|
||||
class="cursor-pointer relative group"
|
||||
>
|
||||
{{ item.name }}
|
||||
<div
|
||||
class="flex flex-col justify-center items-center gap-2 absolute bottom-4 left-0 w-[90px] h-[115px] hidden group-hover:block bg-white p-2 z-index-999 border border-solid border-[#ccc] rounded-lg"
|
||||
>
|
||||
<div class="text-xs text-center mb-2">扫码关注</div>
|
||||
<img class="w-[80px] h-[70px]" :src="item.url" alt="" />
|
||||
<div class="text-xs text-center mb-2">
|
||||
扫码关注
|
||||
</div>
|
||||
<img class="w-[80px] h-[70px]" :src="item.url" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-400 mt-2">
|
||||
<div class="cursor-pointer" @click="toUs('agreement')">用户协议</div>
|
||||
<div class="cursor-pointer" @click="toUs('privacy')">隐私政策</div>
|
||||
<div class="cursor-pointer" @click="toUs('aboutUs')">关于我们</div>
|
||||
<div class="flex justify-between text-xs text-gray-400 mt-2 mb-2">
|
||||
<div class="cursor-pointer" @click="toUs('agreement')">
|
||||
用户协议
|
||||
</div>
|
||||
<div class="cursor-pointer" @click="toUs('privacy')">
|
||||
隐私政策
|
||||
</div>
|
||||
<div class="cursor-pointer" @click="toUs('aboutUs')">
|
||||
关于我们
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<div class="scale-80">
|
||||
魔创未来(盘锦)量子科技有限公司
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<!-- <a class="cursor-pointer scale-80">
|
||||
辽ICP备2024045484号-1
|
||||
</div> -->
|
||||
<a class="text-xs leading-7 hover:opacity-80 mt-[8px]" href="https://beian.miit.gov.cn" rel="noreferrer" target="_blank">辽ICP备2024045484号-1</a>
|
||||
</div>
|
||||
<!-- <div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<div class="cursor-pointer scale-80">广州魔创未来科技有限公司</div>
|
||||
<div class="cursor-pointer scale-80">
|
||||
网信算备
|
||||
</div>
|
||||
<div class="cursor-pointer scale-80">
|
||||
110112129623601230015号
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<div class="cursor-pointer scale-80">京ICP备2023015442号</div>
|
||||
<div class="cursor-pointer scale-80">
|
||||
备案号: Beijing-PianYu-202402
|
||||
</div>
|
||||
<div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<div class="cursor-pointer scale-80">网信算备</div>
|
||||
<div class="cursor-pointer scale-80">110112129623601230015号</div>
|
||||
</div>
|
||||
<div class="justify-between text-xs text-gray-400 mt-2 scale-80">
|
||||
<div class="cursor-pointer scale-80">备案号: Beijing-PianYu-202402</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <NuxtLink
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PlanetHeader />
|
||||
<main class="flex-1 overflow-auto">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style></style>
|
|
@ -1,3 +1,57 @@
|
|||
<script setup>
|
||||
import { useRoute } from '#app'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
definePageMeta({
|
||||
layout: 'home',
|
||||
})
|
||||
const countdown = ref(3)
|
||||
const countdownText = ref('页面即将关闭...')
|
||||
let timer = null
|
||||
|
||||
let urlParams = null
|
||||
|
||||
// const urlParams = new URLSearchParams(window.location.search);
|
||||
const isSuccess = ref(false)
|
||||
async function handleAuthorization() {
|
||||
const uuid = route.query.uuid
|
||||
const code = route.query.code
|
||||
// const uuid = urlParams.get('uuid');
|
||||
// const code = urlParams.get('code');
|
||||
const url = `/wx/uuid/bind/openid?uuid=${uuid}&code=${code}`
|
||||
try {
|
||||
const res = await request.get(url)
|
||||
isSuccess.value = true
|
||||
}
|
||||
catch (err) {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleAuthorization()
|
||||
// urlParams = new URLSearchParams(window.location.search);
|
||||
// const uuid = urlParams.get('uuid');
|
||||
// console.log(uuid);
|
||||
// timer = setInterval(() => {
|
||||
// countdown.value--;
|
||||
// countdownText.value = `${countdown.value}秒后自动关闭...`;
|
||||
// if (countdown.value <= 0) {
|
||||
// clearInterval(timer);
|
||||
// // 关闭页面或跳转
|
||||
// window.close();
|
||||
// // 如果window.close()不生效,可以跳转到指定页面
|
||||
// // window.location.href = '您的目标页面URL'
|
||||
// }
|
||||
// }, 1000);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-white flex items-center justify-center px-4">
|
||||
<!-- <n-button @click="handleAuthorization" type="info">
|
||||
|
@ -23,7 +77,9 @@
|
|||
</svg>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-medium text-gray-900 mb-2 animate-fade-in">登录成功</h2>
|
||||
<h2 class="text-xl font-medium text-gray-900 mb-2 animate-fade-in">
|
||||
登录成功
|
||||
</h2>
|
||||
<!-- <p class="text-sm text-gray-500 animate-fade-in-delay">
|
||||
{{ countdownText }}
|
||||
</p> -->
|
||||
|
@ -32,9 +88,9 @@
|
|||
<div v-else>
|
||||
<div class="wave-loading mb-8">
|
||||
<div class="wave-circle">
|
||||
<div class="wave"></div>
|
||||
<div class="wave"></div>
|
||||
<div class="wave"></div>
|
||||
<div class="wave" />
|
||||
<div class="wave" />
|
||||
<div class="wave" />
|
||||
<div class="logo-container">
|
||||
<!-- <svg class="logo-icon" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" fill="currentColor"/>
|
||||
|
@ -69,7 +125,9 @@
|
|||
|
||||
<!-- 提示信息 -->
|
||||
<div class="mt-4 space-y-2 animate-fade-in">
|
||||
<p class="text-gray-500 text-sm">正在验证您的身份</p>
|
||||
<p class="text-gray-500 text-sm">
|
||||
正在验证您的身份
|
||||
</p>
|
||||
<div class="flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle
|
||||
|
@ -79,12 +137,12 @@
|
|||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
/>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm text-gray-400">请稍候片刻</span>
|
||||
</div>
|
||||
|
@ -106,58 +164,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute } from "#app";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
const route = useRoute();
|
||||
definePageMeta({
|
||||
layout: "home",
|
||||
});
|
||||
const countdown = ref(3);
|
||||
const countdownText = ref("页面即将关闭...");
|
||||
let timer = null;
|
||||
|
||||
let urlParams = null;
|
||||
|
||||
// const urlParams = new URLSearchParams(window.location.search);
|
||||
const isSuccess = ref(false);
|
||||
const handleAuthorization = async () => {
|
||||
const uuid = route.query.uuid;
|
||||
const code = route.query.code;
|
||||
// const uuid = urlParams.get('uuid');
|
||||
// const code = urlParams.get('code');
|
||||
const url = `/wx/uuid/bind/openid?uuid=${uuid}&code=${code}`;
|
||||
try {
|
||||
const res = await request.get(url);
|
||||
isSuccess.value = true;
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleAuthorization()
|
||||
// urlParams = new URLSearchParams(window.location.search);
|
||||
// const uuid = urlParams.get('uuid');
|
||||
// console.log(uuid);
|
||||
// timer = setInterval(() => {
|
||||
// countdown.value--;
|
||||
// countdownText.value = `${countdown.value}秒后自动关闭...`;
|
||||
// if (countdown.value <= 0) {
|
||||
// clearInterval(timer);
|
||||
// // 关闭页面或跳转
|
||||
// window.close();
|
||||
// // 如果window.close()不生效,可以跳转到指定页面
|
||||
// // window.location.href = '您的目标页面URL'
|
||||
// }
|
||||
// }, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes scale-in {
|
||||
0% {
|
||||
|
|
|
@ -43,7 +43,7 @@ getIsMember()
|
|||
const MemberBenefitList = ref([])
|
||||
async function getMemberBenefitList() {
|
||||
try {
|
||||
const res = await request.get('/memberLevel/getMemberBenefitList')
|
||||
const res = await request.get('/memberBenefit/getMemberBenefitList')
|
||||
if (res.code === 200) {
|
||||
MemberBenefitList.value = res.data
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<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, ChevronDown, Heart, MessageSquareMore, UserPlus, Volume2 } from "lucide-vue-next";
|
||||
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, ChevronDown, Heart, MessageSquareMore, UserPlus, Volume2 } from 'lucide-vue-next'
|
||||
|
||||
const currentMsgType = ref("reply");
|
||||
const currentMsgType = ref('reply')
|
||||
const MsgTypeList = ref([
|
||||
// {
|
||||
// label:'官方通知',
|
||||
|
@ -13,27 +13,27 @@ const MsgTypeList = ref([
|
|||
// icon:'Volume2'
|
||||
// },
|
||||
{
|
||||
label: "回复我的",
|
||||
value: "reply",
|
||||
icon: "MessageSquareMore",
|
||||
label: '回复我的',
|
||||
value: 'reply',
|
||||
icon: 'MessageSquareMore',
|
||||
},
|
||||
{
|
||||
label: "收到的赞",
|
||||
value: "like",
|
||||
icon: "Heart",
|
||||
label: '收到的赞',
|
||||
value: 'like',
|
||||
icon: 'Heart',
|
||||
},
|
||||
{
|
||||
label: "关注我的",
|
||||
value: "attention",
|
||||
icon: "UserPlus",
|
||||
label: '关注我的',
|
||||
value: 'attention',
|
||||
icon: 'UserPlus',
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
const iconMap: any = {
|
||||
'official':Volume2,
|
||||
'reply': MessageSquareMore,
|
||||
'like':Heart,
|
||||
'attention':UserPlus,
|
||||
official: Volume2,
|
||||
reply: MessageSquareMore,
|
||||
like: Heart,
|
||||
attention: UserPlus,
|
||||
}
|
||||
|
||||
MsgTypeList.value = MsgTypeList.value.map(item => ({
|
||||
|
@ -42,16 +42,16 @@ MsgTypeList.value = MsgTypeList.value.map(item => ({
|
|||
}))
|
||||
|
||||
const urlList = ref({
|
||||
official: "",
|
||||
reply: "/advice/getCommentMsg",
|
||||
like: "/advice/getLikeMsg",
|
||||
attention: "/advice/getAttentionMsg",
|
||||
});
|
||||
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([])
|
||||
|
@ -64,95 +64,99 @@ const allList = ref({
|
|||
// value:'4'
|
||||
// }
|
||||
// ])
|
||||
const replyParamsType = ref("3");
|
||||
const replyParamsType = ref('3')
|
||||
const replyParamsTypeList = ref([
|
||||
{
|
||||
label: "全部通知",
|
||||
value: "3",
|
||||
label: '全部通知',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: "模型评论",
|
||||
value: "0",
|
||||
label: '模型评论',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: "图片评论",
|
||||
value: "2",
|
||||
label: '图片评论',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: "工作流评论",
|
||||
value: "1",
|
||||
label: '工作流评论',
|
||||
value: '1',
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
const likeParamsType = ref("4");
|
||||
const likeParamsType = ref('4')
|
||||
const likeParamsTypeList = ref([
|
||||
{
|
||||
label: "全部通知",
|
||||
value: "4",
|
||||
label: '全部通知',
|
||||
value: '4',
|
||||
},
|
||||
{
|
||||
label: "模型点赞",
|
||||
value: "0",
|
||||
label: '模型点赞',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: "图片点赞",
|
||||
value: "2",
|
||||
label: '图片点赞',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: "工作流点赞",
|
||||
value: "1",
|
||||
label: '工作流点赞',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: "评论点赞",
|
||||
value: "3",
|
||||
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 url = urlList.value[currentMsgType.value]
|
||||
let productType = ''
|
||||
if (currentMsgType.value === 'reply') {
|
||||
productType = replyParamsType.value
|
||||
}
|
||||
const res = await request.get(url, { productType });
|
||||
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;
|
||||
allList.value[currentMsgType.value] = res.data
|
||||
}
|
||||
}
|
||||
getList();
|
||||
getList()
|
||||
|
||||
function changeType(item: any) {
|
||||
currentMsgType.value = item.value;
|
||||
likeParamsType.value = "4";
|
||||
replyParamsType.value = "3";
|
||||
getList();
|
||||
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;
|
||||
if (type === 'like') {
|
||||
likeParamsType.value = item.value
|
||||
}
|
||||
getList();
|
||||
else {
|
||||
replyParamsType.value = item.value
|
||||
}
|
||||
getList()
|
||||
}
|
||||
// 一键已读
|
||||
async function onAllRead() {
|
||||
try {
|
||||
const res = await request.get("/advice/readAll");
|
||||
const res = await request.get('/advice/readAll')
|
||||
if (res.code === 200) {
|
||||
getList();
|
||||
getList()
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
definePageMeta({
|
||||
layout: "default",
|
||||
});
|
||||
layout: 'default',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -168,13 +172,15 @@ definePageMeta({
|
|||
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="p-3 text-xl">
|
||||
通知
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="msg-item"
|
||||
:style="{ background: item.value === currentMsgType ? '#f5f6fa' : '' }"
|
||||
v-for="(item, index) in MsgTypeList"
|
||||
:key="index"
|
||||
class="msg-item"
|
||||
:style="{ background: item.value === currentMsgType ? '#f5f6fa' : '' }"
|
||||
@click="changeType(item)"
|
||||
>
|
||||
<!-- <Volume2 size="18" /> -->
|
||||
|
@ -218,10 +224,10 @@ definePageMeta({
|
|||
>
|
||||
<div
|
||||
v-for="(item, index) in replyParamsTypeList"
|
||||
:key="index"
|
||||
:style="{
|
||||
color: item.value === replyParamsType ? '#3a75f6' : '',
|
||||
}"
|
||||
:key="index"
|
||||
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||
@click="(event) => handleSelect(item, 'reply')"
|
||||
>
|
||||
|
@ -234,10 +240,10 @@ definePageMeta({
|
|||
>
|
||||
<div
|
||||
v-for="(item, index) in likeParamsTypeList"
|
||||
:key="index"
|
||||
:style="{
|
||||
color: item.value === likeParamsType ? '#3a75f6' : '',
|
||||
}"
|
||||
:key="index"
|
||||
class="hover:bg-gray-100 py-2 cursor-pointer rounded-lg"
|
||||
@click="(event) => handleSelect(item, 'like')"
|
||||
>
|
||||
|
@ -259,6 +265,7 @@ definePageMeta({
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.msg-item {
|
||||
@apply h-14 flex items-center mb-2 py-3 pl-6 rounded-lg cursor-pointer;
|
||||
|
|
|
@ -200,9 +200,9 @@ async function onLike() {
|
|||
|
||||
// 举报
|
||||
const reportParams = ref({
|
||||
reportId: undefined,
|
||||
reportType: undefined,
|
||||
text: '',
|
||||
type: 1,
|
||||
type: 0,
|
||||
})
|
||||
const reportList = ref([])
|
||||
async function getDictType() {
|
||||
|
@ -218,7 +218,7 @@ async function getDictType() {
|
|||
}
|
||||
getDictType()
|
||||
function handleChange(item) {
|
||||
reportParams.value.reportId = item.dictValue
|
||||
reportParams.value.reportType = item.dictValue
|
||||
if (item.dictValue !== '5') {
|
||||
reportParams.value.text = ''
|
||||
}
|
||||
|
@ -231,8 +231,9 @@ function closeReport() {
|
|||
|
||||
// 举报
|
||||
async function onReport() {
|
||||
if (reportParams.value.reportId !== undefined) {
|
||||
if (reportParams.value.reportType !== undefined) {
|
||||
reportParams.value.productId = detailsInfo.value.id
|
||||
reportParams.value.productName = detailsInfo.value.modelName
|
||||
try {
|
||||
const res = await request.post('/report/addReport', reportParams.value)
|
||||
if (res.code === 200) {
|
||||
|
@ -682,7 +683,7 @@ async function buyModel() {
|
|||
:key="index"
|
||||
class="m-4 text-[#fff000]"
|
||||
size="large"
|
||||
:checked="reportParams.reportId === item.dictValue"
|
||||
:checked="reportParams.reportType === item.dictValue"
|
||||
value="Definitely Maybe"
|
||||
name="basic-demo"
|
||||
@change="handleChange(item)"
|
||||
|
@ -691,7 +692,7 @@ async function buyModel() {
|
|||
</n-radio>
|
||||
<n-input
|
||||
v-if="
|
||||
reportParams.reportId !== undefined && reportParams.reportId === '5'
|
||||
reportParams.reportType !== undefined && reportParams.reportType === '5'
|
||||
"
|
||||
v-model:value="reportParams.text"
|
||||
placeholder="点击输入"
|
||||
|
@ -704,7 +705,7 @@ async function buyModel() {
|
|||
<div
|
||||
class="mt-4 w-[100%] h-10 flex rounded-lg text-white items-center justify-center cursor-pointer"
|
||||
:class="[
|
||||
reportParams.reportId !== undefined ? 'bg-[#4c79ee]' : 'bg-[#cccccc]',
|
||||
reportParams.reportType !== undefined ? 'bg-[#4c79ee]' : 'bg-[#cccccc]',
|
||||
]"
|
||||
@click="onReport"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
<script setup lang="ts">
|
||||
import { User } from 'lucide-vue-next'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
// import CreatePlanet from '~/components/CreatePlanet.vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
const userStore = useUserStore()
|
||||
const communityTag = ref('myCreate')
|
||||
const communityTagList = ref([
|
||||
{
|
||||
dictLabel: '我创建的',
|
||||
dictValue: 'myCreate',
|
||||
},
|
||||
{
|
||||
dictLabel: '我加入的',
|
||||
dictValue: 'myJoin',
|
||||
},
|
||||
])
|
||||
|
||||
// 获取列表
|
||||
const listParams = ref({
|
||||
communityTag: null as string | null,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
userId: userStore.userInfo?.userId,
|
||||
isMyCreate: 0,
|
||||
})
|
||||
interface CommunityItem {
|
||||
imageUrl: string
|
||||
publishNum: number
|
||||
communityName: string
|
||||
avatar: string
|
||||
createBy: string
|
||||
createDay: number
|
||||
description: string
|
||||
price: number | null
|
||||
}
|
||||
const listFinish = ref(false)
|
||||
const loading = ref(false)
|
||||
const dataList = ref<CommunityItem[]>([])
|
||||
|
||||
function refresh() {
|
||||
listFinish.value = false
|
||||
listParams.value.pageNum = 1
|
||||
getList()
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
try {
|
||||
if (listFinish.value || loading.value) // 添加loading检查,避免重复请求
|
||||
return
|
||||
loading.value = listParams.value.pageNum === 1 // 只在第一页显示loading
|
||||
const url = communityTag.value === 'myCreate' ? '/community/list' : `/community/myJoin`
|
||||
const res = await request.post<ApiResponse<CommunityItem>>(url, listParams.value)
|
||||
if (res.code === 200) {
|
||||
if (listParams.value.pageNum === 1) {
|
||||
dataList.value = res.rows
|
||||
}
|
||||
else {
|
||||
dataList.value = [...dataList.value, ...res.rows]
|
||||
}
|
||||
|
||||
if (dataList.value.length >= res.total) {
|
||||
listFinish.value = true
|
||||
}
|
||||
listParams.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
dataList.value = []
|
||||
listFinish.value = true
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
getList()
|
||||
|
||||
function changeTag(value: string) {
|
||||
communityTag.value = value
|
||||
listParams.value.pageNum = 1
|
||||
listFinish.value = false
|
||||
getList()
|
||||
}
|
||||
|
||||
const isShowCreateModal = ref(false)
|
||||
function showCreateModal() {
|
||||
isShowCreateModal.value = true
|
||||
}
|
||||
|
||||
function handleCreateSuccess() {
|
||||
refresh()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA]">
|
||||
<div class="px-10 bg-[#fff]">
|
||||
<div class="flex flex-wrap gap-2 py-4 ">
|
||||
<div
|
||||
v-for="(item, index) in communityTagList"
|
||||
:key="index"
|
||||
class="px-[12px] py-[9px] text-sm rounded-lg cursor-pointer"
|
||||
:class="{ 'bg-black text-white': communityTag === item.dictValue, 'bg-white text-[#878d95] hover:bg-gray-100': communityTag !== item.dictValue }"
|
||||
@click="changeTag(item.dictValue)"
|
||||
>
|
||||
{{ item.dictLabel }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<article class="px-10 py-6">
|
||||
<n-infinite-scroll :distance="10" trigger="once" @load="getList">
|
||||
<!-- 添加trigger="once"属性 -->
|
||||
<div v-if="loading" class="grid grid-cols-3 gap-2">
|
||||
<div v-for="i in 9" :key="i" class="bg-white rounded-lg overflow-hidden shadow-sm flex p-4">
|
||||
<n-skeleton class="w-[161px] h-[161px] rounded-lg flex-shrink-0" />
|
||||
<div class="flex flex-col gap-2 px-4 flex-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<n-skeleton text :width="140" />
|
||||
<div class="flex items-center gap-2">
|
||||
<n-skeleton circle width="20px" height="20px" />
|
||||
<n-skeleton text :width="60" />
|
||||
<n-skeleton text :width="80" />
|
||||
</div>
|
||||
<n-skeleton text :repeat="2" />
|
||||
</div>
|
||||
<n-skeleton text :width="60" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="dataList.length > 0" class="grid grid-cols-3 gap-2">
|
||||
<div v-if="communityTag === 'myCreate'" class="bg-black rounded-lg flex flex-col justify-center items-center p-4 hover:shadow-[0_4px_14px_0_rgba(0,0,0,0.1)] transition-all duration-300 cursor-pointer" @click="showCreateModal">
|
||||
<div class="text-white text-xl">
|
||||
魔创星球
|
||||
</div>
|
||||
<div class="text-xs text-[#878d95] mt-1 mb-4">
|
||||
加入星球发布优质的内容吧~
|
||||
</div>
|
||||
<div class="flex text-white bg-gradient-to-r from-[#197dff] to-[#2c4dff] rounded-[4px] px-8 py-3 text-sm cursor-pointer hover:from-[#3b8fff] hover:to-[#4465ff]">
|
||||
创建星球
|
||||
</div>
|
||||
</div>
|
||||
<PlanetItem v-for="(item, index) in dataList" :key="index" :item="item" :type="communityTag" @refresh="refresh" />
|
||||
</div>
|
||||
<div v-if="dataList.length === 0 && listFinish" class="flex flex-col items-center justify-center py-12">
|
||||
<div class="w-48 h-48 mb-4 flex items-center justify-center">
|
||||
<div class="relative">
|
||||
<div class="w-32 h-32 rounded-full border-4 border-gray-200" />
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-24 h-24 rounded-full border-4 border-gray-100" />
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-16 h-16 rounded-full bg-gray-50" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-500 mb-4">
|
||||
这里空空如也,什么都没有找到~
|
||||
</p>
|
||||
<!-- <n-button type="primary" size="small" class="px-6">
|
||||
去发现更多
|
||||
</n-button> -->
|
||||
</div>
|
||||
</n-infinite-scroll>
|
||||
</article>
|
||||
|
||||
<CreatePlanet
|
||||
v-model:show="isShowCreateModal"
|
||||
@success="handleCreateSuccess"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
body,
|
||||
html {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,265 @@
|
|||
<script setup lang="ts">
|
||||
import planetBg from '@/assets/img/planetBg.png'
|
||||
import { NConfigProvider, NImage, NMessageProvider } from 'naive-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
const isShow = ref('publish')
|
||||
const listParams = ref({
|
||||
communityTag: null as string | null,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
userId: userStore.userInfo?.userId,
|
||||
isMyCreate: 1,
|
||||
})
|
||||
interface CommunityItem {
|
||||
imageUrl: string
|
||||
publishNum: number
|
||||
communityName: string
|
||||
avatar: string
|
||||
createBy: string
|
||||
createDay: number
|
||||
description: string
|
||||
price: number | null
|
||||
}
|
||||
const listFinish = ref(false)
|
||||
const loading = ref(false)
|
||||
const communityList = ref<CommunityItem[]>([])
|
||||
const tabList = [
|
||||
{ key: 0, label: '发布的帖子' },
|
||||
{ key: 1, label: '我的问答' },
|
||||
{ key: 2, label: '我的收藏' },
|
||||
]
|
||||
const commentParams = ref({
|
||||
communityId: null as string | null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tenantId: null as string | null,
|
||||
type: 0,
|
||||
isMy: 1,
|
||||
})
|
||||
// function refresh() {
|
||||
// listParams.value.pageNum = 1
|
||||
// getList()
|
||||
// }
|
||||
|
||||
// 获取星球列表
|
||||
async function getCommunityList() {
|
||||
try {
|
||||
if (listFinish.value || loading.value) // 添加loading检查,避免重复请求
|
||||
return
|
||||
loading.value = listParams.value.pageNum === 1 // 只在第一页显示loading
|
||||
const res = await request.post<ApiResponse<CommunityItem>>(`/community/myJoin`, listParams.value)
|
||||
if (res.code === 200) {
|
||||
if (res.rows.length !== 0) {
|
||||
const { id, tenantId } = res.rows[0]
|
||||
commentParams.value.tenantId = tenantId
|
||||
commentParams.value.communityId = id
|
||||
getCommentList()
|
||||
}
|
||||
if (listParams.value.pageNum === 1) {
|
||||
communityList.value = res.rows
|
||||
}
|
||||
else {
|
||||
communityList.value = [...communityList.value, ...res.rows]
|
||||
}
|
||||
|
||||
if (communityList.value.length >= res.total) {
|
||||
listFinish.value = true
|
||||
}
|
||||
listParams.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
communityList.value = []
|
||||
listFinish.value = true
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
getCommunityList()
|
||||
|
||||
// 获取评论列表
|
||||
const commentList = ref([])
|
||||
const commentFinish = ref(false)
|
||||
const commentLoading = ref(false)
|
||||
async function getCommentList() {
|
||||
try {
|
||||
if (commentFinish.value || commentLoading.value) // 添加loading检查,避免重复请求
|
||||
return
|
||||
commentLoading.value = commentParams.value.pageNum === 1 // 只在第一页显示loading personHome/getPersonHomeLis
|
||||
const res = await request.post<ApiResponse<CommunityItem>>(`/personHome/getPersonHomeList`, commentParams.value)
|
||||
if (res.code === 200) {
|
||||
if (listParams.value.pageNum === 1) {
|
||||
commentList.value = res.rows
|
||||
}
|
||||
else {
|
||||
commentList.value = [...commentList.value, ...res.rows]
|
||||
}
|
||||
|
||||
if (commentList.value.length >= res.total) {
|
||||
commentFinish.value = true
|
||||
}
|
||||
listParams.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
commentList.value = []
|
||||
commentFinish.value = true
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
commentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function refresh() {
|
||||
commentFinish.value = false
|
||||
commentLoading.value = false
|
||||
commentParams.value.pageNum = 1
|
||||
getCommentList()
|
||||
}
|
||||
|
||||
// const userInfo = ref({
|
||||
// avatar: userStore.userInfo?.avatar,
|
||||
// nickname: userStore.userInfo?.nickName,
|
||||
// createdCount: 14456,
|
||||
// followCount: 145,
|
||||
// collectCount: 46464,
|
||||
// })
|
||||
|
||||
// 切换星球
|
||||
function changeCommunity(item: any) {
|
||||
commentParams.value.tenantId = item.tenantId
|
||||
commentParams.value.communityId = item.id
|
||||
refresh()
|
||||
}
|
||||
|
||||
// 切换类型
|
||||
function changeTab(key: number) {
|
||||
if (key !== 1) {
|
||||
isShow.value = 'publish'
|
||||
}
|
||||
else {
|
||||
isShow.value = 'question'
|
||||
}
|
||||
commentParams.value.type = key
|
||||
refresh()
|
||||
}
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
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()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA]">
|
||||
<div class="relative">
|
||||
<!-- 背景图片区域 -->
|
||||
<div class="h-[300px] w-full bg-[#E8F3FF]">
|
||||
<img :src="planetBg" alt="背景图片" class="w-full h-full object-cover">
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="absolute inset-x-0 top-[80px]">
|
||||
<div class="max-w-[1140px] mx-auto">
|
||||
<!-- 用户基本信息 -->
|
||||
<client-only>
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<img :src="userStore.userInfo?.avatar" class="w-20 h-20 rounded-full" alt="头像">
|
||||
<div>
|
||||
<div class="text-lg font-medium text-[#1f2329]">
|
||||
{{ userStore.userInfo?.nickName }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 mt-2 text-[#86909C]">
|
||||
<div>{{ selectUserInfo.bean || 0 }} 粉丝</div>
|
||||
<div>{{ selectUserInfo.attention || 0 }} 关注</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
|
||||
<!-- 内容区域(白色背景) -->
|
||||
<div class="bg-white rounded-lg min-h-[500px] mt-8">
|
||||
<!-- 导航标签 -->
|
||||
<div class="flex border-b border-[#E5E6EB]">
|
||||
<div
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
class="px-6 py-3 text-[#1f2329] font-medium cursor-pointer"
|
||||
:class="{ 'text-[#3f7ef7] border-b-2 border-[#3f7ef7]': commentParams.type === item.key }"
|
||||
@click="changeTab(item.key)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 星球分类标签 -->
|
||||
<div class="flex items-center gap-2 p-3">
|
||||
<div
|
||||
v-for="item in communityList"
|
||||
:key="item.id"
|
||||
class="px-3 py-1 text-sm rounded-lg cursor-pointer"
|
||||
:class="[
|
||||
commentParams.communityId === item.id
|
||||
? 'text-white bg-[#1f2329]'
|
||||
: 'text-[#1f2329] hover:text-white hover:bg-[#1f2329]',
|
||||
]"
|
||||
@click="changeCommunity(item)"
|
||||
>
|
||||
{{ item.communityName }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isShow === 'publish'">
|
||||
<NConfigProvider>
|
||||
<NMessageProvider>
|
||||
<PlanetComment
|
||||
:publish-list-params="commentParams"
|
||||
/>
|
||||
</NMessageProvider>
|
||||
</NConfigProvider>
|
||||
</div>
|
||||
<div v-if="isShow === 'question'">
|
||||
<NConfigProvider>
|
||||
<NMessageProvider>
|
||||
<PlanetQuestionComment
|
||||
:publish-list-params="commentParams"
|
||||
/>
|
||||
</NMessageProvider>
|
||||
</NConfigProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -572,9 +572,9 @@ async function showBinding() {
|
|||
try {
|
||||
const res2 = await request.get(`/ali/pay/queryBindStatus`)
|
||||
if (res2.data === '1') {
|
||||
await userStore.getUserInfo()
|
||||
closeBindingModal()
|
||||
message.success('绑定成功!')
|
||||
// window.location.reload()
|
||||
getBindStatus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,267 @@
|
|||
<script setup lang="ts">
|
||||
import { Close } from '@vicons/ionicons5'
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const observer = ref<IntersectionObserver | null>(null)
|
||||
const message = useMessage()
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const { userId } = route.query
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const total = ref(0) // 总条数
|
||||
const loadingTrigger = ref(null)
|
||||
|
||||
const urlList = ref({
|
||||
0: '/personalCenter/selectByUserIdModel',
|
||||
1: '/personalCenter/selectByUserIdWorkFlow',
|
||||
2: '/personalCenter/selectByUserIdImage',
|
||||
})
|
||||
const currentType = ref('0')
|
||||
const typeList = ref([
|
||||
{ id: '0', title: '模型' },
|
||||
{ id: '1', title: '工作流' },
|
||||
{ id: '2', title: '图片' },
|
||||
])
|
||||
const orderOptions = ref([
|
||||
{
|
||||
dictLabel: '最新',
|
||||
dictValue: 'create_time',
|
||||
},
|
||||
{
|
||||
dictLabel: '最热',
|
||||
dictValue: 'like_num',
|
||||
},
|
||||
])
|
||||
|
||||
const isShowFan = ref(false)
|
||||
|
||||
const activeTab = ref('like')
|
||||
function closefanList() {
|
||||
isShowFan.value = false
|
||||
}
|
||||
|
||||
const bannerStyle = {
|
||||
backgroundImage:
|
||||
'url(\'https://img.zcool.cn/community/special_cover/3a9a64d3628c000c2a1000657eec.jpg\')',
|
||||
}
|
||||
|
||||
const publishParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
orderByColumn: 'create_time',
|
||||
userId,
|
||||
})
|
||||
|
||||
const currentUserInfo = ref({})
|
||||
async function getCurrentUserInfo() {
|
||||
const res = await request.get(`/system/user/selectUserById?id=${userId}`)
|
||||
if (res.code === 200) {
|
||||
currentUserInfo.value = res.data
|
||||
}
|
||||
}
|
||||
getCurrentUserInfo()
|
||||
|
||||
// 获取粉丝的列表
|
||||
const attentionList = ref([])
|
||||
const attentionListParams = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
type: userId,
|
||||
})
|
||||
async function getAttentionList() {
|
||||
const res = await request.post(`/attention/selectToAttention`, {
|
||||
...attentionListParams.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
attentionList.value = res.data.list
|
||||
}
|
||||
}
|
||||
getAttentionList()
|
||||
|
||||
// 获取关注的列表
|
||||
const likeList = ref([])
|
||||
const likeListParams = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
type: userId,
|
||||
})
|
||||
async function getLikeList() {
|
||||
const res = await request.post(`/attention/selectAttentionList`, {
|
||||
...likeListParams.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
likeList.value = res.data.list
|
||||
}
|
||||
}
|
||||
getLikeList()
|
||||
|
||||
// 查询是否关注
|
||||
const isAttention = ref(false)
|
||||
async function getIsAttention() {
|
||||
const res = await request.get(`/attention/selectAttention?userId=${userId}`)
|
||||
if (res.code === 200) {
|
||||
isAttention.value = res.data
|
||||
}
|
||||
}
|
||||
getIsAttention()
|
||||
|
||||
// 去关注/取消关注当前用户
|
||||
async function onAttention(item: any) {
|
||||
const myUserId = userStore.userInfo.userId
|
||||
if (myUserId === item.userId) {
|
||||
return message.warning('自己不能关注自己')
|
||||
}
|
||||
let paramsUserId
|
||||
if (item.userId) {
|
||||
paramsUserId = item.userId
|
||||
}
|
||||
else {
|
||||
paramsUserId = userId
|
||||
}
|
||||
try {
|
||||
const res = await request.get(`/attention/addAttention?userId=${paramsUserId}`)
|
||||
if (res.code === 200) {
|
||||
if (res.data) {
|
||||
message.success('关注成功')
|
||||
}
|
||||
else {
|
||||
message.success('取消关注成功')
|
||||
}
|
||||
if (item.userId) {
|
||||
item.attention = !item.attention
|
||||
}
|
||||
else {
|
||||
isAttention.value = !isAttention.value
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
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=${userId}`)
|
||||
if (res.code === 200) {
|
||||
selectUserInfo.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getAttention()
|
||||
|
||||
// 切换模型/工作流/图片
|
||||
function changeType(id: string) {
|
||||
currentType.value = id
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
// 切换点赞的最热/最新
|
||||
function changeLikeOrder(value: string) {
|
||||
publishParams.value.orderByColumn = value
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
function initPageNUm() {
|
||||
publishParams.value.pageNum = 1
|
||||
finished.value = false // 重置加载完成状态
|
||||
getList()
|
||||
}
|
||||
|
||||
// 查询发布模型接口
|
||||
const dataList = ref([])
|
||||
async function getList() {
|
||||
if (loading.value || finished.value)
|
||||
return
|
||||
|
||||
loading.value = true
|
||||
const url = urlList.value[currentType.value]
|
||||
try {
|
||||
const res = await request.post(url, publishParams.value)
|
||||
if (res.code === 200) {
|
||||
// 如果是第一页,直接赋值,否则追加数据
|
||||
if (publishParams.value.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
|
||||
}
|
||||
publishParams.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
dataList.value = []
|
||||
finished.value = true
|
||||
console.log(err)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
getList()
|
||||
|
||||
async function topedRefresh() {
|
||||
if (import.meta.client) {
|
||||
await nextTick()
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto container">
|
||||
<div
|
||||
|
@ -15,16 +279,15 @@
|
|||
class="head-img m-1 h-16 w-16 rounded-full bg-white"
|
||||
:src="currentUserInfo.avatar"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
>
|
||||
</client-only>
|
||||
<!-- {{ userStore.userInfo.avatar }} -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
v-if="isAttention !== null"
|
||||
class="mr-2 cursor-pointer rounded-full bg-gradient-to-r from-[#1f66df] to-[#3faeff] px-4 py-1 text-white"
|
||||
@click="onAttention"
|
||||
v-if="isAttention !== null"
|
||||
>
|
||||
{{ isAttention ? '已关注' : '关注' }}
|
||||
</div>
|
||||
|
@ -43,7 +306,7 @@
|
|||
class="production-state-item mr-5 cursor-pointer"
|
||||
@click="isShowFan = true"
|
||||
>
|
||||
<span class="production-state-number font-bold" >{{
|
||||
<span class="production-state-number font-bold">{{
|
||||
selectUserInfo.bean ? selectUserInfo.bean : 0
|
||||
}}</span>
|
||||
粉丝
|
||||
|
@ -111,11 +374,14 @@
|
|||
/>
|
||||
</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 v-if="loading" class="text-center text-gray-500">
|
||||
加载中...
|
||||
</div>
|
||||
<div class="fan-centent" v-if="isShowFan">
|
||||
|
||||
<div v-if="finished" class="text-center text-gray-500">
|
||||
没有更多数据了
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isShowFan" class="fan-centent">
|
||||
<div
|
||||
class="w-[550px] h-[calc(100vh-100px)] max-h-[700px] m-auto py-0 px-8 pb-[43px] bg-[#fff] rounded-lg relative"
|
||||
>
|
||||
|
@ -137,7 +403,7 @@
|
|||
class="flex justify-between items-center py-2"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<img :src="item.avatar || ''" alt="" class="w-14 h-14 rounded-full mr-2" />
|
||||
<img :src="item.avatar || ''" alt="" class="w-14 h-14 rounded-full mr-2">
|
||||
{{ item.nickName }}
|
||||
</div>
|
||||
<div class="bg-[#f4f5f9] px-4 py-2 rounded-full cursor-pointer" @click="onAttention(item, 'attention')">
|
||||
|
@ -158,7 +424,7 @@
|
|||
class="flex justify-between items-center py-2"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<img :src="item.avatar || ''" alt="" class="w-14 h-14 rounded-full mr-2" />
|
||||
<img :src="item.avatar || ''" alt="" class="w-14 h-14 rounded-full mr-2">
|
||||
{{ item.nickName }}
|
||||
</div>
|
||||
<div class="bg-[#f4f5f9] px-4 py-2 rounded-full cursor-pointer" @click="onAttention(item, 'like')">
|
||||
|
@ -176,263 +442,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Close } from "@vicons/ionicons5";
|
||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
const observer = ref<IntersectionObserver | null>(null);
|
||||
const message = useMessage()
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const { userId } = route.query;
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
const total = ref(0); // 总条数
|
||||
const loadingTrigger = ref(null);
|
||||
|
||||
const urlList = ref({
|
||||
"0": "/personalCenter/selectByUserIdModel",
|
||||
"1": "/personalCenter/selectByUserIdWorkFlow",
|
||||
"2": "/personalCenter/selectByUserIdImage",
|
||||
});
|
||||
const currentType = ref("0");
|
||||
const typeList = ref([
|
||||
{ id: "0", title: "模型" },
|
||||
{ id: "1", title: "工作流" },
|
||||
{ id: "2", title: "图片" },
|
||||
]);
|
||||
const orderOptions = ref([
|
||||
{
|
||||
dictLabel: "最新",
|
||||
dictValue: "create_time",
|
||||
},
|
||||
{
|
||||
dictLabel: "最热",
|
||||
dictValue: "like_num",
|
||||
},
|
||||
]);
|
||||
|
||||
const isShowFan = ref(false);
|
||||
|
||||
const activeTab = ref("like");
|
||||
function closefanList() {
|
||||
isShowFan.value = false
|
||||
}
|
||||
|
||||
|
||||
const bannerStyle = {
|
||||
backgroundImage:
|
||||
"url('https://img.zcool.cn/community/special_cover/3a9a64d3628c000c2a1000657eec.jpg')",
|
||||
};
|
||||
|
||||
const publishParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
orderByColumn: "create_time",
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
const currentUserInfo = ref({});
|
||||
async function getCurrentUserInfo() {
|
||||
const res = await request.get(`/system/user/selectUserById?id=${userId}`);
|
||||
if (res.code === 200) {
|
||||
currentUserInfo.value = res.data;
|
||||
}
|
||||
}
|
||||
getCurrentUserInfo();
|
||||
|
||||
// 获取粉丝的列表
|
||||
const attentionList = ref([]);
|
||||
const attentionListParams = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
type: userId,
|
||||
});
|
||||
async function getAttentionList() {
|
||||
const res = await request.post(`/attention/selectToAttention`, {
|
||||
...attentionListParams.value,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
attentionList.value = res.data.list;
|
||||
}
|
||||
}
|
||||
getAttentionList();
|
||||
|
||||
// 获取关注的列表
|
||||
const likeList = ref([]);
|
||||
const likeListParams = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
type: userId,
|
||||
});
|
||||
async function getLikeList() {
|
||||
const res = await request.post(`/attention/selectAttentionList`, {
|
||||
...likeListParams.value,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
likeList.value = res.data.list;
|
||||
}
|
||||
}
|
||||
getLikeList();
|
||||
|
||||
// 查询是否关注
|
||||
const isAttention = ref(false)
|
||||
async function getIsAttention(){
|
||||
const res = await request.get(`/attention/selectAttention?userId=${userId}`)
|
||||
if(res.code === 200){
|
||||
isAttention.value = res.data
|
||||
}
|
||||
}
|
||||
getIsAttention()
|
||||
|
||||
// 去关注/取消关注当前用户
|
||||
async function onAttention(item:any){
|
||||
const myUserId = userStore.userInfo.userId
|
||||
if(myUserId === item.userId){
|
||||
return message.warning('自己不能关注自己')
|
||||
}
|
||||
let paramsUserId
|
||||
if(item.userId){
|
||||
paramsUserId = item.userId
|
||||
}else{
|
||||
paramsUserId = userId
|
||||
}
|
||||
try {
|
||||
const res = await request.get(`/attention/addAttention?userId=${paramsUserId}`);
|
||||
if (res.code === 200) {
|
||||
if(res.data){
|
||||
message.success('关注成功')
|
||||
}else{
|
||||
message.success('取消关注成功')
|
||||
}
|
||||
if(item.userId){
|
||||
item.attention = !item.attention
|
||||
}else{
|
||||
isAttention.value = !isAttention.value
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
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=${userId}`);
|
||||
if (res.code === 200) {
|
||||
selectUserInfo.value = res.data;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
getAttention();
|
||||
|
||||
|
||||
|
||||
// 切换模型/工作流/图片
|
||||
function changeType(id: string) {
|
||||
currentType.value = id;
|
||||
initPageNUm();
|
||||
}
|
||||
|
||||
// 切换点赞的最热/最新
|
||||
function changeLikeOrder(value: string) {
|
||||
publishParams.value.orderByColumn = value;
|
||||
initPageNUm();
|
||||
}
|
||||
|
||||
function initPageNUm() {
|
||||
publishParams.value.pageNum = 1;
|
||||
finished.value = false; // 重置加载完成状态
|
||||
getList();
|
||||
}
|
||||
|
||||
// 查询发布模型接口
|
||||
const dataList = ref([]);
|
||||
async function getList() {
|
||||
if (loading.value || finished.value) return;
|
||||
|
||||
loading.value = true;
|
||||
const url = urlList.value[currentType.value];
|
||||
try {
|
||||
const res = await request.post(url, publishParams.value);
|
||||
if (res.code === 200) {
|
||||
// 如果是第一页,直接赋值,否则追加数据
|
||||
if (publishParams.value.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;
|
||||
}
|
||||
publishParams.value.pageNum++;
|
||||
}
|
||||
} catch (err) {
|
||||
dataList.value = [];
|
||||
finished.value = true;
|
||||
console.log(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
getList();
|
||||
|
||||
async function topedRefresh() {
|
||||
if (import.meta.client) {
|
||||
await nextTick();
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
initPageNUm();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fan-centent {
|
||||
position: fixed;
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
<script setup lang="ts">
|
||||
import request from '@/utils/request'
|
||||
import { FolderPlus, ImagePlus, MessageCircle, Star, ThumbsUp } from 'lucide-vue-next'
|
||||
|
||||
import { NConfigProvider, NImage, NMessageProvider } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const message = useMessage()
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
const route = useRoute()
|
||||
const typeList = ref([
|
||||
{
|
||||
label: '最新',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
label: '只看星主',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '精选',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '问答',
|
||||
value: 999,
|
||||
},
|
||||
])
|
||||
const showPublishModal = ref(false)
|
||||
|
||||
const publishListParams = ref({
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
type: null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
const questionParams = ref({
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
const questionList = ref([])
|
||||
const isShow = ref('publish')
|
||||
async function handleTypeChange(type: number | null) {
|
||||
publishListParams.value.type = type
|
||||
questionParams.value.pageNum = 1
|
||||
if (type === 999) { // 问答
|
||||
isShow.value = 'question'
|
||||
try {
|
||||
const res = await request.post('/question/list', questionParams.value)
|
||||
if (res.code === 200) {
|
||||
questionList.value = res.rows
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
publishListParams.value.pageNum = 1
|
||||
isShow.value = 'publish'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA] px-10 py-4 flex gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="rounded-lg bg-white">
|
||||
<div class="flex flex-col">
|
||||
<div class="p-4 pb-0">
|
||||
<textarea
|
||||
class="w-full min-h-[100px] bg-[#F7F8FA] rounded-lg p-4 resize-none outline-none cursor-pointer"
|
||||
placeholder="此刻的想法..."
|
||||
@click="showPublishModal = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-2 pt-0">
|
||||
<div class="flex items-center">
|
||||
<button class="p-2 hover:bg-gray-100 rounded text-gray-500" @click="showPublishModal = true">
|
||||
<ImagePlus class="w-5 h-5" />
|
||||
</button>
|
||||
<button class="p-2 hover:bg-gray-100 rounded text-gray-500" @click="showPublishModal = true">
|
||||
<FolderPlus class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg py-4">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-for="item in typeList"
|
||||
:key="item.value"
|
||||
class="flex items-center px-4 py-1 rounded cursor-pointer mr-2"
|
||||
:style="{
|
||||
backgroundColor: publishListParams.type === item.value ? '#000000' : '#ffffff',
|
||||
color: publishListParams.type === item.value ? '#ffffff' : '#878d95',
|
||||
}"
|
||||
@click="handleTypeChange(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isShow === 'publish'">
|
||||
<NConfigProvider>
|
||||
<NMessageProvider>
|
||||
<PlanetComment
|
||||
:publish-list-params="publishListParams"
|
||||
/>
|
||||
</NMessageProvider>
|
||||
</NConfigProvider>
|
||||
</div>
|
||||
<div v-if="isShow === 'question'">
|
||||
<NConfigProvider>
|
||||
<NMessageProvider>
|
||||
<PlanetQuestionComment
|
||||
:publish-list-params="questionParams"
|
||||
/>
|
||||
</NMessageProvider>
|
||||
</NConfigProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[300px]">
|
||||
<PlanetBaseInfo :community-id="route.query.communityId" :tenant-id="route.query.tenantId" />
|
||||
</div>
|
||||
<n-modal
|
||||
v-model:show="showPublishModal"
|
||||
:style="{ width: '640px' }"
|
||||
preset="card"
|
||||
:mask-closable="false"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<PublishContent
|
||||
:community-id="route.query.communityId"
|
||||
:tenant-id="route.query.tenantId"
|
||||
@success="showPublishModal = false"
|
||||
/>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,358 @@
|
|||
<script setup lang="ts">
|
||||
import { formatAmount } from '@/utils'
|
||||
import request from '@/utils/request'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
// import { useRoute } from 'vue-router'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
// const route = useRoute()
|
||||
// const message = useMessage()
|
||||
interface DataDetail {
|
||||
communityIncome: {
|
||||
todayIncome: number
|
||||
yesterdayIncome: number
|
||||
}
|
||||
questionIncome: {
|
||||
todayIncome: number
|
||||
yesterdayIncome: number
|
||||
}
|
||||
totalIncome: number
|
||||
}
|
||||
const dataDetail = ref<DataDetail>({
|
||||
communityIncome: {
|
||||
todayIncome: 0,
|
||||
yesterdayIncome: 0,
|
||||
},
|
||||
questionIncome: {
|
||||
todayIncome: 0,
|
||||
yesterdayIncome: 0,
|
||||
},
|
||||
totalIncome: 0,
|
||||
})
|
||||
async function getEarningDetail() {
|
||||
try {
|
||||
const res = await request.get('/incomeInfo/communityIncome')
|
||||
if (res.code === 200) {
|
||||
dataDetail.value = res.data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
getEarningDetail()
|
||||
|
||||
const integralGold = ref({})
|
||||
async function getIntegralGold() {
|
||||
try {
|
||||
const res = await request.post('/personalCenter/getPointAndWallet')
|
||||
if (res.code === 200) {
|
||||
integralGold.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getIntegralGold()
|
||||
|
||||
// 交易记录列表
|
||||
interface TransactionRecord {
|
||||
userId: string
|
||||
userName: string
|
||||
avatar: string
|
||||
planetId: string
|
||||
planetName: string
|
||||
type: string
|
||||
amount: number
|
||||
planetIncome: number
|
||||
createTime: string
|
||||
}
|
||||
|
||||
const transactionList = ref<TransactionRecord[]>([])
|
||||
const transactionListParams = ref({
|
||||
searchContent: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
})
|
||||
|
||||
// 获取交易记录列表
|
||||
async function getTransactionList() {
|
||||
try {
|
||||
const res = await request.post('/incomeInfo/incomeList', {
|
||||
...transactionListParams.value,
|
||||
pageNum: pagination.value.page,
|
||||
pageSize: pagination.value.pageSize,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
transactionList.value = res.rows
|
||||
pagination.value.itemCount = res.total
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页码变化
|
||||
function handlePageChange(page: number) {
|
||||
pagination.value.page = page
|
||||
getTransactionList()
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
function handleSearch() {
|
||||
pagination.value.page = 1
|
||||
getTransactionList()
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function openWalletPage() {
|
||||
const url = `${window.location.origin}${router.resolve('/wallet').href}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTransactionList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA] py-4">
|
||||
<div class="bg-white rounded-lg py-4 px-6 max-w-[1025px] mx-auto">
|
||||
<!-- 星球收益数据卡片 -->
|
||||
<div class="grid grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-[#F7F8FA] rounded-lg p-4">
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
星球收益(金币)
|
||||
</div>
|
||||
<div class="text-2xl font-medium">
|
||||
{{ formatAmount(dataDetail.communityIncome?.todayIncome) }}
|
||||
</div>
|
||||
<div class="text-xs mt-1">
|
||||
昨日 <span class="text-[#3f7ef7]">{{ formatAmount(dataDetail.communityIncome?.yesterdayIncome) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#F7F8FA] rounded-lg p-4">
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
问答收益(金币)
|
||||
</div>
|
||||
<div class="text-2xl font-medium">
|
||||
{{ formatAmount(dataDetail.questionIncome?.todayIncome) }}
|
||||
</div>
|
||||
<div class="text-xs mt-1">
|
||||
昨日 <span class="text-[#3f7ef7]">{{ formatAmount(dataDetail.questionIncome?.yesterdayIncome) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#F7F8FA] rounded-lg p-4">
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
星球累计收益(金币)
|
||||
</div>
|
||||
<div class="text-2xl font-medium">
|
||||
{{ formatAmount(dataDetail.totalIncome) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#F7F8FA] rounded-lg p-4">
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
金币余额
|
||||
</div>
|
||||
<div class="text-2xl font-medium">
|
||||
{{ formatAmount(integralGold.wallet) }}
|
||||
</div>
|
||||
<div class="text-xs text-[#3f7ef7] mt-1 cursor-pointer" @click="openWalletPage">
|
||||
去提现 >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 积分收益明细 -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-lg font-medium">
|
||||
金币收益明细
|
||||
</div>
|
||||
<div>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="transactionListParams.searchContent"
|
||||
type="text"
|
||||
class="pl-3 pr-8 py-1 border border-gray-200 rounded-lg outline-none focus:border-[#3f7ef7] transition-colors"
|
||||
placeholder="请输入你需要搜索的内容"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<div
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer"
|
||||
@click="handleSearch"
|
||||
>
|
||||
<div class="i-lucide:search w-4 h-4 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[200px_120px_120px_200px_200px_200px] border-b border-[#eee]">
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
用户
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
星球名称
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
类型
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
支付金额(金币)
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
时间
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="transactionList.length !== 0">
|
||||
<div
|
||||
v-for="item in transactionList"
|
||||
:key="item.id"
|
||||
class="grid grid-cols-[200px_120px_120px_200px_200px_200px] border-b border-[#eee] hover:bg-gray-50"
|
||||
>
|
||||
<div class="p-3 flex items-center gap-2 w-[200px]">
|
||||
<img :src="item.avatar" class="w-6 h-6 rounded-full" :alt="item.avatar">
|
||||
<span
|
||||
class="truncate flex-1 cursor-pointer"
|
||||
title="双击复制"
|
||||
@dblclick="copyToClipboard(item.userName)"
|
||||
>
|
||||
{{ item.userName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.communityName }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.type === 0 ? '付费加入' : '付费问答' }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ formatAmount(item.amount) }}
|
||||
</div>
|
||||
<div class="p-3 w-[300px] flex items-center">
|
||||
{{ item.createTime || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center mt-6">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:page-slot="5"
|
||||
class="py-2"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<client-only v-else>
|
||||
<div class="flex justify-center items-center min-h-[200px] text-[#86909c]">
|
||||
暂无数据
|
||||
</div>
|
||||
</client-only>
|
||||
|
||||
<!-- 交易记录列表 -->
|
||||
<!-- <div class="space-y-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="text-sm text-gray-500">
|
||||
用户
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
星球
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
类型
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
支付金额(积分)
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
星主收入(积分)
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
时间
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in transactionList"
|
||||
:key="item.userId"
|
||||
class="flex items-center justify-between py-2"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<img :src="item.avatar" class="w-6 h-6 rounded-full">
|
||||
<span>{{ item.userName }}</span>
|
||||
</div>
|
||||
<div>星球{{ item.planetName }}</div>
|
||||
<div>{{ item.type }}</div>
|
||||
<div>{{ item.amount }}</div>
|
||||
<div>{{ item.planetIncome }}</div>
|
||||
<div class="text-gray-400">
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-pagination .n-pagination-item--button) {
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 8px;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item) {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 8px;
|
||||
min-width: 32px;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item:hover) {
|
||||
color: #1e80ff;
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item--active) {
|
||||
background-color: #1e80ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item--active:hover) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-quick-jumper) {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-quick-jumper input) {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 4px;
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<script setup lang="ts">
|
||||
import { commonApi } from '@/api/common'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
const listParams = ref({
|
||||
communityTag: null as string | null,
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
})
|
||||
const containerRef = ref<HTMLElement | undefined>(undefined)
|
||||
// 标签
|
||||
interface CommunityTag {
|
||||
dictLabel: string
|
||||
dictValue: string | null
|
||||
}
|
||||
const communityTagList = ref<CommunityTag[]>([])
|
||||
async function getDictType() {
|
||||
try {
|
||||
const res = await commonApi.dictType({ type: 'community_tag' })
|
||||
if (res.code === 200) {
|
||||
communityTagList.value = [{
|
||||
dictLabel: '全部',
|
||||
dictValue: null,
|
||||
}, ...res.data] as CommunityTag[]
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
function changeTag(type: string | null) {
|
||||
listParams.value.communityTag = type
|
||||
initGetList()
|
||||
}
|
||||
getDictType()
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: number
|
||||
rows: T[]
|
||||
total: number
|
||||
}
|
||||
|
||||
interface CommunityItem {
|
||||
imageUrl: string
|
||||
publishNum: number
|
||||
communityName: string
|
||||
avatar: string
|
||||
createBy: string
|
||||
createDay: number
|
||||
description: string
|
||||
price: number | null
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
const listFinish = ref(false)
|
||||
const loading = ref(false)
|
||||
const dataList = ref<CommunityItem[]>([])
|
||||
function initGetList() {
|
||||
listFinish.value = false
|
||||
listParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
async function getList() {
|
||||
try {
|
||||
if (listFinish.value || loading.value) // 添加loading检查,避免重复请求
|
||||
return
|
||||
loading.value = listParams.value.pageNum === 1 // 只在第一页显示loading
|
||||
const res = await request.post<ApiResponse<CommunityItem>>('/community/list', listParams.value)
|
||||
if (res.code === 200) {
|
||||
if (listParams.value.pageNum === 1) {
|
||||
dataList.value = res.rows
|
||||
}
|
||||
else {
|
||||
dataList.value = [...dataList.value, ...res.rows]
|
||||
}
|
||||
|
||||
if (dataList.value.length >= res.total) {
|
||||
listFinish.value = true
|
||||
}
|
||||
listParams.value.pageNum++
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
getList()
|
||||
|
||||
function goDetail(id: string) {
|
||||
router.push(`/planet-detail/${id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#f7f8fa] relative">
|
||||
<n-affix
|
||||
:trigger-top="0"
|
||||
position="absolute"
|
||||
:listen-to="() => containerRef"
|
||||
>
|
||||
<div class="px-10 bg-[#fff] sticky top-0 left-0 right-0 z-10">
|
||||
<div class="flex flex-wrap gap-2 py-4">
|
||||
<div
|
||||
v-for="(item, index) in communityTagList"
|
||||
:key="index"
|
||||
class="px-[12px] py-[9px] text-sm rounded-lg cursor-pointer"
|
||||
:class="{ 'bg-black text-white': listParams.communityTag === item.dictValue, 'bg-white text-[#878d95] hover:bg-gray-100': listParams.communityTag !== item.dictValue }"
|
||||
@click="changeTag(item.dictValue)"
|
||||
>
|
||||
{{ item.dictLabel }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-affix>
|
||||
|
||||
<article class="px-10 py-6">
|
||||
<n-infinite-scroll :distance="10" trigger="once" @load="getList">
|
||||
<!-- 添加trigger="once"属性 -->
|
||||
<div v-if="loading" class="grid grid-cols-3 gap-2">
|
||||
<div v-for="i in 9" :key="i" class="bg-white rounded-lg overflow-hidden shadow-sm flex p-4">
|
||||
<n-skeleton class="w-[161px] h-[161px] rounded-lg flex-shrink-0" />
|
||||
<div class="flex flex-col gap-2 px-4 flex-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<n-skeleton text :width="140" />
|
||||
<div class="flex items-center gap-2">
|
||||
<n-skeleton circle width="20px" height="20px" />
|
||||
<n-skeleton text :width="60" />
|
||||
<n-skeleton text :width="80" />
|
||||
</div>
|
||||
<n-skeleton text :repeat="2" />
|
||||
</div>
|
||||
<n-skeleton text :width="60" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="dataList.length > 0" class="grid grid-cols-3 gap-2">
|
||||
<PlanetItem v-for="(item, index) in dataList" :key="index" :item="item" type="all" />
|
||||
<!--
|
||||
<div v-for="(item, index) in dataList" :key="index" class="bg-white rounded-lg overflow-hidden shadow-sm flex p-4 cursor-pointer hover:shadow-[0_4px_14px_0_rgba(0,0,0,0.1)] hover:-translate-y-1 transition-all duration-300" @click="goDetail(item.id)">
|
||||
<div class="w-[161px] h-[161px] relative rounded-lg">
|
||||
<img class="w-full h-full object-cover rounded-lg" :src="item.imageUrl" alt="">
|
||||
<span class="absolute bottom-2 left-2 text-white text-xs">{{ item.publishNum || 0 }}人已经加入</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-4 flex-1 justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="text-base font-bold">
|
||||
{{ item.communityName }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-[#878d95]">
|
||||
<img class="w-[20px] h-[20px] rounded-full" :src="item.avatar" alt="">
|
||||
<div>
|
||||
{{ item.nickName }}
|
||||
</div>
|
||||
<div>创建{{ item.createDay || 0 }}天</div>
|
||||
</div>
|
||||
<div class="text-sm text-[#4a5563] line-clamp-3">
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[#fc4141]">
|
||||
<div v-if="item.price">
|
||||
<span class="text-base font-bold mr-1">
|
||||
{{ item.price }}
|
||||
</span>
|
||||
<span class="text-xs">
|
||||
金币
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
免费
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-if="dataList.length === 0 && listFinish" class="flex flex-col items-center justify-center py-12">
|
||||
<div class="w-48 h-48 mb-4 flex items-center justify-center">
|
||||
<div class="relative">
|
||||
<div class="w-32 h-32 rounded-full border-4 border-gray-200" />
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-24 h-24 rounded-full border-4 border-gray-100" />
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-16 h-16 rounded-full bg-gray-50" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-500 mb-4">
|
||||
这里空空如也,什么都没有找到~
|
||||
</p>
|
||||
<!-- <n-button type="primary" size="small" class="px-6">
|
||||
去发现更多
|
||||
</n-button> -->
|
||||
</div>
|
||||
</n-infinite-scroll>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,367 @@
|
|||
<script setup lang="ts">
|
||||
import { formatFileSize } from '@/utils/index.ts'
|
||||
import { uploadFileBatches } from '@/utils/uploadImg'
|
||||
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
interface MemberItem {
|
||||
communityId: number
|
||||
createTime: string
|
||||
downloadFileUser: {
|
||||
avatarList: string[]
|
||||
count: number
|
||||
}
|
||||
fileName: string
|
||||
fileSize: number
|
||||
id: number
|
||||
tenantId: number
|
||||
uploadUserName: string
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const memberList = ref<MemberItem[]>([])
|
||||
const loading = ref(false)
|
||||
const isInitialized = ref(false)
|
||||
const params = ref({
|
||||
orderByColum: '',
|
||||
pageNum: 1,
|
||||
pageSize: 2,
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
search: '',
|
||||
})
|
||||
|
||||
// 添加分页数据
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 2,
|
||||
itemCount: 0,
|
||||
showSizePicker: false,
|
||||
})
|
||||
|
||||
// 处理分页变化
|
||||
function handlePageChange(page: number) {
|
||||
pagination.value.page = page
|
||||
params.value.pageNum = page
|
||||
getMemberList()
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
function handleSearch(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取成员列表
|
||||
async function getMemberList() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await request.post('/communityFile/list', params.value)
|
||||
if (res.code === 200) {
|
||||
memberList.value = res.rows
|
||||
pagination.value.itemCount = res.total
|
||||
isInitialized.value = true
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMemberList()
|
||||
})
|
||||
|
||||
// 下载文件
|
||||
async function handleSetAdmin(item: MemberItem) {
|
||||
const { id, fileName } = item
|
||||
const res = await request.post('/communityFile/download', {
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
fileId: id,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
const { data } = res
|
||||
// 创建临时下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = data
|
||||
link.download = fileName // 使用原始文件名
|
||||
link.style.display = 'none'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制文本到剪贴板
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('复制成功')
|
||||
}).catch(() => {
|
||||
message.error('复制失败')
|
||||
})
|
||||
}
|
||||
|
||||
const pictureInput = ref<HTMLInputElement | null>(null)
|
||||
function handlePictureInput() {
|
||||
pictureInput.value?.click()
|
||||
}
|
||||
|
||||
// 允许的文件类型
|
||||
const allowedFileTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain',
|
||||
'application/zip',
|
||||
]
|
||||
|
||||
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 file = files[0]
|
||||
if (!allowedFileTypes.includes(file.type)) {
|
||||
message.error('只支持 PDF、Word、Excel、PPT、TXT 和 ZIP 文件')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await uploadFileBatches([file])
|
||||
;(event.target as HTMLInputElement).value = ''
|
||||
const { fileName, objectKey, path, size } = res[0]
|
||||
const res1 = await request.post('/communityFile/upload', {
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
fileName,
|
||||
objectKey,
|
||||
fileSize: size,
|
||||
fileUrl: path,
|
||||
})
|
||||
if (res1.code === 200) {
|
||||
message.success('文件上传成功')
|
||||
params.value.search = ''
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
message.error('文件上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA] px-10 py-6 flex gap-4">
|
||||
<div class="bg-white rounded-lg flex-1">
|
||||
<div class="flex justify-between items-center m-3 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bg-[#328afe] text-white px-3 py-1 rounded-sm cursor-pointer" @click="handlePictureInput">
|
||||
上传文件
|
||||
</div>
|
||||
<div>
|
||||
<!-- 111 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex items-center w-[300px]">
|
||||
<input
|
||||
v-model="params.search"
|
||||
type="text"
|
||||
placeholder="请输入文件名"
|
||||
class="w-full h-9 pl-4 pr-14 border border-[#eee] rounded-lg text-sm placeholder-[#878D95] outline-none focus:border-[#3f7ef7] focus:ring-1 focus:ring-[#3f7ef7] transition-colors"
|
||||
@keyup="handleSearch"
|
||||
>
|
||||
<button class="absolute right-3 flex items-center space-x-1 text-[#4A5563]">
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.333 12.667A5.333 5.333 0 1 0 7.333 2a5.333 5.333 0 0 0 0 10.667zM14 14l-2.9-2.9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<!-- <span class="text-sm font-bold">搜索</span> -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-[300px_150px_150px_200px_130px_100px] border-b border-[#eee]">
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
全部文件
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
文件大小
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
上传人
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
上传时间
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
下载次数
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="p-10">
|
||||
<n-spin />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="item in memberList"
|
||||
:key="item.id"
|
||||
class="grid grid-cols-[300px_150px_150px_200px_130px_100px] border-b border-[#eee] hover:bg-gray-50"
|
||||
>
|
||||
<div class="p-3 flex items-center gap-2">
|
||||
<!-- <img :src="item.avatar" class="w-8 h-8 rounded-full" :alt="item.nickname"> -->
|
||||
<span
|
||||
class="truncate flex-1 cursor-pointer"
|
||||
:title="item.fileName"
|
||||
@dblclick="copyToClipboard(item.fileName)"
|
||||
>
|
||||
{{ item.fileName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ formatFileSize(item.fileSize) }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.uploadUserName || '-' }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.downloadFileUser.count }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
<div class="w-[100px]">
|
||||
<button
|
||||
class="text-[#1e80ff] hover:text-[#3b8fff]"
|
||||
@click="handleSetAdmin(item)"
|
||||
>
|
||||
下载
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="isInitialized && memberList.length > 0" class="flex justify-center mt-4">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:show-size-picker="false"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex justify-center mt-10">
|
||||
暂无数据
|
||||
</div>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip"
|
||||
class="hidden"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</div>
|
||||
<div class="w-[300px]">
|
||||
<PlanetBaseInfo :community-id="route.query.communityId" :tenant-id="route.query.tenantId" />
|
||||
</div>
|
||||
|
||||
<!-- <n-modal
|
||||
v-model:show="showBlackModal"
|
||||
:style="{ width: '480px' }"
|
||||
:mask-closable="false"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<div class="p-6 bg-white rounded-lg">
|
||||
<div class="text-center text-lg font-medium mb-6">
|
||||
拉黑成员
|
||||
</div>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="blackForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
class="space-y-4"
|
||||
>
|
||||
<n-form-item label="拉黑时长" path="blackDay">
|
||||
<div class="flex items-center gap-2">
|
||||
<n-input-number
|
||||
v-model:value="blackForm.blackDay"
|
||||
placeholder="请输入"
|
||||
:min="1"
|
||||
@update:value="() => formRef.value?.validate(['blackDay'])"
|
||||
/>
|
||||
<span class="text-sm">天</span>
|
||||
<div class="relative group">
|
||||
<div class="i-carbon-information text-gray-400 cursor-help" />
|
||||
<div class="absolute left-6 top-0 hidden group-hover:block w-64 p-3 bg-black bg-opacity-75 text-white text-xs rounded-lg">
|
||||
拉黑后该成员将不能对星球进行内容发布、点赞、评论内容等操作!拉黑时长结束后将自动解除
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="拉黑原因" path="blackReason">
|
||||
<n-input
|
||||
v-model:value="blackForm.blackReason"
|
||||
type="textarea"
|
||||
placeholder="请输入内容"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="flex justify-center gap-4 mt-8">
|
||||
<n-button
|
||||
class="w-24 h-9 hover:opacity-90"
|
||||
@click="showBlackModal = false"
|
||||
>
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
class="w-24 h-9 hover:opacity-90"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handleBlack"
|
||||
>
|
||||
确定拉黑
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal> -->
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import PlanetBaseInfo from '@/components/PlanetBaseInfo.vue'
|
||||
import PlanetComment from '@/components/PlanetComment.vue'
|
||||
import PlanetQuestionComment from '@/components/PlanetQuestionComment.vue'
|
||||
import PublishContent from '@/components/PublishContent.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import request from '@/utils/request'
|
||||
import { FolderPlus, ImagePlus } from 'lucide-vue-next'
|
||||
import { NModal } from 'naive-ui'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 社区详情接口返回的数据类型
|
||||
interface CommunityDetail {
|
||||
id: string
|
||||
communityName: string
|
||||
coverImage: string
|
||||
memberCount: number
|
||||
contentCount: number
|
||||
createDays: number
|
||||
score: number
|
||||
}
|
||||
|
||||
const communityDetail = ref<CommunityDetail>()
|
||||
const loading = ref(false)
|
||||
|
||||
// 获取社区详情
|
||||
async function getCommunityDetail() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await request.get('/community/detail', {
|
||||
params: {
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
},
|
||||
})
|
||||
if (res.code === 200) {
|
||||
communityDetail.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('获取社区详情失败:', err)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由参数变化,重新获取数据
|
||||
watchEffect(() => {
|
||||
if (route.query.communityId && route.query.tenantId) {
|
||||
getCommunityDetail()
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main p="x4 y10" text="center teal-700 dark:gray-200">
|
||||
<div text-4xl>
|
||||
<div i-carbon-warning inline-block />
|
||||
</div>
|
||||
<div>Not found</div>
|
||||
<div>
|
||||
<button text-sm btn m="3 t8" @click="router.back()">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
|
@ -0,0 +1,388 @@
|
|||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
interface MemberItem {
|
||||
userId: number | string
|
||||
id: number | string
|
||||
tenantId: number | string
|
||||
avatar: string
|
||||
nickname: string
|
||||
role: '星主' | '管理人' | '成员'
|
||||
type: '付费' | '免费'
|
||||
joinTime: string
|
||||
expireTime: string
|
||||
lastLoginTime: string
|
||||
isBlack: '0' | '1'
|
||||
userType: number
|
||||
communityId: number | string
|
||||
joinType: string
|
||||
}
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const memberList = ref<MemberItem[]>([])
|
||||
const loading = ref(false)
|
||||
const isInitialized = ref(false)
|
||||
const params = ref({
|
||||
orderByColum: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
searchContent: '',
|
||||
})
|
||||
|
||||
// 添加分页数据
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
showSizePicker: false,
|
||||
})
|
||||
|
||||
// 拉黑表单
|
||||
const showBlackModal = ref(false)
|
||||
const blackForm = ref({
|
||||
blackDay: undefined,
|
||||
blackReason: '',
|
||||
userId: '',
|
||||
})
|
||||
|
||||
// 添加表单验证规则
|
||||
const rules = {
|
||||
blackDay: {
|
||||
required: true,
|
||||
message: '请输入拉黑时长',
|
||||
trigger: ['blur', 'change'],
|
||||
type: 'number',
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value === undefined || value === null)
|
||||
return new Error('请输入拉黑时长')
|
||||
return true
|
||||
},
|
||||
},
|
||||
blackReason: {
|
||||
required: true,
|
||||
message: '请输入拉黑原因',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
}
|
||||
|
||||
// 表单ref
|
||||
const formRef = ref()
|
||||
|
||||
// 处理分页变化
|
||||
function handlePageChange(page: number) {
|
||||
pagination.value.page = page
|
||||
params.value.pageNum = page
|
||||
getMemberList()
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
function handleSearch(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取成员列表
|
||||
async function getMemberList() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await request.post('/communityUser/list', params.value)
|
||||
if (res.code === 200) {
|
||||
memberList.value = res.rows
|
||||
pagination.value.itemCount = res.total
|
||||
isInitialized.value = true
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMemberList()
|
||||
})
|
||||
|
||||
// 拉黑/解除拉黑
|
||||
async function handleRemove(item: MemberItem) {
|
||||
if (item.userType === 2)
|
||||
return
|
||||
if (item.isBlack === '1') {
|
||||
// 直接解除拉黑
|
||||
try {
|
||||
const res = await request.post('/communityUser/unBlack', {
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId, // 用户userId
|
||||
userId: item.userId,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
message.success('解除拉黑成功')
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 显示拉黑弹窗
|
||||
blackForm.value = {
|
||||
blackDay: undefined,
|
||||
blackReason: '',
|
||||
userId: item.userId,
|
||||
}
|
||||
showBlackModal.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 确认拉黑
|
||||
async function handleBlack() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
const res = await request.post('/communityUser/black', {
|
||||
communityId: route.query.communityId,
|
||||
tenantId: route.query.tenantId,
|
||||
userId: blackForm.value.userId,
|
||||
blackDay: blackForm.value.blackDay,
|
||||
blackReason: blackForm.value.blackReason,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
message.success('拉黑成功')
|
||||
showBlackModal.value = false
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置管理员
|
||||
async function handleSetAdmin(item: MemberItem) {
|
||||
if (item.userType === 2)
|
||||
return
|
||||
const { tenantId, communityId, userId } = item
|
||||
try {
|
||||
const res = await request.post('/communityUser/manage', {
|
||||
communityId,
|
||||
tenantId,
|
||||
userId,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
message.success('设置成功')
|
||||
params.value.pageNum = 1
|
||||
pagination.value.page = 1
|
||||
getMemberList()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制文本到剪贴板
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('复制成功')
|
||||
}).catch(() => {
|
||||
message.error('复制失败')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA] px-10 py-6 flex gap-4">
|
||||
<div class="bg-white rounded-lg flex-1">
|
||||
<div class="flex justify-between items-center m-3 text-sm">
|
||||
<div class="bg-[#328afe] text-white px-3 py-1 rounded-sm cursor-pointer">
|
||||
分享星球
|
||||
</div>
|
||||
<div class="relative flex items-center w-[300px]">
|
||||
<input
|
||||
v-model="params.searchContent"
|
||||
type="text"
|
||||
placeholder="请输入用户名称"
|
||||
class="w-full h-9 pl-4 pr-14 border border-[#eee] rounded-lg text-sm placeholder-[#878D95] outline-none focus:border-[#3f7ef7] focus:ring-1 focus:ring-[#3f7ef7] transition-colors"
|
||||
@keyup="handleSearch"
|
||||
>
|
||||
<button class="absolute right-3 flex items-center space-x-1 text-[#4A5563]">
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.333 12.667A5.333 5.333 0 1 0 7.333 2a5.333 5.333 0 0 0 0 10.667zM14 14l-2.9-2.9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<!-- <span class="text-sm font-bold">搜索</span> -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-[200px_120px_120px_200px_200px_200px] border-b border-[#eee]">
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
全部成员
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
权限
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
加入类型
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
首次加入时间
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
到期时间
|
||||
</div>
|
||||
<div class="p-3 text-[#1f2329] font-medium">
|
||||
操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="p-10">
|
||||
<n-spin />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="item in memberList"
|
||||
:key="item.id"
|
||||
class="grid grid-cols-[200px_120px_120px_200px_200px_200px] border-b border-[#eee] hover:bg-gray-50"
|
||||
>
|
||||
<div class="p-3 flex items-center gap-2 w-[200px]">
|
||||
<img :src="item.avatar" class="w-8 h-8 rounded-full" :alt="item.nickname">
|
||||
<span
|
||||
class="truncate flex-1 cursor-pointer"
|
||||
title="双击复制"
|
||||
@dblclick="copyToClipboard(item.nickName)"
|
||||
>
|
||||
{{ item.nickName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.userType === 0 ? '成员' : item.userType === 1 ? '管理员' : '群主' }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.userType !== 2 ? item.joinType : '-' }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.startTime || '-' }}
|
||||
</div>
|
||||
<div class="p-3 flex items-center">
|
||||
{{ item.endTime || '-' }}
|
||||
</div>
|
||||
<div class="p-3 w-[300px] flex items-center">
|
||||
<div class="w-[100px]">
|
||||
<button
|
||||
class="text-[#1e80ff] hover:text-[#3b8fff]"
|
||||
@click="handleSetAdmin(item)"
|
||||
>
|
||||
{{ item.userType === 2 ? '-' : item.userType === 0 ? '设为管理员' : '取消管理员' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-[100px]">
|
||||
<button
|
||||
class="text-[#f85149] hover:text-[#ff6b64]"
|
||||
@click="handleRemove(item)"
|
||||
>
|
||||
{{ item.userType === 2 ? '-' : item.isBlack === '0' ? '拉黑' : '解除拉黑' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="isInitialized" class="flex justify-center mt-4">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:show-size-picker="false"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
<n-modal
|
||||
v-model:show="showBlackModal"
|
||||
:style="{ width: '480px' }"
|
||||
:mask-closable="false"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<div class="p-6 bg-white rounded-lg">
|
||||
<div class="text-center text-lg font-medium mb-6">
|
||||
拉黑成员
|
||||
</div>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="blackForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
class="space-y-4"
|
||||
>
|
||||
<n-form-item label="拉黑时长" path="blackDay">
|
||||
<div class="flex items-center gap-2">
|
||||
<n-input-number
|
||||
v-model:value="blackForm.blackDay"
|
||||
placeholder="请输入"
|
||||
:min="1"
|
||||
@update:value="() => formRef.value?.validate(['blackDay'])"
|
||||
/>
|
||||
<span class="text-sm">天</span>
|
||||
<div class="relative group">
|
||||
<div class="i-carbon-information text-gray-400 cursor-help" />
|
||||
<div class="absolute left-6 top-0 hidden group-hover:block w-64 p-3 bg-black bg-opacity-75 text-white text-xs rounded-lg">
|
||||
拉黑后该成员将不能对星球进行内容发布、点赞、评论内容等操作!拉黑时长结束后将自动解除
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="拉黑原因" path="blackReason">
|
||||
<n-input
|
||||
v-model:value="blackForm.blackReason"
|
||||
type="textarea"
|
||||
placeholder="请输入内容"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="flex justify-center gap-4 mt-8">
|
||||
<n-button
|
||||
class="w-24 h-9 hover:opacity-90"
|
||||
@click="showBlackModal = false"
|
||||
>
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
class="w-24 h-9 hover:opacity-90"
|
||||
:theme-overrides="{
|
||||
common: {
|
||||
primaryColor: '#3f7ef7',
|
||||
primaryColorHover: '#3f7ef7',
|
||||
},
|
||||
}"
|
||||
@click="handleBlack"
|
||||
>
|
||||
确定拉黑
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</div>
|
||||
<div class="w-[300px]">
|
||||
<PlanetBaseInfo :community-id="route.query.communityId" :tenant-id="route.query.tenantId" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,110 @@
|
|||
<script setup lang="ts">
|
||||
import planetBg from '@/assets/img/planetBg.png'
|
||||
import { NConfigProvider, NImage, NMessageProvider } from 'naive-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const { communityId, tenantId } = route.query
|
||||
const userStore = useUserStore()
|
||||
definePageMeta({
|
||||
layout: 'planet',
|
||||
})
|
||||
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
interface SelectUserInfo {
|
||||
likeCount: number
|
||||
bean: number
|
||||
download: number
|
||||
attention: number
|
||||
}
|
||||
const selectUserInfo = ref<SelectUserInfo>({
|
||||
likeCount: 0,
|
||||
bean: 0,
|
||||
download: 0,
|
||||
attention: 0,
|
||||
})
|
||||
|
||||
const communityDetail = ref({})
|
||||
async function getCommunityDetail() {
|
||||
try {
|
||||
const res = await request.get(`/community/detail?communityId=${communityId}&tenantId=${tenantId}`)
|
||||
if (res.code === 200) {
|
||||
communityDetail.value = res.data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
getCommunityDetail()
|
||||
|
||||
// 获取关注
|
||||
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()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#F7F8FA]">
|
||||
<div class="relative">
|
||||
<!-- 背景图片区域 -->
|
||||
<div class="h-[300px] w-full bg-[#E8F3FF]">
|
||||
<img :src="planetBg" alt="背景图片" class="w-full h-full object-cover">
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="absolute inset-x-0 top-[80px]">
|
||||
<div class="max-w-[1140px] mx-auto">
|
||||
<!-- 用户基本信息 -->
|
||||
<client-only>
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<img :src="userStore.userInfo?.avatar" class="w-[224px] h-[224px] rounded" alt="头像">
|
||||
<div class="flex flex-col justify-between h-[224px] w-full">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-[20px] font-medium text-[#192029]">
|
||||
{{ userStore.userInfo?.nickName }}
|
||||
</div>
|
||||
<div class="text-[14px] text-[#878d95]">
|
||||
最新更新时间: 3234-32432-32
|
||||
</div>
|
||||
<div class="text-[14px] flex gap-8">
|
||||
<div class="flex items-center gap-3">
|
||||
<img :src="userStore.userInfo?.avatar" class="w-[20px] h-[20px] rounded-full" alt="头像">
|
||||
<div>
|
||||
成员数量: 1234
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
累计更新内容: 1234
|
||||
</div>
|
||||
<div>
|
||||
星球创建天数: 1234
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between p-2 rounded bg-white items-center">
|
||||
<div class="flex items-center gap-2 text-[#fc4141]">
|
||||
<span class="font-bold text-[28px]">228</span> <span class="text-[16px]">积分</span>
|
||||
</div>
|
||||
<div class="text-[14px]">
|
||||
立即加入
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -3,16 +3,14 @@ import { isAmount } from '@/utils/index.ts'
|
|||
import { Close } from '@vicons/ionicons5'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const activeTab = ref('withdrawDetail')
|
||||
const showWalletModal = ref(false)
|
||||
function showModal() {
|
||||
showWalletModal.value = true
|
||||
}
|
||||
const message = useMessage()
|
||||
function closeWalletModal() {
|
||||
showWalletModal.value = false
|
||||
needWalletNum.value = 0
|
||||
}
|
||||
|
||||
// const allDetailList = ref([]);
|
||||
// 获取可提现金额
|
||||
const integralGold = ref({})
|
||||
|
@ -32,10 +30,13 @@ getIntegralGold()
|
|||
// 提现操作
|
||||
const needWalletNum = ref(0)
|
||||
async function handleWallet() {
|
||||
if (loading.value)
|
||||
return
|
||||
if (isAmount(needWalletNum.value)) {
|
||||
if (needWalletNum.value > integralGold.value.wallet)
|
||||
return message.warning('可提现金额不足')
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await request.get(`/ali/pay/fetch?amount=${needWalletNum.value}`)
|
||||
if (res.code === 200) {
|
||||
message.success('提现成功!')
|
||||
|
@ -47,12 +48,20 @@ async function handleWallet() {
|
|||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
else {
|
||||
message.warning('请输入正确的金额')
|
||||
}
|
||||
}
|
||||
|
||||
function closeWalletModal() {
|
||||
showWalletModal.value = false
|
||||
needWalletNum.value = 0
|
||||
}
|
||||
|
||||
// 获取累计收入金额
|
||||
const totalAmount = ref(0)
|
||||
async function getTotalAmount() {
|
||||
|
@ -228,18 +237,21 @@ getWithdrawDetail()
|
|||
可提现金额: ¥ {{ integralGold.wallet || 0 }}
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<n-spin :show="loading">
|
||||
<n-input-number
|
||||
v-model:value="needWalletNum"
|
||||
placeholder="输入要提现的金额"
|
||||
:precision="2"
|
||||
clearable
|
||||
/>
|
||||
</n-spin>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center mt-6">
|
||||
<n-button type="error" @click="closeWalletModal">
|
||||
取消
|
||||
</n-button>
|
||||
<!-- :loading="loading" -->
|
||||
<n-button type="primary" class="ml-8" @click="handleWallet">
|
||||
确定
|
||||
</n-button>
|
||||
|
|
|
@ -184,9 +184,9 @@ async function onLike() {
|
|||
|
||||
// 举报
|
||||
const reportParams = ref({
|
||||
reportId: undefined,
|
||||
reportType: undefined,
|
||||
text: '',
|
||||
type: 3,
|
||||
type: 1,
|
||||
})
|
||||
const reportList = ref([])
|
||||
async function getDictType() {
|
||||
|
@ -202,7 +202,7 @@ async function getDictType() {
|
|||
}
|
||||
getDictType()
|
||||
function handleChange(item) {
|
||||
reportParams.value.reportId = item.dictValue
|
||||
reportParams.value.reportType = item.dictValue
|
||||
if (item.dictValue !== '5') {
|
||||
reportParams.value.text = ''
|
||||
}
|
||||
|
@ -214,8 +214,9 @@ function closeReport() {
|
|||
}
|
||||
|
||||
async function onReport() {
|
||||
if (reportParams.value.reportId !== undefined) {
|
||||
if (reportParams.value.reportType !== undefined) {
|
||||
reportParams.value.productId = detailsInfo.value.id
|
||||
reportParams.value.productName = detailsInfo.value.workflowName
|
||||
try {
|
||||
const res = await request.post('/report/addReport', reportParams.value)
|
||||
if (res.code === 200) {
|
||||
|
@ -601,7 +602,7 @@ async function handelDown() {
|
|||
:key="index"
|
||||
class="m-4 text-[#fff000]"
|
||||
size="large"
|
||||
:checked="reportParams.reportId === item.dictValue"
|
||||
:checked="reportParams.reportType === item.dictValue"
|
||||
value="Definitely Maybe"
|
||||
name="basic-demo"
|
||||
@change="handleChange(item)"
|
||||
|
@ -609,7 +610,7 @@ async function handelDown() {
|
|||
{{ item.dictLabel }}
|
||||
</n-radio>
|
||||
<n-input
|
||||
v-if="reportParams.reportId !== undefined && reportParams.reportId === '5'"
|
||||
v-if="reportParams.reportType !== undefined && reportParams.reportType === '5'"
|
||||
v-model:value="reportParams.text"
|
||||
placeholder="点击输入"
|
||||
type="textarea"
|
||||
|
@ -621,7 +622,7 @@ async function handelDown() {
|
|||
<div
|
||||
class="mt-4 w-[100%] h-10 flex rounded-lg text-white items-center justify-center cursor-pointer"
|
||||
:class="[
|
||||
reportParams.reportId !== undefined
|
||||
reportParams.reportType !== undefined
|
||||
? 'bg-[#4c79ee]'
|
||||
: 'bg-[#cccccc]',
|
||||
]"
|
||||
|
|
|
@ -29,6 +29,7 @@ export const useUserStore = defineStore('user', () => {
|
|||
}
|
||||
}
|
||||
async function getUserInfo() {
|
||||
if (token.value) {
|
||||
const res = await request.get('/system/user/selectUserById', {
|
||||
token: token.value,
|
||||
})
|
||||
|
@ -37,6 +38,7 @@ export const useUserStore = defineStore('user', () => {
|
|||
setUserInfo(res.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 登出
|
||||
function logout() {
|
||||
isLoggedIn.value = false
|
||||
|
|
|
@ -6,20 +6,31 @@ export async function formatDate(timestamp: string) {
|
|||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
export function formatFileSize(bytes:number = 0, decimals = 2) {
|
||||
if (bytes === 0) return '0 B';
|
||||
export function formatFileSize(bytes: number = 0, decimals = 2) {
|
||||
if (bytes === 0)
|
||||
return '0 B'
|
||||
|
||||
const k = 1024; // 1 KB = 1024 B
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); // 计算单位索引
|
||||
const k = 1024 // 1 KB = 1024 B
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)) // 计算单位索引
|
||||
|
||||
// 转换为合适的单位并保留指定小数位数
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(decimals))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
export function isAmount(str:any) {
|
||||
const amountRegex = /^\d+(\.\d{1,2})?$/;
|
||||
return amountRegex.test(str);
|
||||
export function isAmount(str: any) {
|
||||
const amountRegex = /^\d+(\.\d{1,2})?$/
|
||||
return amountRegex.test(str)
|
||||
}
|
||||
/**
|
||||
* 金额转换千分位
|
||||
* @param amount 金额数值
|
||||
* @returns 格式化后的金额字符串
|
||||
*/
|
||||
export function formatAmount(amount: number | undefined): string {
|
||||
if (amount === undefined || amount === null)
|
||||
return '-'
|
||||
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
}
|
||||
|
||||
// function isAmount(str) {
|
||||
|
|
|
@ -66,10 +66,9 @@ class RequestHttp {
|
|||
modalStore.showLoginModal()
|
||||
const userStore = useUserStore()
|
||||
try {
|
||||
|
||||
// await request.post('/logout')
|
||||
// userStore.logout()
|
||||
// navigateTo('/model-square')
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
navigateTo('/model-square')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
|
@ -177,7 +176,7 @@ class RequestHttp {
|
|||
}
|
||||
|
||||
const request = new RequestHttp({
|
||||
baseURL: import.meta.env.VITE_NUXT_ENV || '/api',
|
||||
baseURL: import.meta.env.NODE_ENV === 'production' ? `${import.meta.env.VITE_NUXT_ENV}/api` : '/api',
|
||||
timeout: 20000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
@ -86,7 +86,7 @@ export default defineNuxtConfig({
|
|||
'/api': {
|
||||
// target: 'http://113.45.190.154:8080', // 线上
|
||||
// target: 'http://192.168.2.29:8080', // 代
|
||||
target: 'http://192.168.2.4:8080', // 嗨
|
||||
target: 'http://192.168.2.21:8080', // 嗨
|
||||
// target: 'https://2d1a399f.r27.cpolar.top', // 嗨
|
||||
changeOrigin: true,
|
||||
prependPath: true,
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
"private": true,
|
||||
"packageManager": "pnpm@9.15.1",
|
||||
"scripts": {
|
||||
"build:production": "nuxi build --dotenv .env.production",
|
||||
"build:dev": "nuxi build --dotenv .env.dev",
|
||||
"build": "nuxi build --dotenv .env.pro",
|
||||
"dev:pwa": "VITE_PLUGIN_PWA=true nuxi dev",
|
||||
"dev": "nuxi dev --port 3080",
|
||||
"dev": "nuxi dev --port 8080",
|
||||
"generate": "nuxi generate",
|
||||
"prepare": "nuxi prepare",
|
||||
"start": "node .output/server/index.mjs",
|
||||
|
|
Loading…
Reference in New Issue