| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- <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"
- :noInternalTabs="true"
- :showHead="currentCommonCategoryContentDefine?.props.showHead"
- :showDeadBox="currentCommonCategoryContentDefine?.props.showDeadBox"
- :load="load"
- :tabs="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 ">
- {{ appConfiguration?.articleMark }}
- {{ content.from }}
- </text>
- </template>
- <template v-else-if="tabRenderDefines[tabCurrentId]?.type === 'images'">
- <!-- 图片 -->
- <view v-if="tabRenderDefines[tabCurrentId].prefix" class="d-flex flex-row justify-center align-center mt-2 mb-2">
- <text class="size-s font-bold color-text-content">{{ tabRenderDefines[tabCurrentId].prefix }}</text>
- </view>
- <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"
- :hasBg="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-if="tabRenderDefines[tabCurrentId]?.key === 'vr'">
- <!-- VR参观 -->
- <view class="d-flex flex-row justify-center p-5">
- <Button @click="handleGoToVr(content.vr as string)">
- <text class="iconfont icon-go"></text>
- 点击参观
- </Button>
- </view>
- </template>
- <template v-else>
- <CommonCategoryDetailContentBlocks
- :define="tabRenderDefines[tabCurrentId]"
- :content="content"
- />
- </template>
- </template>
- <template #titleEnd="{ content }">
- <Tag
- v-if="currentCommonCategoryContentDefine?.props.showTag && 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 { navTo } from '@/components/utils/PageAction';
- import { injectAppConfiguration } from '@/api/system/useAppConfiguration';
- import { injectCommonCategory } from './CommonCategoryGlobalLoader';
- import { doLoadDynamicCategoryDataMergeTypeGetColumns, doLoadDynamicDetailData, doSerializeInternalVar, type IHomeCommonCategoryDynamicData } from './CommonCategoryDynamicData';
- import { formatError, StringUtils, waitTimeOut } from '@imengyu/imengyu-utils';
- import { getIsDevtoolsPlatform } from '@/common/utils/MpVersions';
- import type { IHomeCommonCategoryDefine, IHomeCommonCategoryListTabListDataSolve, IHomeCommonCategoryListTabNestCategoryItemDefine } from './CommonCategoryDefine';
- import type { IHomeCommonCategoryDetailDefine, IHomeCommonCategoryDetailTabItemDefine } from './defines/Details';
- import type { DetailTabPageProps } from '../common/DetailTabPage';
- import type { CategoryDefine } from './CommonCategoryBlocks';
- 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 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, { GetContentDetailItem, GetContentListParams } from '@/api/CommonContent';
- import CommonCategoryDetailContentBlocks from './CommonCategoryDetailContentBlocks.vue';
- import ImageGrid from '@/pages/parts/ImageGrid.vue';
- import CommonCategoryListBlock from './CommonCategoryListBlock.vue';
- import { resolveCommonContentSolveProps } from '../common/CommonContent';
- import { getDynamicScript, isDynamicScript } from './CommonCategoryScript';
- export interface CommonCategoryDetailProps extends DetailTabPageProps {
- /**
- * 简介块描述项
- */
- introBlockDescs?: {
- label: string;
- key: string;
- map?: Record<string|number, string>;
- visibleVia?: string;
- }[];
- /**
- * 简介下方块
- */
- introBlocks?: CommonCategoryDetailIntroBlocksDesc[];
- /**
- * 是否显示级别标签
- */
- showTag?: boolean;
- /**
- * 是否显示死亡框
- */
- showDeadBox?: boolean;
- /**
- * 自定义数据接口
- */
- data?: IHomeCommonCategoryDynamicData;
- /**
- * 内容处理
- */
- dataSolve?: IHomeCommonCategoryListTabListDataSolve[];
- }
- export interface CommonCategoryDetailIntroBlocksDesc {
- type: string;
- props?: Record<string, any>;
- }
- 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 appConfiguration = injectAppConfiguration();
- const loadState = ref(false);
- const errorMessage = ref('');
- const currentCommonCategoryDefine = ref<IHomeCommonCategoryDefine['page'][0]>();
- const currentCommonCategoryContentDefine = ref<IHomeCommonCategoryDetailDefine>();
- const commonCategory = injectCommonCategory();
- const tabDefines = ref<IHomeCommonCategoryDetailTabItemDefine[]>([]);
- const tabVisibles = ref<boolean[]>([]);
- const tabRenderDefines = computed(() => {
- const result = {} as Record<number, RenderTabDefine>;
- try {
- tabDefines.value.forEach((item, i) => {
- const renderItem : RenderTabDefine = {
- ...item,
- visible: tabVisibles.value[i],
- 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: item.data,
- 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) {
- await waitTimeOut(1000);
- 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 || '',
- })
- const tabs = currentCommonCategoryContentDefine.value?.props.tabs || [];
- await waitTimeOut(50);
-
- try {
- //特殊处理
- let hasNestCategory = false;
- for (const tab of tabs) {
- if (tab.type === 'nestCategory') {
- tab.categorys = await doLoadDynamicCategoryDataMergeTypeGetColumns(tab.categorys)
- hasNestCategory = true;
- }
- }
- if (hasNestCategory)
- await waitTimeOut(50);
- loadState.value = true;
- await waitTimeOut(50);
- const content = (await pageRef.value?.load(props.pageQuerys)) as GetContentDetailItem;
- //Tab标签动态处理
- function loadByContentData(text: string) {
- let result = text;
- const keys = text.split(':');
- if (keys.length >= 2) {
- switch (keys[1]) {
- case 'byContentType':
- switch(content.type) {
- default:
- case GetContentListParams.TYPE_ARTICLE: result = '相关文章'; break;
- case GetContentListParams.TYPE_VIDEO: result = '视频'; break;
- }
- break;
- case 'bySubListType': {
- const subListKey = keys[2];
- if (subListKey) {
- const list = content?.[subListKey];
- if (Array.isArray(list) && list.length > 0) {
- switch(list[0].type) {
- default:
- case GetContentListParams.TYPE_ARTICLE: result = '相关文章'; break;
- case GetContentListParams.TYPE_ARCHIVE: result = '相关文档'; break;
- case GetContentListParams.TYPE_VIDEO: result = '视频'; break;
- case GetContentListParams.TYPE_IMAGE: result = '相册'; break;
- }
- } else {
- result = '相关';
- }
- }
- break;
- }
- case 'expression': {
- result = getDynamicScript().execute(keys[2], {
- main: content,
- customData: {
- customTabNameIdMap: currentCommonCategoryContentDefine.value?.props.customTabNameIdMap || {},
- },
- }) as any;
- break;
- }
- case 'idMap': {
- if (currentCommonCategoryContentDefine.value?.props.customTabNameIdMap) {
- result = currentCommonCategoryContentDefine.value.props.customTabNameIdMap[content.id];
- }
- }
- }
- }
- return result;
- }
- for (const tab of tabs) {
- if (tab.text.startsWith('dynamic')) {
- tab.text = loadByContentData(tab.text);
- }
- }
- await loadTabVisible(tabs, content);
- } catch (error) {
- console.error(error);
- loadState.value = false;
- errorMessage.value = formatError(error);
- }
- tabDefines.value = tabs;
- }
- watch(() => props.pageConfigName, loadPageConfig);
- onMounted(loadPageConfig);
- const content = ref<any>();
- const descItems = computed(() => (
- currentCommonCategoryContentDefine.value?.props.introBlockDescs || [])
- .map((item) => {
- let value = '';
- if (item.key.startsWith('expression:')) {
- value = getDynamicScript().execute(item.key.substring(11), {
- main: content.value,
- }) as any;
- }
- else
- value = content.value?.[item.key] || '';
- if (item.map)
- value = item.map[value] || '';
- return {
- ...item,
- value,
- }
- })
- .filter((item) => Boolean(item.value))
- .filter((item) => !item.visibleVia || item.visibleVia === 'auto' || getDynamicScript().execute(item.visibleVia, {
- main: item,
- }) as any)
- );
- function onLoaded(d: any) {
- content.value = d;
- }
- async function loadTabVisible(tabs: IHomeCommonCategoryDetailTabItemDefine[], d: any) {
- if (!d)
- return;
- for (let i = 0; i < tabs.length; i++) {
- const tab = tabs[i];
- const v = d[tab.key];
- let check = true
- let visibleCheckBy = 'auto';
- let visibleCheckKeys = [] as string[];
- let visibleCheckExpression = '';
- if (tab.visibleVia && tab.visibleVia.includes(':')) {
- visibleCheckKeys = tab.visibleVia.split(':');
- visibleCheckBy = visibleCheckKeys[0];
- } else if (tab.visibleVia && isDynamicScript(tab.visibleVia)) {
- visibleCheckExpression = tab.visibleVia;
- visibleCheckBy = 'expression';
- }
- switch (visibleCheckBy) {
- default:
- case 'auto': {
- switch (tab.type) {
- case 'intro': check = true; break;
- case 'audio': check = Boolean(d.audio); break;
- case 'video': check = Boolean(d.video); break;
- case 'images': check = Boolean(d.images) && (d.images as string[]).length > 1; break;
- case 'map': check = Boolean(d.latitude) && Boolean(d.longitude); break;
- default:
- if (!v)
- check = false;
- else if (Array.isArray(v))
- check = (v as any[]).length > 0;
- break;
- }
- break;
- }
- case 'expression': {
- check = getDynamicScript().execute(visibleCheckExpression, {
- main: d,
- }) as any;
- break;
- }
- case 'idMap': {
- const idMap = currentCommonCategoryContentDefine.value?.props.customTabVisibleViaIdMap;
- if (idMap && visibleCheckKeys.length >= 2)
- check = idMap[`${d.id}:${visibleCheckKeys[1]}`];
- break;
- }
- }
- tabVisibles.value[i] = tab.visible !== false && check;
- }
- }
- async function load(id: number) {
- if (isNaN(id) || id <= 0)
- throw new Error("请输入ID。如果正在测试,可在后台复制ID");
- if (currentCommonCategoryContentDefine.value?.props.data) {
- return await doLoadDynamicDetailData(
- currentCommonCategoryContentDefine.value?.props.data,
- id,
- props.pageQuerys.modelId && Number(props.pageQuerys.modelId) > 0 ? Number(props.pageQuerys.modelId) : undefined
- );
- }
- let res = await CommonContent.getContentDetail(
- id,
- undefined,
- props.pageQuerys.modelId && Number(props.pageQuerys.modelId) > 0 ? Number(props.pageQuerys.modelId) : undefined
- );
- if (currentCommonCategoryContentDefine.value?.props.dataSolve) {
- res = resolveCommonContentSolveProps([ res ], currentCommonCategoryContentDefine.value.props.dataSolve)[0];
- }
- return res;
- }
- function handleGoToVr(vr: string) {
- navTo('/pages/article/web/ewebview', { url: vr })
- }
- defineExpose({
- getPageShareData() {
- return pageRef.value?.getPageShareData() || {};
- }
- })
- </script>
|