|
|
@@ -0,0 +1,306 @@
|
|
|
+<template>
|
|
|
+ <!--详情页可配置主控-->
|
|
|
+ <FlexCol>
|
|
|
+ <FlexCol v-if="errorMessage" :padding="30" :gap="30" center height="100%">
|
|
|
+ <Result status="error" :description="errorMessage" />
|
|
|
+ <Button type="primary" @click="loadPageConfig">重新加载</Button>
|
|
|
+ </FlexCol>
|
|
|
+ <LoadingPage v-else-if="!loadState" />
|
|
|
+ <DetailTabPage
|
|
|
+ v-else
|
|
|
+ ref="pageRef"
|
|
|
+ v-bind="$attrs"
|
|
|
+ :hasInternalTabs="false"
|
|
|
+ :load="load"
|
|
|
+ :extraTabs="tabRenderDefinesArray"
|
|
|
+ @loaded="onLoaded"
|
|
|
+ >
|
|
|
+ <template #extraTabs="{ content, tabCurrentId }">
|
|
|
+ <template v-if="tabRenderDefines[tabCurrentId].type === 'intro'">
|
|
|
+ <!-- 简介 -->
|
|
|
+ <Parse
|
|
|
+ v-if="content.intro"
|
|
|
+ :content="(content.intro as string)"
|
|
|
+ />
|
|
|
+ <Parse
|
|
|
+ v-if="content.content"
|
|
|
+ :content="content.content"
|
|
|
+ />
|
|
|
+ <text v-if="!(content.intro || content.content)">暂无简介</text>
|
|
|
+ <text v-if="content.from" class="size-s color-text-content-second mr-2 ">以上内容摘自 {{ content.from }}</text>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'images'">
|
|
|
+ <!-- 图片 -->
|
|
|
+ <ImageGrid
|
|
|
+ :images="content.images"
|
|
|
+ :rowCount="2"
|
|
|
+ :preview="true"
|
|
|
+ imageHeight="200rpx"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'video'">
|
|
|
+ <!-- 视频 -->
|
|
|
+ <video
|
|
|
+ v-if="content.video"
|
|
|
+ class="w-100 video"
|
|
|
+ autoplay
|
|
|
+ :poster="content.image"
|
|
|
+ :src="content.video"
|
|
|
+ controls
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'audio'">
|
|
|
+ <!-- 视频 -->
|
|
|
+ <video
|
|
|
+ v-if="content.audio"
|
|
|
+ class="w-100 video"
|
|
|
+ autoplay
|
|
|
+ :poster="content.image"
|
|
|
+ :src="content.audio"
|
|
|
+ controls
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'list'">
|
|
|
+ <!-- 列表 -->
|
|
|
+ <CommonCategoryListBlock
|
|
|
+ v-if="currentCommonCategoryDefine"
|
|
|
+ :currentCommonCategoryDefine="{
|
|
|
+ title: '',
|
|
|
+ name: 'default',
|
|
|
+ content: tabRenderDefines[tabCurrentId].define
|
|
|
+ }"
|
|
|
+ :currentCommonCategoryContentDefine="tabRenderDefines[tabCurrentId].define"
|
|
|
+ :pageQuerys="pageQuerys"
|
|
|
+ :parentData="content"
|
|
|
+ :hasPadding="false"
|
|
|
+ @error="errorMessage = $event"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'rich'">
|
|
|
+ <!-- 富文本 -->
|
|
|
+ <view class="d-flex flex-col mt-3 mb-2">
|
|
|
+ <Parse :content="(content[tabRenderDefines[tabCurrentId].key] as string)" />
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="tabRenderDefines[tabCurrentId].type === 'nestCategory'">
|
|
|
+ <!-- 嵌套分类 -->
|
|
|
+ <CommonCategoryBlocks :categoryDefine="tabRenderDefines[tabCurrentId].categoryDefine" />
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <CommonCategoryDetailContentBlocks
|
|
|
+ :define="tabRenderDefines[tabCurrentId]"
|
|
|
+ :content="content"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ <template #titleEnd="{ content }">
|
|
|
+ <Tag
|
|
|
+ v-if="content.levelText"
|
|
|
+ :text="StringUtils.cutString(content.levelText as string, 4)"
|
|
|
+ size="small" scheme="light" type="primary"
|
|
|
+ class="flex-shrink-0"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template #titleExtra="{ content }">
|
|
|
+ <view class="d-flex flex-col">
|
|
|
+ <IntroBlock small :descItems="descItems" />
|
|
|
+ <CommonCategoryDetailIntroBlocks
|
|
|
+ :introBlocks="currentCommonCategoryContentDefine?.props.introBlocks"
|
|
|
+ :content="content"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ </DetailTabPage>
|
|
|
+ </FlexCol>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, ref, watch } from 'vue';
|
|
|
+import LoadingPage from '@/components/display/loading/LoadingPage.vue';
|
|
|
+import FlexCol from '@/components/layout/FlexCol.vue';
|
|
|
+import Result from '@/components/feedback/Result.vue';
|
|
|
+import Button from '@/components/basic/Button.vue';
|
|
|
+import { CommonCategoryListTabNestCategoryDataToContent, type IHomeCommonCategoryDefine, type IHomeCommonCategoryListTabNestCategoryItemDefine } from './CommonCategoryDefine';
|
|
|
+import { injectCommonCategory } from './CommonCategoryGlobalLoader';
|
|
|
+import { doLoadDynamicCategoryDataMergeTypeGetColumns } from './CommonCategoryDynamicData';
|
|
|
+import { formatError, StringUtils, waitTimeOut } from '@imengyu/imengyu-utils';
|
|
|
+import type { IHomeCommonCategoryDetailDefine, IHomeCommonCategoryDetailTabItemDefine } from './defines/Details';
|
|
|
+import type { DetailTabPageProps, DetailTabPageTabsArray } from '../common/DetailTabPage';
|
|
|
+import type { CategoryDefine } from './CommonCategoryBlocks';
|
|
|
+import DetailTabPage from '../common/DetailTabPage.vue';
|
|
|
+import IntroBlock from '../common/IntroBlock.vue';
|
|
|
+import CommonCategoryDetailIntroBlocks from './CommonCategoryDetailIntroBlocks.vue';
|
|
|
+import Tag from '@/components/display/Tag.vue';
|
|
|
+import CommonCategoryBlocks from './CommonCategoryBlocks.vue';
|
|
|
+import Parse from '@/components/display/parse/Parse.vue';
|
|
|
+import CommonContent from '@/api/CommonContent';
|
|
|
+import CommonCategoryDetailContentBlocks from './CommonCategoryDetailContentBlocks.vue';
|
|
|
+import ImageGrid from '@/pages/parts/ImageGrid.vue';
|
|
|
+import CommonCategoryListBlock from './CommonCategoryListBlock.vue';
|
|
|
+
|
|
|
+export interface CommonCategoryDetailProps extends DetailTabPageProps {
|
|
|
+ /**
|
|
|
+ * 简介块描述项
|
|
|
+ */
|
|
|
+ introBlockDescs?: {
|
|
|
+ label: string;
|
|
|
+ key: string;
|
|
|
+ }[];
|
|
|
+ /**
|
|
|
+ * 简介下方块
|
|
|
+ */
|
|
|
+ introBlocks?: CommonCategoryDetailIntroBlocksDesc[];
|
|
|
+}
|
|
|
+export interface CommonCategoryDetailIntroBlocksDesc {
|
|
|
+ label?: string;
|
|
|
+ type: string;
|
|
|
+ key: string;
|
|
|
+}
|
|
|
+export type RenderTabDefine = IHomeCommonCategoryDetailTabItemDefine & {
|
|
|
+ id: number;
|
|
|
+ categoryDefine?: CategoryDefine[];
|
|
|
+};
|
|
|
+
|
|
|
+const pageRef = ref();
|
|
|
+const props = defineProps({
|
|
|
+ pageConfigName: {
|
|
|
+ type: String,
|
|
|
+ },
|
|
|
+ pageQuerys: {
|
|
|
+ type: Object as () => Record<string, string|number|number[]|undefined>,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+const loadState = ref(false);
|
|
|
+const errorMessage = ref('');
|
|
|
+const currentCommonCategoryDefine = ref<IHomeCommonCategoryDefine['page'][0]>();
|
|
|
+const currentCommonCategoryContentDefine = ref<IHomeCommonCategoryDetailDefine>();
|
|
|
+const commonCategory = injectCommonCategory();
|
|
|
+
|
|
|
+const tabDefines = computed(() => currentCommonCategoryContentDefine.value?.props.tabs || []);
|
|
|
+const tabRenderDefines = computed(() => {
|
|
|
+ const result = {} as Record<number, RenderTabDefine>;
|
|
|
+ try {
|
|
|
+ tabDefines.value.forEach((item, i) => {
|
|
|
+ const renderItem : RenderTabDefine = {
|
|
|
+ ...item,
|
|
|
+ id: i,
|
|
|
+ };
|
|
|
+ function loadNestCategoryData(items: IHomeCommonCategoryListTabNestCategoryItemDefine[]) {
|
|
|
+ return items
|
|
|
+ .filter((item) => item.visible !== false)
|
|
|
+ .map((item) => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ showTitle: item.showTitle !== false,
|
|
|
+ title: item.text,
|
|
|
+ content: CommonCategoryListTabNestCategoryDataToContent(
|
|
|
+ item.data, item
|
|
|
+ ),
|
|
|
+ type: item.type as CategoryDefine['type'],
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ switch (item.type) {
|
|
|
+ case 'nestCategory':
|
|
|
+ renderItem.categoryDefine = loadNestCategoryData(item.categorys);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ result[i] = renderItem;
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ errorMessage.value = formatError(error);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+});
|
|
|
+const tabRenderDefinesArray = computed(() => {
|
|
|
+ return Object.values(tabRenderDefines.value);
|
|
|
+});
|
|
|
+
|
|
|
+async function loadPageConfig() {
|
|
|
+ if (!props.pageConfigName) {
|
|
|
+ errorMessage.value = '配置有误';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentCommonCategoryDefine.value = commonCategory.value.page
|
|
|
+ .find((item) => item.name === props.pageConfigName);
|
|
|
+ if (!currentCommonCategoryDefine.value) {
|
|
|
+ errorMessage.value = '未找到指定的分类配置:' + props.pageConfigName;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (currentCommonCategoryDefine.value.content.type !== 'Details') {
|
|
|
+ errorMessage.value = '分类配置:' + props.pageConfigName + ' 不是详情类型';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentCommonCategoryContentDefine.value =
|
|
|
+ currentCommonCategoryDefine.value.content as IHomeCommonCategoryDetailDefine;
|
|
|
+ uni.setNavigationBarTitle({
|
|
|
+ title: currentCommonCategoryDefine.value?.title || '',
|
|
|
+ })
|
|
|
+
|
|
|
+ await waitTimeOut(100);
|
|
|
+
|
|
|
+ try {
|
|
|
+ //特殊处理
|
|
|
+ let hasNestCategory = false;
|
|
|
+ for (const [_, tab] of Object.entries(tabDefines.value)) {
|
|
|
+ if (tab.type === 'nestCategory') {
|
|
|
+ tab.categorys = await doLoadDynamicCategoryDataMergeTypeGetColumns(tab.categorys)
|
|
|
+ hasNestCategory = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (hasNestCategory)
|
|
|
+ await waitTimeOut(100);
|
|
|
+ loadState.value = true;
|
|
|
+
|
|
|
+ await waitTimeOut(100);
|
|
|
+
|
|
|
+ pageRef.value?.load(props.pageQuerys);
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ loadState.value = false;
|
|
|
+ errorMessage.value = formatError(error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(() => props.pageConfigName, loadPageConfig);
|
|
|
+onMounted(loadPageConfig);
|
|
|
+
|
|
|
+const content = ref<any>();
|
|
|
+const descItems = computed(() => (
|
|
|
+ currentCommonCategoryContentDefine.value?.props.introBlockDescs || [])
|
|
|
+ .map((item) => ({
|
|
|
+ ...item,
|
|
|
+ value: content.value?.[item.key] || '',
|
|
|
+ }))
|
|
|
+);
|
|
|
+
|
|
|
+function onLoaded(d: any) {
|
|
|
+ content.value = d;
|
|
|
+}
|
|
|
+async function load(id: number, tabsArray: DetailTabPageTabsArray) {
|
|
|
+ const d = await CommonContent.getContentDetail(
|
|
|
+ id,
|
|
|
+ undefined,
|
|
|
+ props.pageQuerys.modelId && Number(props.pageQuerys.modelId) > 0 ? Number(props.pageQuerys.modelId) : undefined
|
|
|
+ );
|
|
|
+ for (const tab of tabRenderDefinesArray.value) {
|
|
|
+ const v = d[tab.key];
|
|
|
+ let check = true
|
|
|
+ if (!v)
|
|
|
+ check = false;
|
|
|
+ else if (Array.isArray(v))
|
|
|
+ check = (v as any[]).length > 0;
|
|
|
+
|
|
|
+ tabsArray.getTabById(tab.id)!.visible = tab.visible !== false && check;
|
|
|
+ }
|
|
|
+ return d;
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ getPageShareData() {
|
|
|
+ return pageRef.value?.getPageShareData() || {};
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|