快乐的梦鱼 před 1 týdnem
rodič
revize
b280c05d23

+ 9 - 0
src/pages/article/common/CommonListPage.ts

@@ -0,0 +1,9 @@
+export type CommonListPageItemType = 'image-large-2'|'image-large'|'article-common'|'article-character'|'simple-text';
+
+export const CommonListPageItemTypeOptions = [
+  { value: 'image-large-2', label: '大图2' },
+  { value: 'image-large', label: '大图' },
+  { value: 'article-common', label: '普通文章' },
+  { value: 'article-character', label: '人物文章' },
+  { value: 'simple-text', label: '简单文本' },
+];

+ 2 - 1
src/pages/article/common/CommonListPage.vue

@@ -148,6 +148,7 @@ import AppCofig from '@/common/config/AppCofig';
 import Tabs from '@/components/nav/Tabs.vue';
 import SearchBar from '@/components/form/SearchBar.vue';
 import { resolveCommonContentGetPageDetailUrlAuto } from './CommonContent';
+import type { CommonListPageItemType } from './CommonListPage';
 
 function getImage(item: any) {
   return item.thumbnail || item.image || AppCofig.defaultImage
@@ -224,7 +225,7 @@ export interface CommonListPageProps {
    * 列表项类型
    * @default 'article-common'
    */
-  itemType?: 'image-large-2'|'image-large'|'article-common'|'article-character'|'simple-text'
+  itemType?: CommonListPageItemType
   /**
    * 分页大小
    * @default 8

+ 27 - 0
src/pages/article/data/CommonCategoryBlocks.ts

@@ -0,0 +1,27 @@
+import type { CommonContentApi } from "@/api/CommonContent";
+import type { IHomeCommonCategoryBlock, HomeCommonCategoryBlockProps } from "../common/CommonContent";
+import type { IHomeCommonCategoryListTabNestCategoryItemDefine } from "./CommonCategoryDefine";
+
+export interface CategoryDefine extends Omit<IHomeCommonCategoryListTabNestCategoryItemDefine, 'type'> {
+  title: string;
+  showTitle: boolean;
+  content: CommonContentApi|IHomeCommonCategoryBlock|HomeCommonCategoryBlockProps|null;
+  type?: 'article'|'large-image2'|'horizontal-large'|'large-image'|'large-grid2'|'small-grid2'|'simple-text'
+    |'default'
+    |'CalendarBlock'|'StatsBlock'|'MapBlock'|''
+    |undefined;
+}
+
+export const CommonCategoryBlockType : CategoryDefine['type'][] = [
+  'default',
+  'article',
+  'large-image2',
+  'horizontal-large',
+  'large-image',
+  'large-grid2',
+  'small-grid2',
+  'simple-text',
+  'CalendarBlock',
+  'StatsBlock',
+  'MapBlock',
+];

+ 1 - 9
src/pages/article/data/CommonCategoryBlocks.vue

@@ -180,15 +180,7 @@ import StatsBlock from '@/pages/blocks/StatsBlock.vue';
 import MapCategoryBlock from '@/pages/blocks/MapBlock.vue';
 import Image from '@/components/basic/Image.vue';
 import AppCofig from '@/common/config/AppCofig';
-
-export interface CategoryDefine extends Omit<IHomeCommonCategoryListTabNestCategoryItemDefine, 'type'> {
-  title: string;
-  showTitle: boolean;
-  content: CommonContentApi|IHomeCommonCategoryBlock|HomeCommonCategoryBlockProps|null;
-  type?: 'article'|'large-image2'|'horizontal-large'|'large-image'|'large-grid2'|'small-grid2'|'simple-text'
-    |'CalendarBlock'|'StatsBlock'|'MapBlock'|'CalendarBlock'
-    |undefined;
-}
+import type { CategoryDefine } from './CommonCategoryBlocks';
 
 const props = defineProps({
   /**

+ 4 - 1
src/pages/article/data/CommonCategoryList.vue

@@ -39,9 +39,10 @@ import { waitTimeOut } from '@imengyu/imengyu-utils';
 import { formatError } from '@/common/composeabe/ErrorDisplay';
 import type { SimpleDropDownPickerItem } from '@/common/components/SimpleDropDownPicker.vue';
 import type { CommonListPageProps, DropDownNames } from '../common/CommonListPage.vue';
+import type { CategoryDefine } from './CommonCategoryBlocks';
 import CommonListPage from '../common/CommonListPage.vue';
 import Result from '@/components/feedback/Result.vue';
-import CommonCategoryBlocks, { type CategoryDefine } from './CommonCategoryBlocks.vue';
+import CommonCategoryBlocks from './CommonCategoryBlocks.vue';
 import Footer from '@/components/display/Footer.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import LoadingPage from '@/components/display/loading/LoadingPage.vue';
@@ -249,6 +250,8 @@ async function loadData(
     throw new Error(`配置有误 tab:${tabSelect}`);
   if (tab.type !== 'list')
     return { list: [], total: 0 };
+  if (!tab.data) 
+    throw new Error(`配置有误 tab:${tabSelect} 没有配置列表数据`);
   const res = await doLoadDynamicListData(
     tab.data, 
     page, 

+ 3 - 3
src/pages/article/data/DefaultCategory.json

@@ -2,7 +2,7 @@
   "page": [
     {
       "name": "home",
-      "title": "",
+      "title": "首页",
       "content": {
         "type": "Home",
         "props": {
@@ -258,8 +258,8 @@
                   "data": {
                     "type": "commonContent",
                     "params": {
-                      "mainBodyColumnId": [315],
-                      "modelId": 16
+                      "mainBodyColumnId": [385],
+                      "modelId": 18
                     }
                   },
                   "morePage": "/pages/travel/fashion/list",

+ 1 - 1
src/pages/article/data/defines/List.ts

@@ -74,7 +74,7 @@ export interface IHomeCommonCategoryListTabListDefine {
   /**
    * 列表选项卡数据
    */
-  data: IHomeCommonCategoryDynamicData,
+  data?: IHomeCommonCategoryDynamicData,
   /**
    * 列表选项卡下拉选择定义
    */

+ 1 - 1
src/pages/editor/MiniProgramEditor.vue

@@ -120,7 +120,7 @@ async function saveEditorJson() {
   flex-shrink: 0;
 }
 .panel-props {
-  width: 620px;
+  width: 720px;
   flex-shrink: 0;
 }
 .panel-preview {

+ 42 - 0
src/pages/editor/components/DataSolveEditor.vue

@@ -0,0 +1,42 @@
+<template>
+  <a-select
+    v-model:value="localValue"
+    mode="multiple"
+    style="width: 100%"
+    :options="dataSolveOptions"
+    placeholder="可选"
+    @change="updateValue"
+  />
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+
+const props = defineProps<{
+  modelValue?: string[];
+}>();
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: string[]): void;
+}>();
+
+const dataSolveOptions = [
+  { value: 'none', label: '无' },
+  { value: 'ich', label: '传承相关' },
+  { value: 'common', label: '通用' },
+  { value: 'date', label: '日期' },
+];
+
+const localValue = ref<string[]>(props.modelValue || []);
+
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    localValue.value = newValue || [];
+  },
+  { deep: true, immediate: true }
+);
+
+const updateValue = () => {
+  emit('update:modelValue', localValue.value);
+};
+</script>

