Parcourir la source

📦 非遗基础表和扩展表

快乐的梦鱼 il y a 2 semaines
Parent
commit
8f8b04cd5a

+ 2 - 0
src/api/RequestModules.ts

@@ -42,6 +42,8 @@ function matchNotReportMessage(str: string) {
 function requestInceptor(url: string, req: RequestOptions) {
   //获取store中的token,追加到头;
   const autoStore = useAuthStore();
+  if (!req.header)
+    req.header = {};
   if (StringUtils.isNullOrEmpty((req.header as KeyValue).token as string)) {
     req.header['token'] = autoStore.token;
     req.header['__token__'] = autoStore.token;

+ 172 - 17
src/api/inheritor/InheritorContent.ts

@@ -9,14 +9,33 @@ export class IchInfo extends DataModel<IchInfo> {
       id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
       lonlat: { serverSide: 'undefined' },
       flag: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
+      batch: { clientSide: 'number', serverSide: 'string' },
+      type: { clientSide: 'number', serverSide: 'number' },
       keywords: { clientSide: 'splitCommaArray', serverSide: 'commaArrayMerge' },
-      typicalImages: { 
-        clientSide: 'object', 
-        clientSideChildDataModel: {
-          convertTable: {},
-        }, 
-        serverSide: 'string' 
-      },
+      typicalImages: [
+        { 
+          clientSide: 'object', 
+          clientSideChildDataModel: {
+            convertTable: {},
+          }, 
+          serverSide: 'string' 
+        },
+        {
+          clientSide: 'addDefaultValue',
+          clientSideParam: {
+            defaultValue: [],
+          }
+        },
+      ],
+      expandInfo: { serverSide: 'undefined' },
+    };
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Text') || key.endsWith('_text')) {
+        return {
+          clientSide: 'string',
+          serverSide: 'undefined',
+        };
+      }
     };
     this._afterSolveServer = (self) => {
       self.lonlat = [ self.longitude, self.latitude ];
@@ -24,13 +43,26 @@ export class IchInfo extends DataModel<IchInfo> {
     this._afterSolveClient = (data) => {
       data.longitude = this.lonlat[0];
       data.latitude = this.lonlat[1];
-    }
+    };
+    this._beforeSolveClient = (data) => {
+      this.expandInfo.batch = this.batch;
+      this.expandInfo.region = this.region;
+      this.expandInfo.image = this.image;
+      this.expandInfo.level = this.level!;
+      this.expandInfo.ichType = this.ichType!;
+      this.expandInfo.contentId = this.contentId!;
+      this.expandInfo.collectId = this.collectId!;
+    };
   }
 
   lonlat = [] as (number|string)[];
+  expandInfo = new IchExpandInfo();
 
   id = 0 as number;
+  modelId = 2;
   mainBodyColumnId = 0 as number;
+  contentId = null as number|null;
+  collectId = null as number|null;
   title = '' as string;
   region = null as number|null;
   image = '' as string;
@@ -78,6 +110,68 @@ export class IchInfo extends DataModel<IchInfo> {
   isMultipleClaimsText = '' as string;
   batchText = '' as string;
   ichSiteTypeText = '' as string;
+
+}
+export class IchExpandInfo extends DataModel<IchExpandInfo> {
+  constructor() {
+    super(IchExpandInfo, "非遗项目信息");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      protectLevel: { clientSide: 'number', serverSide: 'string' },
+      id: { clientSide: 'number', serverSide: 'undefined' },
+    };
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Text') || key.endsWith('_text')) {
+        return {
+          clientSide: 'string',
+          serverSide: 'undefined',
+        };
+      }
+      if (key.endsWith('At')) {
+        return {
+          clientSide: 'date',
+          serverSide: 'undefined',
+        };
+      }
+    };
+    this._afterSolveServer = (self) => {
+    };
+    this._afterSolveClient = (data) => {
+    };
+  }
+
+  id = 0 as number;
+  modelId = 2;
+  userId = 0 as number;
+  reviewId = 0 as number;
+  originId = '' as string|null;
+  contentId = 0 as number;
+  name = '' as string;
+  level = 0 as number;
+  ichType = 0 as number;
+  protectLevel = null as number|null;
+  image = '' as string|null;
+  images = [] as string[];
+  otherNames = '' as string|null;
+  history = false;
+  existence = false;
+  folkCulture = '' as string|null;
+  culturalRelic = '' as string|null;
+  description = '' as string|null;
+  desc = '' as string;
+  mapX = '' as string|null;
+  mapY = '' as string|null;
+  declarationRegion = '' as string|null;
+  popularRegion = '' as string|null;
+  createdAt = '' as string;
+  updatedAt = '' as string;
+  deletedAt = '' as string|null;
+  progress = 0 as number;
+  comment = '' as string;
+  levelText = '' as string;
+  ichTypeText = '' as string;
+  protectLevelText = '' as string;
+  progressText = '' as string;
 }
 export class InheritorInfo extends DataModel<InheritorInfo> {
   constructor() {
@@ -88,6 +182,7 @@ export class InheritorInfo extends DataModel<InheritorInfo> {
     }
   }
   id = 0 as number;
+  modelId = 7;
   mainBodyColumnId = 0 as number;
   title = '' as string;
   region = null as number|null;
@@ -130,15 +225,38 @@ export class InheritorInfo extends DataModel<InheritorInfo> {
   ichSiteTypeText = '' as string;
   progressText = '' as string;
 }
-export class SeminarInfo extends DataModel<IchInfo> {
+export class InheritorExpandInfo extends DataModel<InheritorExpandInfo> {
+  constructor() {
+    super(InheritorExpandInfo, "非遗项目信息");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      
+    };
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Text') || key.endsWith('_text')) {
+        return {
+          clientSide: 'string',
+          serverSide: 'undefined',
+        };
+      }
+    };
+    this._afterSolveServer = (self) => {
+    };
+    this._afterSolveClient = (data) => {
+    };
+  }
+  modelId = 7;
+}
+export class SeminarInfo extends DataModel<SeminarInfo> {
   constructor() {
-    super(IchInfo, "传习所信息");
+    super(SeminarInfo, "传习所信息");
     this.setNameMapperCase('Camel', 'Snake');
     this._convertTable = {
       id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
     }
   }
   id = 0 as number;
+  modelId = 17;
   mainBodyColumnId = 0 as number;
   title = '' as string;
   region = null as number|null;
@@ -175,6 +293,28 @@ export class SeminarInfo extends DataModel<IchInfo> {
   batchText = '' as string;
   ichSiteTypeText = '' as string;
 }
+export class SeminarExpandInfo extends DataModel<SeminarExpandInfo> {
+  constructor() {
+    super(SeminarExpandInfo, "非遗项目信息");
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      
+    };
+    this._convertKeyType = (key, direction) => {
+      if (key.endsWith('Text') || key.endsWith('_text')) {
+        return {
+          clientSide: 'string',
+          serverSide: 'undefined',
+        };
+      }
+    };
+    this._afterSolveServer = (self) => {
+    };
+    this._afterSolveClient = (data) => {
+    };
+  }
+  modelId = 17;
+}
 
 export class InheritorContentApi extends AppServerRequestModule<DataModel> {
 
@@ -182,26 +322,41 @@ export class InheritorContentApi extends AppServerRequestModule<DataModel> {
     super();
   }
 
-  async getBaseInfo<T extends DataModel>(modelId: number, newDataModel: new () => T) {
+  async getBaseInfo<T extends DataModel>(newDataModel: new () => T) {
     return (await this.post('/ich/inheritor/baseInfo', {
-      model_id: modelId,
+      model_id: new newDataModel().modelId,
     }, '基础表信息', undefined, newDataModel)).data as T;
   }
   async saveBaseInfo<T extends DataModel>(dataModel: T) {
     return (await this.post('/ich/inheritor/saveBase', dataModel.toServerSide(), '基础内容表采集(非遗,传承人,传习所)'));
   }
-
+  async getExpandInfo<T extends DataModel>(newDataModel: new () => T) {
+    return (await this.post('/ich/inheritor/expandInfo', {
+      model_id: new newDataModel().modelId,
+    }, '扩展表信息', undefined, newDataModel)).data as T;
+  }
+  async saveExpandInfo<T extends DataModel>(dataModel: T) {
+    return (await this.post('/ich/inheritor/saveExpand', dataModel.toServerSide(), '扩展内容表采集(非遗,传承人,传习所)'));
+  }
 
   async getIchInfo() {
-    return await this.getBaseInfo(2, IchInfo);
+    return await this.getBaseInfo(IchInfo);
   }
   async getInheritorInfo() {
-    return await this.getBaseInfo(7, InheritorInfo);
+    return await this.getBaseInfo(InheritorInfo);
   }
   async getSeminarInfo() {
-    return await this.getBaseInfo(17, SeminarInfo);
+    return await this.getBaseInfo(SeminarInfo);
+  }
+  async getIchExpandInfo() {
+    return await this.getExpandInfo(IchExpandInfo);
+  }
+  async getInheritorExpandInfo() {
+    return await this.getExpandInfo(InheritorExpandInfo);
+  }
+  async getSeminarExpandInfo() {
+    return await this.getExpandInfo(SeminarExpandInfo);
   }
-
 }
 
 export default new InheritorContentApi();

+ 1 - 1
src/common/upload/ImageUploadCo.ts

@@ -11,7 +11,7 @@ export function useImageSimpleUploadCo(additionData?: Record<string, any>) : Upl
       CommonContent.uploadSmallFile(requestOption.file, 'image', requestOption.filename, additionData)
         .then((res) => {
           requestOption.onSuccess?.({
-            url: res.url,
+            url: res.fullurl,
             key: res.fullurl,
           }, null);
         }).catch((err) => {

+ 169 - 0
src/components/dynamicf/UploadVideoFormItem.vue

@@ -0,0 +1,169 @@
+<template>
+  <a-upload 
+    v-bind="customProps"
+    :disabled="disabled"
+    v-model:file-list="uploadSubImgList"
+    list-type="text"
+    :class="uploadClass"
+    :max-count="maxCount"
+    :show-upload-list="!single"
+    :customRequest="handleUpload"
+    :before-upload="beforeUpload"
+    @change="handleUploadSubImgChange"
+  >
+    <template v-if="single">
+      <video v-if="value != ''" 
+        :src="(value as string)"
+        alt="avatar"
+        controls
+        :width="singleImageSize.width"
+        :height="singleImageSize.height"
+        :preview="false"
+      />
+      <div v-else :style="{ width: singleImageSize.width, height: singleImageSize.height }">
+        <loading-outlined v-if="uploadingSubImg"></loading-outlined>
+        <plus-outlined v-else></plus-outlined>
+        <div class="ant-upload-text">上传</div>
+      </div>
+    </template>
+    <template v-else>
+      <loading-outlined v-if="uploadingSubImg"></loading-outlined>
+      <plus-outlined v-else></plus-outlined>
+      <div class="ant-upload-text">上传</div>
+    </template>
+  </a-upload>
+</template>
+
+<script lang="ts" setup>
+/**
+ * 上传图片表单控件
+ */
+import { 
+  stringUrlsToUploadedItems, type UploadCoInterface, 
+  type AntUploadRequestOption, type FileInfo, type FileItem 
+} from './UploadImageFormItem';
+import { message, type UploadProps } from 'ant-design-vue';
+import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
+import { type PropType, ref, onMounted, watch } from 'vue';
+import FailImage from '@/assets/images/imageFailed.png';
+
+const props = defineProps({
+  /**
+   * 是否禁用
+   */
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * 预览图加载失败时显示图片
+   */
+  failImage: {
+    type: String,
+    default: () => FailImage,
+  },
+  /**
+   * 上传工厂类
+   */
+  uploadCo: {
+    type: Object as PropType<UploadCoInterface>,
+    default: null,
+  },
+  /**
+   * 上传之前的自定义检查回调
+   * (file: FileItem) => boolean
+   * 如果返回false,将停止上传
+   */
+  beforeUpload: {
+    type: Function,
+    default: null,
+  },
+  /**
+   * 类样式
+   */
+  uploadClass: {},
+  /**
+   * 是否限制单图上传
+   */
+  single: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * single 为false时,限制最多上传图片的数量
+   */
+  maxCount: {
+    type: Number,
+    default: 0,
+  },
+  /**
+   * single 模式下图片显示大小
+   */
+  singleImageSize: {
+    type: Object as PropType<{ width: number, height: number }>,
+    default: () => ({ width: 250, height: 150 })
+  },
+  /**
+   * 参数,可以是单张 string,多张 string[]
+   */
+  value: {},
+  /**
+   * a-upload 其他自定义参数
+   */
+  customProps: {
+    type: Object as PropType<UploadProps>,
+    default: null,
+  },
+});
+
+const emits = defineEmits([
+  'update:value',
+]);
+
+const uploadSubImgList = ref<FileItem[]>([]);
+const uploadingSubImg = ref(false);
+
+onMounted(() => {
+  //将之前上传的图片包括URL设置到已上传列表中
+  if (!props.single) {
+    setTimeout(() => {
+      uploadSubImgList.value = stringUrlsToUploadedItems(props.value instanceof Array ? (props.value as string[] || []) : [])
+    }, 400);
+  }
+});
+
+watch(() => props.value, () => {
+  if (!props.single) {
+    uploadSubImgList.value = stringUrlsToUploadedItems(props.value instanceof Array ? (props.value as string[] || []) : [])
+  }
+});
+
+function handleUpload(requestOption: AntUploadRequestOption) {
+  props.uploadCo?.uploadRequest(requestOption);
+}
+function handleUploadSubImgChange(info: FileInfo) {
+  if (info.file.status === 'uploading') {
+    uploadingSubImg.value = true;
+    return;
+  }
+  if (info.file.status === 'removed') {
+    if (props.single)
+      emits('update:value', '');
+    else
+      emits('update:value', (props.value as string[] || []).filter(url => url != info.file.url));
+    return;
+  }
+  if (info.file.status === 'done') {
+    const url = props.uploadCo?.getUrlByUploadResponse(info.file.response) || '';
+    if (props.single)
+      emits('update:value', url);
+    else
+      emits('update:value', (props.value as string[] || []).concat([ url ]));
+    uploadingSubImg.value = false;
+  }
+  if (info.file.status === 'error') {
+    uploadingSubImg.value = false;
+    message.error('上传失败!' + info.file.response);
+  }
+}
+</script>

+ 3 - 0
src/components/dynamicf/index.ts

@@ -29,6 +29,7 @@ import NumberRange from "./NumberRange.vue";
 import MapPointPicker from "./Map/MapPointPicker.vue";
 import { QuillEditor } from "@vueup/vue-quill";
 import QuillEditorWrapper from "./Editor/QuillEditorWrapper.vue";
+import UploadVideoFormItem from "./UploadVideoFormItem.vue";
 
 export const defaultConfig = {
   internalWidgets: {
@@ -82,6 +83,8 @@ export function registerAllFormComponents() {
     .register('date-time-range', markRaw(WrapperRangePicker), { showTime: true })
     .register('single-image', markRaw(UploadImageFormItem), { single: true })
     .register('mulit-image', markRaw(UploadImageFormItem))
+    .register('single-video', markRaw(UploadVideoFormItem), { single: true })
+    .register('mulit-video', markRaw(UploadVideoFormItem), { single: true })
     .register('actions', markRaw(ActionRender))
     .register('alert', markRaw(Alert))
     .register('string-list', markRaw(SimpleEditDynamicStringListVue))

+ 16 - 6
src/pages/forms/form.vue

@@ -33,7 +33,7 @@
 import InheritorContent from '@/api/inheritor/InheritorContent';
 import type { DataModel } from '@imengyu/js-request-transform';
 import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@imengyu/vue-dynamic-form';
-import { Modal, type FormInstance } from 'ant-design-vue';
+import { message, Modal, type FormInstance } from 'ant-design-vue';
 import { onMounted, ref, toRefs, type PropType } from 'vue';
 import { useRouter } from 'vue-router';
 
@@ -51,7 +51,11 @@ const props = defineProps({
     required: true
   },
   load: {
-    type: Function as PropType<() => Promise<T>>,
+    type: Function as PropType<() => Promise<T|undefined|void>>,
+    default: () => Promise.resolve()
+  },
+  save: {
+    type: Function as PropType<(model: T) => Promise<void>>,
     default: () => Promise.resolve()
   }
 })
@@ -65,17 +69,23 @@ const loadingData = ref(false);
 
 async function handleSubmit() {
   loading.value = true;
+
+  const ref = (form.value?.getFormRef() as FormInstance);
   try {
-    await (form.value?.getFormRef() as FormInstance).validate();
-  } catch {
+    await ref.validate();
+  } catch (e) {
+    message.warn('请填写完整信息');
     loading.value = false;
+    if ((e as any).errorFields)
+      ref.scrollToField((e as any).errorFields[0].name, { block: 'center' })
     return;
   }
   try {
-    await InheritorContent.saveBaseInfo(formModel.value);
+    const result = await InheritorContent.saveBaseInfo(formModel.value);
+    await props.save(formModel.value);
     Modal.success({
       title: '提交成功',
-      content: '您的项目申报已提交成功。请耐心等待审核!',
+      content: result.message,
       onOk() {
         router.push({ path: '/' })
       },

+ 162 - 65
src/pages/forms/ich.vue

@@ -1,21 +1,21 @@
 <template>
-  <!-- 项目申报 -->
-  
+  <!-- 非遗基础表单 -->
   <Form 
     :formModel="formModel"
     :formOptions="formOptions"
     :load="loadData"
+    :save="saveData"
   />
 </template>
 
 <script setup lang="ts">
-import type { IDynamicFormOptions } from '@imengyu/vue-dynamic-form';
+import { ref, type Ref } from 'vue';
+import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
 import Form from './form.vue';
-import { onMounted, ref, type Ref } from 'vue';
 import InheritorContent, { IchInfo } from '@/api/inheritor/InheritorContent';
 import CommonContent from '@/api/CommonContent';
+import type { IDynamicFormOptions } from '@imengyu/vue-dynamic-form';
 import type { SelectProps } from 'ant-design-vue';
-import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
 import type { UploadImageFormItemProps } from '@/components/dynamicf/UploadImageFormItem';
 
 const formModel = ref(new IchInfo()) as Ref<IchInfo>;
@@ -23,36 +23,38 @@ const formOptions = ref<IDynamicFormOptions>({
   formLabelCol: { span: 6 },
   formWrapperCol: { span: 24 },
   formAdditionaProps: {
-    layout: 'vertical'
+    layout: 'vertical',
+    scrollToFirstError: true,
   },
+  formNestNameGenerateType: 'array',
   formItems: [
     {
       type: 'group-flat', label: '非遗信息', name: 'ichInfo',
       childrenColProps: { span: 24 },
       children: [
         { 
-          label: '标题', name: 'title', type: 'text', 
-          additionalProps: { placeholder: '请输入标题' }, 
+          label: '标题', name: 'title', type: 'text',
+          additionalProps: { placeholder: '请输入标题' },
         },
         { 
           label: '级别', name: 'level', type: 'select-id',
-          additionalProps: { 
-            placeholder: '请选择级别', 
-            loadData: async () => (await CommonContent.getCategoryList(2)).map(p => ({ label: p.title, value: p.id, raw: p })) 
+          additionalProps: {
+            placeholder: '请选择级别',
+            loadData: async () => (await CommonContent.getCategoryList(2)).map(p => ({ label: p.title, value: p.id, raw: p }))
           },  
         },
         { 
-          label: '非遗类型', name: 'ichType', type: 'select-id', 
-          additionalProps: { 
-            placeholder: '请选择非遗类型', 
-            loadData: async () => (await CommonContent.getCategoryList(4)).map(p => ({ label: p.title, value: p.id, raw: p })) 
+          label: '非遗类型', name: 'ichType', type: 'select-id',
+          additionalProps: {
+            placeholder: '请选择非遗类型',
+            loadData: async () => (await CommonContent.getCategoryList(4)).map(p => ({ label: p.title, value: p.id, raw: p }))
           },
         },
         { 
-          label: '批次', name: 'batch', type: 'select-id', 
-          additionalProps: { 
-            placeholder: '请选择批次', 
-            loadData: async () => (await CommonContent.getCategoryList(289)).map(p => ({ label: p.title, value: p.id, raw: p })) 
+          label: '批次', name: 'batch', type: 'select-id',
+          additionalProps: {
+            placeholder: '请选择批次',
+            loadData: async () => (await CommonContent.getCategoryList(289)).map(p => ({ label: p.title, value: p.id, raw: p }))
           },
         },
         { label: '简介', name: 'intro', type: 'richtext', additionalProps: { placeholder: '请输入简介' } },
@@ -76,15 +78,37 @@ const formOptions = ref<IDynamicFormOptions>({
         { label: '流行地区', name: 'popularRegion', type: 'text', additionalProps: { placeholder: '请输入流行地区' } },
         { label: '批准时间', name: 'approveTime', type: 'text', additionalProps: { placeholder: '请输入批准时间' } },
         { 
-          label: '代表性图片', name: 'typicalImages', type: 'mulit-image', 
-          additionalProps: { 
-            placeholder: '请上传代表性图片', 
-            tip: '格式要求: JSON数组,包含from、mobile、desc、url字段' 
-          } 
+          type: 'array-object', label: '代表性图片', name: 'typicalImages',
+          formProps: {
+            center: false,
+          },
+          additionalProps: {
+            direction: 'horizontal'
+          },
+          newChildrenObject: (arrayNow) => ({
+            desc: `代表性图片${arrayNow.length+1}`,
+            url: '',
+            from: '',
+            mobile: '',
+          }),
+          children: [
+            { type: 'text', label: '来源', name: 'from', additionalProps: { placeholder: '请输入来源' } },
+            { type: 'text', label: '联系方式', name: 'mobile', additionalProps: { placeholder: '请输入联系方式' } },
+            { type: 'text', label: '说明', name: 'desc', additionalProps: { placeholder: '请输入说明' } },
+            { 
+              label: '图片', name: 'url', type: 'single-image',
+              additionalProps: {
+                name: 'file',
+                placeholder: '请上传图片',
+                uploadCo: useImageSimpleUploadCo(),
+              } as UploadImageFormItemProps,
+            },
+          ]
         },
         { 
-          label: '展厅图片', name: 'ztImage', type: 'single-image', 
-          additionalProps: { 
+          label: '展厅图片', name: 'ztImage', type: 'single-image',
+          additionalProps: {
+            name: 'file',
             placeholder: '请上传展厅图片',
             uploadCo: useImageSimpleUploadCo(),
           } as UploadImageFormItemProps,
@@ -96,66 +120,81 @@ const formOptions = ref<IDynamicFormOptions>({
       childrenColProps: { span: 24 },
       children: [
         { 
-          label: '地区', name: 'region', type: 'select-id', 
-          additionalProps: { 
-            placeholder: '请选择地区', 
+          label: '地区', name: 'region', type: 'select-id',
+          additionalProps: {
+            placeholder: '请选择地区',
             loadData: async () => (await CommonContent.getCategoryList(1)).map(p => ({ label: p.title, value: p.id, raw: p })) ,  
           },
         },
         { 
-          label: '类型', name: 'type', type: 'select', 
-          additionalProps: { 
-            placeholder: '请选择类型', 
+          label: '类型', name: 'type', type: 'select',
+          additionalProps: {
+            placeholder: '请选择类型',
             options: [
-              { text: '文章', value: 1 }, 
-              { text: '音频', value: 2 }, 
-              { text: '视频', value: 3 }, 
-              { text: '相册', value: 4 }, 
-              { text: '数字档案', value: 5 }] 
+              { text: '文章', value: 1 },
+              { text: '音频', value: 2 },
+              { text: '视频', value: 3 },
+              { text: '相册', value: 4 },
+              { text: '数字档案', value: 5 }]
             },  
         },
         { 
-          label: '图片', name: 'image', type: 'single-image', 
-          additionalProps: { 
+          label: '图片', name: 'image', type: 'single-image',
+          additionalProps: {
             placeholder: '请上传图片',
+            name: 'file',
             uploadCo: useImageSimpleUploadCo()
-          } as UploadImageFormItemProps, 
+          } as UploadImageFormItemProps,
         },
         { 
-          label: '图片说明', name: 'imageDesc', type: 'text', 
-          additionalProps: { placeholder: '请输入图片说明' } 
+          label: '图片说明', name: 'imageDesc', type: 'text',
+          additionalProps: { placeholder: '请输入图片说明' }
         },
         { 
-          label: '转自', name: 'from', type: 'text', 
-          additionalProps: { placeholder: '请输入来源' }, 
+          label: '转自', name: 'from', type: 'text',
+          additionalProps: { placeholder: '请输入来源' },
         },
         { 
-          label: '组图', name: 'images', type: 'mulit-image', 
+          label: '组图', name: 'images', type: 'mulit-image',
           hidden: { callback: (_, model) => (model as IchInfo).type !== 4 },
-          additionalProps: { 
+          additionalProps: {
             placeholder: '请上传图片',
             maxCount: 20,
+            name: 'file',
             uploadCo: useImageSimpleUploadCo(),
-          } as UploadImageFormItemProps, 
+          } as UploadImageFormItemProps,
         },
         { 
-          label: '音频', name: 'audio', type: 'text', 
+          label: '音频', name: 'audio', type: 'single-image',
           hidden: { callback: (_, model) => (model as IchInfo).type !== 2 },
-          additionalProps: { placeholder: '请上传音频' }, 
+          additionalProps: {
+            placeholder: '请上传音频',
+            name: 'file',
+            uploadCo: useImageSimpleUploadCo()
+          } as UploadImageFormItemProps,
         },
         { 
-          label: '视频', name: 'video', type: 'text', 
+          label: '视频', name: 'video', type: 'single-video',
           hidden: { callback: (_, model) => (model as IchInfo).type !== 3 },
-          additionalProps: { placeholder: '请上传视频' }, 
+          additionalProps: {
+            placeholder: '请上传视频',
+            name: 'file',
+            uploadCo: useImageSimpleUploadCo()
+          } as UploadImageFormItemProps,  
         },
         { 
-          label: '数字档案', name: 'archives', type: 'text', 
+          label: '数字档案', name: 'archives', type: 'mulit-image',
           hidden: { callback: (_, model) => (model as IchInfo).type !== 5 },
-          additionalProps: { placeholder: '请上传数字档案' }, 
+          additionalProps: {
+            placeholder: '请上传数字档案',
+            maxCount: 20,
+            name: 'file',
+            uploadCo: useImageSimpleUploadCo()
+          } as UploadImageFormItemProps,
         },
         { 
-          label: '标志', name: 'flag', type: 'select', 
-          additionalProps: { 
+          label: '标志', name: 'flag', type: 'select',
+          additionalProps: {
             mode: 'tags',
             options: [  
               { text: '热门', value: 'hot' },
@@ -163,32 +202,85 @@ const formOptions = ref<IDynamicFormOptions>({
               { text: '置顶', value: 'top' },
             ],
             placeholder: '请输入标志' 
-          } as SelectProps, 
+          } as SelectProps,
         },
         { 
-          label: '关键字', name: 'keywords', type: 'select', 
-          additionalProps: { 
+          label: '关键字', name: 'keywords', type: 'select',
+          additionalProps: {
             mode: 'tags',
             options: [],
             placeholder: '请输入关键字,回车添加' 
-          } as SelectProps, 
+          } as SelectProps,
+        },
+        { 
+          label: '描述', name: 'desc', type: 'text-area',
+          additionalProps: { placeholder: '请输入描述' },
         },
         { 
-          label: '描述', name: 'desc', type: 'text-area', 
-          additionalProps: { placeholder: '请输入描述' }, 
+          label: 'TAG', name: 'tags', type: 'text',
+          additionalProps: { placeholder: '请输入TAG' },
         },
         { 
-          label: 'TAG', name: 'tags', type: 'text', 
-          additionalProps: { placeholder: '请输入TAG' }, 
+          label: '备注', name: 'memo', type: 'text-area',
+          additionalProps: { placeholder: '请输入备注' },
         },
+      ]
+    },
+    {
+      type: 'group-object', label: '扩展信息', name: 'expandInfo',
+      childrenColProps: { span: 24 },
+      children: [
         { 
-          label: '备注', name: 'memo', type: 'text-area', 
-          additionalProps: { placeholder: '请输入备注' }, 
+          type: 'select-id', label: '保护级别', name: 'protectLevel', 
+          additionalProps: { 
+            placeholder: '请选择保护级别', 
+            loadData: async () => (await CommonContent.getCategoryList(171)).map(p => ({ label: p.title, value: p.id, raw: p })) 
+          } 
         },
+        { type: 'text', label: '其他名称', name: 'otherNames', additionalProps: { placeholder: '请输入其他名称' } },
+        { type: 'text', label: '实践方式', name: 'practice', additionalProps: { placeholder: '请输入实践方式' } },
+        { type: 'text', label: '表现形式', name: 'expression', additionalProps: { placeholder: '请输入表现形式' } },
+        { type: 'text', label: '遗产持有者', name: 'holders', additionalProps: { placeholder: '请输入遗产持有者' } },
+        { type: 'text', label: '列入名录时间', name: 'joinday', additionalProps: { placeholder: '请选择列入名录时间' } },
+        { type: 'text', label: '形成时间', name: 'formation', additionalProps: { placeholder: '请选择形成时间' } },
+        { type: 'text', label: '分布区域', name: 'area', additionalProps: { placeholder: '请输入分布区域' } },
+        { type: 'text', label: '源流', name: 'origin', additionalProps: { placeholder: '请输入源流' } },
+        { type: 'text', label: '主要特点', name: 'feature', additionalProps: { placeholder: '请输入主要特点' } },
+        { type: 'text', label: '重要功能', name: 'functions', additionalProps: { placeholder: '请输入重要功能' } },
+        { type: 'text', label: '代表作品', name: 'works', additionalProps: { placeholder: '请输入代表作品' } },
+        { type: 'text', label: '保护单位', name: 'unit', additionalProps: { placeholder: '请输入保护单位' } },
+        { type: 'text', label: '保护情况', name: 'protect', additionalProps: { placeholder: '请输入保护情况' } },
+        { type: 'text-area', label: '综合概述', name: 'overview', additionalProps: { placeholder: '请输入综合概述' } },
+        { type: 'text', label: '代表人物', name: 'figures', additionalProps: { placeholder: '请输入代表人物' } },
+        { type: 'text', label: '传承群体', name: 'group', additionalProps: { placeholder: '请输入传承群体' } },
+        { type: 'text', label: '传承方式', name: 'inherit', additionalProps: { placeholder: '请输入传承方式' } },
+        { type: 'text', label: '发展现状', name: 'develop', additionalProps: { placeholder: '请输入发展现状' } },
+        { type: 'text', label: '相关民俗', name: 'customs', additionalProps: { placeholder: '请输入相关民俗' } },
+        { type: 'text', label: '相关信仰', name: 'religion', additionalProps: { placeholder: '请输入相关信仰' } },
+        { type: 'text', label: '文化空间', name: 'space', additionalProps: { placeholder: '请输入文化空间' } },
+        { type: 'text', label: '自然环境', name: 'natural', additionalProps: { placeholder: '请输入自然环境' } },
+        { type: 'text', label: '社会环境', name: 'social', additionalProps: { placeholder: '请输入社会环境' } },
+        { type: 'text', label: '历史沿革', name: 'history', additionalProps: { placeholder: '请输入历史沿革' } },
+        { type: 'text', label: '续存状态', name: 'existence', additionalProps: { placeholder: '请输入续存状态' } },
+        { type: 'text', label: '价值', name: 'meaning', additionalProps: { placeholder: '请输入价值' } },
+        { type: 'text', label: '相关实物', name: 'prop', additionalProps: { placeholder: '请输入相关实物' } },
+        { type: 'text', label: '其他基本情况', name: 'other', additionalProps: { placeholder: '请输入其他基本情况' } },
+        { type: 'mulit-image', label: '相关图片', name: 'images', additionalProps: { placeholder: '请上传相关图片', uploadCo: useImageSimpleUploadCo() } },
+        { type: 'text', label: '相关习俗', name: 'folkCulture', additionalProps: { placeholder: '请输入相关习俗' } },
+        { type: 'text', label: '文物古迹', name: 'culturalRelic', additionalProps: { placeholder: '请输入文物古迹' } },
+        { type: 'text', label: '文献资料', name: 'literature', additionalProps: { placeholder: '请输入文献资料' } },
+        { type: 'text', label: '组织机构', name: 'organization', additionalProps: { placeholder: '请输入组织机构' } },
+        { type: 'text-area', label: '项目描述', name: 'description', additionalProps: { placeholder: '请输入项目描述' } },
+        { type: 'text-area', label: '简介', name: 'desc', additionalProps: { placeholder: '请输入简介' } },
+        { type: 'text', label: '申报地区', name: 'declarationRegion', additionalProps: { placeholder: '请输入申报地区' } },
+        { type: 'text', label: '流行地区', name: 'popularRegion', additionalProps: { placeholder: '请输入流行地区' } },
       ]
     },
   ],
   formRules: {
+    expandInfo: {
+      protectLevel: [{ required: true, message: '请选择保护级别' }],
+    },
     title: [{ required: true, message: '请输入标题' }],
     region: [{ required: true, message: '请选择地区' }],
     type: [{ required: true, message: '请选择类型' }],
@@ -201,5 +293,10 @@ const formOptions = ref<IDynamicFormOptions>({
 
 async function loadData() {
   formModel.value = await InheritorContent.getIchInfo();
+  formModel.value.expandInfo = await InheritorContent.getIchExpandInfo();
 }
+async function saveData(model: IchInfo) {
+  await InheritorContent.saveExpandInfo(model.expandInfo);
+}
+
 </script>