快乐的梦鱼 6 일 전
부모
커밋
3f35410081

+ 2 - 0
src/api/CommonContent.ts

@@ -376,8 +376,10 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
     this.mainBodyId = mainBodyId;
     this.mainBodyColumnId = mainBodyColumnId;
     this.debugName = debugName;
+    this.description = debugName;
   }
 
+  public description: string;
   public mainBodyId: number;
   public mainBodyColumnId?: number|number[];
   public modelId: number;

+ 1 - 1
src/pages/inhert/intangible/DetailsCommon.vue

@@ -69,7 +69,7 @@
       <template v-else-if="tabCurrentId==TAB_ID_PEDIGREE">
         <!-- 传承谱系 -->
         <view class="d-flex flex-col mt-3 mb-2">
-          <Parse :content="content.pedigree" />
+          <Parse :content="(content.pedigree as string)" />
         </view>
       </template>
       <template v-else-if="tabCurrentId==TAB_ID_WORKS">

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

@@ -1,7 +1,7 @@
 
 // 动态数据接口定义
 
-import CommonContent, { GetContentListParams } from "@/api/CommonContent";
+import CommonContent, { CommonContentApi, GetContentListParams } from "@/api/CommonContent";
 import CalendarContent from "@/api/fusion/CalendarContent";
 import ScenicSpotContent from "@/api/fusion/ScenicSpotContent";
 import ActivityContent from "@/api/inheritor/ActivityContent";
@@ -95,7 +95,7 @@ export type IHomeCommonCategoryDynamicData = IHomeCommonCategoryDynamicDataCommo
 /**
  * 序列化接口映射表
  */
