Переглянути джерело

Merge branch 'dev' of http://192.168.1.245:11111/shanming/onlineEducation-front into dev

zhangsq 7 місяців тому
батько
коміт
f53e5ac528

+ 47 - 0
src/api/forum/forumApi.js

@@ -0,0 +1,47 @@
+import { baseRequest } from '@/utils/request'
+
+const request = (url, ...arg) => baseRequest(`/api/webapp/forum/${url}`, ...arg)
+/**
+ * 菜单
+ *
+ * @author yubaoshan
+ * @date 2022-09-22 22:33:20
+ */
+export default {
+	// 查询帖子列表接口
+	forumList(data) {
+		return request('postinfo/page', data, 'get')
+	},
+	// 查询分类列表接口
+	forumTypeList(data) {
+		return request('posttype/list', data, 'get')
+	},
+	// 发帖接口 // 编辑帖子接口
+	submitForm(data, edit = false) {
+		return request(`postinfo/${edit ? 'edit' : 'add'}`, data)
+	},
+	// 帖子详情接口
+	forumTypeDetail(data) {
+		return request('postinfo/detail', data, 'get')
+	},
+	// 点赞接口 // 取消点赞接口
+	postlikeSubmit(data, isLike = 0) {
+		return request(`postlike/${isLike == 0 ? 'add' : 'cancel'}`, data)
+	},
+	// 回复帖子接口 //编辑回复
+	submitPostreply(data, edit = false) {
+		return request(`postreply/${edit ? 'edit' : 'add'}`, data)
+	},
+	// 举报帖子接口
+	reportinfoAdd(data) {
+		return request('reportinfo/add', data)
+	},
+	// 删除自己回复接口
+	postreplyDel(data) {
+		return request('postreply/delete', data)
+	},
+	// 扩展帖子列表,1.查询我发布的 2.查询我回复的 3.查询关于我的 4.查询我点赞的
+	moreList(data) {
+		return request('postinfo/moreList', data, 'get')
+	}
+}

+ 122 - 0
src/components/Comment/index.vue

@@ -0,0 +1,122 @@
+<template>
+	<div>
+		<a-comment
+			v-for="(item, idx) in commentList"
+			:key="idx"
+			:style="{ 'border-bottom': item.parentId == -1 ? '1px solid #ccc' : '' }"
+		>
+			<template #actions>
+				<span key="comment-basic-like">
+					<a-tooltip title="点赞">
+						<template v-if="item.isLike == 1">
+							<HeartOutlined :style="{ color: '#fa6c8d' }" @click="like(item,0)" />
+						</template>
+						<template v-else>
+							<HeartOutlined @click="like(item,1)" />
+						</template>
+					</a-tooltip>
+					<span style="padding-left: 8px; cursor: auto">
+						{{ item.likeCount }}
+					</span>
+				</span>
+				<a-tooltip title="编辑">
+					<EditOutlined
+						v-if="item.isSelf == 1"
+						@click="replyFormRef.onOpen(item, props.params.targetId, item.replyId, true)"
+					/>
+				</a-tooltip>
+				<a-tooltip title="删除">
+					<DeleteOutlined v-if="item.isSelf == 1" @click="showDeleteConfirm(item)" />
+				</a-tooltip>
+
+				<span
+					key="comment-basic-reply-to"
+					@click="replyFormRef.onOpen(item, props.params.targetId, item.replyId, false)"
+					v-if="item.parentId == -1"
+				>
+					回复
+				</span>
+			</template>
+			<template #author>
+				<a>{{ item.userNickname }}</a>
+			</template>
+			<template #avatar>
+				<a-avatar :src="item.userAvatar" :alt="item.userNickname" />
+			</template>
+			<template #content>
+				<div v-html="item.replyContent"></div>
+			</template>
+			<template #datetime>
+				<span>{{ formatDateTime(item.createTime) }}</span>
+			</template>
+			<div v-if="item.children">
+				<a-divider />
+			</div>
+			<Comment
+				v-if="item.children && item.children.length > 0"
+				:commentList="item.children"
+				:params="params"
+				@successful="emit('successful')"
+			></Comment>
+		</a-comment>
+		<replyForm ref="replyFormRef" @successful="emit('successful')" />
+	</div>
+</template>
+
+<script setup name="commentList">
+	import forumApi from '@/api/forum/forumApi'
+	import Comment from '@/components/Comment/index.vue'
+	import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
+	import { createVNode } from 'vue'
+	import { Modal } from 'ant-design-vue'
+	import { parseTime } from '@/utils/exam'
+	import replyForm from '@/views/forum/replyForm.vue'
+	const emit = defineEmits({ successful: null })
+	const replyFormRef = ref()
+
+	const props = defineProps({
+		commentList: {
+			type: Array,
+			default: () => []
+		},
+		params: {
+			type: Object,
+			default: () => {}
+		},
+		count: {
+			type: [String , Number],
+			default: 0
+		}
+	})
+	function formatDateTime(val) {
+		if (!val) return ''
+		return parseTime(val, '{y}-{m}-{d} {h}:{i}:{s}')
+	}
+	const like = (item,l) => {
+		forumApi
+			.postlikeSubmit(
+				{
+					...props.params,
+					targetId: item.replyId
+				},
+				item.isLike
+			)
+			.then((data) => {
+				item.isLike = l
+			})
+	}
+	const showDeleteConfirm = (item) => {
+		Modal.confirm({
+			title: '确定要删除回复?',
+			icon: createVNode(ExclamationCircleOutlined),
+			onOk() {
+				forumApi.postreplyDel([{ replyId: item.replyId }]).then((data) => {
+					emit('successful')
+				})
+			},
+			onCancel() {
+				console.log('Cancel')
+			}
+		})
+	}
+</script>

