CommonListPage.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <template>
  2. <!-- 通用列表页 -->
  3. <FlexCol
  4. :padding="30"
  5. :backgroundColor="hasBg ? 'background.page' : ''"
  6. >
  7. <Tabs
  8. v-if="tabs"
  9. :tabs="tabs"
  10. :width="700"
  11. v-model:currentIndex="tabCurrentIndex"
  12. :autoScroll="false"
  13. @click="handleTabClick"
  14. />
  15. <!-- 搜索 -->
  16. <SearchBar
  17. v-if="showSearch"
  18. v-model="searchValue"
  19. :placeholder="`输入关键词搜索${title}`"
  20. @search="doSearch"
  21. @cancel="doSearch"
  22. />
  23. <!-- 下拉框 -->
  24. <FlexRow
  25. v-if="dropDownNames.length > 0"
  26. :justify="dropDownVisibleCount >= 3 ? 'space-around' : 'space-between'"
  27. align="center"
  28. >
  29. <template v-for="(drop, k) in dropDownNames" :key="k" >
  30. <SimpleDropDownPicker
  31. v-if="!drop.activeTab || drop.activeTab.includes(tabCurrentIndex)"
  32. :modelValue="dropDownValues[k]"
  33. :columns="drop.options"
  34. :style="{maxWidth: `${100/dropDownNames.length}%`}"
  35. @update:modelValue="(v) => handleChangeDropDownValue(k, v)"
  36. />
  37. </template>
  38. <FlexRow v-if="(showTotal && dropDownVisibleCount < 3)" center>
  39. <Text bold color="primary" :text="`总共有 ${ listLoader.total.value } 个`" />
  40. </FlexRow>
  41. </FlexRow>
  42. <FlexRow v-if="(dropDownVisibleCount >= 3 || dropDownVisibleCount == 0 && showTotal)" center :padding="20">
  43. <Text bold color="primary" :text="`总共有 ${ listLoader.total.value } 个`" />
  44. </FlexRow>
  45. <!-- 列表 -->
  46. <FlexRow
  47. position="relative"
  48. wrap justify="space-between" align="stretch"
  49. >
  50. <FlexCol
  51. v-for="(item, i) in listLoader.list.value"
  52. :key="item.id"
  53. :flexGrow="1"
  54. :width="itemType.endsWith('-2') ? '50%' : '100%'"
  55. >
  56. <Box2LineLargeImageUserShadow
  57. v-if="itemType.startsWith('image-large')"
  58. :width="100"
  59. titleColor="black"
  60. :classNames="getItemClass(i)"
  61. :image="getImage(item)"
  62. :titleBox="item.titleBox"
  63. :title="item.title"
  64. :desc="item.desc"
  65. :tags="item.bottomTags"
  66. :badge="item.badge"
  67. @click="goDetails(item, item.id)"
  68. />
  69. <Box2LineImageRightShadow
  70. v-else-if="itemType.startsWith('article-common')"
  71. :width="100"
  72. titleColor="black"
  73. :titleBox="item.titleBox"
  74. :classNames="getItemClass(i)"
  75. :image="getImage(item)"
  76. :title="item.title"
  77. :desc="item.desc"
  78. :tags="item.bottomTags"
  79. :badge="item.badge"
  80. :wideImage="true"
  81. @click="goDetails(item, item.id)"
  82. />
  83. <Box2LineImageRightShadow
  84. v-else-if="itemType.startsWith('article-character')"
  85. :width="100"
  86. :classNames="getItemClass(i)"
  87. :image="getImage(item)"
  88. titleColor="black"
  89. :title="item.title"
  90. :titleBox="item.titleBox"
  91. :tags="item.bottomTags || item.keywords"
  92. :desc="item.desc"
  93. :badge="item.badge"
  94. @click="goDetails(item, item.id)"
  95. />
  96. </FlexCol>
  97. <view v-if="itemType.endsWith('-2') && listLoader.list.value.length % 2 != 0" style="width:50%;" />
  98. </FlexRow>
  99. <SimplePageListLoader :loader="listLoader" />
  100. </FlexCol>
  101. </template>
  102. <script setup lang="ts">
  103. import { computed, nextTick, onMounted, ref, watch, type PropType } from 'vue';
  104. import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
  105. import { navTo } from '@/components/utils/PageAction';
  106. import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
  107. import Box2LineLargeImageUserShadow from '@/common/components/parts/Box2LineLargeImageUserShadow.vue';
  108. import Box2LineImageRightShadow from '@/common/components/parts/Box2LineImageRightShadow.vue';
  109. import SimpleDropDownPicker, { type SimpleDropDownPickerItem } from '@/common/components/SimpleDropDownPicker.vue';
  110. import AppCofig from '@/common/config/AppCofig';
  111. import Tabs from '@/components/nav/Tabs.vue';
  112. import SearchBar from '@/components/form/SearchBar.vue';
  113. import FlexCol from '@/components/layout/FlexCol.vue';
  114. import FlexRow from '@/components/layout/FlexRow.vue';
  115. import Text from '@/components/basic/Text.vue';
  116. function getImage(item: any) {
  117. return item.thumbnail || item.image || AppCofig.defaultImage
  118. }
  119. function getItemClass(index: number) {
  120. return props.itemType.endsWith('-2') ? (index % 2 != 0 ? 'ml-1' : 'mr-1') : ''
  121. }
  122. export interface DropDownNames {
  123. options: SimpleDropDownPickerItem[],
  124. defaultSelectedValue: number|string,
  125. activeTab?: number[],
  126. }
  127. export interface CommonListItem extends Record<string, any> {
  128. id: number,
  129. image: string,
  130. title: string,
  131. }
  132. const props = defineProps({
  133. /**
  134. * 标题
  135. */
  136. title: {
  137. type: String,
  138. default: '',
  139. },
  140. /**
  141. * 分组标签
  142. */
  143. tabs: {
  144. type: Array as PropType<{
  145. id: number,
  146. text: string,
  147. jump?: () => void,
  148. }[]>,
  149. default: null,
  150. },
  151. tabsScrollable: {
  152. type: Boolean,
  153. default: false,
  154. },
  155. /**
  156. * 是否显示搜索框
  157. */
  158. showSearch: {
  159. type: Boolean,
  160. default: true,
  161. },
  162. /**
  163. * 初始搜索文本
  164. */
  165. intitalSearch: {
  166. type: String,
  167. default: '',
  168. },
  169. /**
  170. * 显示总数
  171. */
  172. showTotal: {
  173. type: Boolean,
  174. default: false,
  175. },
  176. /**
  177. * 下拉框选项控制
  178. */
  179. dropDownNames: {
  180. type: Object as PropType<DropDownNames[]>,
  181. default: () => ([]),
  182. },
  183. /**
  184. * 列表项类型
  185. */
  186. itemType: {
  187. type: String as PropType<'image-large-2'|'image-large'|'article-common'|'article-character'>,
  188. default: 'article-common',
  189. },
  190. /**
  191. * 分页大小
  192. */
  193. pageSize: {
  194. type: Number,
  195. default: 8,
  196. },
  197. /**
  198. * 加载数据函数
  199. * @param page 页码,从1开始
  200. * @param pageSize 分页大小
  201. * @param searchText 搜索文本
  202. * @param dropDownValues 下拉框值
  203. */
  204. load: {
  205. type: Function as PropType<(
  206. page: number,
  207. pageSize: number,
  208. searchText: string,
  209. dropDownValues: number[],
  210. tabSelect: number,
  211. ) => Promise<{ list: CommonListItem[], total: number }>>,
  212. required: true,
  213. },
  214. /**
  215. * 点击详情跳转页面路径
  216. */
  217. detailsPage: {
  218. type: [String,Object],
  219. default: '/pages/article/details'
  220. },
  221. /**
  222. * 详情跳转页面参数
  223. */
  224. detailsParams: {
  225. type: Object as PropType<Record<string, any>>,
  226. default: () => ({})
  227. },
  228. hasBg: {
  229. type: Boolean,
  230. default: true,
  231. },
  232. startTabIndex: {
  233. type: Number,
  234. default: undefined,
  235. },
  236. loadMounted: {
  237. type: Boolean,
  238. default: true,
  239. },
  240. })
  241. const emit = defineEmits([ 'goCustomDetails' ])
  242. const dropDownVisibleCount = computed(() => {
  243. let c = 0;
  244. for (const element of props.dropDownNames) {
  245. if (!element.activeTab || element.activeTab.includes(tabCurrentIndex.value))
  246. c++;
  247. }
  248. return c;
  249. })
  250. const dropDownValues = ref<any>([]);
  251. const searchValue = ref(props.intitalSearch);
  252. const listLoader = useSimplePageListLoader(props.pageSize, async (page, pageSize) => {
  253. return await props.load(
  254. page, pageSize,
  255. searchValue.value,
  256. dropDownValues.value,
  257. props.tabs?.[tabCurrentIndex.value]?.id ?? tabCurrentIndex.value,
  258. )
  259. });
  260. const tabCurrentIndex = ref(0)
  261. watch(() => props.intitalSearch, (newValue) => {
  262. searchValue.value = newValue;
  263. });
  264. function handleChangeDropDownValue(index: number, value: number) {
  265. dropDownValues.value[index] = value;
  266. listLoader.reload();
  267. }
  268. function handleTabClick(e: any) {
  269. nextTick(() => {
  270. if (props.tabs?.[tabCurrentIndex.value]?.jump) {
  271. props.tabs[tabCurrentIndex.value].jump?.();
  272. return;
  273. }
  274. listLoader.reload();
  275. })
  276. }
  277. function doSearch() {
  278. listLoader.reload();
  279. }
  280. function goDetails(item: any, id: number) {
  281. if (props.detailsPage == 'custom') {
  282. emit('goCustomDetails', item, id)
  283. return;
  284. }
  285. if (typeof props.detailsPage === 'object' && typeof props.detailsPage[0] === 'string') {
  286. navTo(props.detailsPage[tabCurrentIndex.value], {
  287. ...props.detailsParams,
  288. id
  289. })
  290. return;
  291. }
  292. if (typeof props.detailsPage == 'object' && typeof props.detailsPage[0] === 'object') {
  293. const item = props.detailsPage[tabCurrentIndex.value];
  294. navTo(item.page, {
  295. ...item.params,
  296. id
  297. })
  298. return;
  299. }
  300. navTo(props.detailsPage as string, {
  301. ...props.detailsParams,
  302. id
  303. })
  304. }
  305. function loadDropDownValues() {
  306. dropDownValues.value = [];
  307. for (const element of props.dropDownNames) {
  308. dropDownValues.value.push(element.defaultSelectedValue);
  309. }
  310. }
  311. watch(tabCurrentIndex, () => {
  312. listLoader.reload();
  313. });
  314. watch(() => props.startTabIndex, () => {
  315. if (props.startTabIndex) {
  316. tabCurrentIndex.value = props.startTabIndex;
  317. }
  318. });
  319. watch(() => props.dropDownNames.length, () => {
  320. loadDropDownValues();
  321. listLoader.reload();
  322. });
  323. defineExpose({
  324. load: () => {
  325. listLoader.reload();
  326. },
  327. })
  328. onMounted(() => {
  329. if (props.startTabIndex)
  330. tabCurrentIndex.value = props.startTabIndex;
  331. if (props.title)
  332. uni.setNavigationBarTitle({ title: props.title, })
  333. loadDropDownValues();
  334. if (props.loadMounted)
  335. listLoader.reload();
  336. });
  337. </script>