|
@@ -0,0 +1,300 @@
|
|
|
+<template>
|
|
|
+ <!-- 通用列表页详情 -->
|
|
|
+ <div class="content mb-2">
|
|
|
+ <!-- 搜素栏 -->
|
|
|
+ <div class="row mt-3 align-items-center">
|
|
|
+ <!-- 左栏 -->
|
|
|
+ <div class="col-sm-12 col-md-6 col-lg-6">
|
|
|
+ <!-- 分类 -->
|
|
|
+ <TagBar
|
|
|
+ :tags="tagsData || []"
|
|
|
+ :margin="[30, 70]"
|
|
|
+ v-model:selectedTag="selectedTag"
|
|
|
+ />
|
|
|
+ <!-- 标题 -->
|
|
|
+ <div v-if="showNav" class="nav-back-title">
|
|
|
+ <img src="@/assets/images/BackArrow.png" alt="返回" @click="back" />
|
|
|
+ <h2>{{ title }}</h2>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 右栏 -->
|
|
|
+ <div class="col-sm-12 col-md-6 col-lg-6 d-flex flex-row justify-content-end align-items-start" style="gap:5px">
|
|
|
+ <Dropdown
|
|
|
+ v-for="(drop, k) in dropDownNames" :key="k"
|
|
|
+ :selectedValue="dropDownValues[k] || drop.defaultSelectedValue"
|
|
|
+ :options="drop.options"
|
|
|
+ labelKey="name"
|
|
|
+ valueKey="id"
|
|
|
+ @update:selectedValue="(v) => handleChangeDropDownValue(k, v)"
|
|
|
+ />
|
|
|
+ <SimpleInput v-if="showSearch" v-model="searchText" placeholder="请输入关键词" @enter="handleSearch">
|
|
|
+ <template #suffix>
|
|
|
+ <IconSearch
|
|
|
+ class="search-icon"
|
|
|
+ src="@/assets/images/news/IconSearch.png"
|
|
|
+ alt="搜索"
|
|
|
+ @click="newsLoader.loadData(undefined, true)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </SimpleInput>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ :class="[
|
|
|
+ 'content',
|
|
|
+ 'news-list',
|
|
|
+ rowCount === 1 ? '' : 'grid',
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <!-- 新闻列表 -->
|
|
|
+ <SimplePageContentLoader :loader="newsLoader">
|
|
|
+ <div class="list">
|
|
|
+ <div
|
|
|
+ v-for="(item, k) in newsLoader.list.value"
|
|
|
+ :key="item.id"
|
|
|
+ :class="'item user-select-none main-clickable row-type'+rowType"
|
|
|
+ :style="{ width: rowWidth }"
|
|
|
+ @click="handleShowDetail(item)"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ :src="item.image || defaultImage" alt="新闻图片"
|
|
|
+ />
|
|
|
+ <TitleDescBlock
|
|
|
+ :title="item.title"
|
|
|
+ :desc="item.desc || item.title"
|
|
|
+ :date="DateUtils.formatDate(item.publish_at, DateUtils.FormatStrings.YearCommon)"
|
|
|
+ >
|
|
|
+ <template #addon>
|
|
|
+ <div v-if="item.addItems" class="extra">
|
|
|
+ <div
|
|
|
+ v-for="(addItem, k) in item.addItems"
|
|
|
+ :key="k" class="d-flex flex-row align-items-center">
|
|
|
+ <span class="desc">{{ addItem.name }}:</span>
|
|
|
+ <span>{{ addItem.text || '暂无' }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </TitleDescBlock>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="count of placeholderItemCount"
|
|
|
+ :key="count"
|
|
|
+ class="item empty"
|
|
|
+ :style="{ width: rowWidth }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </SimplePageContentLoader>
|
|
|
+ </div>
|
|
|
+ <!-- 分页 -->
|
|
|
+ <Pagination
|
|
|
+ v-model:currentPage="newsLoader.page.value"
|
|
|
+ :totalPages="newsLoader.totalPages.value"
|
|
|
+ />
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, ref, watch, type PropType } from 'vue';
|
|
|
+import { useSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
|
|
|
+import { usePageAction } from '@/composeable/PageAction';
|
|
|
+import DateUtils from '@/common/utils/DateUtils';
|
|
|
+import TagBar from '../content/TagBar.vue';
|
|
|
+import Dropdown from '../controls/Dropdown.vue';
|
|
|
+import SimpleInput from '../controls/SimpleInput.vue';
|
|
|
+import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
|
|
|
+import Pagination from '../controls/Pagination.vue';
|
|
|
+import TitleDescBlock from '../parts/TitleDescBlock.vue';
|
|
|
+import IconSearch from '../icons/IconSearch.vue';
|
|
|
+
|
|
|
+const { navTo, back } = usePageAction();
|
|
|
+
|
|
|
+export interface DropdownCommonItem {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+}
|
|
|
+export interface DropDownNames {
|
|
|
+ options: (string|DropdownCommonItem)[],
|
|
|
+ defaultSelectedValue: number|string,
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ title: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ showNav: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ prevPage: {
|
|
|
+ type: Object as PropType<{
|
|
|
+ title: string,
|
|
|
+ url?: string,
|
|
|
+ }>,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ dropDownNames: {
|
|
|
+ type: Object as PropType<DropDownNames[]>,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ showSearch: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+ tagsData: {
|
|
|
+ type: Object as PropType<{
|
|
|
+ id: number,
|
|
|
+ name: string,
|
|
|
+ }[]>,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ pageSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 8,
|
|
|
+ },
|
|
|
+ rowCount: {
|
|
|
+ type: Number,
|
|
|
+ default: 2,
|
|
|
+ },
|
|
|
+ rowType: {
|
|
|
+ type: Number,
|
|
|
+ default: 1,
|
|
|
+ },
|
|
|
+ defaultSelectTag: {
|
|
|
+ type: Number,
|
|
|
+ default: 1,
|
|
|
+ },
|
|
|
+ load: {
|
|
|
+ type: Function as PropType<(
|
|
|
+ page: number,
|
|
|
+ pageSize: number,
|
|
|
+ selectedTag: number,
|
|
|
+ searchText: string,
|
|
|
+ dropDownValues: number[],
|
|
|
+ ) => Promise<{
|
|
|
+ page: number,
|
|
|
+ total: number,
|
|
|
+ data: any[],
|
|
|
+ }>>,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ showDetail: {
|
|
|
+ type: Function as PropType<(item: any) => void>,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 点击详情跳转页面路径
|
|
|
+ */
|
|
|
+ detailsPage: {
|
|
|
+ type: String,
|
|
|
+ default: '/news/detail'
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 详情跳转页面参数
|
|
|
+ */
|
|
|
+ detailsParams: {
|
|
|
+ type: Object as PropType<Record<string, any>>,
|
|
|
+ default: () => ({})
|
|
|
+ },
|
|
|
+ defaultImage: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+const realRowCount = computed(() => {
|
|
|
+ if (window.innerWidth < 768)
|
|
|
+ return 1;
|
|
|
+ return props.rowCount;
|
|
|
+});
|
|
|
+const rowWidth = computed(() => {
|
|
|
+ switch (realRowCount.value) {
|
|
|
+ case 2:
|
|
|
+ return `calc(50% - 25px)`;
|
|
|
+ case 3:
|
|
|
+ return `calc(33% - 25px)`;
|
|
|
+ case 4:
|
|
|
+ return `calc(25% - 25px)`;
|
|
|
+ }
|
|
|
+});
|
|
|
+const placeholderItemCount = computed(() => {
|
|
|
+ switch (realRowCount.value) {
|
|
|
+ case 2:
|
|
|
+ case 3:
|
|
|
+ case 4:
|
|
|
+ return newsLoader.list.value.length % realRowCount.value;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+});
|
|
|
+const searchText = ref('');
|
|
|
+const dropDownValues = ref<any>([]);
|
|
|
+
|
|
|
+function handleSearch() {
|
|
|
+ newsLoader.loadData(undefined, true);
|
|
|
+}
|
|
|
+function handleChangeDropDownValue(index: number, value: number) {
|
|
|
+ dropDownValues.value[index] = value;
|
|
|
+ newsLoader.loadData(undefined, true);
|
|
|
+}
|
|
|
+function handleShowDetail(item: any) {
|
|
|
+ if (props.showDetail)
|
|
|
+ return props.showDetail(item);
|
|
|
+ navTo(props.detailsPage, {
|
|
|
+ id: item.id,
|
|
|
+ ...props.detailsParams,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+const newsLoader = useSimplePagerDataLoader(props.pageSize, (page, size) => props.load(
|
|
|
+ page, size,
|
|
|
+ selectedTag.value,
|
|
|
+ searchText.value,
|
|
|
+ dropDownValues.value,
|
|
|
+));
|
|
|
+
|
|
|
+//子分类
|
|
|
+const selectedTag = ref(props.defaultSelectTag);
|
|
|
+
|
|
|
+watch(selectedTag, () => {
|
|
|
+ newsLoader.loadData(undefined, true);
|
|
|
+})
|
|
|
+onMounted(() => {
|
|
|
+ newsLoader.loadData(undefined, true);
|
|
|
+});
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ reload() {
|
|
|
+ newsLoader.loadData(undefined, true);
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+@use "@/assets/scss/colors";
|
|
|
+
|
|
|
+.nav-back-title {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-start;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ font-size: 20px;
|
|
|
+ font-family: SourceHanSerifCNBold;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ img {
|
|
|
+ width: 25px;
|
|
|
+ height: 25px;
|
|
|
+ cursor: pointer;
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.search-icon {
|
|
|
+ width: 25px;
|
|
|
+ height: 25px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: colors.$primary-color;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|