+ 2 - 1
src/components/UpLoadImg/index.vue

@@ -25,7 +25,8 @@ import {ref, reactive, watch, defineProps, defineEmits} from 'vue'
 import {message} from 'ant-design-vue'
 import {PictureOutlined, CloudUploadOutlined} from '@ant-design/icons-vue'
 import tool from "@/utils/tool";
-const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+import sysConfig from '@/config/index'
+const action = ref(sysConfig.API_URL +'/api/webapp/dev/file/uploadMinioReturnId')
 const headers = ref({
 	token: tool.data.get('TOKEN')
 })

+ 139 - 0
src/views/forum/detail.vue

@@ -0,0 +1,139 @@
+<template>
+	<a-card>
+		<div style="display: flex; justify-content: space-between">
+			<div style="display: flex">
+				<a-avatar
+					style="width: 60px; height: 60px"
+					:src="detailObj.userAvatar"
+					:size="{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }"
+				/>
+				<div class="snowy-index-card-left-one-username">
+					<span style="font-weight: 600; margin: 2px; font-size: 18px">{{ detailObj.userNickName }}</span>
+					<span style="color: #6d737b; margin: 2px"
+						>{{ detailObj.typeName }} | {{ formatDateTime(detailObj.lastReplyTime) }}</span
+					>
+				</div>
+			</div>
+		</div>
+		<div class="forum-list-title">{{ detailObj.postTitle }}</div>
+		<div v-html="detailObj.postContent"></div>
+		<div>
+			<span>
+				<a-tooltip title="点赞">
+					<template v-if="detailObj.isLike == 1">
+						<HeartOutlined :style="{ color: '#fa6c8d' }" @click="like(item, 0)" />
+					</template>
+					<template v-else>
+						<HeartOutlined @click="like(item, 1)" />
+					</template>
+				</a-tooltip>
+				<span style="padding-left: 8px">
+					{{ detailObj.likeCount }}
+				</span>
+			</span>
+			<a-tooltip title="编辑" v-if="detailObj.isSelf == 1">
+				<EditOutlined class="ml-2" @click="formRef.onOpen(detailObj, detailObj.postId)" />
+			</a-tooltip>
+
+			<span class="ml-2" style="cursor: pointer" @click="replyFormRef.onOpen(detailObj, detailObj.postId)">
+				<span>回复</span>
+				<span style="padding-left: 8px">{{ detailObj.replyCount }}</span>
+			</span>
+			<a-tooltip title="举报">
+				<WarningOutlined class="ml-2" @click="reportFormRef.onOpen(detailObj, detailObj.postId)" />
+			</a-tooltip>
+		</div>
+	</a-card>
+	<a-card class="mt-2">
+		<Comment :commentList="detailObj.replyList?.records" :params="commentParams" @successful="resetGetList()"></Comment>
+		<div style="display: flex; justify-content: center" class="mt-8">
+			<a-button type="primary" shape="round" @click="morePaging" v-if="moreType"> 加载更多 </a-button>
+			<div v-else>全部加载完毕</div>
+		</div>
+	</a-card>
+	<replyForm ref="replyFormRef" @successful="resetGetList()" />
+	<Form ref="formRef" @successful="resetGetList()" />
+	<reportForm ref="reportFormRef" @successful="resetGetList()"></reportForm>
+</template>
+
+<script setup name="forumDetail">
+	import forumApi from '@/api/forum/forumApi'
+	import { useRoute, useRouter } from 'vue-router'
+	import { parseTime } from '@/utils/exam'
+	import Comment from '@/components/Comment/index.vue'
+	import replyForm from './replyForm.vue'
+	import reportForm from './reportForm.vue'
+	import Form from './form.vue'
+	const reportFormRef = ref()
+	const replyFormRef = ref()
+	const formRef = ref()
+	const route = useRoute()
+	const detailObj = ref({})
+	const moreType = ref(true)
+	const commentParams = ref({
+		likeType: 1,
+		targetId: route.query.postId
+	})
+	const pagination = ref({
+		current: 1,
+		size: 10
+	})
+	function like(item, l) {
+		forumApi.postlikeSubmit({ likeType: 0, targetId: route.query.postId }, item.isLike).then((data) => {
+			item.isLike = l
+			resetGetList()
+		})
+	}
+	function formatDateTime(val) {
+		if (!val) return ''
+		return parseTime(val, '{y}-{m}-{d} {h}:{i}:{s}')
+	}
+	function getDetail(p) {
+		forumApi
+			.forumTypeDetail({
+				postId: route.query.postId,
+				...pagination.value
+			})
+			.then((data) => {
+				if (p) {
+					if (data.replyList.records.length > 0) {
+						detailObj.value.replyList.records = detailObj.value.replyList.records.concat(data.replyList.records)
+					} else {
+						pagination.value.current -= 1
+						moreType.value = false
+					}
+				} else {
+					detailObj.value = data
+				}
+			})
+	}
+	function morePaging() {
+		pagination.value.current += 1
+		getDetail(true)
+	}
+	function resetGetList() {
+		pagination.value.size = 10
+		pagination.value.current = 1
+		moreType.value = true
+		getDetail()
+	}
+	getDetail()
+</script>
+
+<style scoped>
+	.snowy-index-card-left-one-username {
+		margin-left: 8px;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+	}
+	.forum-list-title {
+		font-size: 30px;
+		font-weight: bold;
+	}
+
+	.forum-list-content {
+		font-size: 14px;
+		color: #696969;
+	}
+</style>