-export const SerializedApiMap = {
+export const SerializedApiMap : Record<string, CommonContentApi> = {
   BulidingContent,
   ActivityContent,
   InheritorContent,

+ 52 - 0
src/pages/article/data/defines/Details.ts

@@ -0,0 +1,52 @@
+/**
+ * 页面模板:
+ * 详情页定义
+ */
+
+import type { IHomeCommonCategoryListTabNestCategoryItemDefine } from "./List";
+
+/**
+ * 页面模板:首页定义
+ */
+export interface IHomeCommonCategoryHomeDefine {
+  type: 'Details',
+  props: {
+    /**
+     * 首页标题
+     */
+    title: string,
+    /**
+     * 首页副标题
+     */
+    subTitle: string,
+    /**
+     * 首页banner图
+     */
+    homeBanner: string,
+    /**
+     * 首页按钮
+     */
+    homeButtons: {
+      /**
+       * 按钮标题
+       */
+      title: string,
+      /**
+       * 按钮图标
+       */
+      icon: string,
+      /**
+       * 按钮跳转链接
+       */
+      link: [string, object],
+      /**
+       * 按钮大小
+       */
+      size: number
+    }[],
+    /**
+     * 首页分类项
+     */
+    categorys: IHomeCommonCategoryListTabNestCategoryItemDefine[],
+  },
+}

+ 0 - 0
src/pages/article/data/details.vue


+ 239 - 0
src/pages/editor/components/DropdownDataEditor.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="dropdown-data-editor">
+    <a-form layout="vertical" size="small">
+      <a-form-item label="数据源类型">
+        <a-select
+          :value="currentType"
+          style="width: 100%"
+          :options="typeOptions"
+          placeholder="选择类型"
+          @change="onTypeChange"
+        />
+      </a-form-item>
+
+      <template v-if="currentType === 'commonContent'">
+        <a-form-item label="分类类型 ID (typeId)">
+          <a-input-number
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent)?.typeId"
+            style="width: 100%"
+            :min="1"
+            placeholder="typeId"
+            @update:value="(v: number | undefined) => setCommonContent('typeId', v)"
+          />
+        </a-form-item>
+        <a-form-item label="ID 键 (idKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent)?.idKey"
+            placeholder="可选,默认 id"
+            @change="(e: Event) => setCommonContent('idKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="名称键 (nameKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent)?.nameKey"
+            placeholder="可选,默认 title"
+            @change="(e: Event) => setCommonContent('nameKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="其他参数 (otherParams)">
+          <KeyValueEditor
+            :model-value="(modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent)?.otherParams"
+            @update:modelValue="setCommonContentOtherParams"
+          />
+        </a-form-item>
+      </template>
+
+      <template v-else-if="currentType === 'static'">
+        <a-form-item label="静态数据 (data) JSON 数组">
+          <a-input
+            :value="staticDataJson"
+            placeholder='[{"id": 1, "title": "选项1"}]'
+            :rows="4"
+            @change="(e: Event) => setStaticData((e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="ID 键 (idKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownStaticData)?.idKey"
+            placeholder="可选"
+            @change="(e: Event) => setStatic('idKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="名称键 (nameKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownStaticData)?.nameKey"
+            placeholder="可选"
+            @change="(e: Event) => setStatic('nameKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+      </template>
+
+      <template v-else-if="currentType === 'request'">
+        <a-form-item label="请求方法 (method)">
+          <a-select
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.method"
+            style="width: 100%"
+            :options="methodOptions"
+            @change="(v: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE') => setRequest('method', v)"
+          />
+        </a-form-item>
+        <a-form-item label="请求 URL (url)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.url"
+            placeholder="https://..."
+            @change="(e: Event) => setRequest('url', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="查询参数 (querys)">
+          <KeyValueEditor
+            :model-value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.querys"
+            @update:modelValue="setRequestQuerys"
+          />
+        </a-form-item>
+        <a-form-item label="请求体参数 (params)">
+          <KeyValueEditor
+            :model-value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.params"
+            @update:modelValue="setRequestParams"
+          />
+        </a-form-item>
+        <a-form-item label="ID 键 (idKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.idKey"
+            placeholder="可选"
+            @change="(e: Event) => setRequest('idKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+        <a-form-item label="名称键 (nameKey)">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDropdownDynamicDataRequest)?.nameKey"
+            placeholder="可选"
+            @change="(e: Event) => setRequest('nameKey', (e.target as HTMLInputElement)?.value)"
+          />
+        </a-form-item>
+      </template>
+    </a-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+import KeyValueEditor from './KeyValueEditor.vue';
+import type {
+  IHomeCommonCategoryDropdownDynamicData,
+  IHomeCommonCategoryDropdownDynamicDataCommonContent,
+  IHomeCommonCategoryDropdownDynamicDataRequest,
+  IHomeCommonCategoryDropdownStaticData,
+} from '@/pages/article/data/data-defines/Dropdown';
+
+const props = defineProps<{
+  modelValue?: IHomeCommonCategoryDropdownDynamicData | null;
+}>();
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: IHomeCommonCategoryDropdownDynamicData | undefined): void;
+}>();
+
+const typeOptions = [
+  { value: 'commonContent', label: '通用内容 (commonContent)' },
+  { value: 'static', label: '静态数据 (static)' },
+  { value: 'request', label: '请求 (request)' },
+];
+
+const methodOptions = [
+  { value: 'GET', label: 'GET' },
+  { value: 'POST', label: 'POST' },
+  { value: 'PUT', label: 'PUT' },
+  { value: 'DELETE', label: 'DELETE' },
+  { value: 'HEAD', label: 'HEAD' },
+  { value: 'OPTIONS', label: 'OPTIONS' },
+];
+
+const currentType = computed(() => props.modelValue?.type ?? null);
+
+const staticDataJson = computed(() => {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownStaticData | undefined;
+  if (!cur?.data) return '';
+  try {
+    return Array.isArray(cur.data) ? JSON.stringify(cur.data, null, 2) : '';
+  } catch {
+    return '';
+  }
+});
+
+function onTypeChange(type: 'commonContent' | 'static' | 'request') {
+  if (type === 'commonContent') {
+    emit('update:modelValue', { type: 'commonContent', typeId: 1 });
+  } else if (type === 'static') {
+    emit('update:modelValue', { type: 'static', data: [] });
+  } else if (type === 'request') {
+    emit('update:modelValue', { type: 'request', method: 'GET', url: '' });
+  }
+}
+
+function setCommonContent(key: 'typeId' | 'idKey' | 'nameKey', value: number | string | undefined) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent | undefined;
+  if (!cur || cur.type !== 'commonContent') return;
+  const next = { ...cur, [key]: value };
+  emit('update:modelValue', next);
+}
+
+function setCommonContentOtherParams(otherParams: Record<string, any>) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownDynamicDataCommonContent | undefined;
+  if (!cur || cur.type !== 'commonContent') return;
+  const value = otherParams && Object.keys(otherParams).length > 0 ? otherParams : undefined;
+  emit('update:modelValue', { ...cur, otherParams: value });
+}
+
+function setStatic(key: 'idKey' | 'nameKey', value: string | undefined) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownStaticData | undefined;
+  if (!cur || cur.type !== 'static') return;
+  emit('update:modelValue', { ...cur, [key]: value || undefined });
+}
+
+function setStaticData(str: string) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownStaticData | undefined;
+  if (!cur || cur.type !== 'static') return;
+  let data: Record<string, any>[];
+  try {
+    const parsed = str?.trim() ? JSON.parse(str) : [];
+    data = Array.isArray(parsed) ? parsed : [];
+  } catch {
+    return;
+  }
+  emit('update:modelValue', { ...cur, data });
+}
+
+function setRequest(
+  key: 'method' | 'url' | 'idKey' | 'nameKey',
+  value: string | undefined
+) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownDynamicDataRequest | undefined;
+  if (!cur || cur.type !== 'request') return;
+  const next = { ...cur, [key]: value } as IHomeCommonCategoryDropdownDynamicDataRequest;
+  emit('update:modelValue', next);
+}
+
+function setRequestQuerys(querys: Record<string, any>) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownDynamicDataRequest | undefined;
+  if (!cur || cur.type !== 'request') return;
+  const value = querys && Object.keys(querys).length > 0 ? querys : undefined;
+  emit('update:modelValue', { ...cur, querys: value });
+}
+
+function setRequestParams(params: Record<string, any>) {
+  const cur = props.modelValue as IHomeCommonCategoryDropdownDynamicDataRequest | undefined;
+  if (!cur || cur.type !== 'request') return;
+  const value = params && Object.keys(params).length > 0 ? params : undefined;
+  emit('update:modelValue', { ...cur, params: value });
+}
+</script>
+
+<style scoped>
+.dropdown-data-editor {
+  font-size: 12px;
+  margin-left: 8px;
+  padding: 8px;
+  background: #fafafa;
+  border-radius: 4px;
+}
+</style>

