CommonListBlock.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. <template>
  2. <!-- 通用列表页详情 -->
  3. <div class="content mb-2">
  4. <!-- 搜素栏 -->
  5. <div class="row mt-3 align-items-center">
  6. <!-- 左栏 -->
  7. <div class="col-sm-12 col-md-6 col-lg-6">
  8. <!-- 分类 -->
  9. <TagBar
  10. :tags="tagsData || []"
  11. :margin="[30, 70]"
  12. v-model:selectedTag="selectedTag"
  13. />
  14. <!-- 标题 -->
  15. <div v-if="showNav" class="nav-back-title">
  16. <img src="@/assets/images/BackArrow.png" alt="返回" @click="back" />
  17. <h2>{{ title }}</h2>
  18. </div>
  19. <!-- 标题 -->
  20. <div v-if="showTotal" class="nav-back-title">
  21. 共有 {{ newsLoader.total }} 个{{ title }}
  22. </div>
  23. </div>
  24. <!-- 右栏 -->
  25. <div class="col-sm-12 col-md-6 col-lg-6 d-flex flex-row justify-content-end align-items-start" style="gap:5px">
  26. <Dropdown
  27. v-for="(drop, k) in dropDownNames" :key="k"
  28. :selectedValue="dropDownValues[k]"
  29. :options="drop.options"
  30. labelKey="name"
  31. valueKey="id"
  32. @update:selectedValue="(v) => handleChangeDropDownValue(k, v)"
  33. />
  34. <SimpleInput v-if="showSearch" v-model="searchText" placeholder="请输入关键词" @enter="handleSearch">
  35. <template #suffix>
  36. <IconSearch
  37. class="search-icon"
  38. src="@/assets/images/news/IconSearch.png"
  39. alt="搜索"
  40. @click="newsLoader.loadData(undefined, true)"
  41. />
  42. </template>
  43. </SimpleInput>
  44. </div>
  45. </div>
  46. </div>
  47. <div
  48. :class="[
  49. 'content',
  50. 'news-list',
  51. rowCount === 1 ? '' : 'grid',
  52. ]"
  53. >
  54. <!-- 新闻列表 -->
  55. <SimplePageContentLoader :loader="newsLoader">
  56. <div class="list">
  57. <div
  58. v-for="(item, k) in newsLoader.list.value"
  59. :key="item.id"
  60. :class="'item user-select-none main-clickable row-type'+rowType"
  61. :style="{ width: rowWidth }"
  62. @click="handleShowDetail(item)"
  63. >
  64. <img
  65. :src="item.image || defaultImage" alt="新闻图片"
  66. />
  67. <TitleDescBlock
  68. :title="item.title"
  69. :desc="item.desc || item.title"
  70. >
  71. <template #addon>
  72. <div v-if="item.bottomTags" class="tags">
  73. <div
  74. v-for="(tag, k) in item.bottomTags"
  75. :key="k"
  76. :class="tag ? '' : 'd-none'"
  77. >{{ tag }}</div>
  78. </div>
  79. <div v-if="item.addItems" class="extra">
  80. <div
  81. v-for="(addItem, k) in item.addItems"
  82. :key="k"
  83. class="d-flex flex-row align-items-center"
  84. :class="[
  85. addItem.text ? '' : 'd-none',
  86. ]"
  87. >
  88. <span class="desc">{{ addItem.name }}:</span>
  89. <span>{{ addItem.text }}</span>
  90. </div>
  91. </div>
  92. </template>
  93. </TitleDescBlock>
  94. </div>
  95. <div
  96. v-for="count of placeholderItemCount"
  97. :key="count"
  98. class="item empty"
  99. :style="{ width: rowWidth }"
  100. />
  101. </div>
  102. </SimplePageContentLoader>
  103. </div>
  104. <!-- 分页 -->
  105. <Pagination
  106. v-model:currentPage="newsLoader.page.value"
  107. :totalPages="newsLoader.totalPages.value"
  108. />
  109. </template>
  110. <script setup lang="ts">
  111. import { computed, onMounted, ref, watch, type PropType } from 'vue';
  112. import { useSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
  113. import { usePageAction } from '@/composeable/PageAction';
  114. import DateUtils from '@/common/utils/DateUtils';
  115. import TagBar from '../content/TagBar.vue';
  116. import Dropdown from '../controls/Dropdown.vue';
  117. import SimpleInput from '../controls/SimpleInput.vue';
  118. import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
  119. import Pagination from '../controls/Pagination.vue';
  120. import TitleDescBlock from '../parts/TitleDescBlock.vue';
  121. import IconSearch from '../icons/IconSearch.vue';
  122. const { navTo, back } = usePageAction();
  123. export interface DropdownCommonItem {
  124. id: number;
  125. name: string;
  126. }
  127. export interface DropDownNames {
  128. options: (string|DropdownCommonItem)[],
  129. label?: string,
  130. defaultSelectedValue: number|string,
  131. }
  132. const props = defineProps({
  133. title: {
  134. type: String,
  135. default: '',
  136. },
  137. showNav: {
  138. type: Boolean,
  139. default: false,
  140. },
  141. showTotal: {
  142. type: Boolean,
  143. default: false,
  144. },
  145. prevPage: {
  146. type: Object as PropType<{
  147. title: string,
  148. url?: string,
  149. }>,
  150. default: null,
  151. },
  152. dropDownNames: {
  153. type: Object as PropType<DropDownNames[]>,
  154. default: null,
  155. },
  156. showSearch: {
  157. type: Boolean,
  158. default: true,
  159. },
  160. tagsData: {
  161. type: Object as PropType<{
  162. id: number,
  163. name: string,
  164. }[]>,
  165. default: null,
  166. },
  167. pageSize: {
  168. type: Number,
  169. default: 8,
  170. },
  171. rowCount: {
  172. type: Number,
  173. default: 2,
  174. },
  175. rowType: {
  176. type: Number,
  177. default: 1,
  178. },
  179. defaultSelectTag: {
  180. type: Number,
  181. default: 1,
  182. },
  183. load: {
  184. type: Function as PropType<(
  185. page: number,
  186. pageSize: number,
  187. selectedTag: number,
  188. searchText: string,
  189. dropDownValues: number[],
  190. ) => Promise<{
  191. page: number,
  192. total: number,
  193. data: any[],
  194. }>>,
  195. required: true,
  196. },
  197. showDetail: {
  198. type: Function as PropType<(item: any) => void>,
  199. default: null,
  200. },
  201. /**
  202. * 点击详情跳转页面路径
  203. */
  204. detailsPage: {
  205. type: String,
  206. default: '/news/detail'
  207. },
  208. /**
  209. * 详情跳转页面参数
  210. */
  211. detailsParams: {
  212. type: Object as PropType<Record<string, any>>,
  213. default: () => ({})
  214. },
  215. defaultImage: {
  216. type: String,
  217. default: ''
  218. },
  219. })
  220. const realRowCount = computed(() => {
  221. if (window.innerWidth < 768)
  222. return 1;
  223. return props.rowCount;
  224. });
  225. const rowWidth = computed(() => {
  226. switch (realRowCount.value) {
  227. case 2:
  228. return `calc(50% - 25px)`;
  229. case 3:
  230. return `calc(33% - 25px)`;
  231. case 4:
  232. return `calc(25% - 25px)`;
  233. }
  234. });
  235. const placeholderItemCount = computed(() => {
  236. switch (realRowCount.value) {
  237. case 2:
  238. case 3:
  239. case 4:
  240. return newsLoader.list.value.length % realRowCount.value;
  241. }
  242. return 0;
  243. });
  244. const searchText = ref('');
  245. const dropDownValues = ref<any>([]);
  246. function handleSearch() {
  247. newsLoader.loadData(undefined, true);
  248. }
  249. function handleChangeDropDownValue(index: number, value: number) {
  250. dropDownValues.value[index] = value;
  251. newsLoader.loadData(undefined, true);
  252. }
  253. function handleShowDetail(item: any) {
  254. if (props.showDetail)
  255. return props.showDetail(item);
  256. navTo(props.detailsPage, {
  257. id: item.id,
  258. ...props.detailsParams,
  259. });
  260. }
  261. const newsLoader = useSimplePagerDataLoader(props.pageSize, (page, size) => props.load(
  262. page, size,
  263. selectedTag.value,
  264. searchText.value,
  265. dropDownValues.value,
  266. ));
  267. //子分类
  268. const selectedTag = ref(props.defaultSelectTag);
  269. watch(() => props.defaultSelectTag, (v) => {
  270. selectedTag.value = v;
  271. })
  272. watch(() => props.dropDownNames, () => {
  273. loadDropValues();
  274. })
  275. watch(selectedTag, () => {
  276. newsLoader.loadData(undefined, true);
  277. })
  278. function loadDropValues() {
  279. dropDownValues.value = [];
  280. if (props.dropDownNames)
  281. for (const element of props.dropDownNames)
  282. dropDownValues.value.push(element.defaultSelectedValue);
  283. newsLoader.loadData(undefined, true);
  284. }
  285. onMounted(() => {
  286. setTimeout(() => {
  287. loadDropValues();
  288. }, 400);
  289. })
  290. defineExpose({
  291. reload() {
  292. newsLoader.loadData(undefined, true);
  293. }
  294. })
  295. </script>
  296. <style lang="scss">
  297. @use "@/assets/scss/colors";
  298. .nav-back-title {
  299. display: flex;
  300. flex-direction: row;
  301. align-items: center;
  302. justify-content: flex-start;
  303. h2 {
  304. font-size: 20px;
  305. font-family: SourceHanSerifCNBold;
  306. margin: 0;
  307. }
  308. img {
  309. width: 25px;
  310. height: 25px;
  311. cursor: pointer;
  312. margin-right: 10px;
  313. }
  314. }
  315. .search-icon {
  316. width: 25px;
  317. height: 25px;
  318. cursor: pointer;
  319. color: colors.$primary-color;
  320. }
  321. </style>