+ 132 - 0
src/views/forum/form.vue

@@ -0,0 +1,132 @@
+<template>
+	<xn-form-container
+		:title="formData.id ? '编辑主题' : '增加主题'"
+		:width="700"
+		:visible="visible"
+		:destroy-on-close="true"
+		@close="onClose"
+	>
+		<a-alert
+			class="mb-3"
+			message="欢迎来到论坛 — 衷心感谢你参与讨论!
+			标题是否清晰明了地描述了主题?看
+			起来有意思么?
+			谁会感兴趣?
+			它为什么重要?
+			你期望从社区中获得什么样的回应?
+			选择常用的词句以便别人能找到你的主题。若想要给主题和类似主题分组,选择一个分类。"
+			type="warning"
+		/>
+		<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
+			<a-row :gutter="16">
+				<a-col :span="12">
+					<a-form-item label="标题:" name="postTitle">
+						<a-input v-model:value="formData.postTitle" placeholder="请输入显示名称" allow-clear />
+					</a-form-item>
+				</a-col>
+				<a-col :span="12">
+					<a-form-item label="分类:" name="typeId">
+						<a-select
+							v-model:value="formData.typeId"
+							show-search
+							placeholder="所有分类"
+							style="width: 200px"
+							:options="typeOptions"
+							:filter-option="filterOption"
+						></a-select>
+					</a-form-item>
+				</a-col>
+				<a-col :span="24">
+					<a-form-item label="内容:" name="postContent">
+						<xn-editor v-model="formData.postContent" placeholder="请输入" :height="400"></xn-editor>
+					</a-form-item>
+				</a-col>
+			</a-row>
+		</a-form>
+		<template #footer>
+			<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
+			<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
+		</template>
+	</xn-form-container>
+</template>
+
+<script setup>
+	import { required } from '@/utils/formRules'
+	import SnowflakeId from 'snowflake-id'
+	import tool from '@/utils/tool'
+	import forumApi from '@/api/forum/forumApi'
+	import XnEditor from '@/components/Editor/index.vue'
+	const userInfo = tool.data.get('USER_INFO')
+	const typeOptions = ref([])
+	// 默认是关闭状态
+	const visible = ref(false)
+	const emit = defineEmits({ successful: null })
+	const formRef = ref()
+	const treeData = ref([])
+	// 表单数据,也就是默认给一些数据
+	const formData = ref({
+		postType: 0
+	})
+	// 默认展开的节点(顶级)
+	const defaultExpandedKeys = ref([0])
+	const submitLoading = ref(false)
+	// 模块ID
+	const moduleId = ref('')
+
+	const filterOption = (input, option) => {
+		return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+	}
+
+	// 打开抽屉
+	const onOpen = (record, module) => {
+		moduleId.value = module
+		visible.value = true
+		forumApi.forumTypeList().then((data) => {
+			typeOptions.value = data.map((r) => {
+				return {
+					label: r.typeName,
+					value: r.typeId,
+					...r
+				}
+			})
+		})
+		if (module) {
+			formData.value = Object.assign(formData.value, record)
+		}
+	}
+	// 关闭抽屉
+	const onClose = () => {
+		formRef.value.resetFields()
+		visible.value = false
+	}
+
+	// 默认要校验的
+	const formRules = {
+		postTitle: [required('请输入标题')],
+		typeId: [required('请选择分类')],
+		postContent: [required('请输入内容')]
+	}
+
+	const categoryOptions = tool.dictList('MENU_TYPE')
+	const visibleOptions = tool.dictList('MENU_VISIBLE')
+	// 验证并提交数据
+	const onSubmit = () => {
+		formRef.value
+			.validate()
+			.then(() => {
+				submitLoading.value = true
+				forumApi.submitForm(formData.value, formData.value.postId).then(() => {
+					onClose()
+					emit('successful')
+				})
+			})
+			.finally(() => {
+				submitLoading.value = false
+			})
+	}
+
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({
+		onOpen
+	})
+</script>