+ 103 - 0
src/pages/editor/components/DropdownDefinesEditor.vue

@@ -0,0 +1,103 @@
+<template>
+  <div class="dropdown-defines-editor">
+    <div v-for="(item, i) in items" :key="itemKey(item, i)" class="dropdown-define-item">
+      <a-collapse>
+        <a-collapse-panel :key="i" :header="itemHeader(item, i)">
+          <a-form :labelCol="{ span: 6 }" size="small">
+            <a-form-item label="key">
+              <a-input v-model:value="item.key" placeholder="参数键名,如 level、region" />
+            </a-form-item>
+            <a-form-item label="text">
+              <a-input v-model:value="item.text" placeholder="下拉框显示文本" />
+            </a-form-item>
+            <a-form-item label="formQueryKey">
+              <a-input v-model:value="item.formQueryKey" placeholder="与 URL 查询参数对应" />
+            </a-form-item>
+            <a-form-item label="默认值">
+              <a-input v-model:value="item.defaultValue" placeholder="可选,默认选中的值" />
+            </a-form-item>
+            <a-form-item label="全部选项文本">
+              <a-input v-model:value="item.addAll" placeholder="可选,“全部”选项文本" />
+            </a-form-item>
+            <a-form-item label="数据源">
+              <DropdownDataEditor v-model="item.data" />
+            </a-form-item>
+          </a-form>
+          <a-popconfirm title="确认删除该下拉项?" @confirm="removeItem(i)">
+            <a-button type="link" danger size="small">删除</a-button>
+          </a-popconfirm>
+        </a-collapse-panel>
+      </a-collapse>
+    </div>
+    <a-button type="dashed" block size="small" @click="addItem">+ 添加下拉项</a-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+import type { IHomeCommonCategoryListTabListDropdownDefine } from '@/pages/article/data/CommonCategoryDefine';
+import DropdownDataEditor from './DropdownDataEditor.vue';
+
+const props = defineProps<{
+  modelValue?: IHomeCommonCategoryListTabListDropdownDefine[] | null;
+}>();
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: IHomeCommonCategoryListTabListDropdownDefine[]): void;
+}>();
+
+const items = computed({
+  get() {
+    const list = props.modelValue ?? [];
+    return Array.isArray(list) ? list : [];
+  },
+  set(value: IHomeCommonCategoryListTabListDropdownDefine[]) {
+    emit('update:modelValue', value);
+  },
+});
+
+// 确保父组件拿到的是同一引用下的更新(v-model 绑定的是 tab.dropdownDefines,直接改 item 会反映到原数组)
+// 当用户增删项时需通过 emit 更新数组
+function itemKey(item: IHomeCommonCategoryListTabListDropdownDefine, i: number) {
+  return `${i}-${item.key}-${item.text}`;
+}
+
+function itemHeader(item: IHomeCommonCategoryListTabListDropdownDefine, i: number) {
+  const key = item.key || '?';
+  const text = item.text || '未命名';
+  return `${i + 1}. ${text} (key: ${key})`;
+}
+
+function addItem() {
+  const list = [...(props.modelValue ?? [])];
+  list.push({
+    key: '',
+    text: '',
+    data: { type: 'commonContent', typeId: 1 },
+  });
+  emit('update:modelValue', list);
+}
+
+function removeItem(index: number) {
+  const list = [...(props.modelValue ?? [])];
+  list.splice(index, 1);
+  emit('update:modelValue', list);
+}
+</script>
+
+<style scoped>
+.dropdown-defines-editor {
+  font-size: 12px;
+  margin-left: 8px;
+}
+.dropdown-define-item {
+  margin-bottom: 8px;
+  padding: 8px;
+  background: #fafafa;
+  border-radius: 4px;
+}
+.dropdown-define-item :deep(.ant-collapse) {
+  border: none;
+  background: transparent;
+}
+</style>