+ 34 - 0
src/pages/editor/components/ItemTypeEditor.vue

@@ -0,0 +1,34 @@
+<template>
+  <a-select
+    style="width: 100%"
+    v-model:value="localValue"
+    :options="CommonListPageItemTypeOptions"
+    @change="updateValue"
+  />
+</template>
+
+<script setup lang="ts">
+import { CommonListPageItemTypeOptions } from '@/pages/article/common/CommonListPage';
+import { ref, watch } from 'vue';
+
+const props = defineProps<{
+  modelValue?: string;
+}>();
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: string): void;
+}>();
+
+const localValue = ref<string>(props.modelValue || '');
+
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    localValue.value = newValue || '';
+  },
+  { deep: true, immediate: true }
+);
+
+const updateValue = () => {
+  emit('update:modelValue', localValue.value);
+};
+</script>

+ 12 - 3
src/pages/editor/components/LinkPathEditor.vue

@@ -9,7 +9,7 @@
               style="width: 150px; margin-right: 8px" 
               @change="handlePathTypeChange"
             >
-              <a-select-option value="dynamic-list">动态页面列表</a-select-option>
+              <a-select-option v-if="!props.noParams" value="dynamic-list">动态页面列表</a-select-option>
               <a-select-option value="internal">程序内置页面</a-select-option>
               <a-select-option value="custom">自定义输入</a-select-option>
             </a-select>
