Commit 5b4d9245 by lijiabin

【需求 17679】 wip: 继续完善页面等

parent 797a579b
...@@ -175,10 +175,7 @@ export const deleteArticle = (id: number) => { ...@@ -175,10 +175,7 @@ export const deleteArticle = (id: number) => {
*/ */
export const deleteComment = (commentId: number) => { export const deleteComment = (commentId: number) => {
return service.request({ return service.request({
url: `/api/cultureComment/deleteComment`, url: `/api/cultureComment/deleteComment?commentId=${commentId}`,
method: 'POST', method: 'POST',
data: {
commentId,
},
}) })
} }
...@@ -8,18 +8,7 @@ import type { BackendServicePageResult } from '@/utils/request/types' ...@@ -8,18 +8,7 @@ import type { BackendServicePageResult } from '@/utils/request/types'
*/ */
export const getCaseList = (data: PageSearchParams) => { export const getCaseList = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendServicePageResult<BackendTagListItemDto>>({
url: '/api/cultureCase/caseAuditByPage', url: '/api/cultureCase/caseListByPage',
method: 'POST',
data,
})
}
/**
* 添加轮播图
*/
export const addCarousel = (data: AddOrUpdateCarouselDto) => {
return service.request({
url: '/api/cultureCarousel/addCarousel',
method: 'POST', method: 'POST',
data, data,
}) })
......
...@@ -4,12 +4,54 @@ import type { BackendServicePageResult } from '@/utils/request/types' ...@@ -4,12 +4,54 @@ import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理 积分商城相关接口 // 后台管理 积分商城相关接口
/** /**
* 商品领用列表 * 商品配置列表
*/ */
export const getExchangeList = (params: BackendTagSearchParams) => { export const getShopItemList = (params: BackendTagSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendServicePageResult<BackendTagListItemDto>>({
url: '/api/culture/shop/order/exchangeList', url: '/api/culture/shop/item/productList',
method: 'POST', method: 'POST',
data: params, data: params,
}) })
} }
/**
* 新增/编辑商品
*/
export const addOrUpdateShopItem = (data: AddOrUpdateTagDto) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
url: '/api/culture/shop/item/addOrUpdate',
method: 'POST',
data,
})
}
/*
* 删除商品
*/
export const deleteShopItem = (id: number) => {
return service.request({
url: `/api/culture/shop/item/deleteProduct?id=${id}`,
method: 'POST',
})
}
/**
* 后台商品领用列表
*/
export const getBackendExchangeList = (data: BackendTagSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
url: '/api/culture/shop/order/background/productList',
method: 'POST',
data,
})
}
/**
* 发放 取消发放
*/
export const issueProduct = (data) => {
return service.request({
url: `/api/culture/shop/order/issueProduct`,
method: 'POST',
data,
})
}
...@@ -8,6 +8,7 @@ import type { ...@@ -8,6 +8,7 @@ import type {
YaBiData, YaBiData,
ExchangeYabiRecordItemDto, ExchangeYabiRecordItemDto,
ExchangeGoodsRecordSearchParams, ExchangeGoodsRecordSearchParams,
BackendShopItemDto,
} from './types' } from './types'
/** /**
* 获取用户亚币相关数据 * 获取用户亚币相关数据
...@@ -24,7 +25,7 @@ export const getYaBiData = () => { ...@@ -24,7 +25,7 @@ export const getYaBiData = () => {
* 积分商城商品列表 * 积分商城商品列表
*/ */
export const getShopItemList = (data: ShopSearchParams) => { export const getShopItemList = (data: ShopSearchParams) => {
return service.request<BackendServicePageResult<ShopItemDto>>({ return service.request<BackendServicePageResult<BackendShopItemDto>>({
url: '/api/culture/shop/item/pageList', url: '/api/culture/shop/item/pageList',
method: 'POST', method: 'POST',
data, data,
......
...@@ -12,18 +12,19 @@ export interface ShopSearchParams extends PageSearchParams { ...@@ -12,18 +12,19 @@ export interface ShopSearchParams extends PageSearchParams {
/** /**
* 积分商城商品类型 * 积分商城商品类型
*/ */
export interface ShopItemDto { export interface BackendShopItemDto {
id: number createTime: number
description: string description: string
enable: number enable: BooleanFlag
id: number
imageUrl: string imageUrl: string
itemType: number itemType: ShopGoodsTypeEnum
itemTypeName: string itemTypeName: string
name: string name: string
price: number price: number
region: string region: string
soldOut: boolean soldOut: boolean
sotrOrder: number sortOrder: number
stock: number stock: number
} }
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
<div class="flex-1"> <div class="flex-1">
<div ref="commentInputRef"> <div ref="commentInputRef">
<el-input <el-input
v-model="comment" v-model="myComment"
type="textarea" type="textarea"
placeholder="写下你的评论..." placeholder="写下你的评论..."
:rows="3" :rows="3"
...@@ -68,8 +68,8 @@ ...@@ -68,8 +68,8 @@
</div> </div>
<button <button
class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all" class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled="!comment.trim() || loading" :disabled="!myComment.trim() || loading"
@click="handleComment()" @click="handleMyComment()"
> >
发表 发表
</button> </button>
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
<!-- 评论列表 --> <!-- 评论列表 -->
<div v-loading="loading" class="divide-y divide-gray-100" v-if="list.length"> <div v-loading="loading" class="divide-y divide-gray-100" v-if="list.length">
<div v-for="item in list" :key="item.id"> <div v-for="item in list" :key="item.id">
<div class="p-4 hover:bg-gray-50/50 transition-colors"> <div class="p-4 transition-colors">
<div class="flex gap-3"> <div class="flex gap-3">
<img :src="item.avatar" alt="" class="w-10 h-10 rounded-full object-cover" /> <img :src="item.avatar" alt="" class="w-10 h-10 rounded-full object-cover" />
<div class="flex-1"> <div class="flex-1">
...@@ -134,9 +134,6 @@ ...@@ -134,9 +134,6 @@
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2 mb-1"> <div class="flex items-center gap-2 mb-1">
<span class="font-medium text-sm text-gray-800">{{ child.replyUser }}</span> <span class="font-medium text-sm text-gray-800">{{ child.replyUser }}</span>
<span class="text-xs text-gray-500">{{
dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</div> </div>
<p class="text-sm text-gray-700"> <p class="text-sm text-gray-700">
{{ child.content }} {{ child.content }}
...@@ -169,13 +166,13 @@ ...@@ -169,13 +166,13 @@
</div> </div>
</div> </div>
</div> </div>
<div v-show="showCommentBox(item)" class="flex gap-3"> <div v-show="showCommentBox(item)" class="flex gap-3 mt-4">
<img :src="userInfo?.avatar" alt="" class="w-10 h-10 rounded-full object-cover" /> <img :src="userInfo?.avatar" alt="" class="w-10 h-10 rounded-full object-cover" />
<div class="flex-1"> <div class="flex-1">
<el-input <el-input
v-model="comment" v-model="comment"
type="textarea" type="textarea"
placeholder="写下你的评论..." :placeholder="replyPlaceholder"
:rows="3" :rows="3"
></el-input> ></el-input>
<div class="flex justify-between items-center mt-3"> <div class="flex justify-between items-center mt-3">
...@@ -190,7 +187,7 @@ ...@@ -190,7 +187,7 @@
<button <button
class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all" class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled="!comment.trim()" :disabled="!comment.trim()"
@click="handleComment(item.id)" @click="handleComment"
> >
发表 发表
</button> </button>
...@@ -235,6 +232,8 @@ const { id } = defineProps<{ ...@@ -235,6 +232,8 @@ const { id } = defineProps<{
id: number | string id: number | string
}>() }>()
const total = defineModel<number>('total', { required: true, default: 0 })
const userStore = useUserStore() const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
...@@ -247,7 +246,7 @@ const { triggerAnimation } = useHintAnimation(commentInputRef, { ...@@ -247,7 +246,7 @@ const { triggerAnimation } = useHintAnimation(commentInputRef, {
classes: ['scale-bounce', 'highlight', 'shake-x'], classes: ['scale-bounce', 'highlight', 'shake-x'],
}) })
const { list, searchParams, goToPage, total, loading, changePageSize, refresh } = usePageSearch( const { list, searchParams, goToPage, loading, changePageSize, refresh } = usePageSearch(
getCommentList, getCommentList,
{ {
defaultParams: { defaultParams: {
...@@ -263,7 +262,16 @@ const handleCurrentChange = async (e: number) => { ...@@ -263,7 +262,16 @@ const handleCurrentChange = async (e: number) => {
triggerAnimation() triggerAnimation()
}, 500) }, 500)
} }
// 自己发出的评论
const myComment = ref('')
// 回复别人的
const comment = ref('') const comment = ref('')
// 回复别人placeholder
const replyPlaceholder = ref('回复@')
const currentCommentId = ref(-1)
const handleLickComment = async (item: CommentItemDto) => { const handleLickComment = async (item: CommentItemDto) => {
await addOrCancelCommentLike(item.id) await addOrCancelCommentLike(item.id)
...@@ -278,66 +286,40 @@ const handleLickComment = async (item: CommentItemDto) => { ...@@ -278,66 +286,40 @@ const handleLickComment = async (item: CommentItemDto) => {
} }
} }
const currentParentCommentId = ref(0)
const currentSonCommentId = ref(0)
const handleReply = (item: CommentItemDto) => { const handleReply = (item: CommentItemDto) => {
if (item.pid) { console.log(item)
// 点击的是子评论 replyPlaceholder.value = `回复@${item.replyUser}:`
if (currentSonCommentId.value) {
// 置为空
if (currentSonCommentId.value !== item.id) {
currentSonCommentId.value = item.id
currentParentCommentId.value = item.pid
} else {
currentSonCommentId.value = 0
currentParentCommentId.value = 0
}
} else {
currentSonCommentId.value = item.id
currentParentCommentId.value = item.pid
}
} else {
// 点击的是父评论
if (currentParentCommentId.value) {
// 置为空
if (currentParentCommentId.value !== item.id) {
currentParentCommentId.value = item.id
} else {
currentParentCommentId.value = 0
}
} else {
currentParentCommentId.value = item.id
currentSonCommentId.value = 0
}
}
comment.value = '' comment.value = ''
console.log('parent', currentParentCommentId.value, 'son', currentSonCommentId.value) currentCommentId.value = item.id
} }
const showCommentBox = (item: CommentItemDto) => { const showCommentBox = (item: CommentItemDto) => {
if (currentParentCommentId.value && currentSonCommentId.value) { return (
// 说明在评论子评论 currentCommentId.value === item.id || item.children.some((i) => i.id === currentCommentId.value)
return ( )
item.id === currentParentCommentId.value &&
item.children?.some((i) => i.id === currentSonCommentId.value)
)
} else if (currentParentCommentId.value) {
// 说明在评论父评论
return item.id === currentParentCommentId.value
}
} }
const handleComment = async (pid?: number) => { const handleMyComment = async () => {
await addComment({
articleId: id,
content: myComment.value,
})
ElMessage.success('发表评论成功')
refresh()
myComment.value = ''
total.value++
}
const handleComment = async () => {
console.log(comment.value) console.log(comment.value)
const res = await addComment({ const res = await addComment({
articleId: id, articleId: id,
content: comment.value, content: comment.value,
...(pid ? { pid } : {}), ...(currentCommentId.value ? { pid: currentCommentId.value } : {}),
}) })
console.log(res) console.log(res)
ElMessage.success('发表评论成功') ElMessage.success('发表评论成功')
refresh() refresh()
comment.value = '' comment.value = ''
total.value++
} }
defineExpose({ defineExpose({
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
:on-remove="handleRemove" :on-remove="handleRemove"
:on-change="handleChange" :on-change="handleChange"
:before-remove="handleBeforeRemove" :before-remove="handleBeforeRemove"
:on-exceed="handleExceed"
:multiple="multiple" :multiple="multiple"
:limit="limit" :limit="limit"
:disabled="hasReachedLimit && !multiple"
class="custom-upload" class="custom-upload"
> >
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
...@@ -30,7 +30,7 @@ import type { UploadFileProps } from './types' ...@@ -30,7 +30,7 @@ import type { UploadFileProps } from './types'
const props = withDefaults(defineProps<UploadFileProps>(), { const props = withDefaults(defineProps<UploadFileProps>(), {
limit: 2, limit: 2,
multiple: true, multiple: false,
}) })
const modelValue = defineModel<T>({ const modelValue = defineModel<T>({
...@@ -44,8 +44,11 @@ const dialogVisible = ref(false) ...@@ -44,8 +44,11 @@ const dialogVisible = ref(false)
const isArrayType = computed(() => Array.isArray(modelValue.value)) const isArrayType = computed(() => Array.isArray(modelValue.value))
const hasReachedLimit = computed(() => fileList.value.length >= props.limit) const hasReachedLimit = computed(() => fileList.value.length >= props.limit)
const showUploadBtn = computed(() => (hasReachedLimit.value ? 'none' : 'flex'))
const isInternalUpdate = ref(false) const isInternalUpdate = ref(false)
const uploadProgress = ref(0)
const parseModelValueToUrls = (value: T): string[] => { const parseModelValueToUrls = (value: T): string[] => {
if (!value) return [] if (!value) return []
return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean) return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean)
...@@ -98,8 +101,18 @@ watch( ...@@ -98,8 +101,18 @@ watch(
{ deep: true }, { deep: true },
) )
const handleExceed: UploadProps['onExceed'] = (uploadFiles) => {
console.log('uploadFiles', uploadFiles)
if (uploadFiles.length > props.limit) {
ElMessage.error(`最多上传 ${props.limit} 个文件`)
return
}
}
const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => { const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => {
console.log('uploadFiles', uploadFiles)
if (uploadFiles.length > props.limit) { if (uploadFiles.length > props.limit) {
debugger
ElMessage.error(`最多上传 ${props.limit} 个文件`) ElMessage.error(`最多上传 ${props.limit} 个文件`)
const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid) const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid)
if (index !== -1) { if (index !== -1) {
...@@ -117,7 +130,9 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => ...@@ -117,7 +130,9 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
fileList.value[fileIndex].status = 'uploading' fileList.value[fileIndex].status = 'uploading'
} }
const { data } = await uploadFileApi(uploadFile.raw) const { data } = await uploadFileApi(uploadFile.raw, (progress) => {
console.log('progress', progress)
})
console.log('data', data) console.log('data', data)
const url = data.fileUrl || data.data[0].filePath const url = data.fileUrl || data.data[0].filePath
const name = data.fileName || data.data[0].finalName const name = data.fileName || data.data[0].finalName
...@@ -181,6 +196,9 @@ defineExpose({ ...@@ -181,6 +196,9 @@ defineExpose({
</script> </script>
<style scoped> <style scoped>
.custom-upload :deep(.el-upload--picture-card) {
display: v-bind(showUploadBtn);
}
/* 方案1: 适中尺寸(推荐) */ /* 方案1: 适中尺寸(推荐) */
.custom-upload :deep(.el-upload--picture-card) { .custom-upload :deep(.el-upload--picture-card) {
width: 100px; width: 100px;
......
...@@ -88,6 +88,10 @@ const { maxSize = 500, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } = ...@@ -88,6 +88,10 @@ const { maxSize = 500, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } =
const modelValue = defineModel<string>('modelValue', { required: true }) const modelValue = defineModel<string>('modelValue', { required: true })
const videoInfo = defineModel<VideoInfo | null>('videoInfo', { required: false, default: null }) const videoInfo = defineModel<VideoInfo | null>('videoInfo', { required: false, default: null })
const emit = defineEmits<{
uploadSuccess: [{ file: File; url: string; videoDuration: string }]
}>()
// 响应式数据 // 响应式数据
const uploadRef = ref() const uploadRef = ref()
const uploading = ref(false) const uploading = ref(false)
...@@ -180,6 +184,7 @@ const startUpload = async () => { ...@@ -180,6 +184,7 @@ const startUpload = async () => {
// 根据你的 API 返回结构调整 // 根据你的 API 返回结构调整
const videoData: VideoInfo = { const videoData: VideoInfo = {
// url: data.fileUrl, // url: data.fileUrl,
// 暂时写死
url: 'https://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/readName/mp4/2025/11/12/Common/1762918987602.mp4', url: 'https://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/readName/mp4/2025/11/12/Common/1762918987602.mp4',
name: currentFile.value.name, name: currentFile.value.name,
size: currentFile.value.size, size: currentFile.value.size,
...@@ -194,6 +199,12 @@ const startUpload = async () => { ...@@ -194,6 +199,12 @@ const startUpload = async () => {
// 设置url // 设置url
modelValue.value = videoData.url modelValue.value = videoData.url
// 把 二进制源文件传出来
emit('uploadSuccess', {
file: currentFile.value,
url: videoData.url,
videoDuration: videoData.duration,
})
ElMessage.success('视频上传成功!') ElMessage.success('视频上传成功!')
} catch (error) { } catch (error) {
uploading.value = false uploading.value = false
......
...@@ -117,7 +117,7 @@ export default defineComponent((_, { expose }) => { ...@@ -117,7 +117,7 @@ export default defineComponent((_, { expose }) => {
<el-form-item label="专栏栏目选择" prop="relateColumnId"> <el-form-item label="专栏栏目选择" prop="relateColumnId">
<el-select v-model={form.value.relateColumnId} placeholder="请选择专栏栏目"> <el-select v-model={form.value.relateColumnId} placeholder="请选择专栏栏目">
{columnList.value.map((item) => ( {columnList.value.map((item) => (
<el-option value={item.id}>{item.title}</el-option> <el-option value={item.id} label={item.title}></el-option>
))} ))}
</el-select> </el-select>
</el-form-item> </el-form-item>
......
<template> <template>
<div class="layout-culture pb-11 h-full bg-[linear-gradient(to_bottom,#F0FBFD_0%,#ECEFFF_100%)]"> <div class="layout-culture pb-11 h-full bg-[linear-gradient(to_bottom,#F0FBFD_0%,#ECEFFF_100%)]">
<div <div
class="header flex px-40 items-center justify-between bg-white mb-1 shadow-sm fixed top-0 left-0 right-0 z-10 w-100vw" class="header flex px-40 items-center justify-between bg-white mb-1 shadow-sm fixed top-0 left-0 right-0 z-100 w-100vw"
> >
<!-- Logo区域 --> <!-- Logo区域 -->
<div @click="router.push('/')" class="flex items-center flex-shrink-0 min-w-0 cursor-pointer"> <div @click="router.push('/')" class="flex items-center flex-shrink-0 min-w-0 cursor-pointer">
...@@ -80,7 +80,9 @@ ...@@ -80,7 +80,9 @@
</div> </div>
</div> </div>
<div class="flex-1 w-full flex items-center justify-center"> <div class="flex-1 w-full flex items-center justify-center">
<div class="container max-h-none px-20 lg:px-20 2xl:px-30 transition-all duration-300"> <div
class="container max-h-none px-20 lg:px-20 2xl:px-30 transition-all duration-300 min-h-[calc(100vh-96px)]"
>
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<!-- 注释不能放到keep-alive下面 route是最终的路由信息 Component是当前n级路由的组件 二级路由 homePage videoDetail --> <!-- 注释不能放到keep-alive下面 route是最终的路由信息 Component是当前n级路由的组件 二级路由 homePage videoDetail -->
......
...@@ -9,6 +9,14 @@ export const useUserStore = defineStore('user', () => { ...@@ -9,6 +9,14 @@ export const useUserStore = defineStore('user', () => {
const token = ref(localStorage.getItem('token') || '') const token = ref(localStorage.getItem('token') || '')
// 获取用户信息 // 获取用户信息
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
// {
// email: 'lijiabin@yswg.com.cn',
// password: 'Lijiabin123.',
// }
// {
// email: 'wangshouyong@yswg.com.cn',
// password: 'Wsy123456!',
// }
const { data } = await loginByEmail({ const { data } = await loginByEmail({
email: 'lijiabin@yswg.com.cn', email: 'lijiabin@yswg.com.cn',
password: 'Lijiabin123.', password: 'Lijiabin123.',
......
...@@ -82,45 +82,4 @@ export default class DhRequest { ...@@ -82,45 +82,4 @@ export default class DhRequest {
return Promise.reject(error as BackendServiceResult<T>) return Promise.reject(error as BackendServiceResult<T>)
} }
} }
// async get<T>(
// url: string,
// config: AxiosRequestConfig,
// ): Promise<AxiosResponse<BackendServiceResult<T>>> {
// return this.request<T>({
// url,
// method: 'GET',
// ...config,
// })
// }
// async post<T>(
// url: string,
// config: AxiosRequestConfig,
// ): Promise<AxiosResponse<BackendServiceResult<T>>> {
// return this.request<T>({
// url,
// method: 'POST',
// ...config,
// })
// }
// async put<T>(
// url: string,
// config: AxiosRequestConfig,
// ): Promise<AxiosResponse<BackendServiceResult<T>>> {
// return this.request<T>({
// url,
// method: 'PUT',
// ...config,
// })
// }
// async delete<T>(
// url: string,
// config: AxiosRequestConfig,
// ): Promise<AxiosResponse<BackendServiceResult<T>>> {
// return this.request<T>({
// url,
// method: 'DELETE',
// ...config,
// })
// }
} }
...@@ -45,20 +45,36 @@ ...@@ -45,20 +45,36 @@
> >
<el-table-column type="selection" width="55"></el-table-column> <el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="period" label="案例编号" width="100"></el-table-column> <el-table-column prop="number" label="案例编号" width="100"></el-table-column>
<el-table-column prop="approvalRegion" label="案例标题" width="180"></el-table-column> <el-table-column prop="title" label="案例标题" width="180"></el-table-column>
<el-table-column prop="approvalType" label="案例文本内容" width="120"></el-table-column> <el-table-column prop="content" label="案例文本内容" width="120"></el-table-column>
<el-table-column label="文化关键词" align="center"> <el-table-column label="文化关键词" align="center">
<el-table-column prop="receivableAmount" label="主关键词" width="100"></el-table-column> <el-table-column prop="cultureKeywordMain" label="主关键词" width="120">
<el-table-column prop="receivedAmount" label="次关键词1" width="100"></el-table-column> <template #default="{ row }">
<el-table-column prop="overdueAmount" label="次关键词2" width="100"></el-table-column> <el-tag type="primary"> {{ row.cultureKeywordMain }}</el-tag>
<el-table-column prop="overdueAmount" label="次关键词3" width="100"></el-table-column> </template>
</el-table-column>
<el-table-column prop="cultureKeywordSecond" label="次关键词1" width="100">
<template #default="{ row }">
<el-tag type="primary"> {{ row.cultureKeywordSecond[0] || '' }} </el-tag>
</template>
</el-table-column>
<el-table-column prop="cultureKeywordSecond" label="次关键词2" width="120">
<template #default="{ row }">
<el-tag type="primary"> {{ row.cultureKeywordSecond[1] || '' }} </el-tag>
</template>
</el-table-column>
<el-table-column prop="cultureKeywordSecond" label="次关键词3" width="100">
<template #default="{ row }">
<el-tag type="primary"> {{ row.cultureKeywordSecond[2] || '' }} </el-tag>
</template>
</el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="年度主推关键词(玄天)" align="center"> <el-table-column label="年度主推关键词(选填)" align="center">
<el-table-column prop="annualReceivable" label="主关键词" width="100"></el-table-column> <el-table-column prop="annualReceivable" label="主关键词" width="100"></el-table-column>
<el-table-column prop="annualOverdue" label="次关键词" width="100"></el-table-column> <el-table-column prop="annualOverdue" label="次关键词" width="100"></el-table-column>
</el-table-column> </el-table-column>
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
<div class="search-section"> <div class="search-section">
<div class="flex-1 flex gap-2"> <div class="flex-1 flex gap-2">
<el-input <el-input
v-model="searchParams.title" v-model="searchParams.itemName"
placeholder="请输入栏目标题" placeholder="请输入商品名称"
class="w-200px" class="w-200px"
></el-input> ></el-input>
...@@ -15,21 +15,23 @@ ...@@ -15,21 +15,23 @@
class="search-select" class="search-select"
clearable clearable
> >
<el-option label="发布" :value="1" /> <el-option label="待发货" :value="0" />
<el-option label="隐藏" :value="0" /> <el-option label="已发货" :value="1" />
<el-option label="已取消" :value="2" />
</el-select>
<el-select
v-model="searchParams.source"
placeholder="请选择来源"
class="search-select"
clearable
>
<el-option label="商城" :value="1" />
<el-option label="大转盘" :value="2" />
<el-option label="每日抽奖" :value="3" />
</el-select> </el-select>
<el-button type="primary" :icon="Search" @click="refresh">搜索</el-button> <el-button type="primary" :icon="Search" @click="refresh">搜索</el-button>
<el-button @click="reset">重置</el-button> <el-button @click="reset">重置</el-button>
</div> </div>
<div class="flex justify-end">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增
</el-button>
<el-button type="primary" @click="handleBatchPublish"> 批量发布/隐藏 </el-button>
<el-button type="danger" @click="handleBatchDelete"> 批量删除 </el-button>
</div>
</div> </div>
<!-- 表格区域 --> <!-- 表格区域 -->
...@@ -43,40 +45,58 @@ ...@@ -43,40 +45,58 @@
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="55"> </el-table-column> <el-table-column type="selection" width="55"> </el-table-column>
<el-table-column prop="title" label="产品编号" min-width="200" /> <el-table-column prop="itemName" label="商品名称" min-width="200" />
<el-table-column prop="content" label="商品名称" min-width="200" /> <el-table-column prop="imageUrl" label="图片" width="300">
<el-table-column prop="faceUrl" label="图片" width="300">
<template #default="{ row }"> <template #default="{ row }">
<el-image <el-image
:preview-teleported="true" :preview-teleported="true"
:src="row.faceUrl" :src="row.imageUrl"
class="w-20 h-20 object-cover" class="w-20 h-20 object-cover"
:preview-src-list="[row.faceUrl]" :preview-src-list="[row.imageUrl]"
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="videoUrl" label="来源" min-width="200"> </el-table-column> <el-table-column prop="source" label="来源" min-width="200">
<el-table-column prop="releaseStatus" label="兑换数量" min-width="200"> </el-table-column> <template #default="{ row }">
{{ row.source === 1 ? '商城' : row.source === 2 ? '大转盘' : '每日抽奖' }}
</template>
</el-table-column>
<el-table-column prop="num" label="兑换数量" min-width="200"> </el-table-column>
<el-table-column prop="createTime" label="兑换时间" min-width="200"> <el-table-column prop="createTime" label="兑换时间" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
{{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="showName" label="兑换人" min-width="200" /> <el-table-column prop="userName" label="兑换人" min-width="200" />
<el-table-column prop="showName" label="发放人" min-width="200" /> <el-table-column prop="issuerName" label="发放人" min-width="200" />
<el-table-column prop="memo" label="发放备注" min-width="200" />
<el-table-column prop="createTime" label="发放时间" min-width="200"> <el-table-column prop="issueTime" label="发放时间" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
{{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="showName" label="发放状态" min-width="200" /> <el-table-column prop="status" label="发放状态" min-width="200">
<template #default="{ row }">
{{ row.status === 0 ? '待发货' : row.status === 1 ? '已发货' : '已取消' }}
</template>
</el-table-column>
<el-table-column prop="showName" label="发放人备注" min-width="200" /> <el-table-column prop="showName" label="发放人备注" min-width="200" />
<el-table-column label="操作" width="150" fixed="right"> <el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button> <el-button v-if="row.status === 0" type="primary" link @click="handleIssue(row)">
<el-button type="danger" link @click="handleDelete(row)">删除</el-button> 发放
</el-button>
<el-button
v-if="row.status === 1"
type="warning"
link
@click="handleCancelIssue(row)"
>
取消发放
</el-button>
<!-- <el-button type="danger" link @click="handleDelete(row)">删除</el-button> -->
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -131,15 +151,23 @@ ...@@ -131,15 +151,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { Search, Plus, Upload } from '@element-plus/icons-vue' import { Search, Plus, Upload } from '@element-plus/icons-vue'
import { usePageSearch, useResetData } from '@/hooks' import { usePageSearch, useResetData } from '@/hooks'
import { addOrUpdateColumn, deleteColumn, hideColumn, getExchangeList } from '@/api/backend' import {
addOrUpdateColumn,
deleteColumn,
hideColumn,
getExchangeList,
getBackendExchangeList,
issueProduct,
} from '@/api/backend'
import { updateArticleRecommend } from '@/api' import { updateArticleRecommend } from '@/api'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend' import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ArticleTypeEnum } from '@/constants' import { ArticleTypeEnum } from '@/constants'
import { sortByOriginalOrder } from 'element-plus/es/components/cascader-panel/src/utils.mjs'
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getExchangeList, { usePageSearch(getBackendExchangeList, {
defaultParams: { defaultParams: {
type: ArticleTypeEnum.VIDEO, type: ArticleTypeEnum.VIDEO,
}, },
...@@ -192,24 +220,34 @@ const handleIsRecommendChange = async (row: ArticleItemDto) => { ...@@ -192,24 +220,34 @@ const handleIsRecommendChange = async (row: ArticleItemDto) => {
refresh() refresh()
} }
// 删除 // 取消发放
const handleDelete = async (row: BackendColumnListItemDto) => { const handleCancelIssue = async (row: BackendColumnListItemDto) => {
try { await ElMessageBox.confirm('确定要取消发放吗?', '提示', {
await ElMessageBox.confirm(`确定要删除标签"${row.title}"吗?`, '提示', { confirmButtonText: '确定',
confirmButtonText: '确定', cancelButtonText: '取消',
cancelButtonText: '取消', })
type: 'warning', await issueProduct({
}) id: row.id,
status: 0,
await deleteColumn([row.id]) })
ElMessage.success('取消发放成功')
refresh()
}
ElMessage.success('删除成功') // 发放
refresh() const handleIssue = async (row: BackendColumnListItemDto) => {
} catch (error) { // 弹出message输入框 填写发放备注
if (error !== 'cancel') { const memo = await ElMessageBox.prompt('请输入发放备注', '提示', {
ElMessage.error('删除失败') confirmButtonText: '确定',
} cancelButtonText: '取消',
} })
await issueProduct({
id: row.id,
memo: memo.value,
status: 1,
})
ElMessage.success('发放成功')
refresh()
} }
// 提交表单 // 提交表单
......
...@@ -4,19 +4,41 @@ ...@@ -4,19 +4,41 @@
<div class="search-section"> <div class="search-section">
<div class="flex-1 flex gap-2"> <div class="flex-1 flex gap-2">
<el-input <el-input
v-model="searchParams.title" v-model="searchParams.name"
placeholder="请输入栏目标题" placeholder="请输入商品名称"
class="w-200px" class="w-200px"
></el-input> ></el-input>
<el-select <el-select
v-model="searchParams.status" v-model="searchParams.itemType"
placeholder="请选择发布状态" placeholder="请选择商品类型"
class="search-select" class="search-select"
clearable clearable
> >
<el-option label="发布" :value="1" /> <el-option label="实物" :value="ShopGoodsTypeEnum.REAL_GOODS" />
<el-option label="隐藏" :value="0" /> <el-option label="虚拟" :value="ShopGoodsTypeEnum.VIRTUAL_GOODS" />
</el-select>
<el-select
v-model="searchParams.region"
placeholder="请选择上架地区"
class="search-select"
clearable
>
<el-option
v-for="item in regionListOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select
v-model="searchParams.enable"
placeholder="是否展示在前台"
class="search-select"
clearable
>
<el-option label="是" :value="BooleanFlag.YES" />
<el-option label="否" :value="BooleanFlag.NO" />
</el-select> </el-select>
<el-button type="primary" :icon="Search" @click="refresh">搜索</el-button> <el-button type="primary" :icon="Search" @click="refresh">搜索</el-button>
<el-button @click="reset">重置</el-button> <el-button @click="reset">重置</el-button>
...@@ -27,8 +49,6 @@ ...@@ -27,8 +49,6 @@
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
新增 新增
</el-button> </el-button>
<el-button type="primary" @click="handleBatchPublish"> 批量发布/隐藏 </el-button>
<el-button type="danger" @click="handleBatchDelete"> 批量删除 </el-button>
</div> </div>
</div> </div>
...@@ -43,22 +63,24 @@ ...@@ -43,22 +63,24 @@
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="55"> </el-table-column> <el-table-column type="selection" width="55"> </el-table-column>
<el-table-column prop="title" label="商品编号" min-width="200" /> <el-table-column prop="sortOrder" label="商品排序" min-width="200" />
<el-table-column prop="content" label="商品名称" min-width="200" /> <el-table-column prop="name" label="商品名称" min-width="200" />
<el-table-column prop="faceUrl" label="图片" width="300"> <el-table-column prop="itemTypeName" label="商品类型" min-width="200" />
<el-table-column prop="description" label="商品描述" min-width="200" />
<el-table-column prop="imageUrl" label="图片" width="300">
<template #default="{ row }"> <template #default="{ row }">
<el-image <el-image
:preview-teleported="true" :preview-teleported="true"
:src="row.faceUrl" :src="row.imageUrl"
class="w-20 h-20 object-cover" class="w-20 h-20 object-cover"
:preview-src-list="[row.faceUrl]" :preview-src-list="[row.imageUrl]"
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="videoUrl" label="上架地区" min-width="200"> </el-table-column> <el-table-column prop="region" label="上架地区" min-width="200"> </el-table-column>
<el-table-column prop="releaseStatus" label="库存数量" min-width="200"> </el-table-column> <el-table-column prop="stock" label="库存数量" min-width="200"> </el-table-column>
<el-table-column prop="status" label="兑换所需YA币" min-width="200"> </el-table-column> <el-table-column prop="price" label="兑换所需YA币" min-width="200"> </el-table-column>
<el-table-column prop="showName" label="创建人" min-width="200" /> <el-table-column prop="showName" label="创建人" min-width="200" />
...@@ -67,12 +89,22 @@ ...@@ -67,12 +89,22 @@
{{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs(row.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="状态" min-width="200"> </el-table-column> <el-table-column prop="enable" label="是否展示在前台" min-width="200">
<template #default="{ row }">
<el-switch
:active-value="1"
:inactive-value="0"
active-text="是"
inactive-text="否"
:model-value="row.enable"
@change="handleEnableChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right"> <el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button> <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleHide(row)">隐藏</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button> <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
...@@ -100,17 +132,50 @@ ...@@ -100,17 +132,50 @@
width="500px" width="500px"
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<el-form ref="formRef" :model="form" :rules="formRules" label-width="80px"> <el-form ref="formRef" :model="form" :rules="formRules" label-width="auto">
<el-form-item label="标题" prop="title"> <el-form-item label="商品名称" prop="name">
<el-input v-model="form.title" placeholder="请输入标题" /> <el-input v-model="form.name" placeholder="请输入商品名称" />
</el-form-item> </el-form-item>
<el-form-item label="颜色" prop="color"> <el-form-item label="商品类型" prop="itemType">
<el-color-picker v-model="form.color" /> <el-select v-model="form.itemType" placeholder="请选择商品类型">
<span class="color-value">{{ form.color }}</span> <el-option label="实物" :value="ShopGoodsTypeEnum.REAL_GOODS" />
<el-option label="虚拟" :value="ShopGoodsTypeEnum.VIRTUAL_GOODS" />
</el-select>
</el-form-item>
<el-form-item label="商品描述" prop="description">
<el-input type="textarea" v-model="form.description" placeholder="请输入商品描述" />
</el-form-item>
<el-form-item label="图片" prop="imageUrl">
<UploadFile v-model="form.imageUrl" :limit="1" />
</el-form-item>
<el-form-item label="上架地区" prop="region">
<el-select v-model="form.region" placeholder="请选择上架地区">
<el-option
v-for="item in regionListOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="兑换所需YA币" prop="price">
<el-input-number v-model="form.price" placeholder="请输入" :min="0" />
</el-form-item> </el-form-item>
<el-form-item label="排序" prop="sort"> <el-form-item label="库存数量" prop="stock">
<el-input-number v-model="form.sort" :min="0" :max="100" /> <el-input-number v-model="form.stock" placeholder="请输入" :min="0" />
</el-form-item>
<el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="form.sortOrder" placeholder="请输入" :min="0" />
</el-form-item>
<el-form-item label="是否展示在前台" prop="enable">
<el-switch
v-model="form.enable"
:active-value="1"
:inactive-value="0"
active-text="是"
inactive-text="否"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -128,19 +193,24 @@ ...@@ -128,19 +193,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { Search, Plus, Upload } from '@element-plus/icons-vue' import { Search, Plus, Upload } from '@element-plus/icons-vue'
import { usePageSearch, useResetData } from '@/hooks' import { usePageSearch, useResetData } from '@/hooks'
import { addOrUpdateColumn, deleteColumn, hideColumn } from '@/api/backend' import {
import { getArticleList, updateArticleRecommend } from '@/api' addOrUpdateColumn,
deleteColumn,
hideColumn,
getShopItemList,
addOrUpdateShopItem,
deleteShopItem,
} from '@/api/backend'
import { updateArticleRecommend } from '@/api'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend' import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ArticleTypeEnum } from '@/constants' import { BooleanFlag, ShopGoodsTypeEnum, regionListOptions } from '@/constants'
import UploadFile from '@/components/common/UploadFile/index.vue'
import type { BackendShopItemDto } from '@/api/backend'
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getShopItemList)
defaultParams: {
type: ArticleTypeEnum.VIDEO,
},
})
// 对话框 // 对话框
const dialogVisible = ref(false) const dialogVisible = ref(false)
...@@ -149,12 +219,18 @@ const formRef = ref<FormInstance>() ...@@ -149,12 +219,18 @@ const formRef = ref<FormInstance>()
// 表单数据 // 表单数据
const [form, resetForm] = useResetData<AddOrUpdateColumnDto>({ const [form, resetForm] = useResetData<BackendShopItemDto>({
title: '',
color: '#000000',
id: undefined, id: undefined,
sort: 0, sort: 0,
type: 'column', enable: 1,
imageUrl: '',
itemType: ShopGoodsTypeEnum.REAL_GOODS,
name: '',
price: 0,
region: '',
stock: 0,
sortOrder: 0,
description: '',
}) })
// 表单验证规则 // 表单验证规则
const formRules: FormRules = { const formRules: FormRules = {
...@@ -173,39 +249,22 @@ const handleAdd = () => { ...@@ -173,39 +249,22 @@ const handleAdd = () => {
const handleEdit = (row: BackendColumnListItemDto) => { const handleEdit = (row: BackendColumnListItemDto) => {
resetForm() resetForm()
form.value = { form.value = {
title: row.title, ...row,
color: row.color,
id: row.id,
sort: row.sort,
type: 'column',
} }
dialogVisible.value = true dialogVisible.value = true
} }
// 是否置顶改变
const handleIsRecommendChange = async (row: ArticleItemDto) => {
await updateArticleRecommend(row.id)
ElMessage.success('修改成功')
refresh()
}
// 隐藏
const handleHide = async (row: BackendColumnListItemDto) => {
await hideColumn([row.id])
ElMessage.success('隐藏成功')
refresh()
}
// 删除 // 删除
const handleDelete = async (row: BackendColumnListItemDto) => { const handleDelete = async (row: BackendColumnListItemDto) => {
try { try {
await ElMessageBox.confirm(`确定要删除标签"${row.title}"吗?`, '提示', { await ElMessageBox.confirm(`确定要删除商品"${row.name}"吗?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}) })
await deleteColumn([row.id]) await deleteShopItem(row.id)
refresh()
ElMessage.success('删除成功') ElMessage.success('删除成功')
refresh() refresh()
} catch (error) { } catch (error) {
...@@ -223,9 +282,9 @@ const handleSubmit = async () => { ...@@ -223,9 +282,9 @@ const handleSubmit = async () => {
await formRef.value.validate() await formRef.value.validate()
if (form.value.id) { if (form.value.id) {
await addOrUpdateColumn(form.value) await addOrUpdateShopItem(form.value)
} else { } else {
await addOrUpdateColumn(form.value) await addOrUpdateShopItem(form.value)
} }
ElMessage.success(form.value.id ? '编辑成功' : '新增成功') ElMessage.success(form.value.id ? '编辑成功' : '新增成功')
...@@ -246,20 +305,14 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -246,20 +305,14 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 批量发布/隐藏 const handleEnableChange = async (row: BackendColumnListItemDto) => {
const handleBatchPublish = async () => { console.log(row)
await hideColumn(selectedRows.value.map((item) => item.id)) await addOrUpdateShopItem({
refresh() id: row.id,
selectedRows.value = [] enable: row.enable === BooleanFlag.YES ? BooleanFlag.NO : BooleanFlag.YES,
ElMessage.success('发布/隐藏成功') })
} ElMessage.success('修改成功')
// 批量删除
const handleBatchDelete = async () => {
await deleteColumn(selectedRows.value.map((item) => item.id))
refresh() refresh()
selectedRows.value = []
ElMessage.success('删除成功')
} }
</script> </script>
......
...@@ -23,13 +23,14 @@ ...@@ -23,13 +23,14 @@
</div> </div>
</div> </div>
</div> </div>
<div v-loading="loading"> <div v-if="list.length" v-loading="loading">
<!-- 第一页的特殊布局 --> <!-- 第一页的特殊布局 -->
<div v-show="searchParams.current === 1"> <div v-show="searchParams.current === 1">
<!-- 前三个特殊布局 --> <!-- 前三个特殊布局 -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- 第一个视频 - 占据两列 --> <!-- 第一个视频 - 占据两列 -->
<div <div
v-show="list.length >= 1"
@click="router.push(`/videoDetail/${list[0]?.id}`)" @click="router.push(`/videoDetail/${list[0]?.id}`)"
class="lg:col-span-2 group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-xl transition-all duration-500 cursor-pointer" class="lg:col-span-2 group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-xl transition-all duration-500 cursor-pointer"
> >
...@@ -121,9 +122,9 @@ ...@@ -121,9 +122,9 @@
<!-- 右侧两个视频 --> <!-- 右侧两个视频 -->
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<div <div
v-for="(item, index) in [list[1], list[2]]" v-for="(item, index) in list.slice(1, 3)"
:key="index" :key="index"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer flex-1" class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
@click="router.push(`/videoDetail/${item?.id}`)" @click="router.push(`/videoDetail/${item?.id}`)"
> >
<div class="relative overflow-hidden"> <div class="relative overflow-hidden">
...@@ -132,13 +133,6 @@ ...@@ -132,13 +133,6 @@
class="w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500" class="w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/> />
<div class="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"></div> <div class="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"></div>
<!-- 标签 -->
<!-- <div
class="absolute top-3 left-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-2.5 py-1 rounded-full text-xs font-semibold"
>
{{ item.tagNameList[0] }}
</div> -->
<div <div
v-if="item?.isRecommend" v-if="item?.isRecommend"
class="absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg" class="absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
...@@ -208,7 +202,7 @@ ...@@ -208,7 +202,7 @@
</div> </div>
<!-- 剩余视频 - 标准网格 --> <!-- 剩余视频 - 标准网格 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div v-show="list.length > 3" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div <div
@click="router.push(`/videoDetail/${item.id}`)" @click="router.push(`/videoDetail/${item.id}`)"
v-for="item in list.slice(3)" v-for="item in list.slice(3)"
...@@ -416,6 +410,11 @@ ...@@ -416,6 +410,11 @@
</div> </div>
</div> </div>
</div> </div>
<template v-else>
<div class="flex items-center justify-center h-full">
<el-empty description="暂无数据" />
</div>
</template>
</div> </div>
</template> </template>
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import { ShopGoodsTypeEnum, regionListOptions } from '@/constants' import { ShopGoodsTypeEnum, regionListOptions } from '@/constants'
import type { ExchangeGoodsParams, ShopItemDto } from '@/api' import type { ExchangeGoodsParams, ShopItemDto } from '@/api'
import type { SetupContext } from 'vue' import type { SetupContext } from 'vue'
import ask from '@/assets/img/culture/ask.png'
type ExchangeContentProps = { type ExchangeContentProps = {
item: ShopItemDto item: ShopItemDto
...@@ -25,11 +24,11 @@ export default function ExchangeContent( ...@@ -25,11 +24,11 @@ export default function ExchangeContent(
<div class="relative"> <div class="relative">
<div class="w-32 h-32 bg-gradient-to-br from-orange-100 to-pink-100 rounded-3xl flex items-center justify-center shadow-lg"> <div class="w-32 h-32 bg-gradient-to-br from-orange-100 to-pink-100 rounded-3xl flex items-center justify-center shadow-lg">
<div class="w-20 h-20 bg-white rounded-lg flex items-center justify-center shadow-sm"> <div class="w-20 h-20 bg-white rounded-lg flex items-center justify-center shadow-sm">
<img src={ask} alt={item.name} class="w-16 h-16 object-contain" /> <img src={item.imageUrl} alt={item.name} class="w-16 h-16 object-contain" />
</div> </div>
</div> </div>
<div class="absolute -top-2 -right-2 w-7 h-7 bg-blue-500 rounded-full flex items-center justify-center shadow-md"> <div class="absolute -top-2 -right-2 w-7 h-7 bg-blue-500 rounded-full flex items-center justify-center shadow-md">
<span class="text-white text-sm font-medium">6</span> <span class="text-white text-sm font-medium">{item.stock}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -74,6 +73,7 @@ export default function ExchangeContent( ...@@ -74,6 +73,7 @@ export default function ExchangeContent(
<label class="text-gray-700 text-sm font-medium mb-2 block">选择数量</label> <label class="text-gray-700 text-sm font-medium mb-2 block">选择数量</label>
<el-input-number <el-input-number
min={1} min={1}
max={item.stock}
modelValue={modelValue.num} modelValue={modelValue.num}
onUpdate:modelValue={(value: number) => onUpdate:modelValue={(value: number) =>
context.emit('update:modelValue', { ...modelValue, num: value }) context.emit('update:modelValue', { ...modelValue, num: value })
......
...@@ -42,13 +42,13 @@ ...@@ -42,13 +42,13 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="price" label="商品单价" width="100" align="center" /> <!-- <el-table-column prop="price" label="商品单价" width="100" align="center" /> -->
<el-table-column prop="num" label="兑换数量" width="100" align="center" /> <el-table-column prop="num" label="兑换数量" width="100" align="center" />
<el-table-column label="扣除YA币" width="110" align="center"> <el-table-column label="扣除YA币" width="110" align="center">
<template #default="scope"> <template #default="scope">
<span class="text-red-500 font-semibold"> -{{ scope.row.price * scope.row.num }} </span> <span class="text-red-500 font-semibold"> -{{ scope.row.price }} </span>
</template> </template>
</el-table-column> </el-table-column>
......
...@@ -45,9 +45,9 @@ ...@@ -45,9 +45,9 @@
> >
<div class="w-24 h-24 mb-3 flex items-center justify-center"> <div class="w-24 h-24 mb-3 flex items-center justify-center">
<img <img
src="@/assets/img/culture/ask.png" :src="item.imageUrl"
alt="" alt=""
class="w-full h-full object-contain group-hover:scale-110 transition-transform duration-300" class="rounded-lg w-full h-full object-contain group-hover:scale-110 transition-transform duration-300"
/> />
</div> </div>
<div <div
...@@ -116,9 +116,9 @@ ...@@ -116,9 +116,9 @@
> >
<div class="w-24 h-24 mb-3 flex items-center justify-center"> <div class="w-24 h-24 mb-3 flex items-center justify-center">
<img <img
src="@/assets/img/culture/ask.png" :src="item.imageUrl"
alt="" alt=""
class="w-full h-full object-contain group-hover:scale-110 transition-transform duration-300" class="rounded-lg w-full h-full object-contain group-hover:scale-110 transition-transform duration-300"
/> />
</div> </div>
<div <div
......
<template> <template>
<div <div
class="fixed right-50 top-70% -translate-y-50% flex flex-col items-center content-center gap-5" class="fixed 2xl:right-50 lg:right-20 right-10 top-70% -translate-y-50% flex flex-col items-center content-center gap-5"
> >
<div class="flex flex-col bg-white rounded-8px shadow-md overflow-hidden"> <div class="flex flex-col bg-white rounded-8px shadow-md overflow-hidden">
<div <div
......
...@@ -61,12 +61,14 @@ ...@@ -61,12 +61,14 @@
<!-- 图片内容 --> <!-- 图片内容 -->
<div v-if="articleDetail.imgUrl" class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"> <div v-if="articleDetail.imgUrl" class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<img <el-image
v-for="item in articleDetail.imgUrl.split(',')" v-for="item in articleDetail.imgUrl.split(',')"
:key="item" :key="item"
:src="item" :src="item"
alt="" fit="cover"
class="rounded-lg object-cover w-full h-64 hover:scale-105 transition-transform cursor-pointer" class="rounded-lg w-full h-64 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list="articleDetail.imgUrl.split(',')"
:preview-teleported="true"
/> />
</div> </div>
</div> </div>
...@@ -85,231 +87,7 @@ ...@@ -85,231 +87,7 @@
</div> </div>
<!-- 评论区 --> <!-- 评论区 -->
<Comment ref="commentRef" :id="id" /> <Comment ref="commentRef" :id="id" v-model:total="articleDetail.replyCount" />
<!-- <div
ref="commentRef"
class="mt-6 bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden"
>
<div class="p-4 border-b border-gray-100">
<div class="flex items-center gap-4 justify-between">
<span class="text-lg font-semibold text-gray-800">评论 ({{ total }})</span>
<div class="flex items-center gap-2">
<button
class="cursor-pointer px-3 py-1.5 text-sm bg-gradient-to-r text-gray-600 rounded-full transition-all hover:bg-gray-100"
@click="((searchParams.sortType = 2), refresh())"
:class="{
'bg-gradient-to-r from-blue-500 to-purple-500 text-white! shadow-md ':
searchParams.sortType === 2,
}"
>
最新
</button>
<button
class="cursor-pointer px-3 py-1.5 text-sm bg-gradient-to-r text-gray-600 rounded-full transition-all hover:bg-gray-100"
:class="{
'bg-gradient-to-r from-blue-500 to-purple-500 text-white! shadow-md':
searchParams.sortType === 1,
}"
@click="((searchParams.sortType = 1), refresh())"
>
最多评论
</button>
<button
class="cursor-pointer px-3 py-1.5 text-sm bg-gradient-to-r text-gray-600 rounded-full transition-all hover:bg-gray-100"
:class="{
'bg-gradient-to-r from-blue-500 to-purple-500 text-white! shadow-md':
searchParams.sortType === 4,
}"
@click="((searchParams.sortType = 4), refresh())"
>
最多点赞
</button>
</div>
</div>
</div>
<div>
<div class="p-4 border-b border-gray-100">
<div class="flex gap-3">
<img :src="userInfo?.avatar" alt="" class="w-10 h-10 rounded-full object-cover" />
<div class="flex-1">
<el-input
v-model="comment"
type="textarea"
placeholder="写下你的评论..."
:rows="3"
></el-input>
<div class="flex justify-between items-center mt-3">
<div class="flex items-center gap-2 text-sm text-gray-500">
<button class="hover:text-blue-500 transition-colors">
<i class="i-carbon-face-satisfied"></i>
</button>
<button class="hover:text-blue-500 transition-colors">
<i class="i-carbon-image"></i>
</button>
</div>
<button
class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled="!comment.trim() || loading"
@click="handleComment()"
>
发表
</button>
</div>
</div>
</div>
</div>
<div v-loading="loading" class="divide-y divide-gray-100" v-if="list.length">
<div v-for="item in list" :key="item.id">
<div class="p-4 hover:bg-gray-50/50 transition-colors">
<div class="flex gap-3">
<img :src="item.avatar" alt="" class="w-10 h-10 rounded-full object-cover" />
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="font-semibold text-gray-800">{{ item.replyUser }}</span>
<span
class="px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
>
技术专家
</span>
<span class="px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>置顶</span
>
</div>
<p class="text-gray-700 mb-3 break-all">
{{ item.content }}
</p>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4 text-sm text-gray-500">
<span>{{
dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
}}</span>
<div
class="flex items-center gap-1 cursor-pointer"
@click="handleLickComment(item)"
>
<el-icon
:size="16"
:style="{ color: item.hasPraise ? '#409eff' : '#606266' }"
>
<Pointer />
</el-icon>
<span>{{ item.postPriseCount }}</span>
</div>
<button
class="cursor-pointer hover:text-blue-500 transition-colors"
@click="handleReply(item)"
>
回复
</button>
</div>
</div>
<div v-if="item.children.length" class="mt-3 ml-4 space-y-3">
<div
v-for="child in item.children"
:key="child.id"
class="flex gap-2 p-3 bg-gray-50 rounded-lg"
>
<img :src="child.avatar" alt="" class="w-8 h-8 rounded-full object-cover" />
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="font-medium text-sm text-gray-800">{{
child.replyUser
}}</span>
<span class="text-xs text-gray-500">{{
dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</div>
<p class="text-sm text-gray-700">
{{ child.content }}
</p>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4 text-sm text-gray-500">
<span>{{
dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
}}</span>
<div
class="flex items-center gap-1 cursor-pointer"
@click="handleLickComment(child)"
>
<el-icon
:size="16"
:style="{ color: child.hasPraise ? '#409eff' : '#606266' }"
>
<Pointer />
</el-icon>
<span>{{ child.postPriseCount }}</span>
</div>
<button
@click="handleReply(child)"
class="cursor-pointer hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="showCommentBox(item)" class="flex gap-3">
<img
:src="userInfo?.avatar"
alt=""
class="w-10 h-10 rounded-full object-cover"
/>
<div class="flex-1">
<el-input
v-model="comment"
type="textarea"
placeholder="写下你的评论..."
:rows="3"
></el-input>
<div class="flex justify-between items-center mt-3">
<div class="flex items-center gap-2 text-sm text-gray-500">
<button class="hover:text-blue-500 transition-colors">
<i class="i-carbon-face-satisfied"></i>
</button>
<button class="hover:text-blue-500 transition-colors">
<i class="i-carbon-image"></i>
</button>
</div>
<button
class="cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled="!comment.trim()"
@click="handleComment(item.id)"
>
发表
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-4">
<el-divider class="my-1" />
</div>
</div>
<div class="flex justify-end">
<div class="w-fit p-4">
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
:total="total"
@current-change="
async (e) => {
;(await goToPage(e), handleBackTop())
}
"
@size-change="changePageSize"
layout="prev, pager, next, total"
/>
</div>
</div>
</div>
</div>
</div> -->
</div> </div>
</div> </div>
</template> </template>
......
...@@ -57,9 +57,13 @@ ...@@ -57,9 +57,13 @@
<div class="flex gap-4"> <div class="flex gap-4">
<el-button @click="handleCancel"> 取消 </el-button> <el-button @click="handleCancel"> 取消 </el-button>
<!-- <el-button @click="handlePreview"> 预览 </el-button> --> <!-- <el-button @click="handlePreview"> 预览 </el-button> -->
<el-button type="info" plain @click="handleSubmit"> 存草稿 </el-button> <el-button type="info" plain @click="handleSubmit(ReleaseStatusTypeEnum.DRAFT)">
存草稿
</el-button>
</div> </div>
<el-button type="primary" @click="handleSubmit"> 提交 </el-button> <el-button type="primary" @click="handleSubmit(ReleaseStatusTypeEnum.PUBLISH)">
提交
</el-button>
</div> </div>
</el-form> </el-form>
</div> </div>
...@@ -80,11 +84,6 @@ const router = useRouter() ...@@ -80,11 +84,6 @@ const router = useRouter()
const formRef = useTemplateRef<FormInstance>('formRef') const formRef = useTemplateRef<FormInstance>('formRef')
type FormData = Omit<AddOrUpdateCaseDto, 'tagRelationDtoList'> & {
mainTagId: string
subTagIds: number[]
}
const rules = { const rules = {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }], title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }], content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
...@@ -98,7 +97,7 @@ const [form, resetForm] = useResetData({ ...@@ -98,7 +97,7 @@ const [form, resetForm] = useResetData({
mainTagId: '', mainTagId: '',
subTagIds: [], subTagIds: [],
isSync: BooleanFlag.NO, isSync: BooleanFlag.NO,
releaseStatus: ReleaseStatusTypeEnum.DRAFT, releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
}) })
// 取消 // 取消
...@@ -115,10 +114,11 @@ const handleCancel = () => { ...@@ -115,10 +114,11 @@ const handleCancel = () => {
// 保存草稿 // 保存草稿
const transformData = (formData: FormData): AddOrUpdateCaseDto => { const transformData = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateCaseDto => {
const { mainTagId, subTagIds, ...rest } = formData const { mainTagId, subTagIds, ...rest } = form.value
const obj: AddOrUpdateCaseDto = { const obj: AddOrUpdateCaseDto = {
...rest, ...rest,
releaseStatus,
tagRelationDtoList: [], tagRelationDtoList: [],
} }
// 添加标签内容 // 添加标签内容
...@@ -138,9 +138,9 @@ const transformData = (formData: FormData): AddOrUpdateCaseDto => { ...@@ -138,9 +138,9 @@ const transformData = (formData: FormData): AddOrUpdateCaseDto => {
} }
// 提交 // 提交
const handleSubmit = async () => { const handleSubmit = async (releaseStatus: ReleaseStatusTypeEnum) => {
await formRef.value?.validate() await formRef.value?.validate()
const res = await addOrUpdateCase(transformData(form.value)) const res = await addOrUpdateCase({ ...transformData(releaseStatus) })
if (res) { if (res) {
ElMessage.success('提交成功') ElMessage.success('提交成功')
resetForm() resetForm()
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
<el-icon class="text-2xl text-gray-300"><Document /></el-icon> <el-icon class="text-2xl text-gray-300"><Document /></el-icon>
</div> </div>
<div class="text-gray-500 text-lg mb-2">暂无内容</div> <div class="text-gray-500 text-lg mb-2">暂无内容</div>
<div class="text-gray-400 text-sm">{{ getEmptyText() }}</div>
</div> </div>
<div v-else class="space-y-4"> <div v-else class="space-y-4">
...@@ -42,7 +41,7 @@ ...@@ -42,7 +41,7 @@
<!-- Meta Info --> <!-- Meta Info -->
<div class="flex items-center text-gray-400 text-sm ml-4"> <div class="flex items-center text-gray-400 text-sm ml-4">
<el-button type="primary" link>编辑</el-button> <el-button type="primary" link>编辑</el-button>
<el-button type="danger" link>删除</el-button> <el-button type="danger" link @click="handleDelete(item.id)">删除</el-button>
</div> </div>
</div> </div>
</div> </div>
...@@ -70,8 +69,7 @@ ...@@ -70,8 +69,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Document } from '@element-plus/icons-vue' import { Document } from '@element-plus/icons-vue'
import { getSelfCommentList, deleteComment } from '@/api'
import { getSelfCommentList } from '@/api'
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { commentTypeListOptions } from '@/constants/options' import { commentTypeListOptions } from '@/constants/options'
import dayjs from 'dayjs' import dayjs from 'dayjs'
...@@ -82,7 +80,6 @@ const toggleTab = (key: number) => { ...@@ -82,7 +80,6 @@ const toggleTab = (key: number) => {
refresh() refresh()
} }
// State
const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch( const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch(
getSelfCommentList, getSelfCommentList,
{ {
...@@ -92,22 +89,14 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize } ...@@ -92,22 +89,14 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize }
}, },
) )
// Computed const handleDelete = async (id: number) => {
const paginatedList = computed(() => { await ElMessageBox.confirm('确定删除该评论吗?', '提示', {
const start = (searchParams.value.current - 1) * searchParams.value.size confirmButtonText: '确定',
const end = start + searchParams.value.size cancelButtonText: '取消',
return list.value.slice(start, end) type: 'warning',
}) })
await deleteComment(id)
const getEmptyText = () => { ElMessage.success('删除成功')
const emptyTexts: Record<string, string> = { refresh()
posts: '还没有发布任何帖子',
videos: '还没有上传任何视频',
questions: '还没有提出任何问题',
articles: '还没有发表任何专栏文章',
practice: '还没有分享任何实践经验',
interviews: '还没有参与任何专访',
}
return emptyTexts[searchParams.type] || '暂无数据'
} }
</script> </script>
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
<div class="flex items-center text-gray-400 text-sm ml-4"> <div class="flex items-center text-gray-400 text-sm ml-4">
<el-button type="primary" link @click="handleView(item)">查看</el-button> <el-button type="primary" link @click="handleView(item)">查看</el-button>
<el-button type="danger" link>删除</el-button> <el-button type="danger" link @click="handleDelete(item.id)">删除</el-button>
</div> </div>
</div> </div>
</div> </div>
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Document } from '@element-plus/icons-vue' import { Document } from '@element-plus/icons-vue'
import { getSelfPublishList } from '@/api' import { getSelfPublishList, deleteArticle } from '@/api'
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { articleTypeListOptions } from '@/constants/options' import { articleTypeListOptions } from '@/constants/options'
import dayjs from 'dayjs' import dayjs from 'dayjs'
...@@ -96,4 +96,14 @@ const handleView = (item: SelfPublishDetailDto) => { ...@@ -96,4 +96,14 @@ const handleView = (item: SelfPublishDetailDto) => {
router.push(`/postDetail/${item.id}`) router.push(`/postDetail/${item.id}`)
} }
} }
const handleDelete = async (articleId: number) => {
await ElMessageBox.confirm('确定删除该吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
await deleteArticle(articleId)
refresh()
ElMessage.success('删除成功')
}
</script> </script>
<template> <template>
<div class="min-h-screen px-20"> <!-- 整体页面容器:浅灰背景 -->
<div class="lg:col-span-3"> <div class="max-w-[1200px] mx-auto px-4 space-y-4">
<!-- 视频播放器 --> <!-- 卡片1: 视频播放器区域 -->
<div class="bg-black rounded-lg overflow-hidden shadow-lg"> <div class="bg-white rounded-lg shadow-sm overflow-hidden">
<video <!-- 标题区 -->
ref="videoRef" <div class="p-4 pb-3">
:src="videoDetail?.videoUrl" <h1 class="text-xl md:text-2xl font-medium text-gray-900 mb-2 leading-snug">
class="w-full aspect-video"
controls
@play="handlePlay"
@pause="handlePause"
></video>
</div>
<!-- 上方卡片:视频信息 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-5">
<!-- 标题 -->
<h1 class="text-xl font-semibold text-gray-900 mb-3 leading-tight">
{{ videoDetail?.title }} {{ videoDetail?.title }}
</h1> </h1>
<div class="flex items-center text-xs text-gray-500 gap-4 flex-wrap">
<!-- 数据统计行 -->
<div class="flex items-center gap-4 text-sm text-gray-500 mb-3 flex-wrap">
<span class="flex items-center gap-1">
<i class="i-carbon-view"></i>
{{ formatNumber(videoDetail?.viewCount) }} 播放
</span>
<span class="flex items-center gap-1">
<i class="i-carbon-thumbs-up"></i>
{{ formatNumber(videoDetail?.postPriseCount) }}
</span>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<i class="i-carbon-star"></i> <i class="i-carbon-play-filled text-gray-400"></i>
{{ formatNumber(videoDetail?.collectCount) }} {{ formatNumber(videoDetail?.viewCount) }}播放
</span> </span>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<i class="i-carbon-share"></i> <i class="i-carbon-time text-gray-400"></i>
{{ formatNumber(videoDetail?.shareCount) }}
</span>
<span class="ml-auto text-gray-400 text-xs">
{{ dayjs(videoDetail?.createTime * 1000).format('YYYY-MM-DD HH:mm') }} {{ dayjs(videoDetail?.createTime * 1000).format('YYYY-MM-DD HH:mm') }}
</span> </span>
</div> <span class="text-gray-300">|</span>
<span class="text-orange-500 bg-orange-50 px-2 py-0.5 rounded text-[10px]">
<!-- 标签 --> 未经作者授权,禁止转载
<div class="flex flex-wrap gap-2 mb-3">
<span
v-for="tag in videoDetail?.tagNameList"
:key="tag"
class="px-2.5 py-1 text-xs bg-cyan-50 text-cyan-600 rounded hover:bg-cyan-100 transition-colors cursor-pointer"
>
#{{ tag }}
</span> </span>
</div> </div>
<!-- 简介 -->
<div
v-if="videoDetail?.content"
class="pt-3 border-t border-gray-100 text-sm text-gray-600 leading-relaxed whitespace-pre-wrap"
>
{{ videoDetail?.content }}
</div>
</div> </div>
<!-- 下方卡片:UP主和操作 --> <!-- 视频播放器 -->
<div class="my-4 bg-white rounded-lg shadow-sm border border-gray-200 p-4"> <div class="w-full bg-black aspect-video">
<video
ref="videoRef"
:src="videoDetail?.videoUrl"
class="w-full h-full object-contain"
controls
@play="handlePlay"
@pause="handlePause"
></video>
</div>
<div class="bg-white rounded-xl shadow-sm p-4">
<div class="flex items-center justify-between flex-wrap gap-4"> <div class="flex items-center justify-between flex-wrap gap-4">
<!-- 左侧:UP主信息 --> <!-- 左侧:UP主信息 -->
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<img <img
:src="videoDetail?.createUserAvatar" :src="videoDetail?.createUserAvatar"
alt="" class="w-12 h-12 rounded-full object-cover cursor-pointer hover:opacity-80 transition-opacity ring-2 ring-gray-100"
class="w-10 h-10 rounded-full object-cover cursor-pointer"
/> />
<div> <div class="flex flex-col">
<h3 class="text-sm font-medium text-gray-900 cursor-pointer transition-colors"> <h3
class="text-base font-semibold text-gray-900 cursor-pointer hover:text-blue-500 transition-colors"
>
{{ videoDetail?.createUserName }} {{ videoDetail?.createUserName }}
</h3> </h3>
<p class="text-xs text-gray-400 mt-0.5">
{{ videoDetail?.userSign || '这个人很懒,什么都没有写' }}
</p>
</div> </div>
</div> <div class="flex gap-2 ml-2">
<button
<!-- 右侧:操作按钮 --> class="bg-blue-500 hover:bg-blue-600 text-white text-sm px-5 py-2 rounded-lg transition-all hover:shadow-md flex items-center gap-1.5 font-medium"
<div class="flex items-center gap-2"> >
<el-icon <i class="i-carbon-add text-base"></i> 关注
class="group-hover:text-blue-500! cursor-pointer" </button>
size="20"
:style="{ color: videoDetail?.hasPraised ? '#409eff' : '#606266' }"
>
<Star />
</el-icon>
<span>{{ videoDetail?.praiseCount || 1 }}</span>
<el-icon
class="group-hover:text-blue-500! cursor-pointer"
size="20"
:style="{ color: videoDetail?.hasCollect ? '#409eff' : '#606266' }"
>
<Star />
</el-icon>
<span>{{ videoDetail?.collectionCount || 1 }}</span>
<el-icon class="group-hover:text-blue-500! cursor-pointer" size="20">
<Star />
</el-icon>
<span>{{ videoDetail?.replyCount || 1 }}</span>
</div>
</div>
</div>
<!-- 评论区 -->
<Comment ref="commentRef" :id="videoId" />
<!-- <div
ref="commentRef"
class="mt-6 bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden"
>
<div class="p-6 border-b border-gray-100">
<div class="flex items-center justify-between">
<span class="text-xl font-bold text-gray-800">
评论 {{ formatNumber(videoDetail?.commentCount) }}
</span>
<div class="flex items-center gap-2">
<button <button
v-for="filter in commentFilters" class="border border-gray-200 hover:border-gray-300 hover:bg-gray-50 text-gray-600 text-sm px-4 py-2 rounded-lg transition-all"
:key="filter.value"
class="px-4 py-1.5 text-sm rounded-full transition-all"
:class="
activeFilter === filter.value
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-md'
: 'hover:bg-gray-100 text-gray-600'
"
@click="activeFilter = filter.value"
> >
{{ filter.label }} 私信
</button> </button>
</div> </div>
</div> </div>
</div>
<div class="p-6 border-b border-gray-100"> <!-- 右侧:互动按钮 -->
<div class="flex gap-4"> <div class="flex items-center gap-4">
<img :src="userInfo?.avatar" alt="" class="w-10 h-10 rounded-full object-cover" /> <!-- 点赞 -->
<div class="flex-1"> <button
<el-input class="flex flex-col items-center gap-1 cursor-pointer group transition-all px-3 py-2 rounded-lg hover:bg-pink-50"
v-model="commentContent" :class="videoDetail?.hasPraised ? 'text-pink-500' : 'text-gray-600'"
type="textarea" >
placeholder="发一条友善的评论" <i class="i-carbon-thumbs-up text-2xl group-hover:scale-110 transition-transform"></i>
:rows="3" <span class="text-xs font-medium">{{
:maxlength="500" formatNumber(videoDetail?.praiseCount) || '点赞'
show-word-limit }}</span>
></el-input> </button>
<div class="flex justify-between items-center mt-3">
<div class="flex items-center gap-3"> <!-- 收藏 -->
<button class="text-gray-500 hover:text-blue-500 transition-colors"> <button
<i class="i-carbon-face-satisfied text-xl"></i> class="flex flex-col items-center gap-1 cursor-pointer group transition-all px-3 py-2 rounded-lg hover:bg-blue-50"
</button> :class="videoDetail?.hasCollect ? 'text-blue-500' : 'text-gray-600'"
<button class="text-gray-500 hover:text-blue-500 transition-colors"> >
<i class="i-carbon-image text-xl"></i> <i class="i-carbon-star text-2xl group-hover:scale-110 transition-transform"></i>
</button> <span class="text-xs font-medium">{{
</div> formatNumber(videoDetail?.collectionCount) || '收藏'
<button }}</span>
class="px-8 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed" </button>
:disabled="!commentContent.trim()"
@click="publishComment()" <!-- 分享 -->
> <button
发布 class="flex flex-col items-center gap-1 cursor-pointer group text-gray-600 transition-all px-3 py-2 rounded-lg hover:bg-green-50 hover:text-green-500"
</button> >
</div> <i class="i-carbon-share text-2xl group-hover:scale-110 transition-transform"></i>
</div> <span class="text-xs font-medium">{{
formatNumber(videoDetail?.shareCount) || '分享'
}}</span>
</button>
<!-- 更多 -->
<button
class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-all"
>
<i class="i-carbon-overflow-menu-horizontal text-2xl"></i>
</button>
</div> </div>
</div> </div>
</div>
</div>
<div class="divide-y divide-gray-100"> <!-- 卡片3: 简介与标签 -->
<div <div class="bg-white rounded-lg shadow-sm p-5">
v-for="comment in commentList" <h3 class="text-base font-semibold text-gray-900 mb-3">视频简介</h3>
:key="comment.id"
class="p-6 hover:bg-gray-50/50 transition-colors"
>
<div class="flex gap-4">
<img :src="comment.avatar" alt="" class="w-10 h-10 rounded-full object-cover" />
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="font-semibold text-gray-800">{{ comment.username }}</span>
<span
v-if="comment.isAuthor"
class="px-2 py-0.5 text-xs bg-gradient-to-r from-pink-100 to-red-100 text-pink-600 rounded"
>
UP主
</span>
<span
v-if="comment.isPinned"
class="px-2 py-0.5 text-xs bg-yellow-100 text-yellow-600 rounded"
>
置顶
</span>
</div>
<p class="text-gray-700 mb-3 leading-relaxed">{{ comment.content }}</p>
<div class="flex items-center gap-6 text-sm text-gray-500">
<span>{{ dayjs(comment.createTime * 1000).fromNow() }}</span>
<button
class="flex items-center gap-1 hover:text-blue-500 transition-colors"
@click="handleCommentLike(comment)"
>
<i
:class="
comment.hasLiked
? 'i-carbon-thumbs-up-filled text-blue-500'
: 'i-carbon-thumbs-up'
"
></i>
<span :class="comment.hasLiked ? 'text-blue-500' : ''">{{
comment.likeCount || '点赞'
}}</span>
</button>
<button
class="hover:text-blue-500 transition-colors"
@click="handleReply(comment)"
>
回复
</button>
</div>
<div v-show="replyingCommentId === comment.id" class="mt-4 flex gap-3">
<img :src="userInfo?.avatar" alt="" class="w-8 h-8 rounded-full object-cover" />
<div class="flex-1">
<el-input
v-model="replyContent"
type="textarea"
:placeholder="`回复 @${comment.username}`"
:rows="2"
></el-input>
<div class="flex justify-end gap-2 mt-2">
<button
class="px-4 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded-full transition-all"
@click="replyingCommentId = null"
>
取消
</button>
<button
class="px-4 py-1.5 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full hover:shadow-lg transition-all disabled:opacity-50"
:disabled="!replyContent.trim()"
@click="publishComment(comment.id)"
>
回复
</button>
</div>
</div>
</div>
<div v-if="comment.replies?.length" class="mt-4 space-y-3"> <!-- 简介文本 -->
<div <div
v-for="reply in comment.replies" class="text-sm text-gray-700 leading-7 whitespace-pre-wrap mb-4"
:key="reply.id" :class="{ 'line-clamp-3': !expandDesc }"
class="flex gap-3 p-3 bg-gray-50 rounded-lg" >
> {{ videoDetail?.content || '暂无简介' }}
<img :src="reply.avatar" alt="" class="w-8 h-8 rounded-full object-cover" /> </div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="font-medium text-sm text-gray-800">{{ reply.username }}</span>
<span class="text-gray-400 text-xs">回复</span>
<span class="font-medium text-sm text-gray-800"
>@{{ reply.replyToUser }}</span
>
</div>
<p class="text-sm text-gray-700 mb-2">{{ reply.content }}</p>
<div class="flex items-center gap-4 text-xs text-gray-500">
<span>{{ dayjs(reply.createTime * 1000).fromNow() }}</span>
<button
class="flex items-center gap-1 hover:text-blue-500 transition-colors"
@click="handleCommentLike(reply)"
>
<i
:class="
reply.hasLiked
? 'i-carbon-thumbs-up-filled text-blue-500'
: 'i-carbon-thumbs-up'
"
></i>
<span :class="reply.hasLiked ? 'text-blue-500' : ''">{{
reply.likeCount || '点赞'
}}</span>
</button>
<button
class="hover:text-blue-500 transition-colors"
@click="handleReply(reply, comment.id)"
>
回复
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-center py-6"> <!-- 展开/收起按钮 (如果简介超过3行) -->
<el-pagination <button
v-model:current-page="searchParams.current" v-if="videoDetail?.content && videoDetail.content.length > 100"
v-model:page-size="searchParams.size" @click="expandDesc = !expandDesc"
:total="total" class="text-xs text-blue-500 hover:text-blue-600 font-medium"
@current-change="goToPage" >
@size-change="changePageSize" {{ expandDesc ? '收起' : '展开' }}
layout="prev, pager, next, total" <i :class="expandDesc ? 'i-carbon-chevron-up' : 'i-carbon-chevron-down'"></i>
/> </button>
</div>
</div> --> <!-- 标签 -->
<div
v-if="videoDetail?.tagNameList?.length"
class="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-100"
>
<span
v-for="tag in videoDetail?.tagNameList"
:key="tag"
class="px-3 py-1.5 text-xs bg-gray-50 text-gray-600 rounded-full hover:bg-blue-50 hover:text-blue-500 transition-all cursor-pointer font-medium"
>
# {{ tag }}
</span>
</div>
</div> </div>
<Comment ref="commentRef" :id="videoId" v-model:total="videoDetail.replyCount" />
</div> </div>
</template> </template>
...@@ -313,7 +157,6 @@ ...@@ -313,7 +157,6 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { usePageSearch } from '@/hooks/usePageSearch'
import { getArticleDetail } from '@/api' import { getArticleDetail } from '@/api'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
......
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