update
|
@ -41,5 +41,6 @@
|
|||
],
|
||||
"interline-translate.knownPopularWordCount": 6000,
|
||||
"iconify.annotations": true,
|
||||
"iconify.inplace": true
|
||||
"iconify.inplace": true,
|
||||
"vue3snippets.enable-compile-vue-file-on-did-save-code": false
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { ApiResponse, PaginationParams, PaginationResponse } from '~/types/api'
|
||||
// api/common.ts
|
||||
import request from '~/utils/request'
|
||||
import type { ApiResponse, PaginationParams, PaginationResponse } from '~/types/api'
|
||||
|
||||
export const commonApi = {
|
||||
// 上传文件
|
||||
|
@ -9,13 +9,18 @@ export const commonApi = {
|
|||
formData.append('file', file)
|
||||
return request.post<ApiResponse<{ url: string }>>('/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 获取配置信息
|
||||
getConfig() {
|
||||
return request.get<ApiResponse<Record<string, any>>>('/config')
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户字典
|
||||
dictType(query: any) {
|
||||
return request.get<ApiResponse<Record<string, any>>>(`/system/dict/data/type/${query.type}`)
|
||||
},
|
||||
}
|
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 279 B |
|
@ -7,20 +7,41 @@ export {}
|
|||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
NAtePicker: typeof import('naive-ui')['NAtePicker']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NBadge: typeof import('naive-ui')['NBadge']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
NFormTtem: typeof import('naive-ui')['NFormTtem']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputgroup: typeof import('naive-ui')['NInputgroup']
|
||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTimePicker: typeof import('naive-ui')['NTimePicker']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NTransfer: typeof import('naive-ui')['NTransfer']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
const message = useMessage()
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo
|
||||
const rules = ref({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
validator( value: string) {
|
||||
if (!value) {
|
||||
return new Error('请填写姓名')
|
||||
}
|
||||
else if (!/^(?:[\u4e00-\u9fa5·]{2,16})$/.test(value)) {
|
||||
return new Error('输入正确的姓名')
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger:'blur'
|
||||
},
|
||||
],
|
||||
idCard: [
|
||||
{
|
||||
required: true,
|
||||
validator( value: string) {
|
||||
if (!value) {
|
||||
return new Error('请填身份证号')
|
||||
}
|
||||
else if (!/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/.test(value)) {
|
||||
return new Error('输入正确的身份证号')
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger:'blur'
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const ruleForm = ref({
|
||||
name: '',
|
||||
idCard:'',
|
||||
userId: userInfo.userId
|
||||
})
|
||||
const formRef = ref(null)
|
||||
const saveInfo = async(e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate(async(errors) => {
|
||||
if (!errors) {
|
||||
// try{
|
||||
// const res = await request.post('/system/user/updateIdCard',ruleForm.value)
|
||||
// if(res.code === 200){
|
||||
// const data = await userStore.getUserInfo()
|
||||
// message.success('认证成功')
|
||||
// ruleForm.value.name = ''
|
||||
// ruleForm.value.idCard = ''
|
||||
// onCloseModel()
|
||||
// }
|
||||
// }catch(err) {
|
||||
// console.log('err',err)
|
||||
// }
|
||||
const res = await request.post('/system/user/updateIdCard',ruleForm.value)
|
||||
const data = await userStore.getUserInfo()
|
||||
isVisible.value = false
|
||||
}
|
||||
else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isVisible = ref(false)
|
||||
function onCloseModel() {
|
||||
isVisible.value = false
|
||||
ruleForm.value.name = ''
|
||||
ruleForm.value.idCard = ''
|
||||
}
|
||||
defineExpose({
|
||||
isVisible,
|
||||
})
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// initFormData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="isVisible"
|
||||
:on-after-leave="onCloseModel"
|
||||
preset="card"
|
||||
style="width: 400px"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<n-form ref="formRef" :model="ruleForm" :rules="rules">
|
||||
<n-form-item path="name" label="姓名">
|
||||
<n-input placeholder="请输入姓名" v-model:value="ruleForm.name" @keydown.enter.prevent />
|
||||
</n-form-item>
|
||||
<n-form-item path="idCard" label="身份证号">
|
||||
<n-input placeholder="请输入身份证号" v-model:value="ruleForm.idCard" @keydown.enter.prevent />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="flex justify-center items-center ">
|
||||
<button class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle color-[#fff] border-0 cursor-pointer" @click="saveInfo">
|
||||
实名认证
|
||||
</button>
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form {
|
||||
/* cursor: pointer; */
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
width: 200px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.form-item button {
|
||||
padding: 5px 10px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,166 @@
|
|||
<script setup lang="ts">
|
||||
import defaultAvatar from '@/assets/img/default-avatar.png'
|
||||
import { ref } from 'vue'
|
||||
import { uploadImagesInBatches } from '../utils/uploadImg.ts'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo
|
||||
const message = useMessage()
|
||||
|
||||
const ruleForm = ref({})
|
||||
function onShowModel() {
|
||||
ruleForm.value.nickName = userInfo.nickName
|
||||
ruleForm.value.avatar = userInfo.avatar
|
||||
ruleForm.value.brief = userInfo.brief
|
||||
ruleForm.value.userId = userInfo.userId
|
||||
}
|
||||
|
||||
const rules = ref({
|
||||
nickName: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 12, message: '长度在 3 到 12 个字符', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
// 文件输入引用
|
||||
const pictureInput = ref<HTMLInputElement | null>(null)
|
||||
function handlePictureInput() {
|
||||
pictureInput.value?.click()
|
||||
}
|
||||
|
||||
// 处理图片上传
|
||||
async function handlePictureChange(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files
|
||||
if (!files || files.length === 0)
|
||||
return
|
||||
|
||||
if (files.length > 1) {
|
||||
message.error('只能选择 1 张图片')
|
||||
return
|
||||
}
|
||||
|
||||
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'))
|
||||
if (imageFiles.length === 0) {
|
||||
message.error('请选择有效的图片文件')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const pictureResultList = await uploadImagesInBatches(imageFiles)
|
||||
event.target.value = ''
|
||||
ruleForm.value.avatar = pictureResultList[0]
|
||||
}
|
||||
catch (error) {
|
||||
message.error('图片上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function saveInfo() {
|
||||
const res = await request.post('/system/user/updateUserInfo', ruleForm.value)
|
||||
const data = await userStore.getUserInfo()
|
||||
onCloseModel()
|
||||
}
|
||||
|
||||
const isVisible = ref(false)
|
||||
function onCloseModel() {
|
||||
isVisible.value = false
|
||||
}
|
||||
defineExpose({
|
||||
isVisible,
|
||||
})
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// initFormData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="isVisible"
|
||||
:on-after-leave="onCloseModel"
|
||||
:on-after-enter="onShowModel"
|
||||
preset="card"
|
||||
style="width: 400px"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<div class="rounded-full flex justify-center overflow-hidden mb-4">
|
||||
<img class="block w-[60px] h-[60px] rounded-full" :src="ruleForm.avatar || defaultAvatar" alt="编辑">
|
||||
</div>
|
||||
<div class="flex justify-center mb-2">
|
||||
<button class="cursor-pointer text-center text-base text-[#222] dark:text-[#fff] dark:border-[#AABEFF1A] border-0 bg-[#F1F2F8] hover:bg-[#ECEEF5] dark:bg-[#1C1D29] dark:hover:bg-[#2A2D3D] font-medium leading-[3rem] w-[116px] h-12 rounded-lg" @click="handlePictureInput">
|
||||
更换头像
|
||||
</button>
|
||||
<input
|
||||
ref="pictureInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
@change="handlePictureChange"
|
||||
>
|
||||
</div>
|
||||
<n-form ref="formRef" :model="ruleForm" :rules="rules">
|
||||
<n-form-item path="nickName" label="用户名">
|
||||
<n-input v-model:value="ruleForm.nickName" placeholder="请输入用户名" @keydown.enter.prevent />
|
||||
</n-form-item>
|
||||
<n-form-item label="简介" path="textareaValue">
|
||||
<n-input
|
||||
v-model:value="ruleForm.brief"
|
||||
placeholder="请输入简介"
|
||||
type="textarea"
|
||||
:autosize="{
|
||||
minRows: 3,
|
||||
maxRows: 5,
|
||||
}"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="flex justify-center items-center ">
|
||||
<button class="w-1/2 flex justify-center mx-1 bg-[#f1f2f2] py-3 rounded-lg align-middle border-0 cursor-pointer" @click="onCloseModel">
|
||||
暂不修改
|
||||
</button>
|
||||
<!-- <div class="w-1/2 flex justify-center mx-1 bg-[#f1f2f2] py-3 rounded-lg align-middle">
|
||||
暂不修改
|
||||
</div> -->
|
||||
|
||||
<button class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle color-[#fff] border-0 cursor-pointer" @click="saveInfo">
|
||||
确定修改
|
||||
</button>
|
||||
<!-- <div class="w-1/2 flex justify-center mx-1 bg-[#213df5] py-3 rounded-lg align-middle color-[#fff]">
|
||||
确定修改
|
||||
</div> -->
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form {
|
||||
/* cursor: pointer; */
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
width: 200px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.form-item button {
|
||||
padding: 5px 10px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -1,18 +1,18 @@
|
|||
<template>
|
||||
<n-config-provider :theme="theme" :theme-overrides="themeOverrides">
|
||||
<n-message-provider>
|
||||
<slot></slot>
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { darkTheme, NConfigProvider, NMessageProvider } from 'naive-ui';
|
||||
const theme = darkTheme
|
||||
import { darkTheme, NConfigProvider, NMessageProvider } from 'naive-ui'
|
||||
|
||||
// 可以根据需要配置主题
|
||||
const themeOverrides = {
|
||||
common: {
|
||||
primaryColor: '#18a058'
|
||||
}
|
||||
primaryColor: '#18a058',
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NConfigProvider :theme-overrides="themeOverrides">
|
||||
<NMessageProvider>
|
||||
<slot />
|
||||
</NMessageProvider>
|
||||
</NConfigProvider>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
<script setup lang="ts">
|
||||
import defaultAvatar from '@/assets/img/default-avatar.png'
|
||||
import {
|
||||
Bell,
|
||||
CirclePlus,
|
||||
GraduationCap,
|
||||
HardDriveUpload,
|
||||
Image,
|
||||
Monitor,
|
||||
Workflow,
|
||||
} from 'lucide-vue-next'
|
||||
// import { AtCircle } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// 输入框搜索
|
||||
const searchText = ref('')
|
||||
function onSearch(value: any) {
|
||||
console.log('搜索:', value)
|
||||
// 执行搜索逻辑
|
||||
}
|
||||
|
||||
// 用户下拉选项
|
||||
const notificationOptions = [
|
||||
{
|
||||
label: '系统通知',
|
||||
key: 'system',
|
||||
},
|
||||
{
|
||||
label: '互动消息',
|
||||
key: 'interaction',
|
||||
},
|
||||
]
|
||||
function renderIcon(icon: Component) {
|
||||
return () => {
|
||||
return h(NIcon, null, {
|
||||
default: () => h(icon),
|
||||
})
|
||||
}
|
||||
}
|
||||
// 发布下拉选项
|
||||
const publishOptions = [
|
||||
{
|
||||
label: '模型',
|
||||
key: 'publish-model',
|
||||
icon: renderIcon(HardDriveUpload),
|
||||
},
|
||||
{
|
||||
label: '图片',
|
||||
key: 'picture',
|
||||
icon: renderIcon(Image),
|
||||
},
|
||||
{
|
||||
label: '工作流',
|
||||
key: 'publish-workflow',
|
||||
icon: renderIcon(Workflow),
|
||||
},
|
||||
]
|
||||
const userOptions = ref([
|
||||
{
|
||||
label: '我的模型',
|
||||
key: 'model',
|
||||
},
|
||||
{
|
||||
label: '我的作品',
|
||||
key: 'project',
|
||||
},
|
||||
{
|
||||
label: '我的点赞',
|
||||
key: 'like',
|
||||
},
|
||||
{
|
||||
label: '账号设置',
|
||||
key: 'userSettings',
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
},
|
||||
])
|
||||
|
||||
// 用户下拉选项
|
||||
async function handleUserSelect(key: string) {
|
||||
if (key === 'logout') {
|
||||
try {
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
navigateTo('/model-square')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Logout failed:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 发布下拉选项
|
||||
async function handlePublishSelect(key: string) {
|
||||
if (key === 'picture') {
|
||||
debugger
|
||||
}
|
||||
else {
|
||||
router.push({
|
||||
path: `/${key}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header
|
||||
class="sticky top-0 z-50 flex h-12 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80"
|
||||
>
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Logo 区域调整 -->
|
||||
<div class="flex min-w-[130px] items-center gap-3 pr-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink to="/" class="text-xl font-semibold tracking-tight no-underline">
|
||||
<!-- <img src="/vite.png" alt="Logo" class="h-9 w-9" /> -->
|
||||
魔创未来
|
||||
</NuxtLink>
|
||||
<!-- <span class="text-xl font-semibold tracking-tight">魔创未来</span> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <HeaderSearchInput /> -->
|
||||
<HeaderSearchInput
|
||||
v-model="searchText"
|
||||
@search="onSearch"
|
||||
/>
|
||||
<!-- Search -->
|
||||
<!-- <n-input class="w-[580px]" size="large" placeholder="搜索模型/图片/创作者寻找灵感" round /> -->
|
||||
<!-- <NInput
|
||||
round
|
||||
clearable
|
||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||
class="w-[580px]"
|
||||
>
|
||||
<template #prefix>
|
||||
<Search class="h-4 w-4 text-gray-400" />
|
||||
</template>
|
||||
</NInput> -->
|
||||
</div>
|
||||
|
||||
<!-- Right Actions -->
|
||||
<NSpace align="center" :size="24">
|
||||
<!-- PC Client -->
|
||||
<NButton text class="header-btn-primary">
|
||||
<Monitor class="h-4 w-4 mr-1" />
|
||||
<span>PC客户端</span>
|
||||
</NButton>
|
||||
|
||||
<!-- Tutorials -->
|
||||
<NButton text class="header-btn">
|
||||
<GraduationCap class="h-4 w-4 mr-1" />
|
||||
<span>教程专区</span>
|
||||
</NButton>
|
||||
|
||||
<NDropdown
|
||||
:options="publishOptions"
|
||||
trigger="hover"
|
||||
:on-select="handlePublishSelect"
|
||||
>
|
||||
<NButton text class="header-btn">
|
||||
<CirclePlus class="h-4 w-4 mr-1" />
|
||||
<span>发布</span>
|
||||
</NButton>
|
||||
</NDropdown>
|
||||
|
||||
<!-- Notifications -->
|
||||
<NDropdown :options="notificationOptions" trigger="click">
|
||||
<NBadge :value="5" :max="99" processing>
|
||||
<NButton text circle>
|
||||
<Bell class="h-5 w-5 mr-1" />
|
||||
</NButton>
|
||||
</NBadge>
|
||||
</NDropdown>
|
||||
|
||||
<!-- User -->
|
||||
<div class="min-w-10">
|
||||
<client-only>
|
||||
<NDropdown
|
||||
v-if="userStore.token"
|
||||
:options="userOptions"
|
||||
:on-select="handleUserSelect"
|
||||
trigger="hover"
|
||||
>
|
||||
<NAvatar
|
||||
class="cursor-pointer w-10 h-10"
|
||||
round
|
||||
size="small"
|
||||
:src="
|
||||
userStore.userInfo && userStore.userInfo.avatar
|
||||
? userStore.userInfo.avatar
|
||||
: defaultAvatar
|
||||
"
|
||||
/>
|
||||
</NDropdown>
|
||||
<div
|
||||
v-if="!userStore.token"
|
||||
class="flex text-white bg-[#197dff] rounded-[4px] px-4 py-2 text-xs cursor-pointer hover:bg-[#1a6eff]"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录/注册
|
||||
</div>
|
||||
</client-only>
|
||||
</div>
|
||||
</NSpace>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-btn-primary {
|
||||
@apply border border-solid border-[#3162ff] px-1 py-1 text-[#3162ff] rounded-md hover:text-[#3162ff];
|
||||
}
|
||||
.header-btn {
|
||||
@apply bg-[#f1f1f7] font-bold px-2 py-2 rounded flex items-center hover:text-black hover:bg-[#f1f1f7];
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,50 @@
|
|||
<!-- components/SearchInput.vue -->
|
||||
<script setup>
|
||||
import { CloseCircle } from '@vicons/ionicons5'
|
||||
import {
|
||||
Camera,
|
||||
Search,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'search'])
|
||||
|
||||
function handleSearch() {
|
||||
if (props.modelValue) {
|
||||
emit('search', props.modelValue)
|
||||
}
|
||||
}
|
||||
function clearInput() {
|
||||
emit('update:modelValue', '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative flex items-center bg-[#f1f2f7] rounded-md px-2 py-2">
|
||||
<input
|
||||
type="text"
|
||||
:value="modelValue"
|
||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||
class="w-[280px] h-4 bg-[#f1f2f7] rounded-md border-0 outline-none text-gray-800 text-[12px] placeholder:text-gray-400"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<div class="flex items-center w-5 mr-2">
|
||||
<n-icon v-if="modelValue" size="20" class="cursor-pointer" @click="clearInput">
|
||||
<CloseCircle />
|
||||
</n-icon>
|
||||
</div>
|
||||
|
||||
<Camera class="h-5 w-5 text-gray-400 mr-3 cursor-pointer" />
|
||||
<Search class="h-5 w-5 text-gray-400 mr-3 cursor-pointer" @click="handleSearch" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -1,100 +1,101 @@
|
|||
<script setup lang="ts">
|
||||
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";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
const userStore = useUserStore();
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import phoneImg from '@/assets/img/phone.png'
|
||||
import wechatImg from '@/assets/img/wechat.png'
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { message } = createDiscreteApi(['message'])
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
|
||||
})
|
||||
const regex = /^1[3-9]\d{9}$/
|
||||
const rules: FormRules = {
|
||||
phone: [
|
||||
{ required: true, message: "请输入手机号", trigger: "blur" },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: "手机号格式不正确", trigger: "blur" },
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: regex, message: '手机号格式不正确', trigger: 'blur' },
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
||||
{ len: 4, message: "验证码长度为4位", trigger: "blur" },
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{ len: 4, message: '验证码长度为4位', trigger: 'blur' },
|
||||
],
|
||||
};
|
||||
}
|
||||
// 响应式状态
|
||||
const isVisible = ref(false);
|
||||
const isVisible = ref(false)
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
type LoginMode = "wechat" | "phone";
|
||||
const currentLoginMode = ref<LoginMode>("phone");
|
||||
const codeButtonText = ref("获取验证码");
|
||||
const codeButtonDisabled = ref(false);
|
||||
type LoginMode = 'wechat' | 'phone'
|
||||
const currentLoginMode = ref<LoginMode>('phone')
|
||||
const codeButtonText = ref('获取验证码')
|
||||
const codeButtonDisabled = ref(false)
|
||||
|
||||
const timerCode: Ref<ReturnType<typeof setInterval> | null> = ref(null);
|
||||
const timerCode: Ref<ReturnType<typeof setInterval> | null> = ref(null)
|
||||
interface UserData {
|
||||
code:number,
|
||||
phone:number,
|
||||
code: number
|
||||
phone: number
|
||||
}
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
token: string;
|
||||
code: number
|
||||
msg: string
|
||||
token: string
|
||||
}
|
||||
const formData = ref({
|
||||
|
||||
});
|
||||
})
|
||||
interface LoginModeDescription {
|
||||
title: string;
|
||||
des: string;
|
||||
src: string;
|
||||
title: string
|
||||
des: string
|
||||
src: string
|
||||
}
|
||||
|
||||
type LoginModeDescriptions = Record<LoginMode, LoginModeDescription>;
|
||||
type LoginModeDescriptions = Record<LoginMode, LoginModeDescription>
|
||||
// 登录模式配置
|
||||
const loginModeDescriptions: LoginModeDescriptions = {
|
||||
phone: {
|
||||
title: "登录",
|
||||
des: "如未注册,验证后自动登录",
|
||||
title: '登录',
|
||||
des: '如未注册,验证后自动登录',
|
||||
src: wechatImg,
|
||||
},
|
||||
wechat: {
|
||||
title: "微信一键登录",
|
||||
des: "关注后自动登录",
|
||||
title: '微信一键登录',
|
||||
des: '关注后自动登录',
|
||||
src: phoneImg,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
if (isVisible.value) return;
|
||||
isVisible.value = true;
|
||||
if (isVisible.value)
|
||||
return
|
||||
isVisible.value = true
|
||||
}
|
||||
|
||||
function onCloseLogin() {
|
||||
formData.value = {}
|
||||
isVisible.value = false;
|
||||
isVisible.value = false
|
||||
}
|
||||
|
||||
//切换登录方式
|
||||
// 切换登录方式
|
||||
function toggleMode() {
|
||||
return currentLoginMode.value === "phone" ? "wechat" : "phone";
|
||||
return currentLoginMode.value === 'phone' ? 'wechat' : 'phone'
|
||||
}
|
||||
function setCurrentLoginMode() {
|
||||
currentLoginMode.value = toggleMode();
|
||||
currentLoginMode.value = toggleMode()
|
||||
}
|
||||
interface UserToken{
|
||||
token:string
|
||||
interface UserToken {
|
||||
token: string
|
||||
}
|
||||
// 登录
|
||||
async function handleValidateClick(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate(async(errors:any) => {
|
||||
formRef.value?.validate(async (errors: any) => {
|
||||
if (!errors) {
|
||||
const res = await request.post<ApiResponse<UserData>>('/phoneLogin', {
|
||||
...formData.value
|
||||
...formData.value,
|
||||
})
|
||||
userStore.setToken(res.token)
|
||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
token:res.token
|
||||
token: res.token,
|
||||
})
|
||||
userStore.setUserInfo(res1.user)
|
||||
onCloseLogin()
|
||||
|
@ -106,46 +107,52 @@ async function handleValidateClick(e: MouseEvent) {
|
|||
})
|
||||
}
|
||||
|
||||
interface codeInterface{
|
||||
phone:number
|
||||
interface codeInterface {
|
||||
phone: number
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
async function onGetCode() {
|
||||
if (!regex.test(formData.value.phone))
|
||||
return
|
||||
try {
|
||||
const response = await request.get<codeInterface>("/getCode", {
|
||||
const response = await request.get<codeInterface>('/getCode', {
|
||||
params: {
|
||||
phone:formData.value.phone
|
||||
}
|
||||
});
|
||||
phone: formData.value.phone,
|
||||
},
|
||||
})
|
||||
if (response.code === 200) {
|
||||
codeButtonDisabled.value = true;
|
||||
let countdown = 60;
|
||||
codeButtonText.value = `${countdown}秒后重试`;
|
||||
codeButtonDisabled.value = true
|
||||
let countdown = 60
|
||||
codeButtonText.value = `${countdown}秒后重试`
|
||||
|
||||
if (timerCode.value) clearInterval(timerCode.value);
|
||||
if (timerCode.value)
|
||||
clearInterval(timerCode.value)
|
||||
|
||||
timerCode.value = setInterval(() => {
|
||||
countdown--;
|
||||
countdown--
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timerCode.value);
|
||||
codeButtonDisabled.value = false;
|
||||
codeButtonText.value = "获取验证码";
|
||||
} else {
|
||||
codeButtonText.value = `${countdown}秒后重试`;
|
||||
clearInterval(timerCode.value)
|
||||
codeButtonDisabled.value = false
|
||||
codeButtonText.value = '获取验证码'
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
message.error(response.message || "获取验证码失败!");
|
||||
else {
|
||||
codeButtonText.value = `${countdown}秒后重试`
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
else {
|
||||
message.error(response.message || '获取验证码失败!')
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
const onCloseModel = () =>{
|
||||
function onCloseModel() {
|
||||
formData.value = {}
|
||||
if (timerCode.value) {
|
||||
clearInterval(timerCode.value);
|
||||
clearInterval(timerCode.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,19 +160,18 @@ const onCloseModel = () =>{
|
|||
onUnmounted(() => {
|
||||
formData.value = {}
|
||||
if (timerCode.value) {
|
||||
clearInterval(timerCode.value);
|
||||
clearInterval(timerCode.value)
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
:on-after-leave="onCloseModel"
|
||||
v-model:show="isVisible"
|
||||
:on-after-leave="onCloseModel"
|
||||
preset="card"
|
||||
style="width: 400px"
|
||||
:maskClosable="false"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<div>
|
||||
<!-- 关闭按钮 -->
|
||||
|
@ -185,7 +191,7 @@ onUnmounted(() => {
|
|||
<h2 class="text-center text-xl text-gray-800 font-bold">
|
||||
{{ loginModeDescriptions[currentLoginMode].title }}
|
||||
</h2>
|
||||
<p class="text-center text-sm text-gray-500">
|
||||
<p class="text-center text-sm text-gray-500 my-2">
|
||||
{{ loginModeDescriptions[currentLoginMode].des }}
|
||||
</p>
|
||||
|
||||
|
@ -210,7 +216,7 @@ onUnmounted(() => {
|
|||
type="primary"
|
||||
:disabled="codeButtonDisabled"
|
||||
ghost
|
||||
@click="onGetCode"
|
||||
@click="onGetCode($event)"
|
||||
>
|
||||
{{ codeButtonText }}
|
||||
</n-button>
|
||||
|
@ -239,17 +245,19 @@ onUnmounted(() => {
|
|||
|
||||
<!-- 其他登录方式 -->
|
||||
<div class="text-center">
|
||||
<div class="text-sm text-gray-400">其他登录方式</div>
|
||||
<div class="text-sm text-gray-400">
|
||||
其他登录方式
|
||||
</div>
|
||||
<div class="mt-2 flex items-center justify-center gap-6">
|
||||
<div
|
||||
class="h-10 w-10 cursor-pointer rounded-full p-2 transition-all hover:bg-gray-100"
|
||||
class="cursor-pointer rounded-full p-2 transition-all hover:bg-gray-100"
|
||||
@click="setCurrentLoginMode"
|
||||
>
|
||||
<img
|
||||
:src="loginModeDescriptions[currentLoginMode].src"
|
||||
alt=""
|
||||
class="h-10 w-10"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { X, RotateCcw } from "lucide-vue-next";
|
||||
const userStore = useUserStore();
|
||||
|
||||
interface UserInfoType {
|
||||
nickName: string;
|
||||
avatar: string;
|
||||
brief: string;
|
||||
userId: number | string;
|
||||
}
|
||||
const userInfo = {
|
||||
nickName: userStore.userInfo?.nickName ?? "",
|
||||
avatar: userStore.userInfo?.avatar ?? "",
|
||||
brief: userStore.userInfo?.brief ?? "",
|
||||
userId: userStore.userInfo?.userId ?? "",
|
||||
} as UserInfoType;
|
||||
|
||||
const isVisible = ref(false);
|
||||
function onCloseModel() {
|
||||
isVisible.value = false;
|
||||
}
|
||||
|
||||
const qrUrl = ref("你好达内的房东说房东扫烦恼的");
|
||||
|
||||
|
||||
// 定义会员信息的接口
|
||||
interface Member {
|
||||
id: number;
|
||||
title: string;
|
||||
price: number;
|
||||
originalPrice: number;
|
||||
desc: string;
|
||||
}
|
||||
const memberList = ref<Member[]>([])
|
||||
const currentMember = ref<Member>({
|
||||
id: 1,
|
||||
title: "基础会员",
|
||||
price: 39,
|
||||
originalPrice: 50,
|
||||
desc: "次月续费¥39",
|
||||
})
|
||||
// 二维码是否过去
|
||||
const bindTimeout = ref(false)
|
||||
|
||||
defineExpose({
|
||||
isVisible,
|
||||
});
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// initFormData()
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="isVisible"
|
||||
:preset="null"
|
||||
transform-origin="center"
|
||||
class="custom-modal"
|
||||
>
|
||||
<div class="bg-white rounded-xl w-200">
|
||||
<div
|
||||
class="p-4 flex justify-between rounded-t-xl h-14 bg-gradient-to-r from-[#fcf2da] to-[#f9db9f]"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<img :src="userInfo.avatar" class="w-14 h-14 rounded-full mr-4" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[#814600] text-xl">
|
||||
{{ userInfo.nickName }}
|
||||
</div>
|
||||
<div class="text-[#6a6a6a] text-xs mt-1">您还不是会员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="onCloseModel">
|
||||
<component :is="X" class="h-[18px] w-[18px] text-[#61666d] cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-1 mt-4 px-6">
|
||||
<div
|
||||
v-for="item in 4"
|
||||
:key="item"
|
||||
class="select-none h-44 border-1 border-solid border-[#e9e9e9] rounded-lg bg-[#f9f9f9] flex justify-between items-center flex-col p-4 box-border cursor-pointer"
|
||||
>
|
||||
<!-- bg-gradient-to-b from-[#fdf0dd] to-[#fef8ef] -->
|
||||
<!-- 1px solid #f7b252; -->
|
||||
<div class="text-[#814600] card-item">基本版本Vip连续包月</div>
|
||||
<div class="card-item">
|
||||
<span class="text-[#814600] text-[20px] mr-2">¥</span>
|
||||
<span class="text-[#814600] text-[36px] mr-2">39</span>
|
||||
<span class="text-gray-400 text-[20px] line-through">¥50</span>
|
||||
</div>
|
||||
<div class="card-item text-[#814600] w-7/10 text-center text-[12px]">
|
||||
次月续费¥39
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 px-6">
|
||||
<div class="flex border-collapse ">
|
||||
<div class="member-item bg-[#fef9f8]">基础版VIP</div>
|
||||
<div class="member-item">每月15000点算力</div>
|
||||
<div class="member-item">20G存储空间</div>
|
||||
<div class="member-item">每月800次生图</div>
|
||||
<div class="member-item">2个生图任务并行</div>
|
||||
<div class="member-item">--</div>
|
||||
</div>
|
||||
<div class="flex border-collapse ">
|
||||
<div class="member-item bg-[#fef9f8]">专业版</div>
|
||||
<div class="member-item">每月35000点算力</div>
|
||||
<div class="member-item">50G存储空间</div>
|
||||
<div class="member-item">每月5000次生图</div>
|
||||
<div class="member-item">3个生图任务并行</div>
|
||||
<div class="member-item">可训练XL模型</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 mt-1 text-[#9d8c75] text-[12px]">
|
||||
会员每月算力和加速特权按下月下发, 有效期31天, 到期重置
|
||||
</div>
|
||||
<div class="flex justify-center items-center my-4">
|
||||
<div class="w-30 h-30 flex justify-center items-center relative">
|
||||
<n-qr-code :value="qrUrl" :size="80" class="p-0" />
|
||||
<div v-if="bindTimeout" class="absolute top-0 left-0 w-full h-full flex cursor-pointer flex-col justify-center items-center bg-opacity-50 bg-black text-white">
|
||||
请点击刷新<rotate-ccw/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center ml-2 flex-col pb-[14px]">
|
||||
<div class="flex items-baseline">
|
||||
<div class="text-[#222] font-medium mr-1">支付</div>
|
||||
<div class="text-[#814600] mr-1">
|
||||
<span class="text-[16px] mr-1">¥</span>
|
||||
<span class="text-[40px]">39</span>
|
||||
</div>
|
||||
<div class="px-2 bg-gradient-to-r from-[#ffa700] to-[#ff006b] text-[#814600] text-white rounded-[4px] ml-1 text-[12px">已减¥11</div>
|
||||
</div>
|
||||
<div class="flex items-center mb-1">
|
||||
<img src="@/assets/img/alipay.png" class="mr-1"> 请扫码完成支付
|
||||
</div>
|
||||
<div class="text-[12px]">
|
||||
<span class="text-[#999]">
|
||||
开通即代表同意
|
||||
</span>
|
||||
<span class="text-[#814600]">
|
||||
《魔创未来会员协议》
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#fefaee] text-center text-[#AF7F1A] text-[12px] py-1 rounded-b-lg">需支付差价,计算规则为:专业版年包价格-剩余未下发基础版年包月数*基础版年包购买价格/12</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.member-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 16.66%;
|
||||
height: 40px;
|
||||
border: 1px solid #f0f0f0;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,278 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
CircleAlert,
|
||||
Download,
|
||||
EllipsisVertical,
|
||||
ImagePlay,
|
||||
Play,
|
||||
} from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
currentType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentState: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emit
|
||||
const emit = defineEmits(['topedRefresh'])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 跳转详情
|
||||
function toDetails() {
|
||||
if (props.currentType === '0') {
|
||||
router.push(`/model-details/${props.item.id}`)
|
||||
}
|
||||
else if (props.currentType === '1') {
|
||||
// console.log('object', 111);
|
||||
router.push(`/workflow-details/${props.item.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理下拉菜单选项 编辑/删除/置顶
|
||||
function handleSelect(event: Event, key: string) {
|
||||
event.stopPropagation() // 阻止事件冒泡
|
||||
if (key === 'top') {
|
||||
handleTop()
|
||||
}
|
||||
else if (key === 'delete') {
|
||||
handleDelete()
|
||||
}
|
||||
}
|
||||
|
||||
// 置顶
|
||||
interface TopUrlType {
|
||||
[key: string]: string
|
||||
}
|
||||
const topUrl = ref<TopUrlType>({
|
||||
0: 'model',
|
||||
1: 'WorkFlow',
|
||||
2: 'image',
|
||||
})
|
||||
async function handleTop() {
|
||||
try {
|
||||
const res = await request.get(
|
||||
`/${topUrl.value[props.currentType]}/${props.item.id}/top?isTop=${!props.item
|
||||
.isTop}`,
|
||||
)
|
||||
if (res.code === 200) {
|
||||
// 刷新列表
|
||||
emit('topedRefresh')
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
// 删除
|
||||
async function handleDelete() {
|
||||
if (props.currentType === '0') {
|
||||
}
|
||||
else if (props.currentType === '1') {
|
||||
try {
|
||||
const res = await request.get(`/WorkFlow/deleteWorkFlow?id=${props.item.id}`)
|
||||
if (res.code === 200) {
|
||||
// 刷新列表
|
||||
emit('topedRefresh')
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const res = await request.get(`/image/delete/${props.item.id}`)
|
||||
if (res.code === 200) {
|
||||
// 刷新列表
|
||||
emit('topedRefresh')
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="currentState === 'like' && currentType === '2'">
|
||||
<div
|
||||
class="h-80 rounded-2xl overflow-hidden cursor-pointer relative"
|
||||
@click="toDetails"
|
||||
>
|
||||
<img class="w-full h-full object-cover block" :src="item.userAvatar" alt="">
|
||||
<div
|
||||
class="absolute w-full h-full top-0 left-0 flex justify-between items-center px-4 py-4 box-border bg-[#000] bg-opacity-40"
|
||||
>
|
||||
<div>
|
||||
<img class="w-[40px] h-[40px] rounded-full" :src="item.avatar" alt="">
|
||||
<span>元影AIATIDD</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-center rounded-lg bg-[#fceceb] p-2">
|
||||
<img src="@/assets/img/heart.png" class="w-[20px] h-[20px] mr-1" alt="">
|
||||
<span class="font-bold text-[12px] text-[#ff5e5e]">123</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
class="h-80 rounded-2xl overflow-hidden cursor-pointer relative"
|
||||
@click="toDetails"
|
||||
>
|
||||
<img class="w-full h-full object-cover block" :src="item.userAvatar" alt="">
|
||||
<div
|
||||
v-if="currentState === 'mallProduct' && item.isTop === 1"
|
||||
class="text-[#58c08e] border-[#58c08e] border-solid border-[1px] bg-white rounded-lg px-1 w-10 text-[12px] ml-2 text-center absolute top-4 right-8"
|
||||
>
|
||||
置顶
|
||||
</div>
|
||||
<!-- 在发布中 auditStatus等于代表没有审批通过 -->
|
||||
<div
|
||||
v-if="currentState === 'mallProduct' && item.auditStatus === 4"
|
||||
class="absolute top-0 left-0 w-full h-full text-gray-400 bg-black/50 flex justify-center items-center flex-col"
|
||||
>
|
||||
<component
|
||||
:is="CircleAlert"
|
||||
class="h-[40px] w-[40px] text-white menu-icon m-1 text-gray-400"
|
||||
/>
|
||||
未通过
|
||||
</div>
|
||||
<div
|
||||
class="modelSelectByUserIdModel w-full h-full top-0 left-0 flex px-4 py-4 box-border"
|
||||
>
|
||||
<div
|
||||
v-if="currentState === 'mallProduct' && currentType === '0' && item.modelName"
|
||||
class="text-white text-[12px] px-3 bg-[#000] bg-opacity-40 rounded-lg h-[20px]"
|
||||
>
|
||||
<span>
|
||||
{{ item.modelName }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentState === 'mallProduct' && currentType === '1'"
|
||||
class="text-white text-[12px] px-3 bg-[#000] bg-opacity-40 rounded-lg h-[20px]"
|
||||
>
|
||||
<span> 工作流 </span>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentState === 'mallProduct'"
|
||||
class="modelSelectByUserIdModel-mask h-1/3 absolute top-0 left-0 bg-gradient-to-b from-black/100 to-transparent px-4 py-4 text-white box-border flex justify-end"
|
||||
>
|
||||
<div class="menu-content">
|
||||
<component
|
||||
:is="EllipsisVertical"
|
||||
class="h-[18px] w-[18px] text-white menu-icon"
|
||||
/>
|
||||
<div
|
||||
class="menu-group text-[#000000] text-[12px] bg-white rounded-lg text-center w-20 mt-2 hidden"
|
||||
>
|
||||
<div class="menu-item" @click="(event) => handleSelect(event, 'edit')">
|
||||
编辑
|
||||
</div>
|
||||
<div
|
||||
class="menu-item text-red"
|
||||
@click="(event) => handleSelect(event, 'delete')"
|
||||
>
|
||||
删除
|
||||
</div>
|
||||
<div class="menu-item" @click="(event) => handleSelect(event, 'top')">
|
||||
{{ item.isTop === 1 ? "取消置顶" : "置顶" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentType !== '2'"
|
||||
class="absolute bottom-0 left-0 px-4 py-2 text-white box-border flex justify-between items-center"
|
||||
>
|
||||
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||
<span v-if="currentType === '0'">
|
||||
{{ item.reals }}
|
||||
</span>
|
||||
<span v-if="currentType === '1'">
|
||||
{{ item.useNumber }}
|
||||
</span>
|
||||
<component
|
||||
:is="Download"
|
||||
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||
/>
|
||||
<span v-if="currentType === '0'">
|
||||
{{ item.numbers }}
|
||||
</span>
|
||||
<span v-if="currentType === '1'">
|
||||
{{ item.downloadNumber }}
|
||||
</span>
|
||||
<!-- <component
|
||||
:is="ImagePlay"
|
||||
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||
/>0 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentType !== '2'" class="mt-2 text-[12px] text-[#67787e]">
|
||||
<div class="text-[#000] mb-1">
|
||||
版本名称1 版本名称12
|
||||
<!-- <span>{{ item.userName }}</span> -->
|
||||
</div>
|
||||
<div class="flex">
|
||||
<img
|
||||
class="block w-[20px] h-[20px] rounded-full mr-2"
|
||||
:src="item.userAvatar"
|
||||
alt=""
|
||||
>
|
||||
<span>{{ item.userName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.modelSelectByUserIdModel {
|
||||
position: absolute;
|
||||
&:hover {
|
||||
.modelSelectByUserIdModel-mask {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.modelSelectByUserIdModel-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
display: none;
|
||||
.menu-content {
|
||||
width: 20px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
.menu-content:hover {
|
||||
.menu-group {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.menu-group {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
.menu-item {
|
||||
padding: 4px 0 4px 0;
|
||||
margin: 4px;
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
background-color: #eeeded;
|
||||
// border: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,206 @@
|
|||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Scroll List',
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [1, 2, 3, 4, 5, 6, 7],
|
||||
},
|
||||
initialIndex: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change', 'item-click'])
|
||||
|
||||
const containerRef = ref(null)
|
||||
const listRef = ref(null)
|
||||
const itemRefs = ref([])
|
||||
const currentIndex = ref(props.initialIndex)
|
||||
|
||||
// 居中滚动函数
|
||||
function scrollToCenter(index) {
|
||||
const container = containerRef.value
|
||||
const item = itemRefs.value[index]
|
||||
|
||||
if (!container || !item)
|
||||
return
|
||||
|
||||
const containerWidth = container.offsetWidth
|
||||
const itemWidth = item.offsetWidth
|
||||
const itemLeft = item.offsetLeft
|
||||
const scrollLeft = itemLeft - (containerWidth / 2) + (itemWidth / 2)
|
||||
|
||||
listRef.value.scrollTo({
|
||||
left: scrollLeft,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
function handleItemClick(index) {
|
||||
currentIndex.value = index
|
||||
scrollToCenter(index)
|
||||
emit('item-click', {
|
||||
index,
|
||||
item: props.items[index],
|
||||
})
|
||||
emit('change', index)
|
||||
}
|
||||
|
||||
function jumpUp() {
|
||||
if (currentIndex.value > 0) {
|
||||
currentIndex.value--
|
||||
scrollToCenter(currentIndex.value)
|
||||
emit('change', currentIndex.value)
|
||||
}
|
||||
}
|
||||
|
||||
function jumpDown() {
|
||||
if (currentIndex.value < props.items.length - 1) {
|
||||
currentIndex.value++
|
||||
scrollToCenter(currentIndex.value)
|
||||
emit('change', currentIndex.value)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
jumpUp,
|
||||
jumpDown,
|
||||
currentIndex,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="scroll-list-container">
|
||||
<h2>{{ title }}</h2>
|
||||
<div ref="containerRef" class="wrap">
|
||||
<div ref="listRef" class="list">
|
||||
<div
|
||||
v-for="(item, index) in items"
|
||||
:id="`item${index + 1}`"
|
||||
:key="index" ref="itemRefs"
|
||||
class="item"
|
||||
:class="[{ active: currentIndex === index }]"
|
||||
@click="handleItemClick(index)"
|
||||
>
|
||||
<slot name="item" :item="item" :index="index">
|
||||
{{ item }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<button @click="jumpUp">
|
||||
上一个
|
||||
</button>
|
||||
<button @click="jumpDown">
|
||||
下一个
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scroll-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
width: 400px;
|
||||
outline: 4px solid #666;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
scroll-behavior: smooth;
|
||||
/* 隐藏滚动条但保持功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
/* 为 Webkit 浏览器隐藏滚动条 */
|
||||
.list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: royalblue;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #f44336;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 5px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
/* 添加两侧渐变遮罩效果 */
|
||||
.wrap::before,
|
||||
.wrap::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 30px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* .wrap::before {
|
||||
left: 0;
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 0.9), transparent);
|
||||
}
|
||||
|
||||
.wrap::after {
|
||||
right: 0;
|
||||
background: linear-gradient(to left, rgba(255, 255, 255, 0.9), transparent);
|
||||
} */
|
||||
</style>
|
|
@ -3,20 +3,20 @@
|
|||
// 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
|
||||
}>()
|
||||
|
||||
interface UserToken{
|
||||
token:string
|
||||
interface UserToken {
|
||||
token: string
|
||||
}
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
token: string;
|
||||
code: number
|
||||
msg: string
|
||||
token: string
|
||||
}
|
||||
const qrUrl = ref<string>('')
|
||||
const qrSize = ref(174)
|
||||
|
@ -33,47 +33,46 @@ async function onGetUUid() {
|
|||
if (res.code === 200) {
|
||||
const appid = 'wx82d4c3c96f0ffa5b'
|
||||
const { uuid } = res
|
||||
const redirect_uri = `http://rtec8z.natappfree.cc/wx/uuid/bind/openid?uuid=${uuid}`
|
||||
const codeUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${encodeURIComponent(
|
||||
redirect_uri,
|
||||
)}&response_type=code&scope=snsapi_userinfo&state=123456#wechat_redirect`
|
||||
qrUrl.value = codeUrl
|
||||
let counter = 1
|
||||
pollingTimer && clearTimeout(pollingTimer)
|
||||
pollingTimer = setInterval(async() => {
|
||||
await request.get('/wx/uuid/login',{uuid}).then(async(res)=>{
|
||||
counter++
|
||||
if (counter === 59) {
|
||||
clearTimeout(pollingTimer)
|
||||
bindTimeout.value = true
|
||||
}
|
||||
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)
|
||||
window.location.href = '/'
|
||||
// const parent = getCurrentInstance().parent
|
||||
// parent.exposed.onCloseLogin()
|
||||
// store.userInfo = res.data
|
||||
// router.push('./home')
|
||||
|
||||
// store.dispatch("uuidLogin", res)
|
||||
// that.$store.dispatch("uuidLogin", res)
|
||||
// setTimeout(() => {
|
||||
// that.$router.push({
|
||||
// path: that.redirect || "/"
|
||||
// }).catch(() => {});
|
||||
// }, 1500)
|
||||
}
|
||||
}) .catch((err: any) => {
|
||||
console.log(err)
|
||||
clearTimeout(pollingTimer)
|
||||
const redirect_uri = `http://rtec8z.natappfree.cc/wx/uuid/bind/openid?uuid=${uuid}`
|
||||
const codeUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${encodeURIComponent(
|
||||
redirect_uri,
|
||||
)}&response_type=code&scope=snsapi_userinfo&state=123456#wechat_redirect`
|
||||
qrUrl.value = codeUrl
|
||||
let counter = 1
|
||||
pollingTimer && clearTimeout(pollingTimer)
|
||||
pollingTimer = setInterval(async () => {
|
||||
await request.get('/wx/uuid/login', { uuid }).then(async (res) => {
|
||||
counter++
|
||||
if (counter === 59) {
|
||||
clearTimeout(pollingTimer)
|
||||
bindTimeout.value = true
|
||||
}
|
||||
if (res.status === 1) {
|
||||
clearTimeout(pollingTimer)
|
||||
const userStore = useUserStore()
|
||||
userStore.setToken(res.token)
|
||||
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
|
||||
token: res.token,
|
||||
})
|
||||
}, 1000)
|
||||
userStore.setUserInfo(res1.user)
|
||||
window.location.href = '/'
|
||||
// const parent = getCurrentInstance().parent
|
||||
// parent.exposed.onCloseLogin()
|
||||
// store.userInfo = res.data
|
||||
// router.push('./home')
|
||||
|
||||
// store.dispatch("uuidLogin", res)
|
||||
// that.$store.dispatch("uuidLogin", res)
|
||||
// setTimeout(() => {
|
||||
// that.$router.push({
|
||||
// path: that.redirect || "/"
|
||||
// }).catch(() => {});
|
||||
// }, 1500)
|
||||
}
|
||||
}).catch(() => {
|
||||
clearTimeout(pollingTimer)
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
@ -100,14 +99,14 @@ onBeforeUnmount(() => {
|
|||
<div class="w-[280px] px-5 text-center relative">
|
||||
<div v-if="bindTimeout" class="absolute left-[41px] h-full w-[230px] top-0 z-[9999] flex items-center justify-center flex-col cursor-pointer text-white bg-black/40" @click="onGetUUid">
|
||||
刷新二维码
|
||||
<rotate-ccw/>
|
||||
<RotateCcw />
|
||||
</div>
|
||||
<div v-if="qrUrl" class="relative w-full">
|
||||
<component
|
||||
is="RotateCcw"
|
||||
class="h-[18px] w-[18px] color-white"
|
||||
/>
|
||||
<n-qr-code :value="qrUrl" :size="qrSize" />
|
||||
<!-- <component
|
||||
is="RotateCcw"
|
||||
class="h-[18px] w-[18px] color-white"
|
||||
/> -->
|
||||
<n-qr-code style="padding: 0;" class="p-0" :value="qrUrl" :size="qrSize" />
|
||||
</div>
|
||||
<div v-else class="p-5 text-gray-400">
|
||||
加载中...
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
export const appName = '魔创未来'
|
||||
export const appDescription = '魔创未来'
|
||||
export const authRoutes = ['/personal-center', '/publish-model', '/publish-workflow']
|
||||
export const verifyBlankRoute = ['/member-center']
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
Bell, Binary, Code2, Crown, GraduationCap, Image, LayoutGrid,
|
||||
Lightbulb, Maximize, Monitor, Plus, Search, User, Workflow
|
||||
} from 'lucide-vue-next';
|
||||
import { ref, onMounted } from 'vue'
|
||||
const isClient = ref(false)
|
||||
const modalStore = useModalStore()
|
||||
// import ../assets/img/default-avatar.png
|
||||
import defaultAvatar from '../assets/img/default-avatar.png'
|
||||
Binary,
|
||||
Code2,
|
||||
Crown,
|
||||
Image,
|
||||
LayoutGrid,
|
||||
Lightbulb,
|
||||
Maximize,
|
||||
User,
|
||||
Workflow,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
// 路径到图标的映射
|
||||
const iconMap: any = {
|
||||
'/model-square': LayoutGrid,
|
||||
|
@ -20,207 +25,89 @@ const iconMap: any = {
|
|||
'/api-platform': Code2,
|
||||
'/creator-center': Code2,
|
||||
'/personal-center': User,
|
||||
'/member-center': Crown
|
||||
'/member-center': Crown,
|
||||
}
|
||||
|
||||
|
||||
import { useMenuStore } from '~/stores/menu';
|
||||
const route = useRoute()
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
import { useUserStore } from "@/stores/user";
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
menuStore.setActiveMenu(path)
|
||||
},
|
||||
{ immediate: true } // 这样一进入页面就会执行一次
|
||||
{ immediate: true }, // 这样一进入页面就会执行一次
|
||||
)
|
||||
onMounted(() => {
|
||||
isClient.value = true
|
||||
})
|
||||
|
||||
// 更新菜单项中的图标
|
||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
||||
...item,
|
||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
||||
LucideIcon: iconMap[item.path], // 添加 Lucide 图标组件
|
||||
}))
|
||||
|
||||
// 消息通知下拉选项
|
||||
const notificationOptions = [
|
||||
{
|
||||
label: '系统通知',
|
||||
key: 'system'
|
||||
},
|
||||
{
|
||||
label: '互动消息',
|
||||
key: 'interaction'
|
||||
function handleSide(event: Event, path: string) {
|
||||
if (path === '/member-center') {
|
||||
event.preventDefault() // 阻止默认行为
|
||||
event.stopPropagation() // 阻止事件冒泡
|
||||
const baseUrl = window.location.origin
|
||||
window.open(`${baseUrl}/member-center`, '_blank', 'noopener,noreferrer')
|
||||
// 确保当前路由不变
|
||||
// nextTick(() => {
|
||||
// debugger
|
||||
// if (route.path !== window.location.pathname) {
|
||||
// navigateTo(route.path, { replace: true })
|
||||
// }
|
||||
// })
|
||||
}
|
||||
]
|
||||
|
||||
// 用户下拉选项
|
||||
const userOptions = ref([
|
||||
{
|
||||
label: '我的模型',
|
||||
key: 'model'
|
||||
},
|
||||
{
|
||||
label: '我的作品',
|
||||
key: 'project'
|
||||
},
|
||||
{
|
||||
label: '我的点赞',
|
||||
key: 'like'
|
||||
},
|
||||
{
|
||||
label: '账号设置',
|
||||
key: 'userSettings'
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout'
|
||||
else {
|
||||
menuStore.setActiveMenu(path)
|
||||
}
|
||||
])
|
||||
|
||||
const handleUserSelect = async(key: string) => {
|
||||
if (key === 'logout') {
|
||||
try {
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
window.location.href = '/'
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
modalStore.showLoginModal()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
||||
<!-- Header -->
|
||||
<header class="sticky top-0 z-50 flex h-14 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80">
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Logo 区域调整 -->
|
||||
<div class="flex min-w-[220px] items-center gap-3 pr-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- <img src="/vite.png" alt="Logo" class="h-9 w-9" /> -->
|
||||
<span class="text-xl font-semibold tracking-tight">魔创未来</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<NInput
|
||||
round
|
||||
clearable
|
||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||
class="w-[480px]"
|
||||
>
|
||||
<template #prefix>
|
||||
<Search class="h-4 w-4 text-gray-400" />
|
||||
</template>
|
||||
</NInput>
|
||||
</div>
|
||||
|
||||
<!-- Right Actions -->
|
||||
<NSpace align="center" :size="24">
|
||||
<!-- PC Client -->
|
||||
<NButton text class="flex items-center gap-1.5">
|
||||
<Monitor class="h-4 w-4" />
|
||||
<span>PC客户端</span>
|
||||
</NButton>
|
||||
|
||||
<!-- Tutorials -->
|
||||
<NButton text class="flex items-center gap-1.5">
|
||||
<GraduationCap class="h-4 w-4" />
|
||||
<span>教程专区</span>
|
||||
</NButton>
|
||||
|
||||
<!-- VIP -->
|
||||
<NuxtLink to="/member-center" target="_blank" class="inline-block">
|
||||
<NButton text type="warning" class="flex items-center gap-1.5">
|
||||
<Crown class="h-4 w-4" />
|
||||
<span>会员中心</span>
|
||||
</NButton>
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Create -->
|
||||
<NButton type="primary" round class="flex items-center gap-1.5">
|
||||
<Plus class="h-4 w-4" />
|
||||
<span>发布</span>
|
||||
</NButton>
|
||||
|
||||
<!-- Notifications -->
|
||||
<NDropdown :options="notificationOptions" trigger="click">
|
||||
<NBadge :value="5" :max="99" processing>
|
||||
<NButton text circle>
|
||||
<Bell class="h-5 w-5" />
|
||||
</NButton>
|
||||
</NBadge>
|
||||
</NDropdown>
|
||||
|
||||
<!-- User -->
|
||||
<NDropdown v-if="isClient && userStore.token" :options="userOptions" :on-select="handleUserSelect" trigger="hover" >
|
||||
<NAvatar
|
||||
class="cursor-pointer w-10 h-10"
|
||||
round
|
||||
size="small"
|
||||
:src="userStore.userInfo && userStore.userInfo.avatar ? userStore.userInfo.avatar : defaultAvatar"
|
||||
/>
|
||||
</NDropdown>
|
||||
<div v-else>
|
||||
<div @click="handleLogin" class="text-white bg-[#197dff] rounded-[4px] px-4 py-2 text-xs cursor-pointer hover:bg-[#1a6eff]">登录/注册</div>
|
||||
</div>
|
||||
</NSpace>
|
||||
</header>
|
||||
<Header />
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<nav class="w-[240px] border-r border-gray-100 bg-gray-50/50 py-2 dark:border-dark-700 dark:bg-dark-800/50">
|
||||
<div class="space-y-1 px-2">
|
||||
<NuxtLink
|
||||
v-for="item in menuStore.menuItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="flex items-center gap-3 rounded-lg px-4 py-2.5 text-[15px] font-medium no-underline transition-colors"
|
||||
:class="[
|
||||
menuStore.activeMenu === item.path
|
||||
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'
|
||||
: 'text-gray-600 hover:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-dark-700/50'
|
||||
]"
|
||||
@click="menuStore.setActiveMenu(item.path)"
|
||||
>
|
||||
<!-- 只显示 Lucide 图标 -->
|
||||
<component
|
||||
:is="item.LucideIcon"
|
||||
class="h-[18px] w-[18px]"
|
||||
:class="menuStore.activeMenu === item.path
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500 dark:text-gray-400'"
|
||||
/>
|
||||
<!-- Sidebar -->
|
||||
<nav class="w-[240px] border-r border-gray-100 bg-gray-50/50 py-2 dark:border-dark-700 dark:bg-dark-800/50">
|
||||
<div class="space-y-1 px-2">
|
||||
<NuxtLink
|
||||
v-for="item in menuStore.menuItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="flex items-center gap-3 rounded-lg px-4 py-2.5 text-[15px] font-medium no-underline transition-colors"
|
||||
:class="[
|
||||
menuStore.activeMenu === item.path
|
||||
? 'bg-blue-500/8 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'
|
||||
: 'text-gray-600 hover:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-dark-700/50',
|
||||
]"
|
||||
@click="(event: Event) => handleSide(event, item.path)"
|
||||
>
|
||||
<!-- 只显示 Lucide 图标 -->
|
||||
<component
|
||||
:is="item.LucideIcon"
|
||||
class="h-[14px] w-[14px]"
|
||||
:class="menuStore.activeMenu === item.path
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500 dark:text-gray-400'"
|
||||
/>
|
||||
|
||||
<span>{{ item.label }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</nav>
|
||||
<span>{{ item.label }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<!-- Page Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<!-- 登录框组件 -->
|
||||
|
||||
<!-- 登录框组件 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,232 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
Bell, Binary, Code2, Crown, GraduationCap, Image, LayoutGrid,
|
||||
Lightbulb, Maximize, Monitor, Plus, Search, User, Workflow
|
||||
} from 'lucide-vue-next';
|
||||
import {
|
||||
NAvatar,
|
||||
NBadge, NButton, NDropdown, NInput, NSpace
|
||||
} from 'naive-ui';
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
const userStore = useUserStore()
|
||||
userStore.checkLoginStatus()
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 路径到图标的映射
|
||||
const iconMap: any = {
|
||||
'/model-square': LayoutGrid,
|
||||
'/works-inspire': Lightbulb,
|
||||
'/workspace': Lightbulb,
|
||||
'/web-ui': Image,
|
||||
'/comfy-ui': Workflow,
|
||||
'/training-lora': Binary,
|
||||
'/high-availability': Maximize,
|
||||
'/api-platform': Code2,
|
||||
'/creator-center': Code2,
|
||||
'/personal-center': User,
|
||||
'/member-center': Crown
|
||||
}
|
||||
|
||||
|
||||
import { useMenuStore } from '~/stores/menu';
|
||||
const route = useRoute()
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
menuStore.setActiveMenu(path)
|
||||
},
|
||||
{ immediate: true } // 这样一进入页面就会执行一次
|
||||
)
|
||||
|
||||
|
||||
|
||||
// 更新菜单项中的图标
|
||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
||||
...item,
|
||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
||||
}))
|
||||
|
||||
// 消息通知下拉选项
|
||||
const notificationOptions = [
|
||||
{
|
||||
label: '系统通知',
|
||||
key: 'system'
|
||||
},
|
||||
{
|
||||
label: '互动消息',
|
||||
key: 'interaction'
|
||||
}
|
||||
]
|
||||
const publisOptions = [
|
||||
{
|
||||
label: '模型',
|
||||
key: 'model'
|
||||
},
|
||||
{
|
||||
label: '图片',
|
||||
key: 'picture'
|
||||
},
|
||||
{
|
||||
label:'工作流',
|
||||
key:'workFlow'
|
||||
}
|
||||
]
|
||||
// 用户下拉选项
|
||||
const userOptions = [
|
||||
{
|
||||
label: '个人中心',
|
||||
key: 'profile'
|
||||
},
|
||||
{
|
||||
label: '创作中心',
|
||||
key: 'creator'
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
||||
<!-- Header -->
|
||||
<header class="sticky top-0 z-50 flex h-14 items-center justify-between border-b border-gray-100 bg-white/80 px-6 backdrop-blur dark:border-dark-700 dark:bg-dark-800/80">
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Logo 区域调整 -->
|
||||
<div class="flex min-w-[220px] items-center gap-3 pr-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<img src="/vite.png" alt="Logo" class="h-9 w-9" />
|
||||
<span class="text-xl font-semibold tracking-tight">LibLib AI</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<NInput
|
||||
round
|
||||
clearable
|
||||
placeholder="搜索模型/图片/创作者寻找灵感"
|
||||
class="w-[480px]"
|
||||
>
|
||||
<template #prefix>
|
||||
<Search class="h-4 w-4 text-gray-400" />
|
||||
</template>
|
||||
</NInput>
|
||||
</div>
|
||||
|
||||
<!-- Right Actions -->
|
||||
<NSpace align="center" :size="24">
|
||||
<!-- PC Client -->
|
||||
<NButton text class="flex items-center gap-1.5">
|
||||
<Monitor class="h-4 w-4" />
|
||||
<span>PC客户端</span>
|
||||
</NButton>
|
||||
|
||||
<!-- Tutorials -->
|
||||
<NButton text class="flex items-center gap-1.5">
|
||||
<GraduationCap class="h-4 w-4" />
|
||||
<span>教程专区</span>
|
||||
</NButton>
|
||||
|
||||
<!-- VIP -->
|
||||
<NuxtLink to="/member-center" target="_blank" class="inline-block">
|
||||
<NButton text type="warning" class="flex items-center gap-1.5">
|
||||
<Crown class="h-4 w-4" />
|
||||
<span>会员中心</span>
|
||||
</NButton>
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Create -->
|
||||
<NDropdown :options="publisOptions" trigger="click">
|
||||
<NButton class="flex items-center gap-1.5">
|
||||
<Plus class="h-4 w-4" />
|
||||
<span>发布</span>
|
||||
</NButton>
|
||||
</NDropdown>
|
||||
<!-- Notifications -->
|
||||
<NDropdown :options="notificationOptions" trigger="click">
|
||||
<NBadge :value="5" :max="99" processing>
|
||||
<NButton text circle>
|
||||
<Bell class="h-5 w-5" />
|
||||
</NButton>
|
||||
</NBadge>
|
||||
</NDropdown>
|
||||
|
||||
<!-- User -->
|
||||
<NDropdown :options="userOptions" trigger="click">
|
||||
<NAvatar
|
||||
round
|
||||
size="small"
|
||||
src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
|
||||
/>
|
||||
</NDropdown>
|
||||
</NSpace>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<div>
|
||||
<Header />
|
||||
<main class="flex-1 overflow-auto">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<!-- 登录框组件 -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: rgb(59, 130, 246);
|
||||
--primary-hover: rgb(37, 99, 235);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #e5e7eb;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #d1d5db;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #4b5563;
|
||||
}
|
||||
</style>
|
|
@ -1,137 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
NInput,
|
||||
NButton,
|
||||
NSpace,
|
||||
NDropdown,
|
||||
NAvatar,
|
||||
NBadge
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
Search,
|
||||
Monitor,
|
||||
GraduationCap,
|
||||
Crown,
|
||||
Plus,
|
||||
Bell,
|
||||
LayoutGrid,
|
||||
Lightbulb,
|
||||
Image,
|
||||
Workflow,
|
||||
Binary,
|
||||
Maximize,
|
||||
Code2,
|
||||
PenTool,
|
||||
User,
|
||||
CreditCard
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
// 路径到图标的映射
|
||||
const iconMap: any = {
|
||||
'/model-square': LayoutGrid,
|
||||
'/works-inspire': Lightbulb,
|
||||
'/workspace': Lightbulb,
|
||||
'/web-ui': Image,
|
||||
'/comfy-ui': Workflow,
|
||||
'/training-lora': Binary,
|
||||
'/high-availability': Maximize,
|
||||
'/api-platform': Code2,
|
||||
'/creator-center': Code2,
|
||||
'/personal-center': User,
|
||||
'/member-center': Crown
|
||||
}
|
||||
|
||||
|
||||
import { useMenuStore } from '~/stores/menu'
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
// 更新菜单项中的图标
|
||||
menuStore.menuItems = menuStore.menuItems.map(item => ({
|
||||
...item,
|
||||
LucideIcon: iconMap[item.path] // 添加 Lucide 图标组件
|
||||
}))
|
||||
|
||||
// 消息通知下拉选项
|
||||
const notificationOptions = [
|
||||
{
|
||||
label: '系统通知',
|
||||
key: 'system'
|
||||
},
|
||||
{
|
||||
label: '互动消息',
|
||||
key: 'interaction'
|
||||
}
|
||||
]
|
||||
|
||||
// 用户下拉选项
|
||||
const userOptions = [
|
||||
{
|
||||
label: '个人中心',
|
||||
key: 'profile'
|
||||
},
|
||||
{
|
||||
label: '创作中心',
|
||||
key: 'creator'
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-screen flex-col bg-white dark:bg-dark-800">
|
||||
|
||||
demo
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<slot />
|
||||
</main>
|
||||
demo
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: rgb(59, 130, 246);
|
||||
--primary-hover: rgb(37, 99, 235);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #e5e7eb;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #d1d5db;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #4b5563;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,8 @@
|
|||
// middleware/auth.ts
|
||||
import { authRoutes, verifyBlankRoute } from '@/constants/index'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// const router = useRouter()
|
||||
/**
|
||||
* 全局认证中间件
|
||||
* 用于处理需要登录才能访问的路由
|
||||
|
@ -8,21 +11,19 @@
|
|||
* @param to - 目标路由对象
|
||||
* @returns void | NavigationResult - 如果需要重定向则返回导航结果
|
||||
*/
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const userStore = useUserStore()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// 需要登录权限的路由列表
|
||||
const authRoutes = [
|
||||
'/member-center',
|
||||
// 可以继续添加其他需要登录的路由
|
||||
]
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
if (verifyBlankRoute.includes(to.path) && !verifyBlankRoute.includes(from.path)) {
|
||||
return abortNavigation()
|
||||
}
|
||||
// 如果是需要登录的路由,且用户未登录
|
||||
if (authRoutes.includes(to.path) && !userStore.isLoggedIn) {
|
||||
// 显示登录模态框
|
||||
modalStore.showLoginModal()
|
||||
// 重定向到模型广场页面
|
||||
return navigateTo('/model-square')
|
||||
menuStore.setActiveMenu(from.path)
|
||||
// return abortNavigation()
|
||||
// return navigateTo('/model-square')
|
||||
}
|
||||
})
|
|
@ -32,11 +32,11 @@ const online = useOnline()
|
|||
</template>
|
||||
</ClientOnly> -->
|
||||
|
||||
|
||||
<div>
|
||||
<n-button type="info">测试按钮-3-3</n-button>
|
||||
<n-input placeholder="请输入" />
|
||||
<NButton type="info">
|
||||
测试按钮-3-3
|
||||
</NButton>
|
||||
<NInput placeholder="请输入" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,55 +1,349 @@
|
|||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
layout: 'header',
|
||||
})
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 定义数据接口
|
||||
interface UserData {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
status: number
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo as UserInfoType
|
||||
// 显示支付弹框
|
||||
interface Payment {
|
||||
isVisible: boolean
|
||||
}
|
||||
const PaymentRef = ref<Payment | null>(null)
|
||||
function showPayment() {
|
||||
if (PaymentRef.value) {
|
||||
PaymentRef.value.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
// 定义响应接口
|
||||
interface ApiResponse<T> {
|
||||
code: number
|
||||
data: T
|
||||
message: string
|
||||
// 是否是会员
|
||||
const isMember = ref(false)
|
||||
async function getIsMember() {
|
||||
try {
|
||||
const res = await request.get('/member/isMember')
|
||||
if (res.code === 200) {
|
||||
isMember.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
// getIsMember()
|
||||
|
||||
// 状态变量
|
||||
// 获取积分余额和历史记录
|
||||
const points = ref(0)
|
||||
async function getPoints() {
|
||||
try {
|
||||
const res = await request.get('/member/getPoints')
|
||||
if (res.code === 200) {
|
||||
points.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
// getPoints()
|
||||
|
||||
// 获取会员等级及权益列表
|
||||
const MemberBenefitList = ref([])
|
||||
async function getMemberBenefitList() {
|
||||
try {
|
||||
const res = await request.get('/memberLevel/getMemberBenefitList')
|
||||
if (res.code === 200) {
|
||||
MemberBenefitList.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
// getMemberBenefitList()
|
||||
|
||||
// 获取会员等级列表
|
||||
interface memberLevel {
|
||||
memberName: string
|
||||
unitPrice: number
|
||||
originalPrice: number
|
||||
subscriptionPeriod: number
|
||||
}
|
||||
const memberLevelList = ref<memberLevel[]>([])
|
||||
async function getMemberLevelList() {
|
||||
try {
|
||||
const res = await request.get('/memberLevel/list')
|
||||
if (res.code === 200) {
|
||||
memberLevelList.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getMemberLevelList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
83475982345897234957420435365
|
||||
<div>
|
||||
<div class="test bg-[#403833] flex justify-center items-center h-90">
|
||||
<div class="flex gap-6 w-[1145px]">
|
||||
<!-- 左侧用户信息 -->
|
||||
<div class="w-60 bg-[#2b2421] rounded-lg p-6">
|
||||
<!-- 右上角订阅管理 -->
|
||||
<div class="text-right">
|
||||
<button class="text-[#8b8685] text-sm bg-inherit border-none cursor-pointer">
|
||||
订阅管理 >
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 用户头像和信息 -->
|
||||
<div class="flex flex-col items-center mt-2">
|
||||
<client-only>
|
||||
<img :src="userInfo.avatar" alt="头像" class="w-20 h-20 rounded-full mb-3">
|
||||
<span class="text-white text-sm mb-2">{{ userInfo.nickName }}</span>
|
||||
</client-only>
|
||||
<div class="flex items-center text-[#8b8685] text-xs">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 mr-1"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
|
||||
/>
|
||||
</svg>
|
||||
您还不是魔创未来的会员
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 开通会员按钮 -->
|
||||
<button
|
||||
class="w-full bg-[#f2d5bc] text-[#6b4f3f] rounded-full py-2.5 mt-8 font-medium cursor-pointer"
|
||||
@click="showPayment"
|
||||
>
|
||||
开通会员
|
||||
</button>
|
||||
|
||||
<!-- 兑换会员链接 -->
|
||||
<div class="text-center mt-3">
|
||||
<button class="text-[#8b8685] text-sm bg-inherit border-none cursor-pointer">
|
||||
兑换会员
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="flex-1 space-y-4">
|
||||
<!-- 余额信息卡片 -->
|
||||
<div class="bg-[#2b2421] rounded-lg p-6">
|
||||
<div class="grid grid-cols-3 gap-8">
|
||||
<!-- 算力余额 -->
|
||||
<div>
|
||||
<h3 class="text-[#c3986b] mb-4">
|
||||
算力余额
|
||||
</h3>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="text-[#c3986b] mr-2">300点</span>
|
||||
<button class="text-[#c3986b] bg-inherit border-none cursor-pointer">
|
||||
充值 >
|
||||
</button>
|
||||
</div>
|
||||
<button class="text-white bg-inherit border-none cursor-pointer p-0">
|
||||
算力明细 >
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 生图加速余额 -->
|
||||
<div>
|
||||
<h3 class="text-[#c3986b] mb-4">
|
||||
生图加速余额
|
||||
</h3>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="text-[#c3986b] mr-2">0次</span>
|
||||
<button class="text-[#c3986b] bg-inherit border-none cursor-pointer">
|
||||
充值 >
|
||||
</button>
|
||||
</div>
|
||||
<button class="text-white bg-inherit border-none cursor-pointer p-0">
|
||||
加速明细 >
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 训练加速余额 -->
|
||||
<div>
|
||||
<h3 class="text-[#c3986b] mb-4">
|
||||
训练加速余额
|
||||
</h3>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="text-[#c3986b]">0次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 存储空间卡片 -->
|
||||
<div class="bg-[#2b2421] rounded-lg p-6">
|
||||
<div class="flex items-center gap-6 mb-6">
|
||||
<span class="text-[#c3986b] text-lg">我的存储空间</span>
|
||||
<button class="text-[#8b8685] bg-inherit border-none cursor-pointer p-0">
|
||||
管理图库
|
||||
</button>
|
||||
<button class="text-[#8b8685] bg-inherit border-none cursor-pointer p-0">
|
||||
管理训练
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="relative">
|
||||
<div class="h-1 bg-[#3a322e] rounded-full">
|
||||
<div class="absolute right-0 -top-6 text-[#8b8685] text-sm">
|
||||
0G/3G
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图例 -->
|
||||
<div class="flex gap-4 mt-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full mr-2" />
|
||||
<span class="text-[#8b8685] text-sm">生图</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-2 h-2 bg-purple-500 rounded-full mr-2" />
|
||||
<span class="text-[#8b8685] text-sm">训练</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gradient-to-b from-[#fdf6e3] to-[#ffffff] w-full flex justify-center py-8"
|
||||
>
|
||||
<div class="w-[1145px]">
|
||||
<div class="grid grid-cols-5 gap-1">
|
||||
<div
|
||||
v-for="(item, index) in memberLevelList"
|
||||
:key="index"
|
||||
class="h-56 border-1 border-solid border-[#fff] rounded-lg bg-gradient-to-b from-[#fdf0dd] to-[#fef8ef] flex justify-between items-center flex-col p-4 box-border"
|
||||
>
|
||||
<div class="text-[#e08909] card-item">
|
||||
{{ item.memberName }}
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="text-[#814600] text-[20px] mr-2">¥</span>
|
||||
<span class="text-[#814600] text-[40px] mr-2">{{ item.unitPrice }}</span>
|
||||
<span class="text-gray-400 text-[20px] line-through">¥{{ item.originalPrice }}</span>
|
||||
</div>
|
||||
<div class="card-item text-[#e08909] w-7/10 text-center text-[12px]">
|
||||
{{ item.subscriptionPeriod }}
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<button
|
||||
class="bg-gradient-to-r from-[#fbdfa4] to-[#f3c180] text-[#4c3d33] w-40 h-10 rounded-full text-[12px] border-none cursor-pointer"
|
||||
>
|
||||
立即开通
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-8">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="text-xl font-bold">
|
||||
会员权益
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-1/4 h-20 bg-[#f7f0ea] flex justify-center items-center">
|
||||
会员权益
|
||||
</div>
|
||||
<div class="w-1/4 h-20 bg-[#f5f5f5] flex justify-center items-center">
|
||||
用户免费
|
||||
</div>
|
||||
<div class="w-1/4 h-20 bg-[#fdf6ea] flex justify-center items-center">
|
||||
基础版VIP
|
||||
</div>
|
||||
<div class="w-1/4 h-20 bg-[#fce6bf] flex justify-center items-center">
|
||||
专业版VIP
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
算力
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每天300点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每月15000点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每月35000点
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-1/4 h-10 bg-[#f7f0ea] flex justify-center items-center">
|
||||
算力
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#f5f5f5] flex justify-center items-center">
|
||||
每天300点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fdf6ea] flex justify-center items-center">
|
||||
每月15000点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fce6bf] flex justify-center items-center">
|
||||
每月35000点
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
算力
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每天300点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每月15000点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fff] flex justify-center items-center">
|
||||
每月35000点
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-1/4 h-10 bg-[#f7f0ea] flex justify-center items-center">
|
||||
算力
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#f5f5f5] flex justify-center items-center">
|
||||
每天300点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fdf6ea] flex justify-center items-center">
|
||||
每月15000点
|
||||
</div>
|
||||
<div class="w-1/4 h-10 bg-[#fce6bf] flex justify-center items-center">
|
||||
每月35000点
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[#999] text-[12px]">
|
||||
<div class="mt-1">
|
||||
会员每月算力和加速特权按月下发,有效期31天,到期重置。会员模型下载次数上限为每月200次
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
发票或团队/企业定制需求,请点击立即咨询联系我们,企业合作需求也可直接联系chujie@liblib.ai
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
更多问题可见帮助中心
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Payment ref="PaymentRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.w-64 {
|
||||
width: 16rem;
|
||||
.card-item {
|
||||
@apply flex items-center justify-center h-1/4;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
import {
|
||||
Play,
|
||||
Download,
|
||||
} from "lucide-vue-next";
|
||||
// const route = useRoute<'publishDetails-id'>()
|
||||
|
||||
// const id = route.params.id
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-[1125px]">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[26px] font-bold mr-2">名称</div>
|
||||
<component :is="Play" class="h-[14px] w-[14px] text-white menu-icon m-1" />
|
||||
<span>
|
||||
<!-- {{ item.reals }} -->0
|
||||
</span>
|
||||
<component
|
||||
:is="Download"
|
||||
class="h-[14px] w-[14px] text-white menu-icon m-1"
|
||||
/>
|
||||
<span>
|
||||
<!-- {{ item.numbers }} -->0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,12 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
|
||||
const myItems = [1, 2, 3, 4, 5, 6, 7]
|
||||
const scrollListRef = ref(null)
|
||||
|
||||
// 处理选中项改变
|
||||
function handleChange(index) {
|
||||
console.log('Current index:', index)
|
||||
}
|
||||
|
||||
// 处理项目点击
|
||||
function handleItemClick({ index, item }) {
|
||||
console.log('Clicked item:', item, 'at index:', index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<h1 class="text-2xl font-bold">模型广场</h1>
|
||||
<h1 class="text-2xl font-bold">
|
||||
模型广场
|
||||
</h1>
|
||||
<!-- <ScrollListView
|
||||
ref="scrollListRef"
|
||||
title="My Scroll List"
|
||||
:items="myItems"
|
||||
:initial-index="2"
|
||||
@change="handleChange"
|
||||
@item-click="handleItemClick"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<div class="custom-item">
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</ScrollListView> -->
|
||||
<!-- <button @click="handleTest">测试</button>
|
||||
<client-only>
|
||||
{{ useUser.brief }}
|
||||
</client-only> -->
|
||||
<!-- 这里添加模型广场的具体内容 -->
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,462 @@
|
|||
<script setup lang="ts">
|
||||
import { debug } from 'node:console'
|
||||
import { commonApi } from '@/api/common'
|
||||
import defaultAvatar from '@/assets/img/default-avatar.png'
|
||||
import EditUserInfo from '@/components/EditUserInfo.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { formatDate } from '@/utils/index.ts'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
interface UserInfo {
|
||||
nickName?: string // 使用 ? 表示 nickName 是可选的
|
||||
avatar?: string
|
||||
name?: string
|
||||
brief?: string
|
||||
}
|
||||
const userStore = useUserStore()
|
||||
const userInfo: UserInfo = userStore.userInfo
|
||||
|
||||
// 当前是发布还是点赞?
|
||||
const currentState = ref('mallProduct')
|
||||
// 当前是模型还是工作流还是图片?
|
||||
const currentType = ref('0')
|
||||
|
||||
const orderOptions = ref([
|
||||
{
|
||||
dictLabel: '最新',
|
||||
dictValue: 'create_time',
|
||||
},
|
||||
{
|
||||
dictLabel: '最热',
|
||||
dictValue: 'like_num',
|
||||
},
|
||||
])
|
||||
|
||||
const stateList = ref([
|
||||
{ id: 'mallProduct', title: '发布' },
|
||||
{ id: 'like', title: '点赞' },
|
||||
])
|
||||
const typeList = ref([
|
||||
{ id: '0', title: '模型' },
|
||||
{ id: '1', title: '工作流' },
|
||||
{ id: '2', title: '图片' },
|
||||
])
|
||||
|
||||
// 发布的form查询条件
|
||||
const publishParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
status: '0',
|
||||
orderByColumn: 'create_time',
|
||||
date: null,
|
||||
endTime: '',
|
||||
startTime: '',
|
||||
})
|
||||
|
||||
function initPublishParams() {
|
||||
publishParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
status: '0',
|
||||
orderByColumn: 'create_time',
|
||||
date: null,
|
||||
endTime: '',
|
||||
startTime: '',
|
||||
}
|
||||
}
|
||||
|
||||
// 点赞form的查询条件
|
||||
const likesParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
orderByColumn: 'create_time',
|
||||
})
|
||||
|
||||
function initLikesParams() {
|
||||
likesParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
orderByColumn: 'create_time',
|
||||
}
|
||||
}
|
||||
|
||||
const urlList = ref({
|
||||
mallProduct: {
|
||||
0: '/model/selectByUserIdModel',
|
||||
1: '/model/selectByUserIdWorkFlow',
|
||||
2: '/model/selectByUserIdImage',
|
||||
},
|
||||
like: {
|
||||
0: '/model/likeModel',
|
||||
1: '/model/likeWorkFlow',
|
||||
2: '/model/likeImage',
|
||||
},
|
||||
})
|
||||
|
||||
// 获取数据字典
|
||||
|
||||
const statusOptions = ref([])
|
||||
async function getDictType() {
|
||||
try {
|
||||
const res = await commonApi.dictType({
|
||||
type: 'mall_product_status',
|
||||
})
|
||||
if (res.code === 200 && res.data.length > 0) {
|
||||
statusOptions.value = res.data
|
||||
publishParams.value.status = res.data[0].dictValue
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
getDictType()
|
||||
|
||||
// 编辑用户信息
|
||||
interface EditUserInfoType {
|
||||
isVisible: boolean
|
||||
}
|
||||
const editUserInfoRef = ref<EditUserInfoType | null>(null)
|
||||
function onEditInfo() {
|
||||
if (editUserInfoRef.value) {
|
||||
editUserInfoRef.value.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
// 实名认证
|
||||
interface AuthComponentType {
|
||||
isVisible: boolean
|
||||
}
|
||||
const authenticationRef = ref<AuthComponentType | null>(null)
|
||||
function onAuth() {
|
||||
if (authenticationRef.value) {
|
||||
authenticationRef.value.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
function initChangeParams() {
|
||||
if (currentState.value === 'mallProduct') {
|
||||
initPublishParams()
|
||||
}
|
||||
else {
|
||||
initLikesParams()
|
||||
}
|
||||
getList()
|
||||
}
|
||||
// 切换发布/点赞
|
||||
function changeTabs(id: string) {
|
||||
currentState.value = id
|
||||
currentType.value = '0'
|
||||
initChangeParams()
|
||||
}
|
||||
|
||||
// 切换模型/工作流/图片
|
||||
function changeType(id: string) {
|
||||
currentType.value = id
|
||||
initChangeParams()
|
||||
}
|
||||
function initPageNUm() {
|
||||
if (currentState.value === 'mallProduct') {
|
||||
publishParams.value.pageNum = 1
|
||||
}
|
||||
else {
|
||||
likesParams.value.pageNum = 1
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
// 切换select全部状态/已发布/
|
||||
function changeStatus(value: string) {
|
||||
publishParams.value.status = value
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
// 切换发布的最热/最新
|
||||
function changeOrder(value: string) {
|
||||
publishParams.value.orderByColumn = value
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
// 切换点赞的最热/最新
|
||||
function changeLikeOrder(value: string) {
|
||||
likesParams.value.orderByColumn = value
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
// 切换日期
|
||||
async function changeDate(value: string[]) {
|
||||
publishParams.value.startTime = `${await formatDate(value[0] as string)} 00:00:00`
|
||||
publishParams.value.endTime = `${await formatDate(value[1] as string)} 23:59:59`
|
||||
initPageNUm()
|
||||
}
|
||||
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
interface SelectUserInfo {
|
||||
likeCount: number
|
||||
bean: number
|
||||
download: number
|
||||
attention: number
|
||||
}
|
||||
const selectUserInfo = ref<SelectUserInfo>({
|
||||
likeCount: 0,
|
||||
bean: 0,
|
||||
download: 0,
|
||||
attention: 0,
|
||||
})
|
||||
async function getAttention() {
|
||||
try {
|
||||
const res = await request.get('/attention/selectUserInfo')
|
||||
if (res.code === 200) {
|
||||
selectUserInfo.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getAttention()
|
||||
|
||||
// Banner 样式
|
||||
const bannerStyle = {
|
||||
backgroundImage:
|
||||
'url(\'https://liblibai-web-static.liblib.cloud/liblibai_v4_online/static/_next/static/images/defaultBgImg.381282c0f2b01780c83d8fe6dc0aa90a.png\')',
|
||||
}
|
||||
|
||||
// 定义响应接口
|
||||
interface ApiResponse<T> {
|
||||
code: number
|
||||
rows: T[]
|
||||
message: string
|
||||
}
|
||||
// 定义数据接口
|
||||
interface UserData {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
status: number
|
||||
}
|
||||
// 查询发布模型接口
|
||||
const dataList = ref([])
|
||||
async function getList() {
|
||||
let params = {}
|
||||
if (currentState.value === 'mallProduct') {
|
||||
params = publishParams.value
|
||||
}
|
||||
else {
|
||||
params = likesParams.value
|
||||
}
|
||||
const url = urlList.value[currentState.value][currentType.value]
|
||||
try {
|
||||
const res = await request.post<ApiResponse<UserData>>(url, params)
|
||||
if (res.code === 200) {
|
||||
dataList.value = res.rows
|
||||
}
|
||||
else {
|
||||
dataList.value = []
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
dataList.value = []
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getList()
|
||||
|
||||
function topedRefresh() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto container">
|
||||
<!-- Banner Section -->
|
||||
<div class="banner-content h-32 bg-blue bg-cover bg-center" :style="bannerStyle" />
|
||||
|
||||
<!-- User Info Section -->
|
||||
<div class="info-content mt-[-50px] p-5">
|
||||
<div class="edit-info-content flex items-center">
|
||||
<!-- Avatar -->
|
||||
|
||||
<div class="mc-head mr-5 h-20 w-20 rounded-full bg-white shadow-lg flex items-center justify-center">
|
||||
<div class="mc-head-inner h-18 w-18 m-1 rounded-full bg-blue-200">
|
||||
<client-only>
|
||||
<img
|
||||
v-if="userInfo.avatar"
|
||||
class="head-img m-1 h-16 w-16 rounded-full bg-white"
|
||||
:src="userInfo.avatar ? userInfo.avatar : defaultAvatar"
|
||||
alt="User Avatar"
|
||||
>
|
||||
</client-only>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Info Button -->
|
||||
<div
|
||||
class="edit-info mr-2 cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
|
||||
@click="onEditInfo()"
|
||||
>
|
||||
编辑资料
|
||||
</div>
|
||||
<!-- Real Name Verification -->
|
||||
<div
|
||||
v-if="userInfo.name"
|
||||
class="edit-info rounded-full bg-white px-5 py-2 shadow-md"
|
||||
>
|
||||
已经实名
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="edit-info cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
|
||||
@click="onAuth()"
|
||||
>
|
||||
去实名
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Details -->
|
||||
<div class="user-info mt-4">
|
||||
<div v-if="userInfo.nickName" class="nickname text-2xl font-bold">
|
||||
<!-- {{ userInfo.nickName }} -->微信用户1243
|
||||
</div>
|
||||
<div v-if="userInfo.brief" class="info-desc mt-1 text-sm text-gray-700">
|
||||
{{ userInfo.brief }}
|
||||
</div>
|
||||
<div class="production-state mt-4 flex text-sm text-gray-700">
|
||||
<div class="production-state-item mr-5">
|
||||
<span class="production-state-number font-bold">{{
|
||||
selectUserInfo.bean ? selectUserInfo.bean : 0
|
||||
}}</span>
|
||||
粉丝
|
||||
</div>
|
||||
<div class="production-state-item mr-5">
|
||||
<span class="production-state-number font-bold">{{
|
||||
selectUserInfo.attention ? selectUserInfo.attention : 0
|
||||
}}</span>
|
||||
关注
|
||||
</div>
|
||||
<div class="production-state-item mr-5">
|
||||
<span class="production-state-number font-bold">{{
|
||||
selectUserInfo.download ? selectUserInfo.download : 0
|
||||
}}</span>
|
||||
作品被使用次数
|
||||
</div>
|
||||
<div class="production-state-item">
|
||||
<span class="production-state-number font-bold">{{
|
||||
selectUserInfo.likeCount ? selectUserInfo.likeCount : 0
|
||||
}}</span>
|
||||
作品被点赞次数
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mc-tabs flex px-5 pb-3" style="border-bottom: 1px solid #e0e0e0">
|
||||
<div
|
||||
v-for="(item, index) in stateList"
|
||||
:key="index"
|
||||
class="mc-tabs-btn mr-2 cursor-pointer rounded-full px-5 py-1"
|
||||
:class="{
|
||||
'bg-black text-white': currentState === item.id,
|
||||
'bg-white text-black': currentState !== item.id,
|
||||
}"
|
||||
@click="changeTabs(item.id)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-content mt-4 flex items-center justify-between px-5">
|
||||
<div class="flex items-center rounded-full bg-gray-100">
|
||||
<div
|
||||
v-for="(item, index) in typeList"
|
||||
:key="index"
|
||||
class="m-1 mr-2 cursor-pointer rounded-full px-4 py-1 text-sm"
|
||||
:class="{
|
||||
'bg-white': item.id === currentType,
|
||||
}"
|
||||
@click="changeType(item.id)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Published Works Filters -->
|
||||
<div v-if="currentState === 'mallProduct'" class="flex items-center">
|
||||
<div>
|
||||
<n-select
|
||||
v-model:value="publishParams.status"
|
||||
:options="statusOptions"
|
||||
label-field="dictLabel"
|
||||
value-field="dictValue"
|
||||
placeholder="请选择"
|
||||
style="width: 180px;"
|
||||
class="mr-2"
|
||||
@update:value="changeStatus"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<n-select
|
||||
v-model:value="publishParams.orderByColumn"
|
||||
:options="orderOptions"
|
||||
label-field="dictLabel"
|
||||
value-field="dictValue"
|
||||
placeholder="请选择"
|
||||
class="mr-2 w-28"
|
||||
@update:value="changeOrder"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<n-date-picker
|
||||
v-model:value="publishParams.date"
|
||||
type="daterange"
|
||||
style="width: 230px;"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy.MM.dd"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
clearable
|
||||
@update:value="changeDate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liked Works Filters -->
|
||||
<div v-if="currentState === 'like'" class="flex items-center">
|
||||
<NSelect
|
||||
v-model:value="likesParams.orderByColumn"
|
||||
:options="orderOptions"
|
||||
label-field="dictLabel"
|
||||
value-field="dictValue"
|
||||
placeholder="请选择"
|
||||
class="w-32"
|
||||
@update:value="changeLikeOrder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog Components -->
|
||||
<Authentication ref="authenticationRef" />
|
||||
<EditUserInfo ref="editUserInfoRef" />
|
||||
|
||||
<div class="login-content my-4 grid grid-cols-4 gap-4 px-5">
|
||||
<PersonalCenterCard
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:current-type="currentType"
|
||||
:current-state="currentState"
|
||||
@toped-refresh="topedRefresh"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.edit-info {
|
||||
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
size="large"
|
||||
:style="{
|
||||
maxWidth: '640px',
|
||||
}"
|
||||
>
|
||||
<n-form-item label="模型名称" path="inputValue">
|
||||
<n-input v-model:value="formData.inputValue" placeholder="Input" />
|
||||
</n-form-item>
|
||||
<n-form-item label="模型类型" path="inputValue">
|
||||
<n-select v-model:value="formData.value" :options="cuileioptions" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Checkbox Group" path="checkboxGroupValue">
|
||||
<n-checkbox-group v-model:value="formData.checkboxGroupValue">
|
||||
<n-space>
|
||||
<n-checkbox value="Option 1"> Option 1 </n-checkbox>
|
||||
<n-checkbox value="Option 2"> Option 2 </n-checkbox>
|
||||
<n-checkbox value="Option 3"> Option 3 </n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="Radio Group" path="radioGroupValue">
|
||||
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup1">
|
||||
<n-space>
|
||||
<n-radio value="Radio 1"> Radio 1 </n-radio>
|
||||
<n-radio value="Radio 2"> Radio 2 </n-radio>
|
||||
<n-radio value="Radio 3"> Radio 3 </n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="Radio Button Group" path="radioGroupValue">
|
||||
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup2">
|
||||
<n-radio-button value="Radio 1"> Radio 1 </n-radio-button>
|
||||
<n-radio-button value="Radio 2"> Radio 2 </n-radio-button>
|
||||
<n-radio-button value="Radio 3"> Radio 3 </n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<div style="display: flex; justify-content: flex-end">
|
||||
<n-button round type="primary" @click="addWorkflow"> 验证 </n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
<!-- <pre
|
||||
>{{ JSON.stringify(model, null, 2) }}
|
||||
</pre> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInst, FormItemRule } from "naive-ui";
|
||||
import { useMessage } from "naive-ui";
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { commonApi } from "@/api/common";
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const cuileioptions = ref([
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 1" },
|
||||
{ label: "Option 3", value: "Option 1" },
|
||||
]);
|
||||
const message = useMessage();
|
||||
const rules: Record<string, FormItemRule[]> = {
|
||||
|
||||
}
|
||||
const formData = ref({
|
||||
inputValue: null,
|
||||
textareaValue: null,
|
||||
selectValue: null,
|
||||
multipleSelectValue: null,
|
||||
datetimeValue: null,
|
||||
nestedValue: {
|
||||
path1: null,
|
||||
path2: null,
|
||||
},
|
||||
switchValue: false,
|
||||
checkboxGroupValue: null,
|
||||
radioGroupValue: null,
|
||||
radioButtonGroupValue: null,
|
||||
inputNumberValue: null,
|
||||
timePickerValue: null,
|
||||
transferValue: null,
|
||||
})
|
||||
const addWorkflow = async() => {
|
||||
try {
|
||||
const params = {
|
||||
modelProduct:{
|
||||
modelName: '模型名称',
|
||||
modelTypeId: 1, //,,模型类型
|
||||
category: '1', //垂类分类
|
||||
functions:'1', //'模型功能',
|
||||
tags:'11', //标签(最多三个,切割)string
|
||||
activityId: '1', //参与活动string
|
||||
|
||||
isOriginal:1, //??? 0原创1非原创
|
||||
originalAuthorName:'作者名称',
|
||||
},
|
||||
|
||||
modelVersionList:[
|
||||
{
|
||||
versionName:'1.0', // 版本名称
|
||||
modelId:1, //基础模型
|
||||
versionDescription:'"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //版本描述
|
||||
filePath:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //文件路径
|
||||
fileName:'', //文档里没有 ,表里没有
|
||||
sampleImagePaths:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //第三部的图片路径最多20张,切割
|
||||
triggerWords:'触发词', //触发词
|
||||
isPublic:1, //权限是否公开权限 1公开 2自见
|
||||
allowFusion:1, //待确定
|
||||
allowDownloadImage:1, //允许下载生图
|
||||
allowUsage:1, //是否允许使用
|
||||
allowSoftwareUse:1, //允许在软件旗下使用
|
||||
allowCommercialUse:1, //是否允许商用
|
||||
// 允许模型转售或者融合手出售字段没找到?
|
||||
isExclusiveModel:1,//是否为独家模型这个字段
|
||||
}
|
||||
]
|
||||
}
|
||||
const res = await request.post("/model/insert", params);
|
||||
}catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
// e.preventDefault();
|
||||
// formRef.value?.validate((errors) => {
|
||||
// if (!errors) {
|
||||
// message.success("验证成功");
|
||||
// } else {
|
||||
// console.log(errors);
|
||||
// message.error("验证失败");
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
const modelPartCategory = ref([]); //垂类一级
|
||||
const modelChildCategory = ref([]); //垂类一级
|
||||
const workFlowFunctions = ref([]); //垂类一级
|
||||
|
||||
const getDictType = async() => {
|
||||
// model_part_category:[], //垂类一级
|
||||
// model_child_category:[], //垂类二级
|
||||
// work_flow_functions:[], //功能
|
||||
try {
|
||||
const [res1, res2, res3] = await Promise.all([
|
||||
commonApi.dictType({ type: "model_part_category" }),
|
||||
commonApi.dictType({ type: "model_child_category" }),
|
||||
commonApi.dictType({ type: "work_flow_functions" }),
|
||||
]);
|
||||
|
||||
modelPartCategory.value = res1.data;
|
||||
modelChildCategory.value = res2.data;
|
||||
workFlowFunctions.value = res3.data;
|
||||
let categoryList = modelPartCategory;
|
||||
// 遍历第一个数据数组,检查是否有子项
|
||||
categoryList.value.forEach((item) => {
|
||||
// 给每个对象添加一个children属性,初始化为空数组
|
||||
item.children = [];
|
||||
// 遍历第二个数据数组
|
||||
modelChildCategory.value.forEach((child) => {
|
||||
// 检查parentId是否与dictValue相等
|
||||
if (child.partId === item.dictCode) {
|
||||
// 将符合条件的子项放入children数组中
|
||||
item.children.push(child);
|
||||
}
|
||||
});
|
||||
});
|
||||
categoryList = categoryList;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
size="large"
|
||||
:style="{
|
||||
maxWidth: '640px',
|
||||
}"
|
||||
>
|
||||
<n-form-item label="工作流名称" path="inputValue">
|
||||
<n-input v-model:value="formData.inputValue" placeholder="Input" />
|
||||
</n-form-item>
|
||||
<n-form-item label="工作流名称" path="inputValue">
|
||||
<!-- <n-input v-model:value="model.inputValue" placeholder="Input" /> -->
|
||||
<n-select v-model:value="formData.value" :options="cuileioptions" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Checkbox Group" path="checkboxGroupValue">
|
||||
<n-checkbox-group v-model:value="formData.checkboxGroupValue">
|
||||
<n-space>
|
||||
<n-checkbox value="Option 1"> Option 1 </n-checkbox>
|
||||
<n-checkbox value="Option 2"> Option 2 </n-checkbox>
|
||||
<n-checkbox value="Option 3"> Option 3 </n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="Radio Group" path="radioGroupValue">
|
||||
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup1">
|
||||
<n-space>
|
||||
<n-radio value="Radio 1"> Radio 1 </n-radio>
|
||||
<n-radio value="Radio 2"> Radio 2 </n-radio>
|
||||
<n-radio value="Radio 3"> Radio 3 </n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="Radio Button Group" path="radioGroupValue">
|
||||
<n-radio-group v-model:value="formData.radioGroupValue" name="radiogroup2">
|
||||
<n-radio-button value="Radio 1"> Radio 1 </n-radio-button>
|
||||
<n-radio-button value="Radio 2"> Radio 2 </n-radio-button>
|
||||
<n-radio-button value="Radio 3"> Radio 3 </n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<div style="display: flex; justify-content: flex-end">
|
||||
<n-button round type="primary" @click="addWorkflow"> 验证 </n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
<!-- <pre
|
||||
>{{ JSON.stringify(model, null, 2) }}
|
||||
</pre> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInst, FormItemRule } from "naive-ui";
|
||||
import { useMessage } from "naive-ui";
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { commonApi } from "@/api/common";
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const cuileioptions = ref([
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 1" },
|
||||
{ label: "Option 3", value: "Option 1" },
|
||||
]);
|
||||
const message = useMessage();
|
||||
const rules: Record<string, FormItemRule[]> = {
|
||||
|
||||
}
|
||||
const formData = ref({
|
||||
inputValue: null,
|
||||
textareaValue: null,
|
||||
selectValue: null,
|
||||
multipleSelectValue: null,
|
||||
datetimeValue: null,
|
||||
nestedValue: {
|
||||
path1: null,
|
||||
path2: null,
|
||||
},
|
||||
switchValue: false,
|
||||
checkboxGroupValue: null,
|
||||
radioGroupValue: null,
|
||||
radioButtonGroupValue: null,
|
||||
inputNumberValue: null,
|
||||
timePickerValue: null,
|
||||
transferValue: null,
|
||||
})
|
||||
const addWorkflow = async() => {
|
||||
try {
|
||||
const params = {
|
||||
workFlow:{
|
||||
workflowName:'工作流名称',
|
||||
category:'1', //垂类
|
||||
theme:'1', //主体
|
||||
style:'1', //风格
|
||||
functions:'1', //功能
|
||||
activityParticipation:'1', //参与活动
|
||||
jurisdiction:1, //是否公开权限 1公开 2自见
|
||||
original:1, //0代表原创 1代表转载
|
||||
authorName:'作者名称', //原文作者名字
|
||||
onlineUse:0, //是否允许在线使用0允许 1不允许
|
||||
download: 1, //是否允许下载工作流0允许 1不允许
|
||||
sell:1, //是否允许出售或商用(0允许 1不允许)
|
||||
},
|
||||
|
||||
workFlowVersionList:[
|
||||
{
|
||||
versionName:'1.0', // 版本名称
|
||||
versionDescription:'"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //版本描述
|
||||
filePath:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //文件路径
|
||||
fileName:'文件名称其实是个图片', //文件名称
|
||||
imagePaths:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //第三部的图片路径最多20张,切割
|
||||
hideGenInfo:0, //是否隐藏生成信息 0隐藏 1不隐藏
|
||||
},
|
||||
{
|
||||
versionName:'2.0', // 版本名称
|
||||
versionDescription:'"<p>这是一个描述</p><p><img src=\"https://img1.baidu.com/it/u=3001150338,397170470&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422\" /></p><p>这是两张图片之间的一些文字说明</p><p><img src=\"https://img12.iqilu.com/10339/clue/202405/29/68ec17f5-9621-461f-ad22-a6820a3f9cf5.jpg\" /></p>"', //版本描述
|
||||
filePath:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //文件路径
|
||||
fileName:'文件名称其实是个图片', //文件名称
|
||||
imagePaths:'https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png,https://ybl2112.oss-cn-beijing.aliyuncs.com/2025/JANUARY/2/19/4/877e449c-3c0d-4630-a304-91ec110499f2.png', //第三部的图片路径最多20张,切割
|
||||
hideGenInfo:0, //是否隐藏生成信息 0隐藏 1不隐藏
|
||||
}
|
||||
]
|
||||
}
|
||||
const res = await request.post("/WorkFlow/addWorkFlow", params);
|
||||
}catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
// e.preventDefault();
|
||||
// formRef.value?.validate((errors) => {
|
||||
// if (!errors) {
|
||||
// message.success("验证成功");
|
||||
// } else {
|
||||
// console.log(errors);
|
||||
// message.error("验证失败");
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
const modelPartCategory = ref([]); //垂类一级
|
||||
const modelChildCategory = ref([]); //垂类一级
|
||||
const workFlowFunctions = ref([]); //垂类一级
|
||||
|
||||
const getDictType = async() => {
|
||||
// model_part_category:[], //垂类一级
|
||||
// model_child_category:[], //垂类二级
|
||||
// work_flow_functions:[], //功能
|
||||
try {
|
||||
const [res1, res2, res3, res4] = await Promise.all([
|
||||
commonApi.dictType({ type: "model_part_category" }),
|
||||
commonApi.dictType({ type: "model_child_category" }),
|
||||
commonApi.dictType({ type: "work_flow_functions" }),
|
||||
commonApi.dictType({ type: "work_flow_style" }), //风格
|
||||
]);
|
||||
|
||||
modelPartCategory.value = res1.data;
|
||||
modelChildCategory.value = res2.data;
|
||||
workFlowFunctions.value = res3.data;
|
||||
let categoryList = modelPartCategory;
|
||||
// 遍历第一个数据数组,检查是否有子项
|
||||
categoryList.value.forEach((item) => {
|
||||
// 给每个对象添加一个children属性,初始化为空数组
|
||||
item.children = [];
|
||||
// 遍历第二个数据数组
|
||||
modelChildCategory.value.forEach((child) => {
|
||||
// 检查parentId是否与dictValue相等
|
||||
if (child.partId === item.dictCode) {
|
||||
// 将符合条件的子项放入children数组中
|
||||
item.children.push(child);
|
||||
}
|
||||
});
|
||||
});
|
||||
categoryList = categoryList;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
getDictType()
|
||||
</script>
|
|
@ -1,48 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const route = useRoute<'publishDetails-id'>()
|
||||
const user = useUserStore()
|
||||
const name = route.params.id
|
||||
|
||||
watchEffect(() => {
|
||||
user.setNewName(route.params.id as string)
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
layout: 'home',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div i-twemoji:waving-hand inline-block animate-shake-x animate-duration-5000 text-4xl />
|
||||
<h3 text-2xl font-500>
|
||||
Hi,
|
||||
</h3>
|
||||
<div text-xl>
|
||||
{{ name }}!
|
||||
</div>
|
||||
|
||||
<template v-if="user.otherNames.length">
|
||||
<div my-4 text-sm>
|
||||
<span op-50>Also as known as:</span>
|
||||
<ul>
|
||||
<li v-for="otherName in user.otherNames" :key="otherName">
|
||||
<router-link :to="`/hi/${otherName}`" replace>
|
||||
{{ otherName }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div>
|
||||
<NuxtLink
|
||||
class="m-3 text-sm btn"
|
||||
to="/"
|
||||
>
|
||||
Back
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,290 @@
|
|||
<script setup lang="ts">
|
||||
import { commonApi } from '@/api/common'
|
||||
import {
|
||||
CircleUser,
|
||||
Download,
|
||||
EllipsisVertical,
|
||||
HardDriveUpload,
|
||||
Play,
|
||||
} from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
// 用于版本tabs当前选中的选项卡
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const { id } = route.params as { id: string }
|
||||
const activeTab = ref(null)
|
||||
|
||||
const detailsInfo = ref({})
|
||||
// 版本信息
|
||||
const versionByWorkInfo = ref([])
|
||||
async function getInfo() {
|
||||
try {
|
||||
const res = await request.get(`/WorkFlow/selectWorkFlowById?id=${id}&type=1`)
|
||||
if (res.code === 200) {
|
||||
detailsInfo.value = res.data
|
||||
// 1翻译
|
||||
const res1 = await request.get(
|
||||
`/WorkFlowVersion/selectVersionByWorkId?workId=${res.data.id}`,
|
||||
)
|
||||
if (res1.code === 200 && res1.data.length > 0) {
|
||||
versionByWorkInfo.value = res1.data
|
||||
versionByWorkInfo.value.forEach((item) => {
|
||||
item.imagePathsList = item.imagePaths.split(',')
|
||||
})
|
||||
activeTab.value = versionByWorkInfo.value[0].id
|
||||
const commentRes = await request.get(`/WorkFlowComment/comment/${res.data.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
getInfo()
|
||||
|
||||
// 获取用户点赞/粉丝/关注数量
|
||||
interface SelectUserInfo {
|
||||
likeCount: number
|
||||
bean: number
|
||||
download: number
|
||||
attention: number
|
||||
}
|
||||
const selectUserInfo = ref<SelectUserInfo>({
|
||||
likeCount: 0,
|
||||
bean: 0,
|
||||
download: 0,
|
||||
attention: 0,
|
||||
})
|
||||
|
||||
// 获取点赞粉丝等的数量
|
||||
async function getAttention() {
|
||||
try {
|
||||
const res = await request.get('/attention/selectUserInfo')
|
||||
if (res.code === 200) {
|
||||
selectUserInfo.value = res.data
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
getAttention()
|
||||
|
||||
// async function getAttention() {
|
||||
// try {
|
||||
// const res = await request.get("/WorkFlowComment/comment/{modelId}");
|
||||
// if (res.code === 200) {
|
||||
// selectUserInfo.value = res.data;
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// }
|
||||
// getAttention();
|
||||
|
||||
// async function getVersionByWork() {
|
||||
// try {
|
||||
// if (res.code === 200) {
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// }
|
||||
// getVersionByWork();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-[1125px] p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[26px] font-bold mr-4">
|
||||
{{ detailsInfo.workflowName }}
|
||||
</div>
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||
<component :is="Play" class="h-[14px] w-[14px] text-black menu-icon m-1" />
|
||||
<span> {{ detailsInfo.useNumber }} </span>
|
||||
</div>
|
||||
</template>
|
||||
在线生成数
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full mx-4">
|
||||
<component
|
||||
:is="Download"
|
||||
class="h-[14px] w-[14px] text-black menu-icon m-1"
|
||||
/>
|
||||
<span> {{ detailsInfo.downloadNumber }} </span>
|
||||
</div>
|
||||
</template>
|
||||
下载数
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<div class="flex items-center bg-[#f4f5f9] px-2 rounded-full">
|
||||
<img src="@/assets/img/heart.png" class="w-[14px] h-[14px] mr-1" alt="">
|
||||
<span> <!-- {{ item.numbers }} -->{{ detailsInfo.likeCount }} </span>
|
||||
</div>
|
||||
</template>
|
||||
点赞数
|
||||
</n-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mt-3 mb-5">
|
||||
<div
|
||||
v-for="(item, index) in detailsInfo.styleList"
|
||||
:key="index"
|
||||
class="text-[12px] bg-[#ebf2fe] p-1 px-2 text-[#557abf] mr-4 rounded-md"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-1">
|
||||
<div class="w-2/3">
|
||||
<div class="w-full">
|
||||
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||
<n-tab-pane
|
||||
v-for="(item, index) in versionByWorkInfo"
|
||||
:key="index"
|
||||
:name="item.id"
|
||||
:tab="item.versionName"
|
||||
>
|
||||
<!-- 显示最后一步上传图片的图片 -->
|
||||
<div class="grid grid-cols-2 gap-2.5 box-border">
|
||||
<img
|
||||
v-for="(subItem, index) in item.imagePathsList"
|
||||
:key="index"
|
||||
:src="subItem"
|
||||
class="w-full h-[300px]"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-if="detailsInfo.original === 1" class="font-bold text-[20px] my-6">
|
||||
转载自作者: {{ detailsInfo.authorName }}
|
||||
</div>
|
||||
<!-- 富文本中输入的文字 图片 -->
|
||||
<div class="w-full">
|
||||
<div v-html="item.versionDescription" />
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3 mt-3">
|
||||
<div
|
||||
class="flex justify-between text-[#a3a1a1] text-[12px] items-center -ml-60"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="mr-2">
|
||||
首发时间{{ detailsInfo.createTime }}
|
||||
</div>
|
||||
<div v-if="detailsInfo.updateTime">
|
||||
更新时间:{{ detailsInfo.updateTime }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img src="@/assets/img/heart.png" class="w-[14px] h-[14px] mr-2" alt="">
|
||||
<component
|
||||
:is="EllipsisVertical"
|
||||
class="h-[18px] w-[18px] text-[#557abf] menu-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center mt-10 p-2 bg-[#f3f5f9] w-full rounded-md h-[80px] box-border"
|
||||
>
|
||||
<div class="w-[70px] h-[70px] rounded-full overflow-hidden mr-4">
|
||||
<img
|
||||
src="@/assets/img/default-avatar.png"
|
||||
class="w-full h-full mr-2 block"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text[20px] font-bold">
|
||||
微信用户ddsd
|
||||
</div>
|
||||
<!-- 0代表原创 1代表转载 -->
|
||||
<div v-if="detailsInfo.original === 0" class="text-[14px]">
|
||||
原创作者
|
||||
</div>
|
||||
<div class="flex items-center text-[#969798]">
|
||||
<component
|
||||
:is="CircleUser"
|
||||
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798] m-0"
|
||||
/>
|
||||
<span class="mr-2"> {{ selectUserInfo.bean }} </span>
|
||||
<component
|
||||
:is="HardDriveUpload"
|
||||
class="h-[12px] w-[14px] text-black menu-icon m-1 text-[#969798]"
|
||||
/>
|
||||
<span class="mr-2"> 2 </span>
|
||||
<component
|
||||
:is="Play"
|
||||
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||
/>
|
||||
<span class="mr-2"> {{ detailsInfo.useNumber }} </span>
|
||||
<component
|
||||
:is="Download"
|
||||
class="h-[12px] w-[12px] text-black menu-icon m-1 text-[#969798]"
|
||||
/>
|
||||
<span class="mr-2"> {{ detailsInfo.downloadNumber }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 不支持的bg #b1b2b2 -->
|
||||
<div
|
||||
class="flex items-center justify-center mt-4 w-full h-14 text-white bg-[#3c7af6] w-full rounded-md h-[80px] cursor-pointer"
|
||||
>
|
||||
<component
|
||||
:is="Play"
|
||||
class="h-[20px] w-[20px] text-white menu-icon m-1 text-[#969798]"
|
||||
/>
|
||||
<span class="mr-1"> 立即生图 </span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#eceef4] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]"
|
||||
>
|
||||
<component
|
||||
:is="Download"
|
||||
class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]"
|
||||
/>
|
||||
<span class="mr-1"> 下载 (122.22MB) </span>
|
||||
</div>
|
||||
|
||||
<!-- <div style="background: linear-gradient(135deg,#3cc9ff, #8fa6ff, 41%, #d8b4ff 74%,#326bff)" class="flex items-center justify-center mt-4 w-full h-14 text-black bg-[#fff] w-full rounded-md h-[80px] cursor-pointer hover:bg-[#f1f2f7]">
|
||||
<component :is="Download" class="h-[20px] w-[20px] text-black menu-icon m-1 text-[#969798]" />
|
||||
<span class="mr-1">
|
||||
下载客户端
|
||||
</span>
|
||||
</div> -->
|
||||
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.header-num {
|
||||
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||
}
|
||||
.header-tag {
|
||||
@apply flex items-center bg-[#f4f5f9] px-2 rounded-full;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
|
@ -1,20 +1,21 @@
|
|||
import { setup } from '@css-render/vue3-ssr'
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
import { setup } from '@css-render/vue3-ssr'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const { collect } = setup(nuxtApp.vueApp)
|
||||
const originalRenderMeta = nuxtApp.ssrContext?.renderMeta
|
||||
nuxtApp.ssrContext!.renderMeta = () => {
|
||||
if (!originalRenderMeta) {
|
||||
return {
|
||||
headTags: collect()
|
||||
headTags: collect(),
|
||||
}
|
||||
}
|
||||
const originalMeta = originalRenderMeta()
|
||||
if ('headTags' in originalMeta) {
|
||||
originalMeta.headTags += collect()
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
originalMeta.headTags = collect()
|
||||
}
|
||||
return originalMeta
|
||||
|
@ -22,19 +23,18 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||
}
|
||||
|
||||
const { collect } = setup(nuxtApp.vueApp)
|
||||
useServerHead({
|
||||
style: () => {
|
||||
const stylesString = collect()
|
||||
const stylesArray = stylesString.split(/<\/style>/g).filter(style => style)
|
||||
return stylesArray.map((styleString: string) => {
|
||||
const match = styleString.match(/<style cssr-id="([^"]*)">([\s\S]*)/)
|
||||
if (match) {
|
||||
const id = match[1]
|
||||
return { 'cssr-id': id, children: match[2] }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
useServerHead({
|
||||
style: () => {
|
||||
const stylesString = collect()
|
||||
const stylesArray = stylesString.split(/<\/style>/g).filter(style => style)
|
||||
return stylesArray.map((styleString: string) => {
|
||||
const match = styleString.match(/<style cssr-id="([^"]*)">([\s\S]*)/)
|
||||
if (match) {
|
||||
const id = match[1]
|
||||
return { 'cssr-id': id, 'children': match[2] }
|
||||
}
|
||||
})
|
||||
|
||||
return {}
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
// plugins/pinia-persist.ts
|
||||
import { createPersistedState } from 'pinia-plugin-persistedstate'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.$pinia.use(createPersistedState)
|
||||
})
|
|
@ -2,7 +2,6 @@ import { defineStore } from 'pinia'
|
|||
|
||||
export const useMenuStore = defineStore('menu', () => {
|
||||
const activeMenu = ref('/model-square')
|
||||
|
||||
const menuItems = [
|
||||
{ path: '/model-square', icon: 'i-carbon-gallery', label: '模型广场' },
|
||||
// { path: '/works-inspire', icon: 'i-carbon-light-filled', label: '作品灵感' },
|
||||
|
@ -26,4 +25,24 @@ export const useMenuStore = defineStore('menu', () => {
|
|||
menuItems,
|
||||
setActiveMenu,
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'mc-menu-store',
|
||||
// paths: ['isLoggedIn', 'mymcname', 'token', 'userInfo'], // 只持久化 token
|
||||
storage: import.meta.client ? localStorage : undefined,
|
||||
},
|
||||
// persist: {
|
||||
// storage: {
|
||||
// getItem(key) {
|
||||
// return window.localStorage.getItem(key)
|
||||
// },
|
||||
// setItem(key, value) {
|
||||
// window.localStorage.setItem(key, value)
|
||||
// },
|
||||
// },
|
||||
// serializer: {
|
||||
// deserialize: parse,
|
||||
// serialize: stringify,
|
||||
// },
|
||||
// },
|
||||
})
|
|
@ -8,15 +8,14 @@ export const useModalStore = defineStore('modal', () => {
|
|||
|
||||
function setLoginModal(modalRef: any) {
|
||||
loginModalRef.value = modalRef
|
||||
console.log('Modal ref set:', modalRef)
|
||||
}
|
||||
|
||||
function showLoginModal() {
|
||||
console.log('Showing login modal, ref:', loginModalRef.value)
|
||||
if (loginModalRef.value?.showModal) {
|
||||
loginModalRef.value.showModal()
|
||||
isModalVisible.value = true
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
console.warn('Login modal not initialized')
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +32,6 @@ export const useModalStore = defineStore('modal', () => {
|
|||
isModalVisible,
|
||||
setLoginModal,
|
||||
showLoginModal,
|
||||
hideLoginModal
|
||||
hideLoginModal,
|
||||
}
|
||||
})
|
|
@ -6,26 +6,40 @@ import {
|
|||
stringify,
|
||||
} from 'zipson'
|
||||
|
||||
export interface UserInfoType {
|
||||
nickName: string
|
||||
avatar: string
|
||||
brief: string
|
||||
userId: number | string
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const isLoggedIn = ref(false)
|
||||
const token = ref('')
|
||||
const userInfo = ref({})
|
||||
|
||||
// const userInfo = ref<UserInfoType>({})
|
||||
const userInfo = ref<UserInfoType | null>(null)
|
||||
function setToken(userToken: string) {
|
||||
isLoggedIn.value = true
|
||||
token.value = userToken
|
||||
localStorage.setItem('token', userToken)
|
||||
// localStorage.setItem('token', userToken)
|
||||
}
|
||||
|
||||
function setUserInfo(info: any) {
|
||||
userInfo.value = info
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
const res = await request.get('/system/user/selectUserById', {
|
||||
token: token.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
setUserInfo(res.data)
|
||||
}
|
||||
}
|
||||
// 登出
|
||||
function logout() {
|
||||
isLoggedIn.value = false
|
||||
token.value = ''
|
||||
localStorage.removeItem('token')
|
||||
userInfo.value = {} as UserInfoType
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
|
@ -36,7 +50,6 @@ export const useUserStore = defineStore('user', () => {
|
|||
token.value = savedToken
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isLoggedIn,
|
||||
token,
|
||||
|
@ -45,22 +58,28 @@ export const useUserStore = defineStore('user', () => {
|
|||
setToken,
|
||||
setUserInfo,
|
||||
userInfo,
|
||||
getUserInfo,
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
storage: {
|
||||
getItem(key) {
|
||||
return window.localStorage.getItem(key)
|
||||
},
|
||||
setItem(key, value) {
|
||||
window.localStorage.setItem(key, value)
|
||||
},
|
||||
},
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
},
|
||||
key: 'mc-user-store',
|
||||
// paths: ['isLoggedIn', 'mymcname', 'token', 'userInfo'], // 只持久化 token
|
||||
storage: import.meta.client ? localStorage : undefined,
|
||||
},
|
||||
// persist: {
|
||||
// storage: {
|
||||
// getItem(key) {
|
||||
// return window.localStorage.getItem(key)
|
||||
// },
|
||||
// setItem(key, value) {
|
||||
// window.localStorage.setItem(key, value)
|
||||
// },
|
||||
// },
|
||||
// serializer: {
|
||||
// deserialize: parse,
|
||||
// serialize: stringify,
|
||||
// },
|
||||
// },
|
||||
})
|
||||
|
||||
// import { defineStore } from 'pinia'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export async function formatDate(timestamp: string) {
|
||||
const date = new Date(timestamp)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import axios from 'axios'
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
|
||||
const { message, loadingBar } = createDiscreteApi(['message', 'loadingBar'])
|
||||
|
||||
// 定义响应数据接口
|
||||
|
@ -28,9 +29,8 @@ class RequestHttp {
|
|||
const userStore = useUserStore()
|
||||
const isToken = (config.headers || {}).isToken === false
|
||||
if (userStore.token && !isToken) {
|
||||
config.headers['Authorization'] = 'Bearer ' + userStore.token // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
config.headers.Authorization = `Bearer ${userStore.token}` // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
|
||||
// 开启 loading
|
||||
if (config.loading) {
|
||||
loadingBar.start()
|
||||
|
@ -39,14 +39,13 @@ class RequestHttp {
|
|||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
async (response: AxiosResponse) => {
|
||||
const { data, config } = response
|
||||
|
||||
// 关闭 loading
|
||||
if (config.loading) {
|
||||
loadingBar.finish()
|
||||
|
@ -54,7 +53,24 @@ class RequestHttp {
|
|||
|
||||
// 处理业务状态码
|
||||
if (data.code !== 200) {
|
||||
message.error(data.message || '请求失败')
|
||||
this.handleError(data.code)
|
||||
// token过期以后,需要重新登录
|
||||
if (data.code === 401) {
|
||||
// const modalStore = useModalStore()
|
||||
const userStore = useUserStore()
|
||||
// useUser.logout()
|
||||
// modalStore.showLoginModal()
|
||||
try {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
await request.post('/logout')
|
||||
userStore.logout()
|
||||
navigateTo('/model-square')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
// message.error(data.message || '请求失败')
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
|
@ -67,12 +83,13 @@ class RequestHttp {
|
|||
// 处理错误
|
||||
if (error.response) {
|
||||
this.handleError(error.response.status)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
message.error('网络连接异常')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -104,14 +121,14 @@ class RequestHttp {
|
|||
public get<T = any>(
|
||||
url: string,
|
||||
data?: Record<string, any>,
|
||||
options: RequestOptions = {}
|
||||
options: RequestOptions = {},
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 如果 data 中包含 params,则使用 params 中的值
|
||||
const params = data?.params || data
|
||||
|
||||
return this.instance.get(url, {
|
||||
...options,
|
||||
params // 使用解构后的参数
|
||||
params, // 使用解构后的参数
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -119,17 +136,22 @@ class RequestHttp {
|
|||
public post<T = any>(
|
||||
url: string,
|
||||
data?: Record<string, any>,
|
||||
options: RequestOptions = {}
|
||||
options: RequestOptions = {},
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.post(url, data, options)
|
||||
}
|
||||
|
||||
// PUT 请求
|
||||
// 发送PUT请求,返回Promise<ApiResponse<T>>
|
||||
public put<T = any>(
|
||||
// 请求的URL
|
||||
url: string,
|
||||
// 请求的数据
|
||||
data?: Record<string, any>,
|
||||
options: RequestOptions = {}
|
||||
// 请求的配置
|
||||
options: RequestOptions = {},
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 发送PUT请求
|
||||
return this.instance.put(url, data, options)
|
||||
}
|
||||
|
||||
|
@ -137,7 +159,7 @@ class RequestHttp {
|
|||
public delete<T = any>(
|
||||
url: string,
|
||||
params?: Record<string, any>,
|
||||
options: RequestOptions = {}
|
||||
options: RequestOptions = {},
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.delete(url, { params, ...options })
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import type { ApiResponse } from '~/types/api'
|
||||
|
||||
/**
|
||||
* 分批次上传图片
|
||||
* @param {File[]} files - 需要上传的图片文件数组
|
||||
* @param {string} url - 上传接口的 URL
|
||||
* @param {number} batchSize - 每批次上传的文件数量(默认为 3)
|
||||
* @returns {Promise<{ success: boolean, message: string, data: any[] }>} - 返回上传结果
|
||||
*/
|
||||
|
||||
export async function uploadImagesInBatches(files, batchSize = 3) {
|
||||
const uploadResults = []
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i]
|
||||
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData()
|
||||
formData.append('file', file) // 假设后端接收字段是 `file`
|
||||
|
||||
// 上传当前图片
|
||||
try {
|
||||
const res = await request.post<ApiResponse<{ url: string }>>('/model/file', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
// const res = await mallProductFile(formData)
|
||||
uploadResults.push(res.msg)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`图片上传失败: ${file.name}`, error)
|
||||
uploadResults.push({ success: false, error })
|
||||
}
|
||||
}
|
||||
return uploadResults
|
||||
}
|
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 279 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 860 B |
|
@ -0,0 +1,9 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded-lg font-medium transition-colors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// // 全局变量
|
||||
// $primary-color: #3eaf7c;
|
||||
// $text-color: #2c3e50;
|
||||
|
||||
// :root {
|
||||
// --primary: #{$primary-color};
|
||||
// --text: #{$text-color};
|
||||
// }
|
|
@ -0,0 +1,26 @@
|
|||
// // Import Tailwind
|
||||
// @use 'tailwind';
|
||||
// @use 'variables';
|
||||
|
||||
// // 你的其他全局样式
|
||||
// // Variables
|
||||
// $primary-color: #3eaf7c;
|
||||
// $text-color: #2c3e50;
|
||||
|
||||
// // Global styles
|
||||
// :root {
|
||||
// --primary: #{$primary-color};
|
||||
// --text: #{$text-color};
|
||||
// }
|
||||
|
||||
// // Custom SCSS
|
||||
// body {
|
||||
// font-family: 'Inter', sans-serif;
|
||||
// color: var(--text);
|
||||
// }
|
||||
|
||||
// // You can organize your SCSS with partials
|
||||
// // @import 'components/buttons';
|
||||
// // @import 'components/forms';
|
||||
// // @import 'layouts/header';
|
||||
// // @import 'layouts/footer';
|
|
@ -5,7 +5,6 @@ import nuxt from './.nuxt/eslint.config.mjs'
|
|||
export default nuxt(
|
||||
antfu(
|
||||
{
|
||||
unocss: true,
|
||||
formatters: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { pwa } from './app/config/pwa'
|
||||
import { appDescription } from './app/constants/index'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@vueuse/nuxt',
|
||||
'@unocss/nuxt',
|
||||
'@pinia/nuxt',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@nuxtjs/color-mode',
|
||||
'@vite-pwa/nuxt',
|
||||
'@nuxt/eslint',
|
||||
'nuxtjs-naive-ui',
|
||||
'@pinia-plugin-persistedstate/nuxt',
|
||||
],
|
||||
|
||||
ssr: true,
|
||||
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
devtools: { enabled: true },
|
||||
|
||||
app: {
|
||||
head: {
|
||||
|
@ -36,56 +39,57 @@ export default defineNuxtConfig({
|
|||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// css: [
|
||||
// '@unocss/reset/tailwind.css',
|
||||
// ],
|
||||
css: ['assets/scss/main.scss'],
|
||||
|
||||
colorMode: {
|
||||
classSuffix: '',
|
||||
colorMode: { classSuffix: '' },
|
||||
|
||||
alias: {
|
||||
'@styles': './assets/styles',
|
||||
'assets': './assets',
|
||||
'@assets': './assets',
|
||||
'public': './public',
|
||||
'@': resolve(__dirname, './app'),
|
||||
'~': resolve(__dirname, './app'),
|
||||
'@constants': resolve(__dirname, './app/constants'),
|
||||
},
|
||||
|
||||
build: {
|
||||
transpile:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? ['naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer']
|
||||
: ['@juggle/resize-observer'],
|
||||
transpile: process.env.NODE_ENV === 'production'
|
||||
? ['naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer']
|
||||
: ['@juggle/resize-observer'],
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
'/': { redirect: '/model-square' },
|
||||
'/personal-center': { ssr: true },
|
||||
'/model-square': { ssr: true },
|
||||
'/member-center': { ssr: true },
|
||||
},
|
||||
|
||||
future: {
|
||||
compatibilityVersion: 4,
|
||||
},
|
||||
future: { compatibilityVersion: 4 },
|
||||
|
||||
experimental: {
|
||||
// when using generate, payload js assets included in sw precache manifest
|
||||
// but missing on offline, disabling extraction it until fixed
|
||||
payloadExtraction: false,
|
||||
renderJsonPayloads: true,
|
||||
typedPages: true,
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-08-14',
|
||||
compatibilityDate: '2025-01-23',
|
||||
|
||||
nitro: {
|
||||
devProxy: {
|
||||
'/api': {
|
||||
// 192.168.1.69 海洋
|
||||
// 192.168.2.22 代
|
||||
target: `http://192.168.2.22:8080`,
|
||||
target: 'http://1.13.246.108:8080',
|
||||
changeOrigin: true,
|
||||
prependPath: true,
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
options: {
|
||||
target: 'esnext',
|
||||
},
|
||||
options: { target: 'esnext' },
|
||||
},
|
||||
prerender: {
|
||||
crawlLinks: false,
|
||||
|
@ -93,6 +97,7 @@ export default defineNuxtConfig({
|
|||
ignore: ['/hi'],
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
define: {
|
||||
'process.env.DEBUG': false,
|
||||
|
@ -114,7 +119,13 @@ export default defineNuxtConfig({
|
|||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
],
|
||||
// 避免 vite 热更新时出现警告
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: '@use "@assets/scss/_variables.scss";', // 使用 @import 而不是 @use
|
||||
},
|
||||
},
|
||||
},
|
||||
// optimizeDeps: {
|
||||
// include: ['date-fns-tz/esm/formatInTimeZone']
|
||||
// }
|
||||
|
@ -129,5 +140,16 @@ export default defineNuxtConfig({
|
|||
},
|
||||
},
|
||||
|
||||
pinia: {
|
||||
autoImports: ['defineStore', 'storeToRefs'],
|
||||
},
|
||||
|
||||
pwa,
|
||||
|
||||
tailwindcss: {
|
||||
cssPath: 'assets/scss/_tailwind.scss',
|
||||
configPath: 'tailwind.config.js',
|
||||
exposeConfig: false,
|
||||
viewer: true,
|
||||
},
|
||||
})
|
||||
|
|
23
package.json
|
@ -13,6 +13,16 @@
|
|||
"lint": "eslint .",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"axios": "^1.7.9",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-vue-next": "^0.471.0",
|
||||
"naive-ui": "^2.41.0",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"zipson": "^0.2.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.12.1",
|
||||
"@css-render/vue3-ssr": "^0.15.14",
|
||||
|
@ -25,8 +35,6 @@
|
|||
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
|
||||
"@pinia/nuxt": "^0.9.0",
|
||||
"@types/node": "^22.10.6",
|
||||
"@unocss/eslint-config": "^0.65.3",
|
||||
"@unocss/nuxt": "^0.65.3",
|
||||
"@vite-pwa/nuxt": "^0.10.6",
|
||||
"@vueuse/nuxt": "^12.2.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
@ -38,6 +46,8 @@
|
|||
"nuxtjs-naive-ui": "^1.0.2",
|
||||
"pinia": "^2.3.0",
|
||||
"postcss": "^8.5.1",
|
||||
"sass": "^1.83.4",
|
||||
"sass-loader": "^16.0.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
|
@ -49,14 +59,5 @@
|
|||
"unplugin": "^2.1.0",
|
||||
"vite": "^6.0.6",
|
||||
"vite-plugin-inspect": "^0.10.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@unocss/reset": "^65.4.0",
|
||||
"axios": "^1.7.9",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"lucide-vue-next": "^0.471.0",
|
||||
"naive-ui": "^2.41.0",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"zipson": "^0.2.12"
|
||||
}
|
||||
}
|
||||
|
|
2829
pnpm-lock.yaml
|
@ -1,45 +1,24 @@
|
|||
// tailwind.config.js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
export default {
|
||||
content: [
|
||||
"./components/**/*.{js,vue,ts}",
|
||||
"./layouts/**/*.vue",
|
||||
"./pages/**/*.vue",
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./error.vue",
|
||||
'./components/**/*.{js,vue,ts}',
|
||||
'./layouts/**/*.vue',
|
||||
'./pages/**/*.vue',
|
||||
'./plugins/**/*.{js,ts}',
|
||||
'./app.vue',
|
||||
'./error.vue',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
// 自定义颜色
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
},
|
||||
// 自定义颜色
|
||||
primary: '#3eaf7c',
|
||||
},
|
||||
// 自定义字体
|
||||
fontFamily: {
|
||||
// 自定义字体
|
||||
sans: ['Inter var', 'sans-serif'],
|
||||
},
|
||||
// 自定义断点
|
||||
screens: {
|
||||
'xs': '475px',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
// 禁用预加载(可选)
|
||||
future: {
|
||||
removeDeprecatedGapUtilities: true,
|
||||
purgeLayersByDefault: true,
|
||||
},
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"types": [
|
||||
"naive-ui/volar"
|
||||
]
|
||||
|
|