快乐的梦鱼 5 天之前
父节点
当前提交
5a7a683afe

+ 2 - 0
src/components/display/parse/ParseNodeRender.vue

@@ -45,6 +45,7 @@
   />
   
   <!-- 音频 -->
+  <!-- #ifndef H5 -->
   <audio 
     v-else-if="node.tag === 'audio'" 
     :id="node.attrs?.id" 
@@ -57,6 +58,7 @@
     :poster="node.attrs?.poster || ''" 
     :src="node.attrs?.src as string || ''" 
   />
+  <!-- #endif -->
   
   <!-- 嵌入小程序内容 -->
   <view v-else-if="node.tag === 'inject-mp'">

+ 4 - 0
src/manifest.json

@@ -85,6 +85,10 @@
 	},
 	"vueVersion": "3",
 	"h5": {
+    "router": {
+      "base": "./",
+      "mode": "hash"
+    },
 		"sdkConfigs": {
 			"maps": {
 				"qqmap": {

+ 0 - 7
src/pages.json

@@ -21,13 +21,6 @@
       }
     },
     {
-      "path": "pages/introduction/news",
-      "style": {
-        "navigationBarTitleText": "闽南新鲜事",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
       "path": "pages/introduction/custom/list",
       "style": {
         "navigationBarTitleText": "闽南民俗",

+ 6 - 0
src/pages/article/common/CommonContent.ts

@@ -74,6 +74,12 @@ const resolveCommonContentData = {
     })
     return item;
   },
+  'form': (item: GetContentListItem[]) => {
+    item.forEach(p => {
+      p.desc = `来源:${p.from || '暂无'}` + ' ' + (p.desc || '');
+    })
+    return item;
+  },
   'ich': (item: GetContentListItem[]) => {
     item.forEach(it => {
       it.bottomTags = (it.bottomTags as string[] || []).concat([

+ 1 - 1
src/pages/article/common/DetailTabPage.vue

@@ -51,7 +51,7 @@
                 :content="loader.content.value.content"
               />
               <text v-if="emptyContent">暂无简介</text>
-              <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">本文来自 {{ loader.content.value.from }}</text>
+              <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">以上内容摘自 {{ loader.content.value.from }}</text>
             </template>
             <!-- 图片 -->
             <template v-else-if="tabCurrentId == TAB_ID_IMAGES">

+ 2 - 0
src/pages/article/data/CommonCategoryDynamicData.ts

@@ -27,6 +27,7 @@ import type { IHomeCommonCategoryListTabListDropdownDefine } from "./CommonCateg
 import DiscussContent from "@/api/research/DiscussContent";
 import ResultContent from "@/api/research/ResultContent";
 import { CommonCategorDynamicDropDownValuesToParams } from "./data-defines/Dropdown";
+import NewsIndexContent from "@/api/news/NewsIndexContent";
 
 export * from './data-defines/Category';
 export * from './data-defines/Dropdown';
@@ -119,6 +120,7 @@ export const SerializedApiMap : Record<string, CommonContentApi> = {
   TeamsContent,
   DiscussContent,
   ResultContent,
+  NewsIndexContent,
 };
 
 /**

+ 2 - 2
src/pages/article/data/CommonCategoryGlobalLoader.ts

@@ -26,10 +26,10 @@ export function useCommonCategoryGlobalLoader() {
   async function loadCommonCategory() {
     uni.showLoading({ title: '加载中' });
     try {
-      if (uni.getSystemInfoSync().platform === 'devtools') {
+      /* if (uni.getSystemInfoSync().platform === 'devtools') {
         commonCategoryData.value = DefaultCofig as IHomeCommonCategoryDefine;
         return;
-      }
+      } */
       const category = (await CommonCategoryApi.getConfig()) as any as IHomeCommonCategoryDefine;
       if (category)
         commonCategoryData.value = category;

+ 187 - 4
src/pages/article/data/editor/MiniProgramEditor.vue

@@ -74,10 +74,51 @@
         </a-form>
       </a-modal>
 
+      <!-- 添加页面弹框 -->
+      <a-modal
+        v-model:open="addPageModalVisible"
+        title="添加页面"
+        ok-text="添加"
+        cancel-text="取消"
+        :confirm-loading="addPageLoading"
+        @ok="confirmAddPage"
+      >
+        <a-form layout="vertical" class="add-page-form">
+          <a-form-item label="key(页面唯一标识)" required>
+            <a-input
+              v-model:value="addPageKey"
+              placeholder="如 home、list_1"
+              allow-clear
+            />
+          </a-form-item>
+          <a-form-item label="title(页面标题)" required>
+            <a-input
+              v-model:value="addPageTitle"
+              placeholder="如 首页、列表页"
+              allow-clear
+            />
+          </a-form-item>
+          <a-form-item v-if="addPageTemplate" label="模板">
+            <span>{{ addPageTemplate === 'Home' ? 'Home' : 'CommonList' }}</span>
+          </a-form-item>
+        </a-form>
+      </a-modal>
+
       <div class="editor-body">
         <!-- 左一:页面列表 -->
         <div class="panel panel-pages">
-          <div class="panel-title">页面列表</div>
+          <div class="panel-title panel-title-with-action">
+            <span>页面列表</span>
+            <a-dropdown size="small">
+              <PlusOutlined />
+              <template #overlay>
+                <a-menu @click="onAddPageMenuClick">
+                  <a-menu-item key="Home">Home</a-menu-item>
+                  <a-menu-item key="CommonList">CommonList</a-menu-item>
+                </a-menu>
+              </template>
+            </a-dropdown>
+          </div>
           <a-list
             :data-source="currentEditorJson?.page ?? []"
             size="small"
@@ -86,12 +127,22 @@
             <template #renderItem="{ item }">
               <a-list-item
                 :class="{ 'page-item-active': selectedPage?.name === item.name }"
+                class="page-list-item"
                 @click="selectedPage = item"
               >
                 <a-list-item-meta>
                   <template #title>{{ item.title || item.name }}</template>
                   <template #description>{{ item.name }} · {{ item.content?.type }}</template>
                 </a-list-item-meta>
+                <a-button
+                  type="text"
+                  danger
+                  size="small"
+                  class="page-item-delete"
+                  @click.stop="confirmDeletePage(item)"
+                >
+                  <DeleteOutlined />
+                </a-button>
               </a-list-item>
             </template>
           </a-list>
@@ -99,7 +150,10 @@
 
         <!-- 左二:属性编辑器 -->
         <div class="panel panel-props">
-          <div class="panel-title">属性编辑</div>
+          <div class="panel-title panel-title-with-action">
+            属性编辑
+            <a-button type="primary" size="small" @click="($refs.previewRef as any)?.refresh()">刷新</a-button>
+          </div>
           <div v-if="!selectedPage" class="panel-empty">请选择页面</div>
           <PropsEditorTree
             v-else
@@ -112,6 +166,7 @@
           <div class="panel-title">小程序预览</div>
           <div class="preview-wrap">
             <EditorPreview
+              ref="previewRef"
               :editor-json="currentEditorJson"
               :selected-page="selectedPage"
             />
@@ -125,9 +180,9 @@
 <script setup lang="ts">
 import { computed, onMounted, provide, ref } from 'vue';
 import { ObjectUtils } from '@imengyu/imengyu-utils';
-import { DownOutlined, DownloadOutlined, InfoCircleFilled } from '@ant-design/icons-vue';
+import { DownOutlined, DownloadOutlined, InfoCircleFilled, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import { message, Modal } from 'ant-design-vue';
-import type { IHomeCommonCategoryDefine } from '../CommonCategoryDefine';
+import type { IHomeCommonCategoryDefine, IHomeCommonCategoryListDefine, IHomeCommonCategoryHomeDefine } from '../CommonCategoryDefine';
 import DefaultEditorJson from '../DefaultCategory.json';
 import PropsEditorTree from './subpart/PropsEditorTree.vue';
 import EditorPreview from './subpart/EditorPreview.vue';
@@ -159,8 +214,117 @@ const saveAsModalVisible = ref(false);
 const saveAsVersionName = ref('');
 const saveAsLoading = ref(false);
 
+const addPageModalVisible = ref(false);
+const addPageTemplate = ref<'Home' | 'CommonList' | null>(null);
+const addPageKey = ref('');
+const addPageTitle = ref('');
+const addPageLoading = ref(false);
+
 provide('pageList', pageList);
 
+/** 生成新页面唯一 name */
+function getUniquePageName(prefix: string): string {
+  const pages = currentEditorJson.value?.page ?? [];
+  const names = new Set(pages.map(p => p.name));
+  let name = prefix;
+  let n = 1;
+  while (names.has(name)) {
+    name = `${prefix}_${n}`;
+    n++;
+  }
+  return name;
+}
+
+/** Home 模板 */
+function createHomePageTemplate(): IHomeCommonCategoryHomeDefine {
+  return {
+    type: 'Home',
+    props: {
+      title: '首页',
+      subTitle: '',
+      homeBanner: '',
+      homeButtons: [],
+      categorys: [],
+    },
+  };
+}
+
+/** CommonList 模板 */
+function createCommonListPageTemplate(): IHomeCommonCategoryListDefine {
+  return {
+    type: 'CommonList',
+    props: {
+      showTab: true,
+      showSearch: true,
+      showTotal: true,
+      tabs: [],
+    },
+  };
+}
+
+function onAddPageMenuClick(e: { key: string }) {
+  const template = e.key as 'Home' | 'CommonList';
+  addPageTemplate.value = template;
+  const prefix = template === 'Home' ? 'home' : 'list';
+  addPageKey.value = getUniquePageName(prefix);
+  addPageTitle.value = template === 'Home' ? '首页' : '列表页';
+  addPageModalVisible.value = true;
+}
+
+function confirmAddPage(): Promise<void> | void {
+  const key = addPageKey.value?.trim();
+  const title = addPageTitle.value?.trim();
+  if (!key) {
+    message.warning('请输入 key');
+    return Promise.reject();
+  }
+  if (!title) {
+    message.warning('请输入 title');
+    return Promise.reject();
+  }
+  const pages = currentEditorJson.value?.page ?? [];
+  const exists = pages.some(p => p.name === key);
+  if (exists) {
+    message.warning(`key「${key}」已存在,请使用其他 key`);
+    return Promise.reject();
+  }
+  const template = addPageTemplate.value;
+  if (!template) return Promise.reject();
+  addPageLoading.value = true;
+  const content = template === 'Home'
+    ? createHomePageTemplate()
+    : createCommonListPageTemplate();
+  currentEditorJson.value = {
+    ...currentEditorJson.value,
+    page: [...pages, { name: key, title, content }],
+  };
+  const added = currentEditorJson.value.page[currentEditorJson.value.page.length - 1];
+  selectedPage.value = added;
+  addPageModalVisible.value = false;
+  addPageLoading.value = false;
+  message.success(`已添加页面 ${title} (${key})`);
+}
+
+function confirmDeletePage(page: IHomeCommonCategoryDefine['page'][0]) {
+  Modal.confirm({
+    title: '确认删除',
+    content: `确定要删除页面「${page.title || page.name}」吗?删除后不可恢复。`,
+    okText: '删除',
+    okType: 'danger',
+    cancelText: '取消',
+    onOk() {
+      const pages = currentEditorJson.value?.page ?? [];
+      const index = pages.findIndex(p => p.name === page.name);
+      if (index === -1) return;
+      const next = pages.filter(p => p.name !== page.name);
+      currentEditorJson.value = { ...currentEditorJson.value, page: next };
+      if (selectedPage.value?.name === page.name)
+        selectedPage.value = next[0] ?? null;
+      message.success('已删除页面');
+    },
+  });
+}
+
 async function loadEditorJson(selectDefault = false) {
   try {
     //加载基础配置
@@ -334,6 +498,12 @@ onMounted(async () => {
   border-bottom: 1px solid #eee;
   flex-shrink: 0;
 }
+.panel-title-with-action {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
 .panel-pages {
   width: 220px;
   flex-shrink: 0;
@@ -365,6 +535,19 @@ onMounted(async () => {
   cursor: pointer;
   padding: 8px 12px;
 }
+.page-list-item :deep(.ant-list-item-meta) {
+  flex: 1;
+  min-width: 0;
+}
+.page-list-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.page-item-delete {
+  flex-shrink: 0;
+  padding: 0 4px;
+}
 .page-item-active {
   background: #e6f7ff;
 }

+ 1 - 0
src/pages/article/data/editor/components/DataSolveEditor.vue

@@ -24,6 +24,7 @@ const dataSolveOptions = [
   { value: 'ich', label: '传承相关' },
   { value: 'common', label: '通用' },
   { value: 'date', label: '日期' },
+  { value: 'form', label: '来源' },
 ];
 
 const localValue = ref<string[]>(props.modelValue || []);

+ 10 - 7
src/pages/article/data/editor/components/LinkPathEditor.vue

@@ -6,8 +6,7 @@
           <div class="path-selector">
             <a-select 
               v-model:value="pathType" 
-              style="width: 150px; margin-right: 8px" 
-              @change="handlePathTypeChange"
+              style="width: 150px; margin-right: 8px"
             >
               <a-select-option v-if="!props.noParams" value="dynamic-list">动态页面列表</a-select-option>
               <a-select-option value="internal">程序内置页面</a-select-option>
@@ -110,10 +109,13 @@ const internalPages = computed(() => {
   }));
 });
 
+let noUpdate = false;
+
 // 监听参数变化
 watch(
   () => props.modelValue,
   (newValue) => {
+    noUpdate = true;
     if (Array.isArray(newValue)) {
       linkPath.value = newValue[0] || '';
       localParams.value = newValue[1] as Record<string, string> || {};
@@ -129,6 +131,9 @@ watch(
     } else {
       pathType.value = 'custom';
     }
+    setTimeout(() => {
+      noUpdate = false;
+    }, 200);
   },
   { deep: true, immediate: true }
 );
@@ -140,13 +145,11 @@ watch(
   { deep: true }
 );
 
-// 处理路径类型变化
-const handlePathTypeChange = () => {
-  updateLink();
-};
-
 // 更新链接
 const updateLink = () => {
+  if (noUpdate) {
+    return;
+  }
   if (props.noParams) {
     emit('update:modelValue', linkPath.value);
     return;

+ 11 - 2
src/pages/article/data/editor/subpart/EditorPreview.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="editor-preview">
-    <div v-if="!selectedPage" class="preview-empty">请选择页面进行预览</div>
+    <div v-if="!showPreview ||!selectedPage" class="preview-empty">请选择页面进行预览</div>
     <template v-else>
       <component
         :is="previewComponent"
@@ -16,7 +16,7 @@ import { COMMON_CATEGORY_KEY } from '../../CommonCategoryGlobalLoader';
 import type { IHomeCommonCategoryDefine } from '../../CommonCategoryDefine';
 import HomePage from '@/pages/home/index.vue';
 import CommonCategoryList from '@/pages/article/data/CommonCategoryList.vue';
-import { ObjectUtils } from '@imengyu/imengyu-utils';
+import { ObjectUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 
 const props = defineProps<{
   editorJson: IHomeCommonCategoryDefine;
@@ -25,6 +25,7 @@ const props = defineProps<{
 
 // 注入当前编辑的 JSON,供预览中的 home/list 通过 injectCommonCategory() 读取
 const editorJsonRef = ref(props.editorJson);
+const showPreview = ref(true);
 
 provide(COMMON_CATEGORY_KEY, editorJsonRef);
 
@@ -60,6 +61,14 @@ const previewProps = computed(() => {
   }
   return {};
 });
+
+defineExpose({
+  refresh: async () => {
+    showPreview.value = false;
+    await waitTimeOut(100);
+    showPreview.value = true;
+  },
+});
 </script>
 
 <style scoped>

+ 1 - 1
src/pages/article/details.vue

@@ -44,7 +44,7 @@
               :content="loader.content.value.content"
             />
             <text v-if="emptyContent">暂无简介</text>
-            <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">本文来自 {{ loader.content.value.from }}</text>
+            <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">以上内容摘自 {{ loader.content.value.from }}</text>
           </view>
           
           <!-- 推荐 -->

+ 2 - 3
src/pages/article/index.vue

@@ -8,7 +8,7 @@
       align="left" 
       backgroundColor="background.page" 
     />
-    <News />
+    <CommonCategoryList pageConfigName="news" />
     <Height :height="140" />
     <Tabbar :current="1" />
   </FlexCol>
@@ -17,12 +17,11 @@
 <script setup lang="ts">
 import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
 import Tabbar from '@/common/components/tabs/Tabbar.vue';
-import News from '../introduction/news.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import NavBar from '@/components/nav/NavBar.vue';
 import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
-import Image from '@/components/basic/Image.vue';
 import Height from '@/components/layout/space/Height.vue';
+import CommonCategoryList from './data/CommonCategoryList.vue';
 
 onShareTimeline(() => {
   return {};