index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <div class="forumTop"></div>
  3. <div style="display: flex; justify-content: center" class="main-content-wrapper">
  4. <a-card :bordered="false" class="box-width">
  5. <a-space>
  6. <div style="display: flex">
  7. <a-tooltip :getPopupContainer="(trigger) => trigger.parentElement">
  8. <template #title>
  9. <span>回到首页</span>
  10. </template>
  11. <a-button @click="goToHome">
  12. <HomeOutlined />
  13. </a-button>
  14. </a-tooltip>
  15. </div>
  16. <a-input-search
  17. v-model:value="searchFormState.postTitle"
  18. placeholder="请输入名称关键词"
  19. enter-button
  20. allowClear
  21. @search="onSearch"
  22. :maxlength="30"
  23. />
  24. <a-select
  25. v-model:value="typeVal"
  26. placeholder="所有类型"
  27. style="width: 110px"
  28. :options="typeOptionsVal"
  29. @change="handleChangeVal"
  30. allowClear
  31. ></a-select>
  32. <a-select
  33. v-if="typeVal == 0"
  34. v-model:value="typeValue"
  35. placeholder="所有类型"
  36. style="width: 100px"
  37. :options="typeOptions"
  38. @change="handleChange"
  39. allowClear
  40. ></a-select>
  41. <a-radio-group v-model:value="searchFormState.sortOrder" button-style="solid">
  42. <a-radio-button
  43. v-for="module in moduleTypeList"
  44. :key="module.id"
  45. :value="module.id"
  46. @click="moduleClock(module.id, module.type)"
  47. >
  48. {{ module.title }}
  49. </a-radio-button>
  50. </a-radio-group>
  51. <a-button type="primary" @click="formRef.onOpen(undefined, searchFormState.sortOrder)">
  52. <template #icon><plus-outlined /></template>
  53. 创建新主题
  54. </a-button>
  55. </a-space>
  56. <a-table
  57. ref="table"
  58. class="mt-2"
  59. :columns="columns"
  60. :dataSource="tableData"
  61. :row-key="(record) => record.id"
  62. :custom-row="customRow"
  63. :pagination="false"
  64. style="width: 100%"
  65. :loading="tableLoading"
  66. >
  67. <template #bodyCell="{ column, record }">
  68. <template v-if="column.dataIndex === 'postTitle'">
  69. <div class="forum-list-title one-line">{{ record.postTitle }}</div>
  70. <div class="forum-list-type">
  71. {{ postName(record.postType) }} {{ record.typeName ? '|' : '' }} {{ record.typeName }}
  72. </div>
  73. <div class="forum-list-content one-line" v-html="record.postContent"></div>
  74. </template>
  75. <template v-if="column.dataIndex === 'lastReplyUserAvatar'">
  76. <div v-if="record.replyCount==0">
  77. <span>无人回复</span>
  78. </div>
  79. <div v-if="record.replyCount>0">
  80. <a-tooltip :title="record.lastReplyUserNickName" placement="top">
  81. <a-avatar :src="record.lastReplyUserAvatar"></a-avatar>
  82. </a-tooltip>
  83. </div>
  84. </template>
  85. <template v-if="column.dataIndex === 'lastReplyTime'">
  86. <div>{{ formatDateTime(record.lastReplyTime) }}</div>
  87. </template>
  88. </template>
  89. </a-table>
  90. <div style="text-align: center; cursor: pointer" class="mt-2" @click="moreList">{{ moreText }}</div>
  91. <Form ref="formRef" @successful="resetLoad()" />
  92. </a-card>
  93. </div>
  94. </template>
  95. <script setup name="forumList">
  96. import forumApi from '@/api/forum/forumApi'
  97. import Form from './form.vue'
  98. import { parseTime } from '@/utils/exam'
  99. import { useRoute, useRouter } from 'vue-router'
  100. import EventBus from "@/utils/EventBus";
  101. const route = useRoute()
  102. const router = useRouter()
  103. const formRef = ref()
  104. const tableLoading = ref(false)
  105. let searchFormState = reactive({
  106. sortOrder: 0,
  107. postTitle: '',
  108. postType: route.query.postType ?? null
  109. })
  110. const table = ref(null)
  111. const moduleTypeList = ref([
  112. {
  113. title: '最新',
  114. id: 0,
  115. type: 1,
  116. state: 0
  117. },
  118. {
  119. title: '热门',
  120. id: 1,
  121. type: 1,
  122. state: 1
  123. },
  124. {
  125. title: '我发布的',
  126. id: 2,
  127. type: 2,
  128. state: 1
  129. },
  130. {
  131. title: '我回复的',
  132. id: 3,
  133. type: 2,
  134. state: 2
  135. },
  136. {
  137. title: '关于我的',
  138. id: 4,
  139. type: 2,
  140. state: 3
  141. },
  142. {
  143. title: '我点赞的',
  144. id: 5,
  145. type: 2,
  146. state: 4
  147. }
  148. ])
  149. const columns = [
  150. {
  151. title: '主题',
  152. dataIndex: 'postTitle'
  153. },
  154. {
  155. title: '最后回复人',
  156. dataIndex: 'lastReplyUserAvatar',
  157. align: 'center',
  158. width: 120
  159. },
  160. {
  161. title: '回复量',
  162. dataIndex: 'replyCount',
  163. align: 'center',
  164. width: 100
  165. },
  166. {
  167. title: '浏览量',
  168. dataIndex: 'viewCount',
  169. align: 'center',
  170. width: 100
  171. },
  172. {
  173. title: '最后回复时间',
  174. dataIndex: 'lastReplyTime',
  175. align: 'center',
  176. width: 120
  177. }
  178. ]
  179. const typeVal = ref('所有类型')
  180. const typeOptionsVal = ref([
  181. {
  182. label: '普通帖子',
  183. value: 0
  184. },
  185. {
  186. label: '技术支持',
  187. value: 1
  188. },
  189. {
  190. label: '内容纠错',
  191. value: 2
  192. },
  193. {
  194. label: '章节讨论',
  195. value: 3
  196. }
  197. ])
  198. const pagination = ref({
  199. current: 1,
  200. size: 10
  201. })
  202. const postName = computed(() => {
  203. return (val) => {
  204. return typeOptionsVal.value.find((r) => r.value == val).label
  205. }
  206. })
  207. const typeValue = ref('所有分类')
  208. const typeOptions = ref([])
  209. const handleChange = (value) => {
  210. searchFormState.typeId = value
  211. resetLoad()
  212. }
  213. const handleChangeVal = (value) => {
  214. searchFormState.postType = value
  215. searchFormState.typeId = ''
  216. typeValue.value = '所有分类'
  217. resetLoad()
  218. }
  219. function formatDateTime(val) {
  220. if (!val) return ''
  221. return parseTime(val, '{y}-{m}-{d} {h}:{i}:{s}')
  222. }
  223. // 查询
  224. const onSearch = () => {
  225. resetLoad()
  226. }
  227. function customRow(record) {
  228. return {
  229. onClick: () => itemSelect(record)
  230. }
  231. }
  232. function itemSelect(record) {
  233. router.push({
  234. path: '/forum/detail',
  235. query: {
  236. postId: record.postId,
  237. postType: route.query.postType ?? ''
  238. }
  239. })
  240. }
  241. function getTypeList() {
  242. forumApi.forumTypeList().then((data) => {
  243. typeOptions.value = data.map((r) => {
  244. return {
  245. label: r.typeName,
  246. value: r.typeId,
  247. ...r
  248. }
  249. })
  250. })
  251. }
  252. const exType = ref(1)
  253. const tableData = ref([])
  254. const tableTotal = ref(0)
  255. const resetLoad = () => {
  256. pagination.value.current = 1
  257. moreText.value = '加载更多'
  258. loadData(pagination.value)
  259. }
  260. const loadData = (parameter, a) => {
  261. tableLoading.value = true
  262. if (exType.value == 2) {
  263. let postExtend = moduleTypeList.value.find((r) => r.id == searchFormState.sortOrder).state
  264. return forumApi
  265. .moreList(Object.assign(parameter, searchFormState, { postExtend: postExtend }))
  266. .then((data) => {
  267. tableTotal.value = data.total
  268. if (a) {
  269. tableData.value = Object.assign(tableData.value, data.records)
  270. } else {
  271. if (data) {
  272. tableData.value = data.records
  273. } else {
  274. tableData.value = []
  275. }
  276. }
  277. })
  278. .finally(() => {
  279. tableLoading.value = false
  280. })
  281. } else {
  282. return forumApi
  283. .forumList(Object.assign(parameter, searchFormState))
  284. .then((data) => {
  285. tableTotal.value = data.total
  286. if (a) {
  287. tableData.value = tableData.value.concat(data.records)
  288. } else {
  289. if (data) {
  290. tableData.value = data.records
  291. } else {
  292. tableData.value = []
  293. }
  294. }
  295. })
  296. .finally(() => {
  297. tableLoading.value = false
  298. })
  299. }
  300. }
  301. // 切换应用标签查询菜单列表
  302. const moduleClock = (value, t) => {
  303. exType.value = t
  304. searchFormState.sortOrder = value
  305. pagination.value.current = 1
  306. moreText.value = '加载更多'
  307. loadData(pagination.value)
  308. }
  309. const moreText = ref()
  310. const moreList = () => {
  311. if (tableTotal.value > pagination.value.current * pagination.value.size) {
  312. pagination.value.current += 1
  313. loadData(pagination.value, 1)
  314. moreText.value = '加载更多'
  315. } else {
  316. moreText.value = '全部加载完成'
  317. }
  318. }
  319. // 定义滚动函数
  320. const handleScroll = () => {
  321. const tableContainer = window.document.querySelector('.main-content-wrapper')
  322. const scrollPosition = tableContainer.scrollTop
  323. const isBottom = tableContainer.scrollHeight - scrollPosition - 20 < tableContainer.clientHeight
  324. if (isBottom) {
  325. if (tableTotal.value > pagination.value.current * pagination.value.size) {
  326. pagination.value.current += 1
  327. loadData(pagination.value, 1)
  328. moreText.value = '加载更多'
  329. } else {
  330. moreText.value = '全部加载完成'
  331. }
  332. }
  333. }
  334. //回到首页
  335. const goToHome = () => {
  336. router.push({
  337. path: '/'
  338. })
  339. EventBus.emit('onSetHeader','')
  340. }
  341. onMounted(() => {
  342. getTypeList()
  343. loadData(pagination.value)
  344. nextTick(() => {
  345. // 添加scroll监听
  346. if (table.value) {
  347. const tableContainer = window.document.querySelector('.main-content-wrapper')
  348. tableContainer.addEventListener('scroll', handleScroll)
  349. }
  350. })
  351. })
  352. onBeforeUnmount(() => {
  353. // 移除scroll监听
  354. nextTick(() => {
  355. if (table.value) {
  356. const tableContainer = window.document.querySelector('.main-content-wrapper')
  357. tableContainer.removeEventListener('scroll', handleScroll)
  358. }
  359. })
  360. })
  361. </script>
  362. <style scoped>
  363. .forum-list-title {
  364. font-size: 16px;
  365. font-weight: bold;
  366. }
  367. .forum-list-type {
  368. font-size: 12px;
  369. }
  370. .forum-list-content {
  371. font-size: 14px;
  372. color: #696969;
  373. }
  374. .one-line {
  375. max-width: 900px;
  376. display: -webkit-box;
  377. -webkit-box-orient: vertical;
  378. -webkit-line-clamp: 1;
  379. line-clamp: 1;
  380. overflow: hidden;
  381. }
  382. .forumTop {
  383. background-color: #1890ff;
  384. height: 55px;
  385. }
  386. .main-content-wrapper {
  387. padding: 0px;
  388. height: calc(100% - 55px);
  389. }
  390. .box-width {
  391. width: 1624px;
  392. }
  393. </style>