+ 244 - 0
src/views/forum/index.vue

@@ -0,0 +1,244 @@
+<template>
+	<a-card :bordered="false">
+		<a-space>
+			<a-input-search
+				v-model:value="searchFormState.postTitle"
+				placeholder="请输入名称关键词"
+				enter-button
+				allowClear
+				@search="onSearch"
+			/>
+			<a-select
+				v-model:value="typeValue"
+				show-search
+				placeholder="所有分类"
+				style="width: 200px"
+				:options="typeOptions"
+				:filter-option="filterOption"
+				@change="handleChange"
+			></a-select>
+			<a-select
+				v-model:value="typeValueEx"
+				allowClear
+				placeholder="请选择"
+				style="width: 200px"
+				:options="typeOptionsEx"
+				@change="handleChangeEx"
+			></a-select>
+			<a-radio-group v-model:value="searchFormState.sortOrder" button-style="solid">
+				<a-radio-button
+					v-for="module in moduleTypeList"
+					:key="module.id"
+					:value="module.id"
+					@click="moduleClock(module.id)"
+				>
+					{{ module.title }}
+				</a-radio-button>
+			</a-radio-group>
+			<a-button type="primary" @click="formRef.onOpen(undefined, searchFormState.sortOrder)">
+				<template #icon><plus-outlined /></template>
+				创建新主题
+			</a-button>
+		</a-space>
+		<s-table ref="table" :columns="columns" :data="loadData" :row-key="(record) => record.id" :custom-row="customRow">
+			<template #bodyCell="{ column, record }">
+				<template v-if="column.dataIndex === 'postTitle'">
+					<div class="forum-list-title">{{ record.postTitle }}</div>
+					<div class="forum-list-type">{{ record.typeName }}</div>
+					<div class="forum-list-content one-line" v-html="record.postContent"></div>
+				</template>
+				<template v-if="column.dataIndex === 'lastReplyUserAvatar'">
+					<a-tooltip :title="record.lastReplyUserNickName" placement="top">
+						<a-avatar :src="record.lastReplyUserAvatar"></a-avatar>
+					</a-tooltip>
+				</template>
+				<template v-if="column.dataIndex === 'lastReplyTime'">
+					<div>{{ formatDateTime(record.lastReplyTime) }}</div>
+				</template>
+			</template>
+		</s-table>
+	</a-card>
+	<Form ref="formRef" @successful="table.refresh(true)" />
+</template>
+
+<script setup name="forumList">
+	import forumApi from '@/api/forum/forumApi'
+	import Form from './form.vue'
+	import { parseTime } from '@/utils/exam'
+	import { useRoute, useRouter } from 'vue-router'
+	const route = useRoute()
+	const router = useRouter()
+	const formRef = ref()
+	let searchFormState = reactive({
+		sortOrder: 0,
+		postTitle: '',
+		postType: route.query.postType ?? null
+	})
+	const table = ref(null)
+	const moduleTypeList = ref([
+		{
+			title: '最新',
+			id: 0
+		},
+		{
+			title: '热门',
+			id: 1
+		}
+	])
+	const columns = [
+		{
+			title: '主题',
+			dataIndex: 'postTitle'
+		},
+		{
+			title: '最后回复人',
+			dataIndex: 'lastReplyUserAvatar',
+			align: 'center',
+			width: 100
+		},
+		{
+			title: '回复量',
+			dataIndex: 'replyCount',
+			align: 'center',
+			width: 100
+		},
+		{
+			title: '浏览量',
+			dataIndex: 'viewCount',
+			align: 'center',
+			width: 100
+		},
+		{
+			title: '最后回复时间',
+			dataIndex: 'lastReplyTime',
+			align: 'center',
+			width: 120
+		}
+	]
+	const typeValue = ref('所有分类')
+	const typeOptions = ref([])
+	const handleChange = (value) => {
+		exType.value = false
+		typeValueEx.value = ''
+		searchFormState.typeId = value
+		table.value.refresh(true)
+	}
+	const filterOption = (input, option) => {
+		return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+	}
+	function formatDateTime(val) {
+		if (!val) return ''
+		return parseTime(val, '{y}-{m}-{d} {h}:{i}:{s}')
+	}
+	// 查询
+	const onSearch = () => {
+		typeValueEx.value = ''
+		exType.value = false
+		table.value.refresh(true)
+	}
+	function customRow(record) {
+		return {
+			onClick: () => itemSelect(record)
+		}
+	}
+	function itemSelect(record) {
+		router.push({
+			path: '/forum/detail',
+			query: {
+				postId: record.postId,
+				postType: route.query.postType ?? ''
+			}
+		})
+	}
+	function getTypeList() {
+		forumApi.forumTypeList().then((data) => {
+			typeOptions.value = data.map((r) => {
+				return {
+					label: r.typeName,
+					value: r.typeId,
+					...r
+				}
+			})
+		})
+	}
+	getTypeList()
+
+	const typeValueEx = ref('')
+	const exType = ref(false)
+	const typeOptionsEx = ref([
+		{
+			label: '我发布的',
+			value: 1
+		},
+		{
+			label: '我回复的',
+			value: 2
+		},
+		{
+			label: '关于我的',
+			value: 3
+		},
+		{
+			label: '我点赞的',
+			value: 4
+		}
+	])
+	const handleChangeEx = (value) => {
+		if (value) {
+			exType.value = true
+		} else {
+			exType.value = false
+			typeValueEx.value = ''
+		}
+
+		table.value.refresh(true)
+	}
+	const loadData = (parameter) => {
+		if (exType.value) {
+			return forumApi.moreList(Object.assign(parameter, {postExtend:typeValueEx.value})).then((data) => {
+				if (data) {
+					return data
+				} else {
+					return []
+				}
+			})
+		} else {
+			return forumApi.forumList(Object.assign(parameter, searchFormState)).then((data) => {
+				if (data) {
+					return data
+				} else {
+					return []
+				}
+			})
+		}
+	}
+	// 切换应用标签查询菜单列表
+	const moduleClock = (value) => {
+		exType.value = false
+		typeValueEx.value = ''
+		searchFormState.sortOrder = value
+		table.value.refresh(true)
+	}
+</script>
+<style scoped>
+	.forum-list-title {
+		font-size: 16px;
+		font-weight: bold;
+	}
+
+	.forum-list-type {
+		font-size: 12px;
+	}
+
+	.forum-list-content {
+		font-size: 14px;
+		color: #696969;
+	}
+
+	.one-line {
+		display: -webkit-box;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 1;
+		overflow: hidden;
+	}
+</style>

