index.vue 8.6 KB

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