@@ -58,7 +58,7 @@
             />
           </div>
         </a-form-item>    
-        <a-form-item v-if="pathType !== 'dynamic-list'" label="参数设置">
+        <a-form-item v-if="!props.noParams && pathType !== 'dynamic-list'" label="参数设置">
           <KeyValueEditor
             v-model="localParams"
             :forceOneLevel="true"
@@ -80,9 +80,14 @@ import { CommonCategoryListPath } from '@/pages/article/data/CommonCategoryPathD
 // 定义props
 const props = defineProps<{
   modelValue?: string|[string, object]|undefined;
+  /**
+   * 是否禁用参数设置
+   * @default false
+   */
+  noParams?: boolean|undefined;
 }>();
 const emit = defineEmits<{
-  (e: 'update:modelValue', value: [string, object]): void;
+  (e: 'update:modelValue', value: string|[string, object]): void;
 }>();
 
 const pageList = inject<(IHomeCommonCategoryDefine['page'][0])[]>('pageList', []);
@@ -142,6 +147,10 @@ const handlePathTypeChange = () => {
 
 // 更新链接
 const updateLink = () => {
+  if (props.noParams) {
+    emit('update:modelValue', linkPath.value);
+    return;
+  }
   if (pathType.value === 'dynamic-list') {
     localParams.value = {
       pageConfigName: localPageConfigName.value || '',

+ 26 - 25
src/pages/editor/editors/CommonListPropsEditor.vue

@@ -1,23 +1,20 @@
 <template>
   <div class="common-list-props-editor">
     <a-form :labelCol="{ span: 6 }" size="small">
-      <a-form-item label="页面标题 (title)">
-        <a-input v-model:value="props.props.title" />
-      </a-form-item>
       <a-form-item label="显示 Tab">
-        <a-switch v-model:checked="props.props.showTab" :checked-value="true" :un-checked-value="false" />
+        <a-checkbox v-model:checked="props.props.showTab" :indeterminate="props.props.showTab === undefined" />
       </a-form-item>
       <a-form-item label="显示搜索">
-        <a-switch v-model:checked="props.props.showSearch" :checked-value="true" :un-checked-value="false" />
+        <a-checkbox v-model:checked="props.props.showSearch" :indeterminate="props.props.showSearch === undefined" />
       </a-form-item>
       <a-form-item label="显示总数">
-        <a-switch v-model:checked="props.props.showTotal" :checked-value="true" :un-checked-value="false" />
+        <a-checkbox v-model:checked="props.props.showTotal" :indeterminate="props.props.showTotal === undefined" />
       </a-form-item>
-      <a-form-item label="列表项类型 (itemType)">
-        <a-input v-model:value="props.props.itemType" placeholder="如 image-large-2" />
+      <a-form-item label="列表项类型">
+        <ItemTypeEditor v-model="props.props.itemType" />
       </a-form-item>
-      <a-form-item label="详情页 (detailsPage)">
-        <a-input v-model:value="props.props.detailsPage" />
+      <a-form-item label="详情页">
+        <LinkPathEditor v-model="(props.props.detailsPage as string)" :noParams="true" />
       </a-form-item>
     </a-form>
 
@@ -26,7 +23,7 @@
         <div v-for="(tab, i) in tabItems" :key="tabKey(tab, i)" class="nested-item tab-item">
           <a-collapse>
             <a-collapse-panel :key="i" :header="tabHeader(tab)">
-              <a-form size="small">
+              <a-form :labelCol="{ span: 4 }" size="small">
                 <a-form-item label="文本">
                   <a-input v-model:value="tab.text" />
                 </a-form-item>
@@ -43,14 +40,16 @@
                     style="width: 100%"
                   />
                 </a-form-item>
-                <a-form-item label="列表选项卡详情页">
+                <a-form-item label="列表详情页">
                   <LinkPathEditor v-model="tab.detailsPage" />
                 </a-form-item>
-                <a-form-item label="显示(仅在选项卡可见时)">
-                  <a-switch 
-                    :checked="tab.visible !== false" 
-                    @change="tab.visible = $event"
-                  />
+                <a-form-item label="显示">
+                  <a-checkbox
+                    v-model:checked="tab.visible" 
+                    :indeterminate="tab.visible === undefined"
+                  >
+                    (仅在TAB组件可见时有效)
+                  </a-checkbox>
                 </a-form-item>
                 <template v-if="tab.type === 'list'">
                   <a-form-item label="数据接口">
@@ -60,7 +59,7 @@
                     <!--TODO:下拉选择定义控制 -->
                   </a-form-item>
                   <a-form-item label="数据处理显示">
-                    <!--TODO:数据处理显示 -->
+                    <DataSolveEditor v-model="tab.dataSolve" />
                   </a-form-item>
                 </template>
                 <template v-else-if="tab.type === 'jump'">
@@ -75,9 +74,8 @@
                   </a-form-item>
                 </template>
                 <template v-else-if="tab.type === 'nestCategory'">
-                  <a-form-item label="子分类 (categorys)">
-                    
-                    <!--TODO: 使用子分类组件-->
+                  <a-form-item label="子栏目">
+                    <NestCategoryEditor :categorys="tab.categorys" />
                   </a-form-item>
                 </template>
                 <a-popconfirm
@@ -102,6 +100,9 @@ import type { IHomeCommonCategoryListDefine, IHomeCommonCategoryListTabItemDefin
 import KeyValueEditor from '../components/KeyValueEditor.vue';
 import DynamicDataEditor from '../components/DynamicDataEditor.vue';
 import LinkPathEditor from '../components/LinkPathEditor.vue';
+import DataSolveEditor from '../components/DataSolveEditor.vue';
+import NestCategoryEditor from '../subpart/NestCategoryEditor.vue';
+import ItemTypeEditor from '../components/ItemTypeEditor.vue';
 
 type TabItem = IHomeCommonCategoryListTabItemDefine;
 
@@ -114,9 +115,9 @@ const emit = defineEmits<{
 
 const activeKeys = ref<string[]>(['tabs']);
 const tabTypeOptions = [
-  { value: 'list', label: '列表' },
-  { value: 'jump', label: '跳转' },
-  { value: 'nestCategory', label: '子分类' },
+  { value: 'list', label: '通用列表' },
+  { value: 'jump', label: '跳转页面' },
+  { value: 'nestCategory', label: '子栏目' },
 ];
 const tabItems = computed(() => (props.props?.tabs || []) as TabItem[]);
 
@@ -130,7 +131,7 @@ function tabHeader(tab: TabItem) {
 }
 function addTab() {
   props.props!.tabs = props.props!.tabs || [];
-  (props.props!.tabs as TabItem[]).push({ text: '新 Tab', type: 'nestCategory', categorys: [] });
+  (props.props!.tabs as TabItem[]).push({ text: '新 Tab', type: 'list' });
 }
 function removeTab(i: number) {
   props.props!.tabs?.splice(i, 1);

+ 2 - 31
src/pages/editor/editors/HomePropsEditor.vue

@@ -36,37 +36,8 @@
         </div>
       </a-collapse-panel>
 
-      <a-collapse-panel key="categorys" header="首页分类 (categorys)">
-        <div v-for="(cat, i) in props.props.categorys" :key="i" class="nested-item category-item">
-          <a-collapse>
-            <a-collapse-panel :key="i" :header="cat.text || `分类 ${i + 1}`">
-              <a-form layout="vertical" size="small">
-                <a-form-item label="文本">
-                  <a-input v-model:value="cat.text" />
-                </a-form-item>
-                <a-form-item label="显示标题">
-                  <a-switch v-model:checked="cat.showTitle" :checked-value="true" :un-checked-value="false" />
-                </a-form-item>
-                <a-form-item label="类型 (type)">
-                  <a-input v-model:value="cat.type" />
-                </a-form-item>
-                <a-form-item label="详情页">
-                  <a-input v-model:value="cat.detailsPage" />
-                </a-form-item>
-                <a-form-item label="更多页">
-                  <LinkPathEditor v-model="cat.morePage"/>
-                </a-form-item>
-                <a-form-item label="数据 (data)">
-                  <DynamicDataEditor v-model="cat.data" />
-                </a-form-item>
-                <a-popconfirm title="确定要删除这个分类吗?" @confirm="removeCategory(i)">
-                  <a-button type="link" danger size="small">删除分类</a-button>
-                </a-popconfirm>
-              </a-form>
-            </a-collapse-panel>
-          </a-collapse>
-        </div>
-        <a-button type="dashed" block size="small" @click="addCategory">+ 添加分类</a-button>
+      <a-collapse-panel key="categorys" header="首页栏目">
+        <NestCategoryEditor :categorys="props.props.categorys" />
       </a-collapse-panel>
     </a-collapse>
   </div>

+ 8 - 2
src/pages/editor/subpart/EditorPreview.vue

@@ -11,11 +11,12 @@
 </template>
 
 <script setup lang="ts">
-import { provide, computed, toRef, markRaw, type Component } from 'vue';
+import { provide, computed, toRef, markRaw, type Component, ref, watch } from 'vue';
 import { COMMON_CATEGORY_KEY } from '@/pages/article/data/CommonCategoryGlobalLoader';
 import type { IHomeCommonCategoryDefine } from '@/pages/article/data/CommonCategoryDefine';
 import HomePage from '@/pages/home/index.vue';
 import CommonCategoryList from '@/pages/article/data/CommonCategoryList.vue';
+import { ObjectUtils } from '@imengyu/imengyu-utils';
 
 const props = defineProps<{
   editorJson: IHomeCommonCategoryDefine;
@@ -23,9 +24,14 @@ const props = defineProps<{
 }>();
 
 // 注入当前编辑的 JSON,供预览中的 home/list 通过 injectCommonCategory() 读取
-const editorJsonRef = toRef(props, 'editorJson');
+const editorJsonRef = ref(props.editorJson);
+
 provide(COMMON_CATEGORY_KEY, editorJsonRef);
 
+watch(() => props.editorJson, (newJson) => {
+  editorJsonRef.value = ObjectUtils.clone(newJson);
+}, { deep: true, immediate: true });
+
 const previewComponent = computed<Component | null>(() => {
   const page = props.selectedPage;
   if (!page?.content) 

+ 109 - 0
src/pages/editor/subpart/NestCategoryEditor.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="nest-category-editor">
+    <div v-for="(cat, i) in categorys" :key="i" class="nested-item">
+      <a-collapse>
+        <a-collapse-panel :key="i" :header="cat.text || `子分类 ${i + 1}`">
+          <a-form :labelCol="{ span: 6 }" size="small">
+            <a-form-item label="显示标题">
+              <a-checkbox 
+                v-model:checked="cat.showTitle"
+                :indeterminate="cat.showTitle === undefined"
+              />
+            </a-form-item>
+            <a-form-item label="标题">
+              <a-input v-model:value="cat.text" />
+            </a-form-item>
+            <a-form-item label="显示更多">
+              <a-checkbox 
+                v-model:checked="cat.showMore" 
+                :indeterminate="cat.showMore === undefined"
+              />
+            </a-form-item>
+            <a-form-item label="更多页">
+              <LinkPathEditor v-model="cat.morePage" />
+            </a-form-item>
+            <a-form-item label="更多文本">
+              <a-input v-model:value="cat.moreText" />
+            </a-form-item>
+            <a-form-item label="可见">
+              <a-checkbox v-model:checked="cat.visible" :indeterminate="cat.visible === undefined" />
+            </a-form-item>
+            <a-form-item label="类型">
+              <a-select v-model:value="cat.type" style="width: 100%" allowClear placeholder="请选择类型">
+                <a-select-option v-for="type in blockTypes" :key="type" :value="type">
+                  {{ type }}
+                </a-select-option>
+              </a-select>
+            </a-form-item>
+            <a-form-item label="数据源">
+              <DynamicDataEditor v-model="cat.data" />
+            </a-form-item>
+            <a-form-item label="加载数量">
+              <a-input-number v-model:value="cat.count" :min="1" style="width: 100%" />
+            </a-form-item>
+            <a-form-item label="数据展示">
+              <DataSolveEditor v-model="cat.dataSolve" />
+            </a-form-item>
+            <a-form-item label="列表页项类型">
+              <ItemTypeEditor v-model="cat.itemType" />
+            </a-form-item>
+            <a-form-item label="列表页详情页">
+              <a-input v-model:value="cat.detailsPage" />
+            </a-form-item>
+            <a-popconfirm title="确定删除该子分类吗?" @confirm="remove(i)">
+              <a-button type="link" danger size="small">删除子分类</a-button>
+            </a-popconfirm>
+          </a-form>
+        </a-collapse-panel>
+      </a-collapse>
+    </div>
+    <a-button type="dashed" block size="small" @click="add">+ 添加子分类</a-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { IHomeCommonCategoryListTabNestCategoryItemDefine } from '@/pages/article/data/CommonCategoryDefine';
+import type { IHomeCommonCategoryDynamicData } from '@/pages/article/data/CommonCategoryDynamicData';
+import LinkPathEditor from '../components/LinkPathEditor.vue';
+import DynamicDataEditor from '../components/DynamicDataEditor.vue';
+import DataSolveEditor from '../components/DataSolveEditor.vue';
+import { CommonCategoryBlockType } from '@/pages/article/data/CommonCategoryBlocks';
+import ItemTypeEditor from '../components/ItemTypeEditor.vue';
+
+const props = defineProps<{
+  categorys: IHomeCommonCategoryListTabNestCategoryItemDefine[];
+}>();
+
+const blockTypes = CommonCategoryBlockType;
+
+function add() {
+  props.categorys.push({
+    text: '新子分类',
+    type: '',
+    data: undefined as unknown as IHomeCommonCategoryDynamicData,
+  });
+}
+
+function remove(i: number) {
+  props.categorys.splice(i, 1);
+}
+</script>
+
+<style scoped>
+.nest-category-editor {
+  font-size: 12px;
+}
+.nested-item {
+  margin-bottom: 12px;
+  padding: 8px;
+  background: #fafafa;
+  border-radius: 4px;
+}
+.nested-item :deep(.ant-collapse) {
+  border: none;
+  background: transparent;
+}
+.nested-item :deep(.ant-collapse-item) {
+  border: none;
+}
+</style>