+ 1 - 9
src/pages/editor/components/DynamicDataEditor.vue

@@ -28,14 +28,6 @@
             @change="(e: Event) => setMainBodyColumnId('commonContent', (e.target as HTMLInputElement)?.value)"
           />
         </a-form-item>
-        <a-form-item label="分类类型 ID (typeId)">
-          <a-input-number
-            :value="(modelValue as IHomeCommonCategoryDynamicDataCommonContent)?.params?.typeId"
-            style="width: 100%"
-            placeholder="可选"
-            @update:value="(v: number | undefined) => setCommonContent('typeId', v)"
-          />
-        </a-form-item>
       </template>
 
       <template v-else-if="currentType === 'serializedApi'">
@@ -130,7 +122,7 @@ const methodOptions = [
 ];
 
 const serializedApiOptions = computed(() =>
-  Object.keys(SerializedApiMap).map((name) => ({ value: name, label: name }))
+  Object.keys(SerializedApiMap).map((name) => ({ value: name, label: `${name} (${SerializedApiMap[name].description})` }))
 );
 
 const currentType = computed(() => props.modelValue?.type ?? null);

+ 47 - 35
src/pages/editor/components/KeyValueEditor.vue

@@ -1,47 +1,54 @@
 <template>
   <div class="key-value-editor">
-    <div class="key-value-container">
-      <div 
-        v-for="item in localItems" 
-        :key="item.key"
-        class="key-value-item"
-      >
-        <a-input
-          v-model:value="item.key"
-          placeholder="键"
-          class="key-input"
-          @blur="updateKey(item)"
-        />
-        <value-editor
-          v-model:modelValue="item.value"
-          @update:modelValue="updateValue"
-          :forceOneLevel="forceOneLevel"
-          class="value-input"
-        />
-        <a-popconfirm
-          title="确定要删除这个项吗?"
-          ok-text="确认"
-          cancel-text="取消"
-          @confirm="removeItem(item.key)"
+    <a-button type="dashed" block @click="modalVisible = true">
+      {{ itemCount }} 条
+    </a-button>
+    <a-modal
+      v-model:open="modalVisible"
+      title="键值编辑"
+      width="640px"
+      :footer="null"
+      destroy-on-close
+    >
+      <div class="key-value-container">
+        <div
+          v-for="item in localItems"
+          :key="item.key"
+          class="key-value-item"
         >
-          <a-button 
-            type="text" 
-            danger 
-            class="item-remove"
+          <a-input
+            v-model:value="item.key"
+            placeholder="键"
+            class="key-input"
+            @blur="updateKey(item)"
+          />
+          <value-editor
+            v-model:modelValue="item.value"
+            @update:modelValue="updateValue"
+            :forceOneLevel="forceOneLevel"
+            class="value-input"
+          />
+          <a-popconfirm
+            title="确定要删除这个项吗?"
+            ok-text="确认"
+            cancel-text="取消"
+            @confirm="removeItem(item.key)"
           >
-            删除
-          </a-button>
-        </a-popconfirm>
+            <a-button type="text" danger class="item-remove">
+              删除
+            </a-button>
+          </a-popconfirm>
+        </div>
+        <a-button type="dashed" block @click="addItem">
+          <plus-outlined /> 添加项
+        </a-button>
       </div>
-      <a-button type="dashed" block @click="addItem">
-        <plus-outlined /> 添加项
-      </a-button>
-    </div>
+    </a-modal>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue';
+import { ref, computed, watch } from 'vue';
 import { PlusOutlined } from '@ant-design/icons-vue';
 import ValueEditor from './ValueEditor.vue';
 
@@ -68,8 +75,11 @@ type LocalItem = {
   type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null';
 };
 
+const modalVisible = ref(false);
 const localItems = ref<LocalItem[]>([]);
 
+const itemCount = computed(() => localItems.value.length);
+
 function getType(value: any): LocalItem['type'] {
   if (value === null) {
     return 'null';
@@ -166,6 +176,8 @@ const removeItem = (key: string) => {
 
 .key-value-container {
   width: 100%;
+  max-height: 500px;
+  overflow-y: auto;
 }
 
 .key-value-item {

+ 2 - 1
src/pages/editor/editors/CommonListPropsEditor.vue

@@ -56,7 +56,7 @@
                     <DynamicDataEditor v-model="tab.data" />
                   </a-form-item>
                   <a-form-item label="下拉选择定义">
-                    <!--TODO:下拉选择定义控制 -->
+                    <DropdownDefinesEditor v-model="tab.dropdownDefines" />
                   </a-form-item>
                   <a-form-item label="数据处理显示">
                     <DataSolveEditor v-model="tab.dataSolve" />
@@ -103,6 +103,7 @@ import LinkPathEditor from '../components/LinkPathEditor.vue';
 import DataSolveEditor from '../components/DataSolveEditor.vue';
 import NestCategoryEditor from '../subpart/NestCategoryEditor.vue';
 import ItemTypeEditor from '../components/ItemTypeEditor.vue';
+import DropdownDefinesEditor from '../components/DropdownDefinesEditor.vue';
 
 type TabItem = IHomeCommonCategoryListTabItemDefine;
 

+ 1 - 1
src/pages/inhert/intangible/details.vue

@@ -4,7 +4,7 @@
 
 <script setup lang="ts">
 import { ref } from 'vue';
-import DetailsCommon from './DetailsCommon.vue';
+import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
 import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
 
 const pageRef = ref();

+ 1 - 1
src/pages/inhert/product/details.vue

@@ -5,7 +5,7 @@
 <script setup lang="ts">
 import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
 import { ref } from 'vue';
-import DetailsCommon from '../intangible/DetailsCommon.vue';
+import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
 
 const pageRef = ref();
 

+ 1 - 1
src/pages/inhert/seminar/details.vue

@@ -5,7 +5,7 @@
 <script setup lang="ts">
 import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
 import { ref } from 'vue';
-import DetailsCommon from '../intangible/DetailsCommon.vue';
+import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
 
 const pageRef = ref();
 

+ 1 - 1
src/pages/inhert/songs/details.vue

@@ -4,7 +4,7 @@
 
 <script setup lang="ts">
 import { ref } from 'vue';
-import DetailsCommon from '../intangible/DetailsCommon.vue';
+import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
 import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
 
 const pageRef = ref();