CommonListPage.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <template>
  2. <!-- 资讯详情页 -->
  3. <div class="main-background">
  4. <div class="nav-placeholder"></div>
  5. <!-- 新闻 -->
  6. <section class="main-section main-background main-background-type0 small-h">
  7. <div class="content mb-2">
  8. <!-- 路径 -->
  9. <a-breadcrumb>
  10. <a-breadcrumb-item><a href="javascript:;" @click="navTo('/')">首页</a></a-breadcrumb-item>
  11. <a-breadcrumb-item v-if="prevPage"><a href="javascript:;" @click="prevPage.url ? navTo(prevPage.url) : back()">{{ prevPage.title }}</a></a-breadcrumb-item>
  12. <a-breadcrumb-item>{{ title }}</a-breadcrumb-item>
  13. </a-breadcrumb>
  14. </div>
  15. <div class="content mb-2">
  16. <!-- 搜素栏 -->
  17. <div class="row mt-3">
  18. <!-- 左栏 -->
  19. <div class="col-sm-12 col-md-6 col-lg-6">
  20. <!-- 分类 -->
  21. <TagBar
  22. :tags="tagsData || []"
  23. :margin="[30, 70]"
  24. v-model:selectedTag="selectedTag"
  25. />
  26. </div>
  27. <!-- 右栏 -->
  28. <div class="col-sm-12 col-md-6 col-lg-6 d-flex flex-row justify-content-end">
  29. <Dropdown
  30. v-for="(drop, k) in dropDownNames" :key="k"
  31. :selectedValue="dropDownValues[k] || drop.defaultSelectedValue"
  32. :options="drop.options"
  33. @update:selectedValue="(v) => handleChangeDropDownValue(k, v)"
  34. />
  35. <SimpleInput v-if="showSearch" v-model="searchText" placeholder="请输入关键词" @enter="handleSearch">
  36. <template #suffix>
  37. <IconSearch
  38. class="search-icon"
  39. src="@/assets/images/news/IconSearch.png"
  40. alt="搜索"
  41. @click="newsLoader.loadData(undefined, true)"
  42. />
  43. </template>
  44. </SimpleInput>
  45. </div>
  46. </div>
  47. </div>
  48. <div
  49. :class="[
  50. 'content',
  51. 'news-list',
  52. rowCount === 1 ? '' : 'grid',
  53. ]"
  54. >
  55. <!-- 新闻列表 -->
  56. <SimplePageContentLoader :loader="newsLoader">
  57. <div class="list">
  58. <div
  59. v-for="(item, k) in newsLoader.list.value"
  60. :key="item.id"
  61. class="item user-select-none main-clickable"
  62. :style="{ width: rowWidth }"
  63. @click="navTo('/news/detail', { id: item.id })"
  64. >
  65. <img :src="item.image" alt="新闻图片" />
  66. <TitleDescBlock
  67. :title="item.title"
  68. :desc="item.desc || item.title"
  69. :date="DateUtils.formatDate(item.publish_at, DateUtils.FormatStrings.YearCommon)"
  70. @click="handleShowDetail(item)"
  71. />
  72. </div>
  73. <div
  74. v-for="count of placeholderItemCount"
  75. :key="count"
  76. class="item empty"
  77. :style="{ width: rowWidth }"
  78. />
  79. </div>
  80. </SimplePageContentLoader>
  81. </div>
  82. <!-- 分页 -->
  83. <Pagination
  84. v-model:currentPage="newsLoader.page.value"
  85. :totalPages="newsLoader.totalPages.value"
  86. />
  87. </section>
  88. </div>
  89. </template>
  90. <script setup lang="ts">
  91. import { computed, onMounted, ref, watch, type PropType } from 'vue';
  92. import { useSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
  93. import { usePageAction } from '@/composeable/PageAction';
  94. import DateUtils from '@/common/utils/DateUtils';
  95. import TagBar from '../content/TagBar.vue';
  96. import Dropdown from '../controls/Dropdown.vue';
  97. import SimpleInput from '../controls/SimpleInput.vue';
  98. import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
  99. import Pagination from '../controls/Pagination.vue';
  100. import TitleDescBlock from '../parts/TitleDescBlock.vue';
  101. import IconSearch from '../icons/IconSearch.vue';
  102. const { navTo, back } = usePageAction();
  103. export interface DropdownCommonItem {
  104. value: number;
  105. title: string;
  106. }
  107. export interface DropDownNames {
  108. options: (string|DropdownCommonItem)[],
  109. defaultSelectedValue: number|string,
  110. }
  111. const props = defineProps({
  112. title: {
  113. type: String,
  114. default: '',
  115. },
  116. prevPage: {
  117. type: Object as PropType<{
  118. title: string,
  119. url?: string,
  120. }>,
  121. default: null,
  122. },
  123. dropDownNames: {
  124. type: Object as PropType<DropDownNames[]>,
  125. default: null,
  126. },
  127. showSearch: {
  128. type: Boolean,
  129. default: true,
  130. },
  131. tagsData: {
  132. type: Object as PropType<{
  133. id: number,
  134. name: string,
  135. }[]>,
  136. default: null,
  137. },
  138. pageSize: {
  139. type: Number,
  140. default: 8,
  141. },
  142. rowCount: {
  143. type: Number,
  144. default: 2,
  145. },
  146. rowType: {
  147. type: Number,
  148. default: 1,
  149. },
  150. defaultSelectTag: {
  151. type: Number,
  152. default: 1,
  153. },
  154. load: {
  155. type: Function as PropType<(
  156. page: number,
  157. pageSize: number,
  158. selectedTag: number,
  159. searchText: string,
  160. dropDownValues: number[],
  161. ) => Promise<{
  162. page: number,
  163. total: number,
  164. data: any[],
  165. }>>,
  166. required: true,
  167. },
  168. })
  169. const realRowCount = computed(() => {
  170. if (window.innerWidth < 768)
  171. return 1;
  172. return props.rowCount;
  173. });
  174. const rowWidth = computed(() => {
  175. switch (realRowCount.value) {
  176. case 2:
  177. return `calc(50% - 25px)`;
  178. case 3:
  179. return `calc(33% - 25px)`;
  180. case 4:
  181. return `calc(25% - 25px)`;
  182. }
  183. });
  184. const placeholderItemCount = computed(() => {
  185. switch (realRowCount.value) {
  186. case 2:
  187. case 3:
  188. case 4:
  189. return newsLoader.list.value.length % realRowCount.value;
  190. }
  191. return 0;
  192. });
  193. const searchText = ref('');
  194. const dropDownValues = ref<any>([]);
  195. function handleSearch() {
  196. newsLoader.loadData(undefined, true);
  197. }
  198. function handleChangeDropDownValue(index: number, value: number) {
  199. dropDownValues.value[index] = value;
  200. newsLoader.loadData(undefined, true);
  201. }
  202. function handleShowDetail(item: any) {
  203. navTo('/news/detail', { id: item.id });
  204. }
  205. const newsLoader = useSimplePagerDataLoader(props.pageSize, (page, size) => props.load(
  206. page, size,
  207. selectedTag.value,
  208. searchText.value,
  209. dropDownValues.value,
  210. ));
  211. //子分类
  212. const selectedTag = ref(props.defaultSelectTag);
  213. watch(selectedTag, () => {
  214. newsLoader.loadData(undefined, true);
  215. })
  216. onMounted(() => {
  217. newsLoader.loadData(undefined, true);
  218. });
  219. </script>
  220. <style lang="scss">
  221. @use "@/assets/scss/colors";
  222. .search-icon {
  223. width: 25px;
  224. height: 25px;
  225. cursor: pointer;
  226. color: colors.$primary-color;
  227. }
  228. </style>