+ 106 - 0
src/views/forum/replyForm.vue

@@ -0,0 +1,106 @@
+<template>
+	<xn-form-container title="回复" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
+		<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
+			<a-row :gutter="16">
+				<a-col :span="24">
+					<a-form-item label="内容:" name="replyContent">
+						<xn-editor v-model="formData.replyContent" placeholder="请输入" :height="400"></xn-editor>
+					</a-form-item>
+				</a-col>
+			</a-row>
+		</a-form>
+		<template #footer>
+			<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
+			<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
+		</template>
+	</xn-form-container>
+</template>
+
+<script setup>
+	import { required } from '@/utils/formRules'
+	import SnowflakeId from 'snowflake-id'
+	import tool from '@/utils/tool'
+	import forumApi from '@/api/forum/forumApi'
+	import XnEditor from '@/components/Editor/index.vue'
+	// 默认是关闭状态
+	const visible = ref(false)
+	const emit = defineEmits({ successful: null })
+	const formRef = ref()
+	const treeData = ref([])
+	// 表单数据,也就是默认给一些数据
+	const formData = ref({})
+	// 默认展开的节点(顶级)
+	const submitLoading = ref(false)
+	// 模块ID
+	const moduleId = ref('')
+	//回复ID
+	const replyid = ref('')
+	//类型
+	const typeOptions = ref([])
+	const handleChange = (value) => {
+		searchFormState.typeId = value
+		table.value.refresh(true)
+	}
+	const filterOption = (input, option) => {
+		return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+	}
+
+	// 打开抽屉
+	const onOpen = (record, module, childId, formType) => {
+		moduleId.value = module
+		replyid.value = childId ?? '-1'
+		visible.value = true
+		formData.value.formType = formType
+		if (formType) {
+			formData.value.replyContent = record.replyContent
+		} else {
+			formData.value.replyContent = ''
+		}
+	}
+	// 关闭抽屉
+	const onClose = () => {
+		formRef.value.resetFields()
+		visible.value = false
+	}
+
+	// 默认要校验的
+	const formRules = {
+		replyContent: [required('请输入内容')]
+	}
+
+	const categoryOptions = tool.dictList('MENU_TYPE')
+	const visibleOptions = tool.dictList('MENU_VISIBLE')
+	// 验证并提交数据
+	const onSubmit = () => {
+		formRef.value
+			.validate()
+			.then(() => {
+				submitLoading.value = true
+				let params = {}
+				if (formData.value.formType) {
+					params = {
+						...formData.value,
+						replyId: replyid.value
+					}
+				} else {
+					params = {
+						...formData.value,
+						postId: moduleId.value,
+						parentId: replyid.value
+					}
+				}
+
+				forumApi.submitPostreply(params, formData.value.formType).then(() => {
+					onClose()
+					emit('successful')
+				})
+			})
+			.finally(() => {
+				submitLoading.value = false
+			})
+	}
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({
+		onOpen
+	})
+</script>

