Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporateCulture-qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
王立鹏
corporateCulture-qd
Commits
dedb7b4a
Commit
dedb7b4a
authored
Dec 03, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 feat: 完善问吧、 scroll等
parent
82eec017
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
578 additions
and
169 deletions
+578
-169
App.vue
src/App.vue
+0
-1
types.ts
src/api/article/types.ts
+1
-0
index.ts
src/api/shop/index.ts
+0
-1
index.vue
src/components/common/Comment/index.vue
+7
-2
index.vue
src/components/common/PublishBox/index.vue
+2
-2
useScrollTop.tsx
src/hooks/useScrollTop.tsx
+23
-3
onlineTime.vue
src/layoutCulture/components/onlineTime.vue
+2
-2
index.vue
src/layoutCulture/index.vue
+19
-4
guards.ts
src/router/guards.ts
+7
-3
scrollStorage.ts
src/router/scrollStorage.ts
+11
-1
reset.css
src/style/reset.css
+3
-0
axios.ts
src/utils/request/axios.ts
+0
-1
index.vue
src/views/homePage/askTab/index.vue
+68
-33
index.vue
src/views/homePage/index.vue
+28
-14
exchangeContent.tsx
src/views/pointsStore/components/exchangeContent.tsx
+3
-3
index.vue
src/views/pointsStore/index.vue
+127
-91
rewardDialog.vue
src/views/videoDetail/components/rewardDialog.vue
+6
-3
index.vue
src/views/videoDetail/index.vue
+271
-5
No files found.
src/App.vue
View file @
dedb7b4a
...
@@ -13,7 +13,6 @@ const locale = ref(zhCn)
...
@@ -13,7 +13,6 @@ const locale = ref(zhCn)
// userStore.fetchUserInfo().then((res) => {
// userStore.fetchUserInfo().then((res) => {
// console.log(res)
// console.log(res)
// })
// })
console
.
log
(
'App.vue mounted'
)
onMounted
(()
=>
{
onMounted
(()
=>
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
initWxConfig
()
initWxConfig
()
...
...
src/api/article/types.ts
View file @
dedb7b4a
...
@@ -147,6 +147,7 @@ export interface ArticleItemDto {
...
@@ -147,6 +147,7 @@ export interface ArticleItemDto {
showComment
?:
boolean
showComment
?:
boolean
relateColumn
?:
string
relateColumn
?:
string
rewardNum
:
number
rewardNum
:
number
isExpand
:
boolean
}
}
/**
/**
...
...
src/api/shop/index.ts
View file @
dedb7b4a
...
@@ -3,7 +3,6 @@ import type { BackendServicePageResult, PageSearchParams } from '@/utils/request
...
@@ -3,7 +3,6 @@ import type { BackendServicePageResult, PageSearchParams } from '@/utils/request
import
type
{
import
type
{
ExchangeGoodsParams
,
ExchangeGoodsParams
,
ExchangeGoodsRecordItemDto
,
ExchangeGoodsRecordItemDto
,
ShopItemDto
,
ShopSearchParams
,
ShopSearchParams
,
YaBiData
,
YaBiData
,
ExchangeYabiRecordItemDto
,
ExchangeYabiRecordItemDto
,
...
...
src/components/common/Comment/index.vue
View file @
dedb7b4a
...
@@ -254,7 +254,7 @@
...
@@ -254,7 +254,7 @@
</div>
</div>
</div>
</div>
<div
class=
"px-4"
>
<div
class=
"px-4"
>
<el-divider
class=
"my-
1
"
/>
<el-divider
class=
"my-
2!
"
/>
</div>
</div>
</div>
</div>
<!-- 底部分页 -->
<!-- 底部分页 -->
...
@@ -289,6 +289,10 @@ const { id, defaultSize = 10 } = defineProps<{
...
@@ -289,6 +289,10 @@ const { id, defaultSize = 10 } = defineProps<{
defaultSize
?:
number
defaultSize
?:
number
}
>
()
}
>
()
const
emit
=
defineEmits
<
{
(
e
:
'commentSuccess'
):
void
}
>
()
const
total
=
defineModel
<
number
>
(
'total'
,
{
required
:
true
,
default
:
0
})
const
total
=
defineModel
<
number
>
(
'total'
,
{
required
:
true
,
default
:
0
})
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
...
@@ -393,6 +397,7 @@ const handleMyComment = async () => {
...
@@ -393,6 +397,7 @@ const handleMyComment = async () => {
refresh
()
refresh
()
myComment
.
value
=
''
myComment
.
value
=
''
total
.
value
++
total
.
value
++
emit
(
'commentSuccess'
)
}
}
const
handleComment
=
async
(
index
:
number
)
=>
{
const
handleComment
=
async
(
index
:
number
)
=>
{
...
@@ -405,9 +410,9 @@ const handleComment = async (index: number) => {
...
@@ -405,9 +410,9 @@ const handleComment = async (index: number) => {
comment
.
value
=
''
comment
.
value
=
''
total
.
value
++
total
.
value
++
handleBackTopChildren
(
index
)
handleBackTopChildren
(
index
)
// 只需要刷新当前的评论
// 只需要刷新当前的评论
search
()
search
()
emit
(
'commentSuccess'
)
}
}
// 展开回复 获取子评论列表
// 展开回复 获取子评论列表
...
...
src/components/common/PublishBox/index.vue
View file @
dedb7b4a
...
@@ -227,11 +227,11 @@ const handleDeleteImg = (img: string) => {
...
@@ -227,11 +227,11 @@ const handleDeleteImg = (img: string) => {
const
validateForm
=
()
=>
{
const
validateForm
=
()
=>
{
if
(
!
form
.
value
.
title
)
{
if
(
!
form
.
value
.
title
)
{
ElMessage
.
error
(
'请输入
实践
标题'
)
ElMessage
.
error
(
'请输入标题'
)
return
false
return
false
}
}
if
(
!
form
.
value
.
content
)
{
if
(
!
form
.
value
.
content
)
{
ElMessage
.
error
(
'请输入
实践
内容'
)
ElMessage
.
error
(
'请输入内容'
)
return
false
return
false
}
}
if
(
!
form
.
value
.
mainTagId
)
{
if
(
!
form
.
value
.
mainTagId
)
{
...
...
src/hooks/useScrollTop.tsx
View file @
dedb7b4a
...
@@ -26,9 +26,13 @@ export const useScrollTop = (
...
@@ -26,9 +26,13 @@ export const useScrollTop = (
)
=>
{
)
=>
{
const
{
compatFixedHeader
=
true
}
=
options
const
{
compatFixedHeader
=
true
}
=
options
const
handleBackTop
=
(
currentIndex
:
number
=
0
)
=>
{
const
handleBackTop
=
(
currentIndex
:
number
=
0
):
Promise
<
void
>
=>
{
return
new
Promise
((
resolve
)
=>
{
const
initDoms
=
unref
(
el
)
const
initDoms
=
unref
(
el
)
if
(
!
initDoms
)
return
if
(
!
initDoms
)
{
resolve
()
return
}
let
doms
=
[]
let
doms
=
[]
...
@@ -38,6 +42,20 @@ export const useScrollTop = (
...
@@ -38,6 +42,20 @@ export const useScrollTop = (
doms
=
initDoms
doms
=
initDoms
}
}
const
finish
=
()
=>
{
console
.
log
(
'scrollend'
)
resolve
()
}
// 手动添加一次scrollend事件
window
.
addEventListener
(
'scrollend'
,
finish
,
{
once
:
true
})
// 有时候在滚轮就在原位置 不会触发 scrollend事件 所以手动触发一次
setTimeout
(()
=>
{
window
.
removeEventListener
(
'scrollend'
,
finish
)
resolve
()
},
1000
)
// 下面会触发scrollend事件一次
const
dom
=
doms
[
currentIndex
]
as
HTMLElement
|
Window
const
dom
=
doms
[
currentIndex
]
as
HTMLElement
|
Window
if
(
dom
instanceof
Window
)
{
if
(
dom
instanceof
Window
)
{
window
.
scrollTo
({
window
.
scrollTo
({
...
@@ -50,15 +68,17 @@ export const useScrollTop = (
...
@@ -50,15 +68,17 @@ export const useScrollTop = (
if
(
compatFixedHeader
)
{
if
(
compatFixedHeader
)
{
const
top
=
dom
?.
getBoundingClientRect
?.().
top
+
window
.
scrollY
-
52
const
top
=
dom
?.
getBoundingClientRect
?.().
top
+
window
.
scrollY
-
52
window
.
scrollTo
({
window
.
scrollTo
({
top
,
top
,
// 可以设置滚动的距离
behavior
:
'smooth'
,
behavior
:
'smooth'
,
})
})
}
else
{
}
else
{
dom
?.
scrollIntoView
?.({
dom
?.
scrollIntoView
?.({
// 只能滚动到dom的顶部 不能设置滚动的距离
behavior
:
'smooth'
,
behavior
:
'smooth'
,
block
:
'start'
,
block
:
'start'
,
})
})
}
}
})
}
}
return
{
return
{
...
...
src/layoutCulture/components/onlineTime.vue
View file @
dedb7b4a
...
@@ -106,13 +106,13 @@ const formatSeconds = computed(() => {
...
@@ -106,13 +106,13 @@ const formatSeconds = computed(() => {
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
const
{
data
}
=
await
getTodayOnlineSeconds
()
const
{
data
}
=
await
getTodayOnlineSeconds
()
currentSeconds
.
value
=
parseInt
(
data
)
currentSeconds
.
value
=
data
})
})
setInterval
(()
=>
{
setInterval
(()
=>
{
currentSeconds
.
value
++
currentSeconds
.
value
++
},
1000
)
},
1000
)
set
Timeout
(
async
()
=>
{
set
Interval
(
async
()
=>
{
heartbeat
()
heartbeat
()
},
1000
*
30
)
},
1000
*
30
)
</
script
>
</
script
>
src/layoutCulture/index.vue
View file @
dedb7b4a
...
@@ -74,8 +74,18 @@
...
@@ -74,8 +74,18 @@
<el-dropdown-menu>
<el-dropdown-menu>
<el-dropdown-item
:command=
"ArticleTypeEnum.POST"
>
帖子
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.POST"
>
帖子
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.PRACTICE"
>
实践
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.PRACTICE"
>
实践
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.COLUMN"
>
专栏
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.VIDEO"
>
视频
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.INTERVIEW"
>
专访
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.QUESTION"
>
问吧
</el-dropdown-item>
<el-dropdown-item
v-if=
"userInfo.isOfficialAccount"
:command=
"ArticleTypeEnum.COLUMN"
>
专栏
</el-dropdown-item
>
<el-dropdown-item
v-if=
"userInfo.isOfficialAccount"
:command=
"ArticleTypeEnum.INTERVIEW"
>
专访
</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown-menu>
</
template
>
</
template
>
</el-dropdown>
</el-dropdown>
...
@@ -133,9 +143,14 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
...
@@ -133,9 +143,14 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
return
key
return
key
}
}
const
handlePost
=
async
(
type
:
string
)
=>
{
const
handlePost
=
async
(
type
:
ArticleTypeEnum
)
=>
{
// router.push(command)
if
(
type
===
ArticleTypeEnum
.
VIDEO
)
{
router
.
push
(
'/publishVideo'
)
}
else
if
(
type
===
ArticleTypeEnum
.
QUESTION
)
{
router
.
push
(
`/homePage/askTab#tabsRef?t=
${
Date
.
now
()}
`
)
}
else
{
PublishDialogRef
.
value
?.
open
(
type
)
PublishDialogRef
.
value
?.
open
(
type
)
}
}
}
const
isDropdownHover
=
ref
(
false
)
const
isDropdownHover
=
ref
(
false
)
</
script
>
</
script
>
...
...
src/router/guards.ts
View file @
dedb7b4a
...
@@ -22,9 +22,9 @@ export function registerRouterGuards(router: Router) {
...
@@ -22,9 +22,9 @@ export function registerRouterGuards(router: Router) {
const
code
=
parseCode
(
to
.
fullPath
)
const
code
=
parseCode
(
to
.
fullPath
)
// code是否来自企业微信 1 不是 0 是 2 开发人员登录方式
// code是否来自企业微信 1 不是 0 是 2 开发人员登录方式
const
isCodeLogin
=
parseIsCodeLogin
()
//
const isCodeLogin = parseIsCodeLogin()
const
cutEmail
=
parseIsCutEmail
()
//
const cutEmail = parseIsCutEmail()
console
.
log
(
code
,
isCodeLogin
,
cutEmail
)
//
console.log(code, isCodeLogin, cutEmail)
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
if
(
code
)
{
if
(
code
)
{
console
.
log
(
'code'
,
code
)
console
.
log
(
'code'
,
code
)
...
@@ -41,4 +41,8 @@ export function registerRouterGuards(router: Router) {
...
@@ -41,4 +41,8 @@ export function registerRouterGuards(router: Router) {
return
true
return
true
}
}
})
})
// router.afterEach((to, from) => {
// console.log('afterEach to', to)
// console.log('afterEach from', from)
// })
}
}
src/router/scrollStorage.ts
View file @
dedb7b4a
...
@@ -37,6 +37,10 @@ export function clearScrollPosition(path?: string): void {
...
@@ -37,6 +37,10 @@ export function clearScrollPosition(path?: string): void {
*/
*/
export
const
scrollBehavior
:
RouterScrollBehavior
=
(
to
,
from
,
savedPosition
)
=>
{
export
const
scrollBehavior
:
RouterScrollBehavior
=
(
to
,
from
,
savedPosition
)
=>
{
return
new
Promise
((
resolve
)
=>
{
return
new
Promise
((
resolve
)
=>
{
console
.
log
(
'触发路由滚动'
)
// setTimeout(() => {
// console.log(document.querySelector('#tabsRef'))
// }, 1000)
// 1. 如果有浏览器保存的位置(前进/后退),优先使用
// 1. 如果有浏览器保存的位置(前进/后退),优先使用
if
(
savedPosition
)
{
if
(
savedPosition
)
{
resolve
(
savedPosition
)
resolve
(
savedPosition
)
...
@@ -45,20 +49,26 @@ export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) =>
...
@@ -45,20 +49,26 @@ export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) =>
// 2. 如果有锚点,滚动到锚点
// 2. 如果有锚点,滚动到锚点
if
(
to
.
hash
)
{
if
(
to
.
hash
)
{
console
.
log
(
to
.
hash
)
setTimeout
(()
=>
{
resolve
({
resolve
({
el
:
to
.
hash
,
el
:
to
.
hash
.
split
(
'?'
)[
0
],
//去除?后面的查询字符串
behavior
:
'smooth'
,
// 平滑滚动
behavior
:
'smooth'
,
// 平滑滚动
top
:
52
,
})
})
},
800
)
return
return
}
}
// 3. 检查是否有保存的滚动位置
// 3. 检查是否有保存的滚动位置
const
savedScrollY
=
scrollPositionMap
.
get
(
to
.
fullPath
)
const
savedScrollY
=
scrollPositionMap
.
get
(
to
.
fullPath
)
if
(
savedScrollY
!==
undefined
)
{
if
(
savedScrollY
!==
undefined
)
{
setTimeout
(()
=>
{
resolve
({
resolve
({
top
:
savedScrollY
,
top
:
savedScrollY
,
behavior
:
'smooth'
,
behavior
:
'smooth'
,
})
})
},
300
)
return
return
}
}
...
...
src/style/reset.css
View file @
dedb7b4a
...
@@ -260,3 +260,6 @@ a {
...
@@ -260,3 +260,6 @@ a {
*/
*/
/* End of reset.css */
/* End of reset.css */
body
{
font-family
:
'Segoe UI Emoji'
;
}
src/utils/request/axios.ts
View file @
dedb7b4a
...
@@ -22,7 +22,6 @@ export default class DhRequest {
...
@@ -22,7 +22,6 @@ export default class DhRequest {
this
.
instance
.
interceptors
.
request
.
use
(
this
.
instance
.
interceptors
.
request
.
use
(
async
(
config
)
=>
{
async
(
config
)
=>
{
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
console
.
log
(
userStore
.
token
)
const
token
=
userStore
.
token
const
token
=
userStore
.
token
if
(
token
)
{
if
(
token
)
{
config
.
headers
.
Authorization
=
token
config
.
headers
.
Authorization
=
token
...
...
src/views/homePage/askTab/index.vue
View file @
dedb7b4a
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
<!-- 主要内容区域 -->
<!-- 主要内容区域 -->
<div
class=
"mx-auto py-6"
>
<div
class=
"mx-auto py-6"
>
<PublishBox
:type=
"ArticleTypeEnum.QUESTION"
/>
<PublishBox
:type=
"ArticleTypeEnum.QUESTION"
ref=
"publishBoxRef"
/>
<div
v-loading=
"loading"
v-if=
"list.length"
>
<div
v-loading=
"loading"
v-if=
"list.length"
>
<!-- 问题列表 -->
<!-- 问题列表 -->
<div
class=
"space-y-4"
>
<div
class=
"space-y-4"
>
...
@@ -38,22 +38,29 @@
...
@@ -38,22 +38,29 @@
/>
/>
<!-- 问题内容 -->
<!-- 问题内容 -->
<div>
<div>
<p
class=
"text-gray-600 text-base leading-relaxed"
>
<p
:ref=
"(e) => (contentRefList[index] = e as HTMLElement)"
class=
"text-gray-600 text-base leading-relaxed transition-all duration-300"
:class=
"
{ 'line-clamp-3': !item.isExpand }"
>
{{
item
.
content
}}
{{
item
.
content
}}
</p>
</p>
<!--
line-clamp-2
-->
<!--
展开/收起按钮 靠右边布局
-->
<
!-- 展开/收起按钮 --
>
<
div
class=
"flex justify-end"
>
<!--
<el-button
<el-button
v-if=
"item.content.length > 100 && !item.isExpand
"
v-if=
"isOverThreeLine(index)
"
@
click=
"handleReadMore
(item)"
@
click=
"handleExpand
(item)"
type=
"primary"
type=
"primary"
text
text
size=
"small"
size=
"small"
class=
"mt-2 p-0
text-blue-500"
class=
"
text-blue-500"
>
>
阅读全文
{{
item
.
isExpand
?
'收起'
:
'阅读全文'
}}
<el-icon
class=
"ml-1"
><ArrowDown
/></el-icon>
<el-icon
class=
"ml-1"
:class=
"
{ 'rotate-180': item.isExpand }">
</el-button>
-->
<ArrowDown
/>
</el-icon>
</el-button>
</div>
</div>
</div>
</div>
</div>
<!-- 底部信息栏 -->
<!-- 底部信息栏 -->
...
@@ -73,13 +80,13 @@
...
@@ -73,13 +80,13 @@
}}
</span>
}}
</span>
<!-- 操作按钮组 -->
<!-- 操作按钮组 -->
<
!--
<
div
class=
"flex items-center"
>
<div
class=
"flex items-center"
>
<el-button
size=
"small"
plain
>
<el-button
size=
"small"
plain
>
<el-icon><Plus
/></el-icon>
<el-icon><Plus
/></el-icon>
添加
添加
</el-button>
</el-button>
<el-button
size=
"small"
plain
@
click=
"handle
Answer
(index)"
>
<el-button
size=
"small"
plain
@
click=
"handle
Comment
(index)"
>
<el-icon><Edit
/></el-icon>
<el-icon><Edit
/></el-icon>
回答
回答
</el-button>
</el-button>
...
@@ -93,7 +100,7 @@
...
@@ -93,7 +100,7 @@
<el-icon><Warning
/></el-icon>
<el-icon><Warning
/></el-icon>
举报
举报
</el-button>
</el-button>
</div>
-->
</div>
</div>
</div>
<!-- 右侧:统计信息 -->
<!-- 右侧:统计信息 -->
...
@@ -129,10 +136,11 @@
...
@@ -129,10 +136,11 @@
</div>
</div>
<Transition
name=
"fade"
>
<Transition
name=
"fade"
>
<Comment
<Comment
v-
if
=
"item.showComment"
v-
show
=
"item.showComment"
:id=
"item.id"
:id=
"item.id"
:total=
"item.replyCount"
:total=
"item.replyCount"
:defaultSize=
"5"
:defaultSize=
"5"
@
commentSuccess=
"() => handleCommentSuccess(index)"
/>
/>
</Transition>
</Transition>
</el-card>
</el-card>
...
@@ -171,21 +179,19 @@
...
@@ -171,21 +179,19 @@
</div>
</div>
</
template
>
</
template
>
</div>
</div>
<el-tour
v-model=
"open"
>
<el-tour-step
:target=
"publishBoxRef?.$el"
placement=
"right"
>
<div>
在这里发布你的问题
</div>
</el-tour-step>
<
template
#
indicators
></
template
>
</el-tour>
</div>
</div>
</template>
</template>
<
script
setup
lang=
"ts"
name=
"CultureAsk"
>
<
script
setup
lang=
"ts"
name=
"CultureAsk"
>
import
Tabs
from
'@/components/common/Tabs'
import
Tabs
from
'@/components/common/Tabs'
import
{
import
{
Star
,
View
,
ChatDotRound
,
Refresh
}
from
'@element-plus/icons-vue'
Plus
,
Edit
,
Star
,
Warning
,
View
,
ChatDotRound
,
ArrowDown
,
Refresh
,
}
from
'@element-plus/icons-vue'
import
Comment
from
'@/components/common/Comment/index.vue'
import
Comment
from
'@/components/common/Comment/index.vue'
import
{
useScrollTop
,
usePageSearch
}
from
'@/hooks'
import
{
useScrollTop
,
usePageSearch
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
TABS_REF_KEY
}
from
'@/constants'
...
@@ -193,7 +199,12 @@ import PublishBox from '@/components/common/PublishBox/index.vue'
...
@@ -193,7 +199,12 @@ import PublishBox from '@/components/common/PublishBox/index.vue'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
dayjs
from
'dayjs'
import
dayjs
from
'dayjs'
import
{
getArticleList
,
addOrCanceArticlelCollect
}
from
'@/api'
import
{
getArticleList
,
addOrCanceArticlelCollect
}
from
'@/api'
import
{
ArticleItemDto
}
from
'@/api/article/types'
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
const
route
=
useRoute
()
const
open
=
ref
(
false
)
const
publishBoxRef
=
useTemplateRef
(
'publishBoxRef'
)
const
activeTab
=
ref
(
'最新'
)
const
activeTab
=
ref
(
'最新'
)
const
tabs
=
[
const
tabs
=
[
{
label
:
'最新'
,
value
:
'最新'
},
{
label
:
'最新'
,
value
:
'最新'
},
...
@@ -209,6 +220,7 @@ const { list, total, searchParams, loading, refresh } = usePageSearch(getArticle
...
@@ -209,6 +220,7 @@ const { list, total, searchParams, loading, refresh } = usePageSearch(getArticle
list
.
map
((
item
)
=>
({
list
.
map
((
item
)
=>
({
...
item
,
...
item
,
showComment
:
false
,
showComment
:
false
,
isExpand
:
false
,
})),
})),
})
})
...
@@ -227,13 +239,8 @@ const handleComment = (index: number) => {
...
@@ -227,13 +239,8 @@ const handleComment = (index: number) => {
list
.
value
[
index
]
!
.
showComment
=
!
list
.
value
[
index
]
!
.
showComment
list
.
value
[
index
]
!
.
showComment
=
!
list
.
value
[
index
]
!
.
showComment
}
}
const
handleAnswer
=
(
index
:
number
)
=>
{
const
handleCommentSuccess
=
(
index
:
number
)
=>
{
console
.
log
(
index
)
list
.
value
[
index
]
!
.
replyCount
++
}
const
handleReadMore
=
(
item
:
any
)
=>
{
// 直接展开
item
.
isExpand
=
true
}
}
const
handleRefresh
=
()
=>
{
const
handleRefresh
=
()
=>
{
...
@@ -243,6 +250,34 @@ const handleRefresh = () => {
...
@@ -243,6 +250,34 @@ const handleRefresh = () => {
const
handleTabChange
=
()
=>
{
const
handleTabChange
=
()
=>
{
handleRefresh
()
handleRefresh
()
}
}
const
contentRefList
=
ref
<
HTMLElement
[]
>
([])
// 检测当前是否超过三行 要用到具体的dom
const
isOverThreeLine
=
(
index
:
number
)
=>
{
if
(
!
contentRefList
.
value
[
index
])
return
false
const
lineHeight
=
parseFloat
(
getComputedStyle
(
contentRefList
.
value
[
index
]).
lineHeight
)
const
height
=
contentRefList
.
value
[
index
]
!
.
scrollHeight
const
maxHeight
=
lineHeight
*
3
return
height
>
maxHeight
}
const
handleExpand
=
(
item
:
ArticleItemDto
)
=>
{
item
.
isExpand
=
!
item
.
isExpand
}
// 监听路由变化 如果包含#tabsRef 则打开漫游
watch
(
()
=>
route
.
fullPath
,
(
newVal
)
=>
{
if
(
newVal
.
includes
(
'#tabsRef'
))
{
setTimeout
(()
=>
{
open
.
value
=
true
},
1500
)
}
},
)
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
.fade-enter-from
,
.fade-enter-from
,
...
...
src/views/homePage/index.vue
View file @
dedb7b4a
...
@@ -10,7 +10,11 @@
...
@@ -10,7 +10,11 @@
<div
class=
"flex gap-3"
>
<div
class=
"flex gap-3"
>
<div
class=
"left flex-1 basis-full xl:basis-3/4 transition-all duration-500"
>
<div
class=
"left flex-1 basis-full xl:basis-3/4 transition-all duration-500"
>
<div
ref=
"tabsRef"
class=
"tabs-container h-75px flex relative rounded-lg mb-3 shadow-md"
>
<div
id=
"tabsRef"
ref=
"tabsRef"
class=
"tabs-container h-75px flex relative rounded-lg mb-3 shadow-md"
>
<div
<div
v-for=
"tab in tabs"
v-for=
"tab in tabs"
:key=
"tab.path"
:key=
"tab.path"
...
@@ -202,7 +206,7 @@
...
@@ -202,7 +206,7 @@
>
>
<div
class=
"flex items-center min-w-0 flex-1"
>
<div
class=
"flex items-center min-w-0 flex-1"
>
<div
class=
"h-70px flex items-center justify-center"
>
<div
class=
"h-70px flex items-center justify-center"
>
<svg-icon
:name=
"item.svgName"
size=
"
50
"
/>
<svg-icon
:name=
"item.svgName"
size=
"
46
"
/>
</div>
</div>
<div
<div
class=
"flex flex-col items-start justify-center ml-2 sm:ml-3 min-w-0 flex-1"
class=
"flex flex-col items-start justify-center ml-2 sm:ml-3 min-w-0 flex-1"
...
@@ -228,7 +232,7 @@
...
@@ -228,7 +232,7 @@
class
=
"w-72px h-32px shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none text-xs sm:text-sm rounded-full"
class
=
"w-72px h-32px shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none text-xs sm:text-sm rounded-full"
:
class
=
"[
:
class
=
"[
item.currentCount === item.limitCount
item.currentCount === item.limitCount
? 'bg-#FFC5A1'
? 'bg-#FFC5A1
cursor-not-allowed
'
: 'bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] hover:-translate-y-1 transition-all duration-200 cursor-pointer',
: 'bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] hover:-translate-y-1 transition-all duration-200 cursor-pointer',
]"
]"
@
click
=
"handleTask(item)"
@
click
=
"handleTask(item)"
...
@@ -255,6 +259,13 @@
...
@@ -255,6 +259,13 @@
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
el
-
tour
v
-
model
=
"open"
>
<
el
-
tour
-
step
:
target
=
"dailySignBtnRef"
>
<
div
>
签到成功后,可以获得亚币奖励
<
/div
>
<
/el-tour-step
>
<
template
#
indicators
><
/template
>
<
/el-tour
>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
<
script
setup
lang
=
"ts"
>
<
script
setup
lang
=
"ts"
>
...
@@ -267,21 +278,20 @@ import { getTaskList, dailySign, getCarouselList, getUserAccountData, getRecordD
...
@@ -267,21 +278,20 @@ import { getTaskList, dailySign, getCarouselList, getUserAccountData, getRecordD
import
{
TaskTypeEnum
,
TaskDateLimitTypeText
}
from
'@/constants'
import
{
TaskTypeEnum
,
TaskDateLimitTypeText
}
from
'@/constants'
import
type
{
CarouselItemDto
,
TaskItemDto
,
UserAccountDataDto
,
UserRecordDataDto
}
from
'@/api'
import
type
{
CarouselItemDto
,
TaskItemDto
,
UserAccountDataDto
,
UserRecordDataDto
}
from
'@/api'
import
{
TABS_REF_KEY
,
levelListOptions
}
from
'@/constants'
import
{
TABS_REF_KEY
,
levelListOptions
}
from
'@/constants'
import
{
useScrollTop
,
useHintAnimation
}
from
'@/hooks'
import
{
useScrollTop
}
from
'@/hooks'
const
route
=
useRoute
()
const
route
=
useRoute
()
const
router
=
useRouter
()
const
router
=
useRouter
()
const
open
=
ref
(
false
)
const
levelContainerRef
=
useTemplateRef
<
HTMLElement
>
(
'levelContainerRef'
)
const
levelContainerRef
=
useTemplateRef
<
HTMLElement
>
(
'levelContainerRef'
)
const
dailySignBtnRef
=
useTemplateRef
<
HTMLElement
>
(
'dailySignBtnRef'
)
const
dailySignBtnRef
=
useTemplateRef
<
HTMLElement
>
(
'dailySignBtnRef'
)
const
{
handleBackTop
}
=
useScrollTop
(
levelContainerRef
)
const
{
handleBackTop
}
=
useScrollTop
(
levelContainerRef
)
const
{
triggerAnimation
}
=
useHintAnimation
(
dailySignBtnRef
,
{
classes
:
[
'scale-bounce'
,
'highlight'
,
'shake-y'
],
}
)
const
getThirdLevelKey
=
(
route
:
RouteLocationNormalizedLoadedGeneric
)
=>
{
const
getThirdLevelKey
=
(
route
:
RouteLocationNormalizedLoadedGeneric
)
=>
{
// console.log(route, '三级路由')
console
.
log
(
route
.
fullPath
,
'三级路由'
)
return
route
.
fullPath
// console.log(route.path, 11111111111111)
// return route.fullPath // fullpath带有query参数
return
route
.
path
}
}
const
carouselList
=
ref
<
CarouselItemDto
[]
>
([])
const
carouselList
=
ref
<
CarouselItemDto
[]
>
([])
...
@@ -364,21 +374,23 @@ const onDailySign = async () => {
...
@@ -364,21 +374,23 @@ const onDailySign = async () => {
const
{
data
}
=
await
getUserAccountData
()
const
{
data
}
=
await
getUserAccountData
()
userAccountData
.
value
=
data
userAccountData
.
value
=
data
ElMessage
.
success
(
'签到成功'
)
ElMessage
.
success
(
'签到成功'
)
open
.
value
=
false
}
}
const
handleTask
=
(
item
:
TaskItemDto
)
=>
{
const
handleTask
=
async
(
item
:
TaskItemDto
)
=>
{
console
.
log
(
item
)
console
.
log
(
item
)
if
(
item
.
currentCount
===
item
.
limitCount
)
return
if
(
item
.
currentCount
===
item
.
limitCount
)
return
// 先暂时写死
// 先暂时写死
if
(
item
.
svgName
===
'daily_sign'
)
{
if
(
item
.
svgName
===
'daily_sign'
)
{
//每日签到
//每日签到
handleBackTop
()
await
handleBackTop
()
triggerAnimation
()
open
.
value
=
true
// triggerAnimation()
}
else
if
(
item
.
svgName
===
'valid_comments'
)
{
}
else
if
(
item
.
svgName
===
'valid_comments'
)
{
// 发布评论
// 发布评论
ElMessage
.
info
(
'快去文章评论区去发表评论吧~'
)
ElMessage
.
info
(
'快去文章评论区去发表评论吧~'
)
}
else
if
(
item
.
svgName
===
'topic_publish'
)
{
}
else
if
(
item
.
svgName
===
'topic_publish'
)
{
router
.
push
(
'/homePage/askTab'
)
router
.
push
(
`/homePage/askTab#tabsRef?t=${Date.now()
}
`
)
// 加一个时间戳 是因为对于不同的时间戳 用的keepalive是 path 都是同一个组件 在组件里面监听watch(()=>to.fullPath) 每次都能监听得到
}
else
if
(
item
.
svgName
===
'answer_ask'
)
{
}
else
if
(
item
.
svgName
===
'answer_ask'
)
{
// 回答问题
// 回答问题
router
.
push
(
'/homePage/askTab'
)
router
.
push
(
'/homePage/askTab'
)
...
@@ -417,9 +429,11 @@ const refreshTaskData = async (refreshRecordData = false) => {
...
@@ -417,9 +429,11 @@ const refreshTaskData = async (refreshRecordData = false) => {
specialTaskList
.
value
=
data
.
filter
((
item
)
=>
item
.
taskType
===
TaskTypeEnum
.
SPECIAL_TASK
)
specialTaskList
.
value
=
data
.
filter
((
item
)
=>
item
.
taskType
===
TaskTypeEnum
.
SPECIAL_TASK
)
}
}
// 刷新任务进度
onActivated
(()
=>
{
onActivated
(()
=>
{
refreshTaskData
(
false
)
refreshTaskData
(
false
)
}
)
}
)
onMounted
(()
=>
{
onMounted
(()
=>
{
initPage
()
initPage
()
}
)
}
)
...
...
src/views/pointsStore/components/exchangeContent.tsx
View file @
dedb7b4a
...
@@ -2,11 +2,11 @@
...
@@ -2,11 +2,11 @@
* 确认兑换商品的弹窗内容
* 确认兑换商品的弹窗内容
*/
*/
import
{
ShopGoodsTypeEnum
,
regionListOptions
}
from
'@/constants'
import
{
ShopGoodsTypeEnum
,
regionListOptions
}
from
'@/constants'
import
type
{
ExchangeGoodsParams
,
ShopItemDto
}
from
'@/api'
import
type
{
BackendShopItemDto
,
ExchangeGoodsParams
}
from
'@/api'
import
type
{
SetupContext
}
from
'vue'
import
type
{
SetupContext
}
from
'vue'
type
ExchangeContentProps
=
{
type
ExchangeContentProps
=
{
item
:
ShopItemDto
item
:
Backend
ShopItemDto
modelValue
:
ExchangeGoodsParams
modelValue
:
ExchangeGoodsParams
}
}
type
ExchangeContentEvents
=
{
type
ExchangeContentEvents
=
{
...
@@ -90,7 +90,7 @@ export default function ExchangeContent(
...
@@ -90,7 +90,7 @@ export default function ExchangeContent(
ExchangeContent
.
props
=
{
ExchangeContent
.
props
=
{
item
:
{
item
:
{
type
:
Object
as
PropType
<
ShopItemDto
>
,
type
:
Object
as
PropType
<
Backend
ShopItemDto
>
,
required
:
true
,
required
:
true
,
},
},
modelValue
:
{
modelValue
:
{
...
...
src/views/pointsStore/index.vue
View file @
dedb7b4a
<
template
>
<
template
>
<div
class=
"min-h-screen bg-white"
>
<div>
<div
class=
"max-w-[1440px] mx-auto"
>
<div
class=
"flex gap-6"
>
<!-- 顶部积分卡片 -->
<!-- 左侧:商品列表区域 -->
<div
class=
"flex-1 min-w-0"
>
<!-- 虚拟装饰区域 -->
<div
<div
class=
"bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg p-5 shadow-sm mb-8 border border-purple-100"
class=
"bg-white/90 backdrop-blur-sm rounded-2xl p-6 shadow-lg mb-6 border border-white/60"
>
<div
class=
"flex justify-between items-center flex-wrap gap-4"
>
<div
class=
"flex items-baseline gap-3"
>
<span
class=
"text-gray-700 text-base font-medium"
>
当前YA币:
</span>
<span
class=
"text-[#8b5cf6] text-4xl font-bold"
>
{{
currentYaBi
}}
</span>
</div>
<div
class=
"flex gap-3"
>
<button
class=
"cursor-pointer px-6 py-2.5 bg-white text-[#8b5cf6] rounded-full text-sm font-medium border-2 border-[#8b5cf6] hover:bg-[#8b5cf6] hover:text-white transition-all duration-300 shadow-sm hover:shadow-md"
@
click=
"onOpenExchangeGoodsRecordDialog"
>
>
商品领取列表
<div
class=
"flex items-center gap-3 mb-6"
>
</button>
<div
<button
class=
"w-1.5 h-7 bg-gradient-to-b from-[#8b5cf6] to-[#6366f1] rounded-full shadow-sm"
class=
"cursor-pointer px-6 py-2.5 bg-gradient-to-r from-[#8b5cf6] to-[#6366f1] text-white rounded-full text-sm font-medium hover:shadow-lg transition-all duration-300"
></div>
@
click=
"onOpenExchangeYabiRecordDialog"
>
YA币收支记录
</button>
</div>
</div>
</div>
<!-- 虚拟装饰区域 -->
<div
class=
"mb-10"
>
<div
class=
"flex items-center gap-3 mb-5"
>
<div
class=
"w-1 h-6 bg-gradient-to-b from-[#8b5cf6] to-[#6366f1] rounded-full"
></div>
<h2
class=
"text-xl font-bold text-gray-800"
>
虚拟装饰
</h2>
<h2
class=
"text-xl font-bold text-gray-800"
>
虚拟装饰
</h2>
</div>
</div>
<div
<template
v-if=
"virtualGoodsList.length"
>
class=
"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-5"
<div
class=
"grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4"
>
>
<div
<div
v-for=
"item in virtualGoodsList"
v-for=
"item in virtualGoodsList"
:key=
"item.id"
:key=
"item.id"
class=
"group bg-gradient-to-br from-purple-50 to-pink-50 rounded-lg p-5 flex flex-col items-center hover:shadow-xl transition-all duration-300 cursor-pointer border border-transparent hover:border-purple-200 hover:-translate-y-1
"
class=
"group bg-white rounded-lg p-4 flex flex-col items-center shadow-md transition-all duration-200 cursor-pointer border border-gray-200 hover:border-blue-300
"
@
click=
"onExchangeGoods(item)"
@
click=
"onExchangeGoods(item)"
>
>
<div
class=
"w-24 h-24 mb-3 flex items-center justify-center"
>
<div
class=
"w-20 h-20 mb-3 flex items-center justify-center bg-blue-50/50 rounded-lg p-2"
>
<img
<img
:src=
"item.imageUrl"
:src=
"item.imageUrl"
alt=
""
alt=
""
class=
"rounded-lg w-full h-full object-contain group-hover:scale-110 transition-transform duration-300
"
class=
"rounded-lg w-full h-full object-contain
"
/>
/>
</div>
</div>
<div
<div
...
@@ -55,17 +36,14 @@
...
@@ -55,17 +36,14 @@
>
>
{{
item
.
name
}}
{{
item
.
name
}}
</div>
</div>
<div
<div
class=
"bg-blue-600 text-white text-xs px-4 py-1.5 rounded font-medium"
>
class=
"bg-gradient-to-r from-pink-500 to-rose-500 text-white text-xs px-4 py-1.5 rounded-full font-medium shadow-sm"
{{
item
.
price
}}
积分
>
{{
item
.
price
}}
积分
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div
class=
"flex justify-end mt-4"
>
<div
class=
"flex justify-end mt-6"
>
<div
class=
"bg-gray-50 rounded-xl shadow-sm border border-gray-200 p-2"
>
<div
class=
"bg-gray-50 rounded-lg shadow-sm border border-gray-200 p-3"
>
<el-pagination
<el-pagination
size=
"small"
size=
"small"
v-model:current-page=
"virtualGoodsSearchParams.current"
v-model:current-page=
"virtualGoodsSearchParams.current"
...
@@ -76,49 +54,57 @@
...
@@ -76,49 +54,57 @@
/>
/>
</div>
</div>
</div>
</div>
</
template
>
<div
v-else
class=
"flex justify-center items-center py-12"
>
<el-empty
description=
"暂无"
/>
</div>
</div>
</div>
<!-- 实物奖品区域 -->
<!-- 实物奖品区域 -->
<div
class=
"mb-10"
>
<div
class=
"bg-white/90 backdrop-blur-sm rounded-2xl p-6 shadow-lg border border-white/60"
>
<div
class=
"flex items-end gap-3 mb-5"
>
<div
class=
"flex items-center gap-3 mb-6"
>
<div
class=
"w-1 h-6 bg-gradient-to-b from-[#8b5cf6] to-[#6366f1] rounded-full"
></div>
<div
class=
"w-1.5 h-7 bg-gradient-to-b from-[#8b5cf6] to-[#6366f1] rounded-full shadow-sm"
></div>
<h2
class=
"text-xl font-bold text-gray-800"
>
亚声实物
</h2>
<h2
class=
"text-xl font-bold text-gray-800"
>
亚声实物
</h2>
</div>
</div>
<!-- 地区筛选 -->
<!-- 地区筛选 -->
<div
class=
"flex gap-3 text-sm mb-6 items-center flex-wrap"
>
<div
class=
"flex gap-3 text-sm mb-6 items-center flex-wrap"
>
<span
class=
"text-gray-600 font-medium"
>
地区:
</span>
<span
class=
"text-gray-600 font-medium"
>
地区:
</span>
<!--
<el-tabs
type=
"card"
v-model=
"realGoodsSearchParams.region"
@
change=
"onChangeRegion"
>
<button
<el-tab-pane
v-for=
"item in tabs"
v-for=
"item in tabs"
:key=
"item.value"
:key=
"item.value"
:label=
"item.label"
class=
"cursor-pointer px-3 py-1.5 text-sm transition-all relative"
:name=
"item.value"
@
click=
"onChangeRegion(item.value)"
/>
:class=
"{
</el-tabs>
-->
'text-indigo-600 font-medium': realGoodsSearchParams.region === item.value,
<Tabs
'text-gray-600 hover:text-gray-900': realGoodsSearchParams.region !== item.value,
:tabs=
"tabs"
}"
v-model=
"realGoodsSearchParams.region"
>
@
change=
"onChangeRegion"
{{ item.label }}
class=
"bg-pink-500"
<span
/>
v-if=
"realGoodsSearchParams.region === item.value"
class=
"absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-b from-[#8b5cf6] to-[#6366f1]"
></span>
</button>
</div>
</div>
<div
v-show=
"realGoodsList.length"
>
<
template
v-if=
"realGoodsList.length"
>
<div
<div
class=
"grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4"
>
class=
"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-5"
>
<div
<div
v-for=
"item in realGoodsList"
v-for=
"item in realGoodsList"
:key=
"item.id"
:key=
"item.id"
class=
"group bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg p-5 flex flex-col items-center hover:shadow-xl transition-all duration-300 cursor-pointer border border-transparent hover:border-blue-200 hover:-translate-y-1
"
class=
"group bg-white rounded-lg p-4 flex flex-col items-center shadow-md transition-all duration-200 cursor-pointer border border-gray-200 hover:border-blue-300
"
@
click=
"onExchangeGoods(item)"
@
click=
"onExchangeGoods(item)"
>
>
<div
class=
"w-24 h-24 mb-3 flex items-center justify-center"
>
<div
class=
"w-20 h-20 mb-3 flex items-center justify-center bg-blue-50/50 rounded-lg p-2"
>
<img
<img
:src=
"item.imageUrl"
:src=
"item.imageUrl"
alt=
""
alt=
""
class=
"rounded-lg w-full h-full object-contain group-hover:scale-110 transition-transform duration-300
"
class=
"rounded-lg w-full h-full object-contain
"
/>
/>
</div>
</div>
<div
<div
...
@@ -126,17 +112,14 @@
...
@@ -126,17 +112,14 @@
>
>
{{
item
.
name
}}
{{
item
.
name
}}
</div>
</div>
<div
<div
class=
"bg-blue-600 text-white text-xs px-4 py-1.5 rounded font-medium"
>
class=
"bg-gradient-to-r from-pink-500 to-rose-500 text-white text-xs px-4 py-1.5 rounded-full font-medium shadow-sm"
{{
item
.
price
}}
积分
>
{{
item
.
price
}}
积分
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div
class=
"flex justify-end mt-4"
>
<div
class=
"flex justify-end mt-6"
>
<div
class=
"bg-gray-50 rounded-xl shadow-sm border border-gray-200 p-2"
>
<div
class=
"bg-gray-50 rounded-lg shadow-sm border border-gray-200 p-3"
>
<el-pagination
<el-pagination
v-model:current-page=
"realGoodsSearchParams.current"
v-model:current-page=
"realGoodsSearchParams.current"
v-model:page-size=
"realGoodsSearchParams.size"
v-model:page-size=
"realGoodsSearchParams.size"
...
@@ -146,40 +129,93 @@
...
@@ -146,40 +129,93 @@
/>
/>
</div>
</div>
</div>
</div>
</
template
>
<div
v-else
class=
"flex justify-center items-center py-12"
>
<el-empty
description=
"暂无"
/>
</div>
</div>
<!-- 空状态 -->
<div
v-show=
"!realGoodsList.length"
class=
"flex justify-center items-center py-20"
>
<el-empty
description=
"暂无数据"
/>
</div>
</div>
</div>
</div>
<!-- 底部提示 -->
<!-- 右侧:信息面板 -->
<div
class=
"mt-16 text-center"
>
<div
class=
"w-80 flex-shrink-0 space-y-6"
>
<!-- YA币信息卡片 -->
<div
<div
class=
"inline-flex items-center gap-2 bg-blue-50 border border-blue-200 rounded-full px-6 py-3 text-sm text-gray-600"
class=
"bg-white/90 backdrop-blur-sm rounded-2xl p-6 shadow-lg border border-white/60 sticky top-[52px]"
>
<div
class=
"text-center mb-6"
>
<div
class=
"text-gray-500 text-sm mb-2"
>
当前YA币
</div>
<div
class=
"text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600 text-5xl font-bold"
>
{{ currentYaBi }}
</div>
</div>
<!-- 操作按钮 -->
<div
class=
"space-y-3 mb-6"
>
<button
class=
"cursor-pointer w-full px-4 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl text-sm font-medium hover:shadow-lg hover:scale-[1.02] transition-all duration-300"
@
click=
"onOpenExchangeYabiRecordDialog"
>
YA币收支记录
</button>
<button
class=
"cursor-pointer w-full px-4 py-3 bg-white text-gray-700 rounded-xl text-sm font-medium border border-gray-200 hover:border-blue-400 hover:shadow-md transition-all duration-300"
@
click=
"onOpenExchangeGoodsRecordDialog"
>
>
实物商品兑换后请联系相关负责人领取奖励 —
商品领取列表
<span
class=
"text-[#6366f1] cursor-pointer font-medium hover:underline"
>
</button>
联系人对照表
</div>
</span>
<!-- 温馨提示 -->
<div
class=
"bg-amber-50 border border-amber-200/60 rounded-xl p-4"
>
<div
class=
"flex items-start gap-2"
>
<svg
class=
"w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5"
fill=
"currentColor"
viewBox=
"0 0 20 20"
>
<path
fill-rule=
"evenodd"
d=
"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule=
"evenodd"
/>
</svg>
<div
class=
"text-sm text-gray-700"
>
<div
class=
"font-medium mb-1"
>
温馨提示
</div>
<p
class=
"text-xs leading-relaxed"
>
实物商品兑换后请联系相关负责人领取奖励
<span
class=
"text-blue-600 cursor-pointer hover:underline ml-1"
>
查看联系人
</span>
</p>
</div>
</div>
</div>
<!-- 统计信息(可选) -->
<div
class=
"mt-6 pt-6 border-t border-gray-100 space-y-3"
>
<div
class=
"flex justify-between items-center text-sm"
>
<span
class=
"text-gray-500"
>
虚拟装饰
</span>
<span
class=
"font-medium text-gray-700"
>
{{ virtualGoodsTotal }} 件
</span>
</div>
<div
class=
"flex justify-between items-center text-sm"
>
<span
class=
"text-gray-500"
>
实物商品
</span>
<span
class=
"font-medium text-gray-700"
>
{{ realGoodsTotal }} 件
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ExchangeGoodsRecordDialog
ref=
"exchangeGoodsRecordDialogRef"
/>
<ExchangeGoodsRecordDialog
ref=
"exchangeGoodsRecordDialogRef"
/>
<ExchangeYabiRecordDialog
ref=
"exchangeYabiRecordDialogRef"
/>
<ExchangeYabiRecordDialog
ref=
"exchangeYabiRecordDialogRef"
/>
</div>
</div>
</div>
</template>
</template>
<
script
setup
lang=
"tsx"
>
<
script
setup
lang=
"tsx"
>
import
{
exchangeGoods
,
getShopItemList
,
getYaBiData
}
from
'@/api'
import
{
exchangeGoods
,
getShopItemList
,
getYaBiData
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
usePageSearch
}
from
'@/hooks'
import
{
regionListOptions
,
ShopGoodsTypeEnum
}
from
'@/constants'
import
{
regionListOptions
,
ShopGoodsTypeEnum
}
from
'@/constants'
import
type
{
ExchangeGoodsParams
,
ShopItemDto
}
from
'@/api'
import
type
{
ExchangeGoodsParams
,
BackendShopItemDto
}
from
'@/api'
import
Tabs
from
'@/components/common/Tabs'
import
ExchangeContent
from
'./components/exchangeContent.tsx'
import
ExchangeContent
from
'./components/exchangeContent.tsx'
import
ExchangeGoodsRecordDialog
from
'./components/exchangeGoodsRecordDilaog.vue'
import
ExchangeGoodsRecordDialog
from
'./components/exchangeGoodsRecordDilaog.vue'
import
ExchangeYabiRecordDialog
from
'./components/exchangeYabiRecordDilaog.vue'
import
ExchangeYabiRecordDialog
from
'./components/exchangeYabiRecordDilaog.vue'
...
@@ -221,7 +257,8 @@ const {
...
@@ -221,7 +257,8 @@ const {
},
},
})
})
const
onChangeRegion
=
()
=>
{
const
onChangeRegion
=
(
region
:
string
)
=>
{
realGoodsSearchParams
.
value
.
region
=
region
realGoodsGoToPage
(
1
)
realGoodsGoToPage
(
1
)
}
}
...
@@ -241,8 +278,7 @@ const getYaBiDataFn = async () => {
...
@@ -241,8 +278,7 @@ const getYaBiDataFn = async () => {
// 兑换商品
// 兑换商品
const
onExchangeGoods
=
async
(
item
:
ShopItemDto
)
=>
{
const
onExchangeGoods
=
async
(
item
:
BackendShopItemDto
)
=>
{
console
.
log
(
item
)
const
form
=
ref
<
ExchangeGoodsParams
>
({
const
form
=
ref
<
ExchangeGoodsParams
>
({
itemId
:
item
.
id
,
itemId
:
item
.
id
,
num
:
1
,
num
:
1
,
...
...
src/views/videoDetail/components/rewardDialog.vue
View file @
dedb7b4a
...
@@ -29,13 +29,15 @@
...
@@ -29,13 +29,15 @@
<div
class=
"reward-character"
>
<div
class=
"reward-character"
>
<img
<img
v-if=
"option.amount === 1"
v-if=
"option.amount === 1"
src=
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3C
circle cx='50' cy='50' r='45' fill='%23e0e0e0'/%3E%3Ctext x='50' y='65' font-size='40' text-anchor='middle' fill='%23666'%3E🪙%3C/text
%3E%3C/svg%3E"
src=
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3C
defs%3E%3ClinearGradient id='coin1' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23fef3c7'/%3E%3Cstop offset='30%25' style='stop-color:%23fde68a'/%3E%3Cstop offset='70%25' style='stop-color:%23fcd34d'/%3E%3Cstop offset='100%25' style='stop-color:%23f59e0b'/%3E%3C/linearGradient%3E%3CradialGradient id='highlight1' cx='35%25' cy='35%25'%3E%3Cstop offset='0%25' style='stop-color:%23ffffff;stop-opacity:0.9'/%3E%3Cstop offset='50%25' style='stop-color:%23ffffff;stop-opacity:0.3'/%3E%3Cstop offset='100%25' style='stop-color:%23ffffff;stop-opacity:0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='50' cy='50' r='44' fill='none' stroke='%23f59e0b' stroke-width='3' opacity='0.4'/%3E%3Ccircle cx='50' cy='50' r='40' fill='url(%23coin1)' stroke='%23d97706' stroke-width='6'/%3E%3Ccircle cx='50' cy='50' r='33' fill='none' stroke='%23fbbf24' stroke-width='2' opacity='0.6'/%3E%3Ccircle cx='50' cy='50' r='27' fill='none' stroke='%23f59e0b' stroke-width='1.5' opacity='0.5'/%3E%3Cpath d='M50 30 L54 42 L67 42 L56 50 L60 62 L50 54 L40 62 L44 50 L33 42 L46 42 Z' fill='%23d97706' opacity='0.8'/%3E%3Ccircle cx='50' cy='50' r='40' fill='url(%23highlight1)' opacity='0.5'/
%3E%3C/svg%3E"
alt=
"1YA币"
alt=
"1YA币"
class=
"w-full h-full"
class=
"w-full h-full"
/>
/>
<!-- 2YA币 - 双枚金币(星形居中版) -->
<img
<img
v-else
v-else
src=
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3C
circle cx='35' cy='50' r='30' fill='%23ffd700'/%3E%3Ccircle cx='65' cy='50' r='30' fill='%23ffd700'/%3E%3Ctext x='50' y='65' font-size='30' text-anchor='middle' fill='%23fff'%3E🪙%3C/text
%3E%3C/svg%3E"
src=
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3C
defs%3E%3ClinearGradient id='coin2' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23fef3c7'/%3E%3Cstop offset='30%25' style='stop-color:%23fde68a'/%3E%3Cstop offset='70%25' style='stop-color:%23fcd34d'/%3E%3Cstop offset='100%25' style='stop-color:%23f59e0b'/%3E%3C/linearGradient%3E%3CradialGradient id='highlight2' cx='35%25' cy='35%25'%3E%3Cstop offset='0%25' style='stop-color:%23ffffff;stop-opacity:0.9'/%3E%3Cstop offset='50%25' style='stop-color:%23ffffff;stop-opacity:0.3'/%3E%3Cstop offset='100%25' style='stop-color:%23ffffff;stop-opacity:0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Cg opacity='0.85'%3E%3Ccircle cx='35' cy='50' r='32' fill='none' stroke='%23f59e0b' stroke-width='2' opacity='0.4'/%3E%3Ccircle cx='35' cy='50' r='29' fill='url(%23coin2)' stroke='%23d97706' stroke-width='4'/%3E%3Ccircle cx='35' cy='50' r='24' fill='none' stroke='%23fbbf24' stroke-width='1.5' opacity='0.6'/%3E%3Ccircle cx='35' cy='50' r='20' fill='none' stroke='%23f59e0b' stroke-width='1' opacity='0.5'/%3E%3Cpath d='M35 33 L37.5 42 L47 42 L39 48 L41.5 57 L35 51 L28.5 57 L31 48 L23 42 L32.5 42 Z' fill='%23d97706' opacity='0.8'/%3E%3Ccircle cx='35' cy='50' r='29' fill='url(%23highlight2)' opacity='0.5'/%3E%3C/g%3E%3Cg%3E%3Ccircle cx='65' cy='50' r='32' fill='none' stroke='%23f59e0b' stroke-width='2' opacity='0.4'/%3E%3Ccircle cx='65' cy='50' r='29' fill='url(%23coin2)' stroke='%23d97706' stroke-width='4'/%3E%3Ccircle cx='65' cy='50' r='24' fill='none' stroke='%23fbbf24' stroke-width='1.5' opacity='0.6'/%3E%3Ccircle cx='65' cy='50' r='20' fill='none' stroke='%23f59e0b' stroke-width='1' opacity='0.5'/%3E%3Cpath d='M65 33 L67.5 42 L77 42 L69 48 L71.5 57 L65 51 L58.5 57 L61 48 L53 42 L62.5 42 Z' fill='%23d97706' opacity='0.8'/%3E%3Ccircle cx='65' cy='50' r='29' fill='url(%23highlight2)' opacity='0.5'/%3E%3C/g
%3E%3C/svg%3E"
alt=
"2YA币"
alt=
"2YA币"
class=
"w-full h-full"
class=
"w-full h-full"
/>
/>
...
@@ -58,7 +60,7 @@
...
@@ -58,7 +60,7 @@
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
addOrCancelArticleReward
,
getYaBiData
}
from
'@/api'
import
{
addOrCancelArticleReward
,
getYaBiData
}
from
'@/api'
const
rewardNum
=
defineModel
<
number
>
(
'rewardNum'
,
{
required
:
true
})
interface
RewardOption
{
interface
RewardOption
{
amount
:
number
amount
:
number
icon
:
string
icon
:
string
...
@@ -109,6 +111,7 @@ const handleConfirm = async () => {
...
@@ -109,6 +111,7 @@ const handleConfirm = async () => {
})
})
ElMessage
.
success
(
'打赏成功!'
)
ElMessage
.
success
(
'打赏成功!'
)
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
rewardNum
.
value
+=
selectedAmount
.
value
}
}
defineExpose
({
defineExpose
({
...
...
src/views/videoDetail/index.vue
View file @
dedb7b4a
...
@@ -92,11 +92,99 @@
...
@@ -92,11 +92,99 @@
<span
class=
"text-base"
>
{{
videoDetail
?.
replyCount
||
0
}}
</span>
<span
class=
"text-base"
>
{{
videoDetail
?.
replyCount
||
0
}}
</span>
</el-button>
</el-button>
<!-- 打赏 -->
<!-- 打赏 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors"
@
click=
"handleReward"
>
<el-button
<!--
<el-icon><Star
/></el-icon>
-->
text
{{
videoDetail
?.
rewardNum
}}
class=
"reward-button flex items-center gap-2 px-4 py-2 rounded-lg bg-white/40 hover:bg-white/70 backdrop-blur-sm border border-blue-100/30 hover:border-blue-200/50 transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-blue-100/50"
<span
class=
"text-base"
>
打赏
</span>
@
click=
"handleReward"
>
<!-- 金币容器 - 带多重动画 -->
<div
class=
"coin-wrapper relative"
>
<!-- 改进版金币图标 - 立体金币设计 -->
<svg
class=
"coin-icon"
viewBox=
"0 0 24 24"
width=
"18"
height=
"18"
>
<defs>
<!-- 金色渐变 -->
<linearGradient
id=
"coinGradient"
x1=
"0%"
y1=
"0%"
x2=
"100%"
y2=
"100%"
>
<stop
offset=
"0%"
style=
"stop-color: #fef3c7"
/>
<stop
offset=
"30%"
style=
"stop-color: #fde68a"
/>
<stop
offset=
"70%"
style=
"stop-color: #fcd34d"
/>
<stop
offset=
"100%"
style=
"stop-color: #f59e0b"
/>
</linearGradient>
<!-- 高光效果 -->
<radialGradient
id=
"highlight"
cx=
"35%"
cy=
"35%"
>
<stop
offset=
"0%"
style=
"stop-color: #ffffff; stop-opacity: 0.9"
/>
<stop
offset=
"50%"
style=
"stop-color: #ffffff; stop-opacity: 0.3"
/>
<stop
offset=
"100%"
style=
"stop-color: #ffffff; stop-opacity: 0"
/>
</radialGradient>
</defs>
<!-- 外圈装饰 -->
<circle
cx=
"12"
cy=
"12"
r=
"10.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.8"
opacity=
"0.4"
/>
<!-- 金币主体 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#coinGradient)"
stroke=
"#d97706"
stroke-width=
"1.5"
/>
<!-- 内圈装饰线 -->
<circle
cx=
"12"
cy=
"12"
r=
"8"
fill=
"none"
stroke=
"#fbbf24"
stroke-width=
"0.5"
opacity=
"0.6"
/>
<circle
cx=
"12"
cy=
"12"
r=
"6.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.3"
opacity=
"0.5"
/>
<!-- 星形图案(代替¥符号) -->
<path
d=
"M12 5 L13 9 L17 9 L14 11.5 L15 15 L12 13 L9 15 L10 11.5 L7 9 L11 9 Z"
fill=
"#d97706"
opacity=
"0.8"
/>
<!-- 高光 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#highlight)"
opacity=
"0.5"
/>
</svg>
<!-- 悬停时的光晕效果 -->
<div
class=
"coin-glow absolute inset-0 rounded-full"
></div>
<!-- 闪光粒子效果 -->
<div
class=
"sparkle sparkle-1"
></div>
<div
class=
"sparkle sparkle-2"
></div>
<div
class=
"sparkle sparkle-3"
></div>
</div>
<span
class=
"ml-2 reward-number font-medium text-gray-700"
>
{{
videoDetail
?.
rewardNum
}}
</span>
<span
class=
"ml-1 reward-text text-sm text-gray-600"
>
打赏
</span>
</el-button>
</el-button>
<!-- 更多 -->
<!-- 更多 -->
<button
<button
class=
"p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-all"
class=
"p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-all"
...
@@ -133,7 +221,7 @@
...
@@ -133,7 +221,7 @@
</div>
</div>
<Comment
ref=
"commentRef"
:id=
"videoId"
v-model:total=
"videoDetail.replyCount"
/>
<Comment
ref=
"commentRef"
:id=
"videoId"
v-model:total=
"videoDetail.replyCount"
/>
<RewardDialog
ref=
"rewardDialogRef"
/>
<RewardDialog
ref=
"rewardDialogRef"
v-model:rewardNum=
"videoDetail.rewardNum"
/>
</div>
</div>
</
template
>
</
template
>
...
@@ -207,3 +295,181 @@ onMounted(async () => {
...
@@ -207,3 +295,181 @@ onMounted(async () => {
videoDetail
.
value
=
data
videoDetail
.
value
=
data
})
})
</
script
>
</
script
>
<
style
scoped
>
/* 按钮整体动画 */
/* 保持之前的所有动画样式不变 */
.reward-button
{
position
:
relative
;
overflow
:
visible
;
}
.coin-wrapper
{
width
:
18px
;
height
:
18px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
}
.coin-icon
{
font-size
:
88px
;
animation
:
coin-float
2.5s
ease-in-out
infinite
;
transition
:
all
0.3s
ease
;
}
.reward-button
:hover
.coin-icon
{
animation
:
coin-spin
0.6s
ease-in-out
,
coin-float
2.5s
ease-in-out
infinite
;
}
@keyframes
coin-float
{
0
%,
100
%
{
transform
:
translateY
(
0px
)
rotate
(
0deg
);
}
50
%
{
transform
:
translateY
(
-3px
)
rotate
(
5deg
);
}
}
@keyframes
coin-spin
{
0
%
{
transform
:
rotateY
(
0deg
);
}
100
%
{
transform
:
rotateY
(
360deg
);
}
}
.coin-glow
{
background
:
radial-gradient
(
circle
,
rgba
(
251
,
191
,
36
,
0.4
)
0%
,
transparent
70%
);
opacity
:
0
;
transform
:
scale
(
0.8
);
transition
:
all
0.3s
ease
;
}
.reward-button
:hover
.coin-glow
{
opacity
:
1
;
transform
:
scale
(
1.5
);
animation
:
glow-pulse
1.5s
ease-in-out
infinite
;
}
@keyframes
glow-pulse
{
0
%,
100
%
{
opacity
:
0.6
;
transform
:
scale
(
1.5
);
}
50
%
{
opacity
:
1
;
transform
:
scale
(
1.8
);
}
}
.sparkle
{
position
:
absolute
;
width
:
4px
;
height
:
4px
;
background
:
#fbbf24
;
border-radius
:
50%
;
opacity
:
0
;
pointer-events
:
none
;
}
.reward-button
:hover
.sparkle
{
animation
:
sparkle-burst
0.8s
ease-out
;
}
.sparkle-1
{
top
:
-2px
;
left
:
50%
;
animation-delay
:
0s
;
--tx
:
0
;
--ty
:
-10px
;
}
.sparkle-2
{
top
:
50%
;
right
:
-2px
;
animation-delay
:
0.15s
;
--tx
:
10px
;
--ty
:
0
;
}
.sparkle-3
{
bottom
:
-2px
;
left
:
50%
;
animation-delay
:
0.3s
;
--tx
:
0
;
--ty
:
10px
;
}
@keyframes
sparkle-burst
{
0
%
{
opacity
:
1
;
transform
:
translate
(
0
,
0
)
scale
(
0
);
}
50
%
{
opacity
:
1
;
}
100
%
{
opacity
:
0
;
transform
:
translate
(
var
(
--tx
),
var
(
--ty
))
scale
(
1
);
}
}
.reward-number
{
transition
:
all
0.2s
ease
;
}
.reward-button
:hover
.reward-number
{
animation
:
number-bounce
0.5s
ease
;
color
:
#3b82f6
;
}
@keyframes
number-bounce
{
0
%,
100
%
{
transform
:
translateY
(
0
);
}
25
%
{
transform
:
translateY
(
-3px
);
}
50
%
{
transform
:
translateY
(
0
);
}
75
%
{
transform
:
translateY
(
-2px
);
}
}
.reward-text
{
transition
:
all
0.3s
ease
;
}
.reward-button
:hover
.reward-text
{
color
:
#3b82f6
;
transform
:
translateX
(
2px
);
}
.reward-button
:active
{
transform
:
scale
(
0.95
);
}
.reward-button
:active
.coin-icon
{
animation
:
coin-click
0.3s
ease
;
}
@keyframes
coin-click
{
0
%
{
transform
:
scale
(
1
);
}
50
%
{
transform
:
scale
(
0.85
)
rotate
(
15deg
);
}
100
%
{
transform
:
scale
(
1
)
rotate
(
0deg
);
}
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment