Commit 322d93db authored by tyyin lan's avatar tyyin lan

feat: 礼包兑换&路由调整&首页样式调整

parent 9e9b2a7f
......@@ -23,6 +23,7 @@
"axios": "^1.7.7",
"clipboardy": "^4.0.0",
"dayjs": "^1.11.13",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.7",
"pinia": "^2.2.2",
"spark-md5": "^3.0.2",
......@@ -34,6 +35,7 @@
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"@commitlint/types": "^19.0.3",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.16.5",
"@types/spark-md5": "^3.0.4",
"@types/validator": "^13.12.1",
......
This diff is collapsed.
import { request } from '@/utils/request'
// 根据ID获取推荐模板信息
export function fetchGiftCodeRedemption<T>(redemptionCode: string) {
return request.post<T>(`/bizRedemptionRest/equityRedeem.json?redemptionCode=${redemptionCode}`)
}
......@@ -24,3 +24,7 @@ export function fetchUniversalCurrency<T>(token?: string) {
},
})
}
export function fetchUserInfo<T>() {
return request.post<T>('/bizMemberInfoRest/indexGetMemberInfo.json')
}
......@@ -4,9 +4,11 @@ import { ref } from 'vue'
import { themeOverrides } from '@/config/theme-config'
import { useResizeObserver } from '@vueuse/core'
import { useDesignSettingStore } from '@/store/modules/design-setting'
import { useUserStore } from './store/modules/user'
// import { NThemeEditor } from 'naive-ui'
const designSettingStore = useDesignSettingStore()
const userStore = useUserStore()
const currentLocale = ref(zhTW)
const currentDateLocale = ref(dateZhTW)
......@@ -33,6 +35,11 @@ useResizeObserver(rootContainer, (entries) => {
designSettingStore.toggleSidebarDisplayStatus('expand')
}
})
;(function () {
if (userStore.isLogin) {
useUserStore().fetchUpdateUserInfo()
}
})()
</script>
<template>
......
......@@ -34,7 +34,7 @@ const modules: Record<string, any> = import.meta.glob(['./modules/**/*.ts', '!./
export const sidebarMenus: any[] = menuFilterSort([...routes])
const router = createRouter({
export const router = createRouter({
history: getHistoryMode(import.meta.env.VITE_ROUTER_MODE),
routes: [...routes, ...baseRoutes],
strict: true,
......
import { type RouteRecordRaw } from 'vue-router'
// import Layout from '@/layout/index.vue'
import Index from '@/views/index/index.vue'
// export default [
// {
// path: '/',
// name: 'Root',
// meta: {
// rank: 1001,
// title: '',
// },
// component: Layout,
// redirect: '/home',
// children: [
// {
// path: '/home',
// name: 'Home',
// meta: {
// rank: 1001,
// title: '首页',
// icon: 'material-symbols:home-outline',
// },
// component: Home,
// },
// ],
// },
// ] as RouteRecordRaw[]
export default [
{
path: '/',
......@@ -39,20 +13,20 @@ export default [
children: [
{
path: '/',
redirect: 'home',
redirect: 'workbench',
},
{
path: 'home',
name: 'Home',
path: 'workbench',
name: 'Workbench',
meta: {
title: '首页',
title: '工作台',
rank: 1001,
},
component: () => import('@/views/home/home.vue'),
component: () => import('@/views/workbench/workbench.vue'),
},
{
path: '/work/videos',
name: 'work-videos',
name: 'WorkVideos',
meta: {
title: '我的作品',
rank: 1001,
......@@ -61,7 +35,7 @@ export default [
},
{
path: '/work/draft',
name: 'work-draft',
name: 'WorkDraft',
meta: {
title: '草稿箱',
rank: 1001,
......
import { defineStore } from 'pinia'
import { ss } from '@/utils/storage'
import { type UserState, type UserInfo, UserStoreStorageKeyEnum } from '../types/user'
import { fetchUserInfo } from '@/apis/user'
function createDefaultUserInfoFactory(): UserInfo {
return {
memberId: null,
account: '',
avatarUrl: '',
nickName: '',
mobilePhone: '',
equityNum: 0,
taskConfigNum: 0,
}
}
......@@ -43,5 +47,11 @@ export const useUserStore = defineStore('user-store', {
this.userInfo = userInfo
ss.set(UserStoreStorageKeyEnum.userInfo, userInfo)
},
fetchUpdateUserInfo() {
fetchUserInfo<UserInfo>().then((res) => {
this.updateUserInfo(res.data)
})
},
},
})
export interface UserInfo {
memberId: number | null
account: string
mobilePhone: string
nickName: string
avatarUrl: string
equityNum: number
taskConfigNum: number
}
export interface UserState {
......
import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios'
import { BASE_URLS } from '@/config/base-url'
import { useUserStore } from '@/store/modules/user'
import { useRouter } from 'vue-router'
import { router } from '@/router/index'
import { debounce } from 'lodash-es'
interface PagingInfoParams {
pageNo: number
......@@ -17,13 +18,21 @@ export interface Response<T> {
const ENV = import.meta.env.VITE_APP_ENV
function handleLogout() {
const router = useRouter()
const handleLogout = debounce(
() => {
const currentRoute = router.currentRoute.value
const currentRoute = router.currentRoute.value
router.replace({ name: 'Login', query: { redirect: encodeURIComponent(currentRoute.fullPath) } })
window.$message.warning('身份已过期,请重新登录')
}
if (currentRoute.name === 'Login') {
return
}
router.replace({ name: 'Login', query: { redirect: encodeURIComponent(currentRoute.fullPath) } })
useUserStore().logout()
window.$message.warning('身份已过期,请重新登录')
},
700,
{ leading: true, trailing: false },
)
const service = axios.create({
baseURL: `${BASE_URLS[ENV]}/api/rest`,
......@@ -59,7 +68,7 @@ service.interceptors.response.use(
// 处理响应错误
if (response && [500, 502].includes(response.status) && window.location.hash === '#/500')
useRouter().push({ name: 'ServerError' })
router.push({ name: 'ServerError' })
return Promise.reject(error)
},
......
......@@ -58,7 +58,7 @@ async function handleGetDigitalHumanDialogueConfig() {
}
})
.catch(() => {
router.replace({ name: 'Home' })
router.replace({ name: 'Workbench' })
})
}
......
<script setup lang="ts">
import { fetchGiftCodeRedemption } from '@/apis/gift-redemption'
import type { FormInst } from 'naive-ui'
import { ref, shallowReadonly, useTemplateRef } from 'vue'
const isShowGiftCodeRedemptionModal = defineModel<boolean>('isShowGiftCodeRedemptionModal', { default: false })
const exchangeInfoFormRef = useTemplateRef<FormInst>('exchangeInfoFormRef')
const exchangeInfoForm = ref({
redemptionCode: '',
})
const exchangeInfoFormRules = shallowReadonly({
redemptionCode: {
required: true,
message: '請填冩禮包碼',
trigger: 'blur',
},
})
const exchangeSubmitBtnLoading = ref(false)
function handleExchangeSubmit() {
exchangeInfoFormRef.value?.validate((errors) => {
if (errors) return ''
exchangeSubmitBtnLoading.value = true
fetchGiftCodeRedemption<{ awardType: string; awardNum: number }>(exchangeInfoForm.value.redemptionCode)
.then((res) => {
setTimeout(() => {
window.$dialog.success({
title: '禮包兌換成功',
content: `兌換內容:${res.data.awardNum}靈荳`,
contentStyle: { fontSize: '14px', marginTop: '20px' },
closable: false,
positiveText: '知道了',
positiveButtonProps: {
type: 'info',
size: 'medium',
},
})
}, 100)
})
.catch(() => {
setTimeout(() => {
window.$dialog.error({
title: '禮包兌換失敗',
content: '請重新兌換或聯繫客服',
contentStyle: { fontSize: '14px', marginTop: '20px' },
closable: false,
positiveText: '知道了',
positiveButtonProps: {
type: 'info',
size: 'medium',
},
})
}, 200)
})
.finally(() => {
exchangeSubmitBtnLoading.value = false
isShowGiftCodeRedemptionModal.value = false
})
})
}
function onModalAfterLeave() {
exchangeInfoFormRef.value?.restoreValidation()
exchangeInfoForm.value.redemptionCode = ''
}
</script>
<template>
<n-modal v-model:show="isShowGiftCodeRedemptionModal" :mask-closable="false" :on-after-leave="onModalAfterLeave">
<n-card
class="!w-[600px]"
title="禮包碼兌換"
:bordered="false"
size="medium"
closable
@close="() => (isShowGiftCodeRedemptionModal = false)"
>
<n-form
ref="exchangeInfoFormRef"
label-placement="left"
label-width="auto"
:model="exchangeInfoForm"
:rules="exchangeInfoFormRules"
>
<n-form-item label="禮包碼" path="redemptionCode">
<n-input v-model:value="exchangeInfoForm.redemptionCode" placeholder="請輸入禮包碼" />
</n-form-item>
</n-form>
<div class="mb-[20px] mt-[16px]">
<h2>兌換説明:</h2>
<div class="mt-[6px] pl-[8px] leading-7">
<div>1、每個禮包碼僅限使用一次;</div>
<div>2、禮包內容和適用條件以活動説明爲準;</div>
<div>3、兌換時,請準確無誤地輸入禮包碼,包括字母大小冩、數字位數;</div>
<div>4、禮包碼一旦兌換成功,獎勵將直接髮放至您的賬戶中,不可轉移給其他賬戶;</div>
<div>5、本活動最終解釋權歸萃想智能雲創所有,如有任何爭議,萃想保留決定權;</div>
<div>6、如遇無法正常兌換或其他問題,請尋求客服幫助。客服聯繫方式:蒐索微信公衆號:萃想AI工坊,後颱留言。</div>
</div>
</div>
<template #footer>
<div class="text-end">
<n-space justify="end">
<n-button @click="() => (isShowGiftCodeRedemptionModal = false)">取消</n-button>
<n-button type="info" :loading="exchangeSubmitBtnLoading" @click="handleExchangeSubmit">確定兌換</n-button>
</n-space>
</div>
</template>
</n-card>
</n-modal>
</template>
<script setup lang="ts">
import { NDropdown } from 'naive-ui'
import { h, ref } from 'vue'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import { computed, h, ref, shallowRef } from 'vue'
import { NAvatar } from 'naive-ui'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/modules/user'
import { Right, Logout } from '@icon-park/vue-next'
import { Icon } from '@icon-park/vue-next/lib/runtime'
import { onMounted } from 'vue'
import { fetchUniversalCurrency } from '@/apis/user'
import { Logout } from '@icon-park/vue-next'
import { Gift } from '@icon-park/vue-next'
import GiftCodeRedemption from '../components/gift-code-redemption.vue'
const router = useRouter()
const userStore = useUserStore()
const userInfo = userStore.userInfo
const UniversalCurrency = ref()
const options = [
{
key: 'nickName',
type: 'render',
render: renderCustomHeader,
},
{
label: '购买记录',
key: 'purchaseRecord',
icon: renderIcon(Right),
},
{
label: '使用记录',
key: 'usageRecord',
icon: renderIcon(Right),
},
{
type: 'divider',
key: 'd1',
},
const dropdownOptions = shallowRef([
{
label: '退出登录',
key: 'logout',
icon: renderIcon(Logout),
icon: () => h(Logout, { theme: 'outline', size: 12, strokeWidth: 3 }),
},
]
function getUniversalCurrency() {
fetchUniversalCurrency().then((res) => {
if (res.code !== 0) return ''
UniversalCurrency.value = res.data
})
}
])
function renderIcon(icon: Icon) {
return () => {
return h(icon, {
theme: 'outline',
size: 15,
fill: '#333639',
})
}
}
const userInfo = computed(() => userStore.userInfo)
function renderCustomHeader() {
return h('div', [
h('div', { class: 'flex w-[200px]' }, [
h(NAvatar, {
round: true,
class: 'w-[50px] h-[50px] mr-[6px] ml-[15px] mt-[10px]',
src:
userInfo.avatarUrl || 'https://mkp-dev.oss-cn-shenzhen.aliyuncs.com/game-template/20230103/1672717001352.png',
}),
h('div', { class: 'text-[16px] mt-[20px] ml-[3px]' }, [userInfo.nickName]),
]),
h('div', { class: 'flex items-center justify-between mt-[10px] mb-[8px] ml-[15px]' }, [
h('div', ['灵豆余额']),
h('div', { class: 'flex' }, [
h('span', { class: 'mr-[5px] text-[14px]' }, [UniversalCurrency.value]),
h('img', {
class: 'mr-[9px] w-[16px] h-[16px] mt-[1px] ml-[5px]',
src: new URL('@/assets/images/icon-universalcurrency.png', import.meta.url).href,
}),
]),
]),
])
}
async function handleSelect(key: string | number) {
if (key === 'purchaseRecord') {
console.log('购买记录')
} else if (key === 'usageRecord') {
console.log('使用记录')
} else if (key === 'logout') {
await userStore.logout()
router.replace({ name: 'Login' })
window.$message.success('成功退出登錄')
return
const isShowGiftCodeRedemptionModal = ref(false)
function handleDropdownSelect(key: string | number) {
switch (key) {
case 'logout':
userStore.logout()
router.replace({ name: 'Login' })
window.$message.success('已退出登錄')
break
}
}
onMounted(() => {
getUniversalCurrency()
})
</script>
<template>
<header class="flex h-[56px] justify-between">
<div class="font-600 flex items-center pl-4 text-xl">萃想智能云创</div>
<div class="flex items-center pr-4">
<div class="mx-6">
<div class="mx-3">
<n-button
class="!rounded-[6px]"
color="#fff"
text-color="#151b26"
@click="() => (isShowGiftCodeRedemptionModal = true)"
>
<template #icon><Gift theme="outline" size="14" fill="#333" :stroke-width="3" /></template>
<span class="text-[14px]">禮包碼兌換</span>
</n-button>
</div>
<!-- <div class="mx-3">
<n-button class="!rounded-[6px]" color="#ffecd4" text-color="#151b26">
<template #icon><CustomIcon class="text-lg" icon="mingcute:pig-money-line" /></template>
<span class="text-[14px]">充值</span>
</n-button>
</div> -->
<div class="ml-3 flex items-center justify-center">
<NDropdown :options="dropdownOptions" trigger="hover" class="dropdown-style" @select="handleDropdownSelect">
<NAvatar
class="cursor-pointer"
round
size="small"
object-fit="cover"
:src="
userInfo.avatarUrl ||
'https://mkp-dev.oss-cn-shenzhen.aliyuncs.com/game-template/20230103/1672717001352.png'
"
/>
</NDropdown>
</div>
<NDropdown :options="options" trigger="hover" class="dropdown-style" @select="handleSelect">
<NAvatar
class="cursor-pointer"
round
size="small"
object-fit="cover"
src="https://mkp-dev.oss-cn-shenzhen.aliyuncs.com/game-template/20230103/1672717001352.png"
/>
</NDropdown>
</div>
</header>
<GiftCodeRedemption v-model:is-show-gift-code-redemption-modal="isShowGiftCodeRedemptionModal" />
</template>
......@@ -5,7 +5,7 @@ import MainContent from './main-content.vue'
</script>
<template>
<div class="h-screen bg-[#f3f4fb]">
<div class="h-screen min-w-[1280px] bg-[#f3f4fb]">
<div class="mx-auto h-full max-w-[1440px]">
<n-layout content-class="layout-wrapper-content" class="h-full !bg-transparent">
<n-layout-header class="mb-[16px] !bg-transparent">
......
<script setup lang="ts">
import { shallowReadonly } from 'vue'
import { h, ref, shallowReadonly, watchEffect } from 'vue'
import type { MenuOption } from 'naive-ui'
import { useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { Workbench, DocumentFolder, Inbox, CommentOne } from '@icon-park/vue-next'
const router = useRouter()
const route = useRoute()
function iconConfigFactory(): any {
return { theme: 'outline', size: 14, strokeWidth: 3 }
}
const menuOptions = shallowReadonly<MenuOption[]>([
{
type: 'group',
label: '视频',
key: 'videos',
key: 'Videos',
children: [
{
label: '工作颱',
key: 'home',
onClick: () => router.push({ name: 'Home' }),
key: 'Workbench',
icon: () => h(Workbench, { ...iconConfigFactory() }),
},
{
label: '我的作品',
key: 'opus',
onClick: () => router.push({ name: 'work-videos' }),
key: 'WorkVideos',
icon: () => h(DocumentFolder, { ...iconConfigFactory() }),
},
{
label: '草稿箱',
key: 'draft',
onClick: () => router.push({ name: 'work-draft' }),
key: 'WorkDraft',
icon: () => h(Inbox, { ...iconConfigFactory() }),
},
],
},
{
type: 'group',
label: '對話',
key: 'dialogue',
key: 'Dialogue',
children: [
{
label: '我的對話',
key: 'dialogueList',
onClick: () => router.push({ name: 'DialogueList' }),
key: 'DialogueList',
icon: () => h(CommentOne, { ...iconConfigFactory() }),
},
],
},
])
const currentMenuValue = ref('')
watchEffect(() => {
currentMenuValue.value = route.name as string
})
function onMenuValueChange(key: string) {
router.push({ name: key })
}
</script>
<template>
<section class="h-full pr-[24px]">
<div class="h-full rounded-[16px] bg-white p-[16px]">
<n-menu :options="menuOptions" />
<section class="h-full overflow-hidden pr-[24px]">
<div class="h-full overflow-auto rounded-[16px] bg-white py-[16px] pl-[16px]">
<n-scrollbar>
<div class="pr-[16px]">
<n-menu :value="currentMenuValue" :options="menuOptions" :indent="12" :on-update:value="onMenuValueChange" />
</div>
</n-scrollbar>
</div>
</section>
</template>
......@@ -227,10 +227,13 @@ function handleLoginSubmit(method: LoginMethod) {
memberId: res.data.memberId,
mobilePhone: res.data.mobilePhone,
nickName: res.data.nickName,
account: res.data.account,
equityNum: res.data.equityNum,
taskConfigNum: res.data.taskConfigNum,
})
const redirectUrl = decodeURIComponent((route.query.redirect as string) || '')
router.replace({ path: redirectUrl ? redirectUrl : '/home' })
router.replace({ path: redirectUrl ? redirectUrl : '/workbench' })
window.$message.success('登錄成功')
......
export function splitTextIntoParagraphs(text: string): string[] {
const minLength = 100
const maxLength = 150
const paragraphs: string[] = []
let start = 0
while (start < text.length) {
let end = start + maxLength
if (end >= text.length) {
paragraphs.push(text.slice(start))
break
}
while (end > start + minLength && text[end] !== ' ' && text[end] !== '.') {
end--
}
if (end === start + minLength) {
end = start + maxLength
}
paragraphs.push(text.slice(start, end).trim())
start = end
}
return paragraphs
}
export function splitArticle(article: string): string[] {
const sentences = article.match(/[^。!?\n]+[。!?]/g) || []
const result: string[] = []
let currentParagraph = ''
for (const sentence of sentences) {
if (currentParagraph.length + sentence.length > 150) {
if (currentParagraph.length >= 100) {
result.push(currentParagraph.trim())
currentParagraph = sentence
} else {
currentParagraph += sentence
}
} else {
currentParagraph += sentence
}
}
if (currentParagraph.length > 0) {
result.push(currentParagraph.trim())
}
return result
}
<script setup lang="ts">
import { fetchUniversalCurrency } from '@/apis/user'
import { useUserStore } from '@/store/modules/user'
import { Right } from '@icon-park/vue-next'
import { onMounted, ref } from 'vue'
import { computed, ref } from 'vue'
import CreateDigitalHumanDialogueModal, {
DigitalHumanDialogueForm,
} from '@/views/dialogue-detail/components/create-digital-human-dialogue-modal.vue'
......@@ -11,29 +9,14 @@ import { DigitalHumanDialogueConfig } from '@/store/types/digital-human-dialogue
import { fetchSaveDigitalHumanDialogueConfig } from '@/apis/digital-human-dialogue'
import { useRouter } from 'vue-router'
const UniversalCurrency = ref()
const userStore = useUserStore()
const userInfo = userStore.userInfo
const userInfo = computed(() => userStore.userInfo)
function handleGoToCreation() {
console.log('立即创作')
}
function getUniversalCurrency() {
fetchUniversalCurrency().then((res) => {
if (res.code !== 0) return ''
UniversalCurrency.value = res.data
})
}
function handleRechargeCurrency() {
console.log('充值')
}
onMounted(() => {
getUniversalCurrency()
})
const digitalHumanDialogueStore = useDigitalHumanDialogueStore()
const router = useRouter()
......@@ -99,43 +82,33 @@ async function handleCreateDigitalHumanDialogue(digitalHumanDialogueForm: Digita
<img src="@/assets/videoSlide.png" alt="數字人生成" />
</div>
</div>
<div class="border-1 ml-[20px] h-[173px] w-[325px] rounded-[20px] bg-[#ffffff] text-[14px]">
<div>
<div class="flex text-[16px]">
<div class="ml-[20px] mt-[16px] h-[56px] w-[56px] overflow-hidden rounded-full">
<n-avatar
round
:size="56"
:src="
userInfo.avatarUrl ||
'https://mkp-dev.oss-cn-shenzhen.aliyuncs.com/game-template/20230103/1672717001352.png'
"
/>
</div>
<div class="ml-[14px] mt-[30px]">{{ userInfo.nickName }}</div>
<div class="flex items-center px-[26px] py-[20px]">
<n-avatar
round
object-fit="cover"
size="large"
:src="
userInfo.avatarUrl ||
'https://mkp-dev.oss-cn-shenzhen.aliyuncs.com/game-template/20230103/1672717001352.png'
"
/>
<div class="ml-[10px] flex-1 truncate text-[18px]">{{ userInfo.account || '-' }}</div>
</div>
<div class="flex items-center">
<div class="flex flex-1 flex-col items-center text-[16px]">
<div class="font-600 mb-2 truncate text-[18px]">{{ userInfo.taskConfigNum || '-' }}</div>
<div>創作</div>
</div>
<div class="mt-[25px] flex">
<div class="mr-[50px]">
<div class="ml-[70px] text-center text-[16px] text-[#101010]">12</div>
<div class="ml-[72px] mt-[6px]">創作</div>
</div>
<div class="h-[45px] w-[1px] bg-[#BBBBBB]"></div>
<div class="flex">
<div class="ml-[56px]">
<div class="text-center text-[16px] text-[#101010]">{{ UniversalCurrency }}</div>
<div class="mt-[6px]">餘額</div>
</div>
<div
class="ml-[15px] h-[24px] cursor-pointer leading-[24px] text-[#036CFD]"
@click="handleRechargeCurrency"
>
充值
</div>
<div class="mt-[5px] cursor-pointer">
<Right theme="outline" size="15" fill="#036CFD" />
</div>
</div>
<div class="h-[34px] w-[2px] bg-[#bbb]"></div>
<div class="flex flex-1 flex-col items-center text-[16px]">
<div class="font-600 mb-2 truncate text-[18px]">{{ userInfo.equityNum || '-' }}</div>
<div>餘額</div>
</div>
</div>
</div>
......
<script setup lang="ts">
// import { fetchRecentCreationList } from '@/apis/drafts'
// import { useUserStore } from '@/store/modules/user'
import { Right } from '@icon-park/vue-next'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// const userStore = useUserStore()
// const token = userStore.token
onMounted(() => {
// getRecentCreationList()
})
function handleGoToDrafts() {
router.push('/work/draft')
}
// function getRecentCreationList() {
// fetchRecentCreationList(token).then((res) => {
// if (res.code !== 0) return ''
// console.log(res)
// })
// }
</script>
<template>
<div class="rounded-[16px] bg-white px-[24px] pt-[24px]">
<div class="mb-[16px] flex justify-between text-[18px] text-[#000]">
<div>最近創作</div>
<div class="flex h-[22px] cursor-pointer items-center text-[14px] text-[#5b647a]" @click="handleGoToDrafts">
<div>查看全部</div>
<Right theme="outline" size="16" fill="#5b647a" />
</div>
</div>
<div class="overflow-y-auto text-nowrap">
<n-scrollbar :x-scrollable="true">
<div v-for="item in 7" :key="item" class="mr-[10px] inline-block pb-[16px] text-center">
<div
class="relative mb-[12px] h-[145px] w-[145px] cursor-pointer overflow-hidden rounded-[12px] bg-[#f3f4fb]"
>
<img
class="z-1 relative h-full w-full object-contain transition-[scale] duration-300 ease-in-out hover:scale-110"
src="https://meta-human-editor-prd.cdn.bcebos.com/2024-04-17T16:53:29.223639/sd-qdmv5bsghiyx1xb9z_1713344008761.png?x-bce-process=image/format,f_auto/resize,w_500/quality,q_90"
alt="cover-image"
/>
<img
class="absolute left-0 top-0 h-full w-full object-cover blur-[32px]"
src="https://meta-human-editor-prd.cdn.bcebos.com/2024-04-17T16:53:29.223639/sd-qdmv5bsghiyx1xb9z_1713344008761.png?x-bce-process=image/format,f_auto/resize,w_500/quality,q_90"
alt="cover-image"
/>
<div
class="absolute bottom-[8px] left-[8px] z-10 rounded-[4px] bg-[rgba(0,0,0,.5)] px-[6px] py-[2px] text-[12px] text-[#fff]"
>
精品视频
</div>
</div>
<n-ellipsis class="!max-w-[150px] cursor-default text-[#151b26]">金融课程2024-09-11 09:48:41</n-ellipsis>
</div>
</n-scrollbar>
</div>
</div>
</template>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment