提交 b1b50306 authored 作者: 王鹏飞's avatar 王鹏飞

chore(bbs): 完善bbs功能

上级 e0e5fd07
......@@ -67,8 +67,8 @@ export function getQuestionCategory(params: { project_tag: string }) {
export function collectionResource(data: {
course_id: string
semester_id: string
chapter_id: string
section_id: string
chapter_id?: string
section_id?: string
source_id: string
type: number
status: number
......
......@@ -6,14 +6,16 @@ interface Props {
const props = defineProps<Props>()
function show(fileType: string) {
return props.info?.type?.includes(fileType)
const type = props.info?.type || ''
if (typeof type === 'string') type.includes(fileType)
return false
}
</script>
<template>
<div class="icon-resource">
<img src="@/assets/images/icon_mp4.png" v-if="resourceType === 2 || show('mp4')" />
<img src="@/assets/images/icon_work.png" v-else-if="resourceType === 3" />
<img src="@/assets/images/icon_mp4.png" v-if="resourceType === 2" />
<img src="@/assets/images/icon_work.png" v-else-if="resourceType === 3 || resourceType === 12" />
<img src="@/assets/images/icon_live.png" v-else-if="resourceType === 6" />
<img src="@/assets/images/icon_exam.png" v-else-if="resourceType === 9" />
<img src="@/assets/images/icon_ppt.png" v-else-if="show('pptx')" />
......
......@@ -63,3 +63,9 @@ const init = {
<template>
<editor :init="init" v-bind="$attrs" style="width: 100%" />
</template>
<style lang="scss">
.tox-tinymce-aux {
z-index: 3000 !important;
}
</style>
import { getCourseList, getCourseChapterList } from '@/api/base'
import { val } from 'dom7'
interface Semester {
id: string
......@@ -21,7 +20,7 @@ interface Chapter {
}
export function useGetCourseList() {
const courseId = ref('')
const courseValue = ref('')
const courses = ref<Course[]>([])
const chapters = ref<Chapter[]>([])
// 获取课程列表
......@@ -32,20 +31,20 @@ export function useGetCourseList() {
}
// 获取章节列表
function getChapters() {
if (!courseId.value) {
if (!courseValue.value) {
chapters.value = []
return
}
getCourseChapterList({ course_id: courseId.value }).then(res => {
getCourseChapterList({ course_id: courseValue.value }).then(res => {
chapters.value = res.data.items
})
}
getCourses()
watch(courseId, () => {
watch(courseValue, () => {
getChapters()
})
return { courses, courseId, chapters }
return { courses, courseValue, chapters }
}
......@@ -34,7 +34,7 @@ function handleSubmit() {
const create = () => {
const params = Object.assign({}, form, { files: form.files.length ? JSON.stringify(form.files) : '' })
replyToPost(params).then(() => {
ElMessage({ message: '发成功', type: 'success' })
ElMessage({ message: '发成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
......@@ -42,7 +42,13 @@ const create = () => {
</script>
<template>
<el-dialog title="发表回复" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-dialog
append-to-body
align-center
title="发表回复"
width="800px"
:close-on-click-modal="false"
@update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk label-position="top">
<el-form-item prop="content">
<AppEditor v-model="form.content" :height="300" />
......
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { DiscussItem } from '../types'
import { ElMessage } from 'element-plus'
import { ElMessage, ElInput } from 'element-plus'
import DiscussItemCommentList from './DiscussItemCommentList.vue'
import FileItem from './FileItem.vue'
import FileList from './FileList.vue'
import { replyToPost } from '../api'
interface Props {
landlordId: string
data: DiscussItem
}
const props = defineProps<Props>()
......@@ -16,7 +17,8 @@ const username = $computed(() => {
return user.realname || user.nickname || user.username
})
const formVisible = $ref(false)
const commentListRef = $ref<InstanceType<typeof DiscussItemCommentList> | null>(null)
const formRef = $ref<FormInstance>()
const form = reactive({
content: '',
......@@ -25,6 +27,15 @@ const form = reactive({
const rules = ref<FormRules>({
content: [{ required: true, message: '请输入回复内容', trigger: 'blur' }]
})
let formVisible = $ref(false)
const inputRef = $ref<InstanceType<typeof ElInput> | null>(null)
function handleReply() {
formVisible = !formVisible
nextTick(() => {
formVisible && inputRef?.focus()
})
}
// 发布回复
function handleSubmit() {
formRef?.validate().then(() => {
......@@ -35,6 +46,9 @@ function handleSubmit() {
reply_id: props.data.id,
content: form.content
}).then(() => {
formVisible = false
form.content = ''
commentListRef?.refresh()
ElMessage({ message: '回复成功', type: 'success' })
})
})
......@@ -44,32 +58,61 @@ function handleSubmit() {
<template>
<section class="discuss-item">
<div class="discuss-item__left">
<div class="discuss-item__landlord" v-if="data.sso_id === landlordId"><span>楼主</span></div>
<div class="discuss-item__avatar">
<img :src="data.sso_user.avatar || 'https://webapp-pub.ezijing.com/website/base/images/default.jpg'" />
</div>
<p class="discuss-item__username">{{ username }}</p>
<template v-if="data.sso_type === 2">
<el-button round plain size="small" type="primary">老师</el-button>
</template>
<template v-else>
<img
src="@/assets/images/icon_bbs_level_1.png"
width="40"
title="学徒"
v-if="data.sso_user.discussion_level === 0" />
<img
src="@/assets/images/icon_bbs_level_2.png"
width="40"
title="学者"
v-if="data.sso_user.discussion_level === 1" />
<img
src="@/assets/images/icon_bbs_level_3.png"
width="40"
title="大师"
v-if="data.sso_user.discussion_level === 2" />
<img
src="@/assets/images/icon_bbs_level_4.png"
width="40"
title="智者"
v-if="data.sso_user.discussion_level === 3" />
</template>
</div>
<div class="discuss-item__right">
<div class="discuss-item__main">
<div class="discuss-item__content" v-html="data.content"></div>
<ul class="discuss-item__files" v-if="data.files.length">
<li v-for="(item, index) in data.files" :key="index"><FileItem :data="item" /></li>
</ul>
<FileList preview :data="data.files" v-if="data.files.length" />
</div>
<p class="discuss-item__time">{{ data.updated_time }}</p>
<div class="discuss-item__comment"><p @click="formVisible = !formVisible">回复</p></div>
<div class="discuss-item__comment"><p @click="handleReply">回复</p></div>
<div class="discuss-item-form" v-if="formVisible">
<p class="discuss-item-form__title">回复本楼</p>
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk>
<el-form-item prop="content">
<el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6 }" v-model="form.content" />
<el-input
type="textarea"
:autosize="{ minRows: 6, maxRows: 6 }"
:maxlength="100"
v-model="form.content"
ref="inputRef" />
</el-form-item>
<el-row justify="end">
<el-button round type="primary" @click="handleSubmit">发表回复</el-button>
</el-row>
</el-form>
</div>
<DiscussItemCommentList :data="data" />
<DiscussItemCommentList :data="data" ref="commentListRef" />
</div>
</section>
</template>
......@@ -80,12 +123,30 @@ function handleSubmit() {
border-bottom: 4px solid #e8e8e8;
}
.discuss-item__left {
position: relative;
width: 240px;
padding: 40px;
text-align: center;
background-color: #f9f9fc;
box-sizing: border-box;
}
.discuss-item__landlord {
position: absolute;
left: 0;
top: 0;
width: 60px;
height: 60px;
color: #fff;
background-color: var(--main-color);
z-index: 2000;
clip-path: polygon(0 0, 100% 0, 0% 100%);
span {
position: absolute;
left: 5px;
top: 10px;
transform: rotate(-45deg);
}
}
.discuss-item__avatar {
width: 100px;
height: 100px;
......@@ -98,6 +159,7 @@ function handleSubmit() {
}
.discuss-item__username {
margin-top: 14px;
margin-bottom: 10px;
font-size: 16px;
color: #333;
}
......@@ -116,6 +178,9 @@ function handleSubmit() {
.discuss-item__content {
font-size: 16px;
color: #666;
img {
max-width: 100%;
}
}
.discuss-item__time {
margin-top: 20px;
......
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { DiscussItem, DiscussCommentItem, User } from '../types'
import { ElMessage } from 'element-plus'
import { ElMessage, ElInput } from 'element-plus'
import { getCommitList, replyToPost } from '../api'
interface Props {
......@@ -9,21 +9,34 @@ interface Props {
}
const props = defineProps<Props>()
let page = $ref(1)
const params = reactive({ page: 1, limit: 3, floor_id: props.data.id })
let list = $ref<DiscussCommentItem[]>([])
let hasMore = $ref(true)
let loading = $ref(false)
function fetchList() {
getCommitList({ floor_id: props.data.id, page }).then(res => {
list = page === 1 ? res.data.data : list.concat(res.data.data)
page++
if (loading) return
loading = true
getCommitList(params)
.then(res => {
list = params.page === 1 ? res.data.data : list.concat(res.data.data)
hasMore = res.data.data.length >= params.limit
params.page++
})
.finally(() => {
loading = false
})
}
function refetch() {
page = 1
function refresh() {
params.page = 1
fetchList()
}
// 加载更多
function loadMore() {
if (params.limit === 3) {
params.page = 1
}
params.limit = 20
fetchList()
}
......@@ -32,11 +45,23 @@ const currentList = $computed(() => {
return props.data.child_replies
})
onMounted(() => {
hasMore = props.data.child_replies.length >= 3
})
const inputRef = $ref<InstanceType<typeof ElInput> | null>(null)
let activeComment = $ref<DiscussCommentItem>()
function handleReply(data: DiscussCommentItem) {
if (data.id === activeComment?.id && formVisible) {
formVisible = false
return
}
activeComment = data
formVisible = true
form.content = `回复@${getUsername(data.sso_user)}:`
nextTick(() => {
inputRef?.focus()
})
}
let formVisible = $ref(false)
......@@ -58,7 +83,8 @@ function handleSubmit() {
reply_id: activeComment.id,
content: form.content.replace(`回复@${getUsername(activeComment.sso_user)}:`, '')
}).then(() => {
refetch()
formVisible = false
refresh()
ElMessage({ message: '回复成功', type: 'success' })
})
})
......@@ -66,10 +92,12 @@ function handleSubmit() {
function getUsername(data: User) {
return data.realname || data.username || data.nickname
}
defineExpose({ refresh })
</script>
<template>
<div class="discuss-comment-list">
<div class="discuss-comment-list" v-if="currentList.length">
<div class="discuss-comment-item" v-for="item in currentList" :key="item.id">
<img
:src="item.sso_user.avatar || 'https://webapp-pub.ezijing.com/website/base/images/default.jpg'"
......@@ -89,14 +117,19 @@ function getUsername(data: User) {
</div>
</div>
</div>
<p class="more"><span @click="loadMore">查看更多</span></p>
<p class="more" v-if="hasMore"><span @click="loadMore">查看更多</span></p>
<div class="discuss-item-form" v-if="formVisible">
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk>
<el-form-item prop="content">
<el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6 }" v-model="form.content" />
<el-input
type="textarea"
:autosize="{ minRows: 6, maxRows: 6 }"
:maxlength="100"
v-model="form.content"
ref="inputRef" />
</el-form-item>
<el-row justify="end">
<el-button round type="primary" auto-insert-space @click="handleSubmit">发表</el-button>
<el-button round type="primary" @click="handleSubmit">发表回复</el-button>
</el-row>
</el-form>
</div>
......
<script setup lang="ts">
import type { File } from '../types'
interface Props {
data: File
}
const props = defineProps<Props>()
const isVideo = $computed(() => {
return props.data.url.includes('.mp4')
})
</script>
<template>
<div class="file-item">
<video :src="data.url" controls v-if="isVideo"></video>
<img :src="data.url" v-else />
</div>
</template>
<style lang="scss">
.file-item {
img {
width: 160px;
height: 90px;
object-fit: cover;
}
video {
width: 400px;
}
}
</style>
<script setup lang="ts">
import type { File } from '../types'
import { VideoPlay } from '@element-plus/icons-vue'
interface Props {
preview?: boolean
data: File[] | Record<string, File[]>
}
const props = defineProps<Props>()
const images = $computed(() => {
if (!Array.isArray(props.data)) return []
return props.data.filter(item => !isVideo(item.url))
})
const currentImages = $computed(() => {
if (props.preview) return images
return images.filter((item, index) => index < 3)
})
const imageSrcList = $computed(() => {
return props.preview ? images.map(item => item.url) : []
})
const videos = $computed(() => {
if (!Array.isArray(props.data)) return []
return props.data.filter(item => isVideo(item.url))
})
function isVideo(url: string) {
return url.includes('.mp4')
}
</script>
<template>
<div class="file-list">
<div class="file-item" v-for="(item, index) in currentImages" :key="index">
<p class="file-item__count" v-if="images.length > 3 && index === 0">{{ images.length }}</p>
<el-image
style="width: 100%; height: 100%"
:src="item.url + '?x-oss-process=image/resize,m_fill,h_90,w_160'"
:initial-index="index"
:preview-src-list="imageSrcList"
fit="cover" />
</div>
<div class="file-item" v-for="(item, index) in videos" :key="index">
<video :src="item.url" />
<el-icon class="icon-video-play"><VideoPlay /></el-icon>
</div>
</div>
</template>
<style lang="scss">
.file-list {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.file-item {
position: relative;
width: 160px;
height: 90px;
video {
width: 160px;
height: 90px;
}
.icon-video-play {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 30px;
color: #fff;
}
}
.file-item__count {
position: absolute;
left: 0;
top: 10px;
padding: 0 10px;
height: 18px;
font-size: 12px;
color: #fff;
line-height: 18px;
text-align: center;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
}
</style>
......@@ -7,18 +7,25 @@ import { createPost } from '../api'
import { useMapStore } from '@/stores/map'
import { useGetCourseList } from '@/composables/useGetCourseList'
interface Props {
courseId?: string
semesterId?: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const { courses, courseId, chapters } = useGetCourseList()
const { courses, courseValue, chapters } = useGetCourseList()
const types = useMapStore().getMapValuesByKey('learning_discussion_type')
const formRef = $ref<FormInstance>()
const form = reactive({
semester_id: '',
course_id: '',
semester_id: props.semesterId || '',
course_id: props.courseId || '',
chapter_id: '',
type: '',
title: '',
......@@ -33,7 +40,7 @@ const rules = ref<FormRules>({
content: [{ required: true, message: '请输入正文内容', trigger: 'blur' }]
})
watchEffect(() => {
courseId.value = form.course_id
courseValue.value = form.course_id
const course = courses.value.find(item => item.course_id === form.course_id)
form.semester_id = course ? course.semester.id : ''
})
......@@ -53,10 +60,16 @@ const create = () => {
</script>
<template>
<el-dialog title="发帖" width="800px" @update:modelValue="$emit('update:modelValue')">
<el-dialog
append-to-body
align-center
title="发帖"
width="800px"
:close-on-click-modal="false"
@update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk label-position="top">
<el-row justify="space-between">
<el-form-item label="相关课程" prop="course_id">
<el-form-item label="相关课程" prop="course_id" v-if="!courseId">
<el-select filterable v-model="form.course_id">
<el-option v-for="item in courses" :key="item.id" :label="item.name" :value="item.course_id"></el-option>
</el-select>
......
<script setup lang="ts">
import type { PostItem } from '../types'
import FileItem from './FileItem.vue'
import FileList from './FileList.vue'
import { useMapStore } from '@/stores/map'
interface Props {
......@@ -24,20 +24,20 @@ const username = $computed(() => {
<template>
<section class="post-item">
<router-link :to="`/bbs/${data.id}`" target="_blank">
<div class="post-item-hd">
<p class="post-item__type" :class="`type-${data.type}`">{{ typeText }}</p>
<h2 class="post-item__title">{{ data.title }}</h2>
<h2 class="post-item__title">{{ data.title }}<span class="is-hot" v-if="data.is_hot"></span></h2>
<p class="post-item__reply_count">{{ data.reply_count }}回帖</p>
</div>
<div class="post-item-bd">
<ul class="post-item__files" v-if="data.reply?.files.length">
<li v-for="(item, index) in data.reply.files" :key="index"><FileItem :data="item" /></li>
</ul>
<FileList :data="data.reply?.files" />
</div>
<div class="post-item-ft">
<p class="post-item__username">{{ username }}</p>
<p class="post-item__time">{{ data.updated_time }}</p>
</div>
</router-link>
</section>
</template>
......@@ -45,7 +45,17 @@ const username = $computed(() => {
.post-item {
padding: 30px 0;
border-bottom: 1px solid #e6e6e6;
border-top: 1px solid #e6e6e6;
&:hover {
.post-item__title {
color: var(--main-color);
}
}
}
.post-item + .post-item {
border-top: none;
}
.post-item-hd {
display: flex;
}
......@@ -67,6 +77,18 @@ const username = $computed(() => {
font-weight: 400;
line-height: 20px;
color: #666;
.is-hot {
display: inline-block;
margin-left: 14px;
width: 20px;
height: 20px;
font-size: 14px;
line-height: 20px;
color: #fff;
background-color: #ff8923;
text-align: center;
border-radius: 2px;
}
}
.post-item__reply_count {
font-size: 14px;
......
<script setup lang="ts">
import type { TopPostItem } from '../types'
import { getTopPostList } from '../api'
interface Props {
courseId?: string
semesterId?: string
}
const props = defineProps<Props>()
let list = $ref<TopPostItem[]>([])
function fetchList() {
getTopPostList().then(res => {
getTopPostList({ course_id: props.courseId, semester_id: props.semesterId }).then(res => {
list = res.data.list
})
}
......@@ -15,12 +21,14 @@ onMounted(fetchList)
<template>
<section class="pined-post" v-if="list.length">
<section class="pined-post-item" v-for="item in list" :key="item.id">
<router-link :to="`/bbs/${item.id}`" target="_blank" class="pined-post-item__inner">
<p class="t1">置顶</p>
<p class="t2">{{ item.title }}</p>
<p class="t3">
<span>{{ item.type }}</span>
<span>{{ item.course.name }}</span>
</p>
<p class="t4">{{ item.updated_time }}</p>
</router-link>
</section>
</section>
</template>
......@@ -28,12 +36,18 @@ onMounted(fetchList)
<style lang="scss">
.pined-post {
padding-bottom: 16px;
border-bottom: 1px solid #e6e6e6;
}
.pined-post-item {
.pined-post-item__inner {
display: flex;
align-items: center;
}
.pined-post-item {
padding: 14px 0;
&:hover {
.t2 {
color: var(--main-color);
}
}
.t1 {
padding: 0 4px;
height: 20px;
......@@ -56,6 +70,7 @@ onMounted(fetchList)
text-align: center;
span {
display: inline-block;
padding: 0 20px;
min-width: 90px;
height: 30px;
line-height: 30px;
......
......@@ -31,7 +31,9 @@ export interface PostItem extends Post {
sso_user: User
}
export type TopPostItem = Pick<Post, 'id' | 'title' | 'type' | 'created_time' | 'updated_time' | 'top_time'>
export type TopPostItem = Pick<Post, 'id' | 'title' | 'type' | 'created_time' | 'updated_time' | 'top_time'> & {
course: Course
}
export interface DiscussItem {
id: string
......@@ -89,3 +91,9 @@ export interface File {
size: string
upload_time: string
}
export interface Course {
id: string
name: string
cover: string
}
......@@ -7,9 +7,16 @@ import { getPostList } from '../api'
import { useMapStore } from '@/stores/map'
import { useGetCourseList } from '@/composables/useGetCourseList'
interface Props {
courseId?: string
semesterId?: string
}
const props = defineProps<Props>()
const PostForm = defineAsyncComponent(() => import('../components/PostForm.vue'))
const { courses, courseId, chapters } = useGetCourseList()
const { courses, courseValue, chapters } = useGetCourseList()
const courseList = $computed(() => {
return [{ value: '', label: '全部课程' }, ...courses.value]
})
......@@ -24,9 +31,38 @@ const currentTypes = $computed(() => {
const appList = $ref<InstanceType<typeof AppList> | null>(null)
const params = reactive({ search_by: 0, order_by: 0, course_id: '', semester_id: '', chapter_id: '', type: '' })
const params = reactive({
search_by: 0,
order_by: 0,
course_id: props.courseId || '',
semester_id: props.semesterId || '',
chapter_id: '',
type: ''
})
// 列表配置
const listOptions = computed(() => {
const filters = [
{
type: 'select',
prop: 'chapter_id',
placeholder: '请选择',
options: chapterList,
labelKey: 'name',
valueKey: 'id'
},
{ type: 'select', prop: 'search_by', placeholder: '请选择', options: bbsSearchByList },
{ type: 'select', prop: 'type', placeholder: '请选择', options: currentTypes },
{ type: 'select', prop: 'order_by', placeholder: '请选择', options: bbsOrderByList }
]
!props.courseId &&
filters.unshift({
type: 'select',
prop: 'course_id',
placeholder: '请选择',
options: courseList,
labelKey: 'name',
valueKey: 'course_id'
})
return {
hasFilterButton: false,
remote: {
......@@ -37,34 +73,14 @@ const listOptions = computed(() => {
requestParams.chapter_id = ''
}
params.course_id = requestParams.course_id || ''
courseId.value = params.course_id
courseValue.value = params.course_id
return requestParams
},
callback(res: { total: number; data: any }) {
return { total: res.total, list: res.data }
}
},
filters: [
{
type: 'select',
prop: 'course_id',
placeholder: '请选择',
options: courseList,
labelKey: 'name',
valueKey: 'course_id'
},
{
type: 'select',
prop: 'chapter_id',
placeholder: '请选择',
options: chapterList,
labelKey: 'name',
valueKey: 'id'
},
{ type: 'select', prop: 'search_by', placeholder: '请选择', options: bbsSearchByList },
{ type: 'select', prop: 'type', placeholder: '请选择', options: currentTypes },
{ type: 'select', prop: 'order_by', placeholder: '请选择', options: bbsOrderByList }
]
filters
}
})
// 刷新
......@@ -77,18 +93,23 @@ const postFormVisible = $ref(false)
<template>
<AppList v-bind="listOptions" ref="appList">
<template #header-prepend>
<el-button round type="primary" @click="postFormVisible = true">我要发帖</el-button>
<el-button round type="primary" @click="postFormVisible = true" style="margin-right: 30px">我要发帖</el-button>
</template>
<template #body="{ data }">
<!-- 置顶帖子 -->
<PostPinned></PostPinned>
<PostPinned :courseId="courseId" :semesterId="semesterId"></PostPinned>
<template v-if="data.length">
<PostItem :data="item" v-for="item in data" :key="item.id"></PostItem>
</template>
<el-empty description="暂无数据" v-else />
</template>
</AppList>
<PostForm v-model="postFormVisible" @update="handleRefetch" v-if="postFormVisible"></PostForm>
<PostForm
:courseId="courseId"
:semesterId="semesterId"
v-model="postFormVisible"
@update="handleRefetch"
v-if="postFormVisible"></PostForm>
</template>
<style lang="scss" scoped>
......
<script setup lang="ts">
import type { Post } from '../types'
import { ArrowUp } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue'
import DiscussItem from '../components/DiscussItem.vue'
import { getPostAndDiscussList } from '../api'
import { collectionResource } from '@/api/base'
const DiscussForm = defineAsyncComponent(() => import('../components/DiscussForm.vue'))
......@@ -35,22 +36,40 @@ function handleRefetch() {
appList?.refetch()
}
const discussFormVisible = $ref(false)
// 收藏/取消收藏
function toggleCollection() {
collectionResource({
course_id: detail.course_id,
semester_id: detail.semester_id,
chapter_id: detail.chapter_id,
source_id: detail.id,
type: 6,
status: 1
}).then(() => {
// data.collection_count = data.collection_count ? 0 : 1
})
}
</script>
<template>
<div class="bbs">
<div class="bbs-hd" v-if="detail">
<h1>{{ detail.title }}</h1>
<el-button round auto-insert-space>收藏</el-button>
<el-button round auto-insert-space @click="toggleCollection">收藏</el-button>
<el-button round type="primary" auto-insert-space @click="discussFormVisible = true">回复</el-button>
</div>
<AppList v-bind="listOptions" ref="appList">
<template #body="{ data }">
<DiscussItem :data="item" v-for="item in data" :key="item.id"></DiscussItem>
<DiscussItem :landlordId="detail.sso_id" :data="item" v-for="item in data" :key="item.id"></DiscussItem>
</template>
</AppList>
</div>
<DiscussForm v-model="discussFormVisible" :id="id" @update="handleRefetch" v-if="discussFormVisible"></DiscussForm>
<el-backtop :right="22" :bottom="100">
<el-icon><ArrowUp /></el-icon>
<span class="t1">返回<br />顶部</span>
</el-backtop>
</template>
<style lang="scss">
......@@ -63,12 +82,34 @@ const discussFormVisible = $ref(false)
}
}
.bbs-hd {
position: relative;
padding: 20px;
display: flex;
align-items: center;
border-bottom: 1px solid #e4e4e4;
box-shadow: #e4e4e4 0px 5px 10px;
z-index: 10;
h1 {
flex: 1;
}
}
.el-backtop {
width: 60px;
height: 60px;
color: #000;
.t1 {
display: none;
font-size: 14px;
color: #fff;
}
&:hover {
background-color: var(--main-color);
.el-icon {
display: none;
}
.t1 {
display: inline-block;
}
}
}
</style>
<!-- 论坛 -->
<script setup lang="ts"></script>
<script setup lang="ts">
import BBSIndex from '@/modules/bbs/views/Index.vue'
const route = useRoute()
const courseId = route.query.course_id as string
const semesterId = route.query.semester_id as string
</script>
<template>
<el-empty description="暂无数据" />
<div style="margin: 0 -20px">
<BBSIndex :courseId="courseId" :semesterId="semesterId"></BBSIndex>
</div>
</template>
import type { CourseType, ChapterType, ResourceType } from '@/types'
type CollectionInfo = ResourceType & {
id: string
sso_id: string
sso_type: number
title: string
semester_id: string
class_id: string
course_id: string
chapter_id: string
type: number
is_top: number
top_time: null
is_hot: number
pv: number
uv: number
reply_count: number
created_time: string
updated_time: string
delete_time: number
}
export interface CollectionType {
chapter_id: string
course_id: string
id: string
info: ResourceType
info: CollectionInfo
section_id: string
semester_id: string
semester: CollectionSemesterType
......
......@@ -148,7 +148,7 @@ function targetUrl2(item: CollectionType) {
<p>
<router-link :to="targetUrl(item)" target="_blank">
<ResourceIcon :resourceType="item.resource_type" :info="item.info" />
{{ item.info.name || item.info.paper_title }}
{{ item.info.name || item.info.title || item.info.paper_title }}
</router-link>
</p>
<p>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论