+ 132 - 0
src/views/forum/reportForm.vue

@@ -0,0 +1,132 @@
+<template>
+	<xn-form-container title="回复" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
+		<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
+			<a-row :gutter="16">
+				<a-col :span="12">
+					<a-form-item label="举报类型:" name="reportReasonType">
+						<a-select
+							v-model:value="formData.reportReasonType"
+							show-search
+							placeholder="所有分类"
+							style="width: 200px"
+							:options="typeOptions"
+							:filter-option="filterOption"
+							@change="handleChange"
+						></a-select>
+					</a-form-item>
+				</a-col>
+				<a-col :span="24">
+					<a-form-item label="举报原因:" name="reportDetail">
+						<a-textarea v-model:value="formData.reportDetail" placeholder="请输入举报原因" :rows="4" />
+					</a-form-item>
+				</a-col>
+				<a-col :span="24">
+					<a-form-item label="证据图片:" name="evidenceScreenshot">
+						<UpLoadImg ref="upLoadImgRef" @handlerUpImage="handlerUpImage"></UpLoadImg>
+					</a-form-item>
+				</a-col>
+			</a-row>
+		</a-form>
+		<template #footer>
+			<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
+			<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
+		</template>
+	</xn-form-container>
+</template>
+
+<script setup>
+	import { required } from '@/utils/formRules'
+	import SnowflakeId from 'snowflake-id'
+	import tool from '@/utils/tool'
+	import forumApi from '@/api/forum/forumApi'
+	import XnEditor from '@/components/Editor/index.vue'
+	// 默认是关闭状态
+	const visible = ref(false)
+	const emit = defineEmits({ successful: null })
+	const formRef = ref()
+	const treeData = ref([])
+	// 表单数据,也就是默认给一些数据
+	const formData = ref({})
+	// 默认展开的节点(顶级)
+	const submitLoading = ref(false)
+	// 模块ID
+	const moduleId = ref('')
+	//回复ID
+	const replyid = ref('')
+	//类型
+	const typeOptions = ref([
+		{
+			label: '垃圾广告',
+			value: 0
+		},
+		{
+			label: '色情内容',
+			value: 1
+		},
+		{
+			label: '人身攻击',
+			value: 2
+		},
+		{
+			label: '整治敏感',
+			value: 3
+		},
+		{
+			label: '其他',
+			value: 4
+		}
+	])
+	const handleChange = (value) => {
+		formData.value.reportReasonType = value
+	}
+	const filterOption = (input, option) => {
+		return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+	}
+	const handlerUpImage = (id) => {
+		formData.value.evidenceScreenshot = id
+	}
+	// 打开抽屉
+	const onOpen = (record, module) => {
+		moduleId.value = module
+		visible.value = true
+	}
+	// 关闭抽屉
+	const onClose = () => {
+		formRef.value.resetFields()
+		visible.value = false
+	}
+
+	// 默认要校验的
+	const formRules = {
+		reportReasonType: [required('请选择举报类型')],
+		reportDetail: [required('请输入举报原因')],
+		evidenceScreenshot: [required('请上传证据图片')],
+	}
+
+	const categoryOptions = tool.dictList('MENU_TYPE')
+	const visibleOptions = tool.dictList('MENU_VISIBLE')
+	// 验证并提交数据
+	const onSubmit = () => {
+		formRef.value
+			.validate()
+			.then(() => {
+				submitLoading.value = true
+				forumApi
+					.reportinfoAdd({
+						...formData.value,
+						postId: moduleId.value
+					})
+					.then(() => {
+						onClose()
+						emit('successful')
+					})
+			})
+			.finally(() => {
+				submitLoading.value = false
+			})
+	}
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({
+		onOpen
+	})
+</script>