瀏覽代碼

优化表单

快乐的梦鱼 2 月之前
父節點
當前提交
6bb13d1370

+ 1 - 0
src/App.vue

@@ -43,6 +43,7 @@ configTheme((theme) => {
 <style>
 	/*每个页面公共css */
   @import "@/static/css/font.css";
+  @import "@/components/index.scss";
 
   page {
     background: #F8F8F8;

+ 5 - 1
src/common/components/dynamicf/ComponentRender.vue

@@ -19,7 +19,7 @@
   </template>
   <!-- 业务代码结束 -->
   <template v-else>
-    <text>Fallback: unknow form type '{{ item.type }}' item: {{  JSON.stringify(item) }}</text>
+    <text>Fallback: unknow form type '{{ item.type }}' item: {{ name }}</text>
   </template>
 </template>
 
@@ -45,6 +45,10 @@ const props = defineProps({
     type: Object,
     default: () => ({})
   },
+  name: {
+    type: String,
+    default: '',
+  },
 });
 const emit = defineEmits(['update:modelValue']);
 

+ 162 - 166
src/components/dynamic/DynamicFormControl.vue

@@ -9,30 +9,17 @@
   </text>
   <Field
     ref="formItemRef"
-    v-else-if="item.type === 'text'"
+    v-else-if="item.type && filedInternalTypes.includes(item.type)"
     :label="label"
-    :name="item.fullName"
+    :multiline="item.type === 'textarea'"
+    :tags="item.type.endsWith('-tag')"
+    :name="item.name"
     :modelValue="model"
     @update:modelValue="onValueChanged"
     :maxlength="260"
     :showBottomBorder="!isLast"
     :required="Boolean(item.rules?.length)"
-    v-bind="{ 
-      ...params,
-      ...extraDefine?.itemProps || {},
-      ...item.formProps,
-    }"
-  />
-  <Field
-    ref="formItemRef"
-    v-else-if="item.type === 'textarea'"
-    multiline
-    :label="label"
-    :name="item.fullName"
-    :modelValue="model"
-    :showBottomBorder="!isLast"
-    :required="Boolean(item.rules?.length)"
-    @update:modelValue="onValueChanged"
+    :rules="item.rules"
     v-bind="{ 
       ...params,
       ...extraDefine?.itemProps || {},
@@ -43,175 +30,178 @@
     v-else
     ref="formItemRef"
     :label="label"
-    :name="item.fullName"
+    :name="item.name"
     :required="Boolean(item.rules?.length)"
     :showRightArrow="extraDefine?.needArrow"
     :showBottomBorder="!isLast"
     :requireChildRef="() => itemRef"
+    :rules="item.rules"
     v-bind="{ 
       ...extraDefine?.itemProps || {},
       ...item.formProps,
     }"
   >
-    <!-- <text>fullName: {{item.fullName}}</text> -->
-    <template v-if="item.type === 'custom'">
-      <slot name="formCeil"
-        :name="item.fullName"
-        :item="item"
-        :model="model"
-        :onModelUpdate="onValueChanged"
-        :rawModel="rawModel"
-        :parentModel="parentModel"
-        :parent="parent"
-        :rules="item.rules"
-        :disabled="disabled"
-      />
-    </template>
-    <template v-else-if="item.type === 'number'">
-      <Stepper
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="params"
-      />
-    </template>
-    <template v-else-if="item.type === 'switch'">
-      <Switch
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="params"
-      />
-    </template>
-    <template v-else-if="item.type === 'radio-value'">
-      <RadioValue
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any as RadioValueProps)"
-      />
-    </template>
-    <template v-else-if="item.type === 'radio-id'">
-      <RadioIdField
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any as RadioIdFieldProps)"
-      />
-    </template>
-    <template v-else-if="item.type === 'select'">
-      <view>
-        <NaPickerField 
+    <!-- <text>fullName: {{item.name}}</text> -->
+    <slot name="insertion">
+      <template v-if="item.type === 'custom'">
+        <slot name="formCeil"
+          :name="item.fullName"
+          :item="item"
+          :model="model"
+          :onModelUpdate="onValueChanged"
+          :rawModel="rawModel"
+          :parentModel="parentModel"
+          :parent="parent"
+          :rules="item.rules"
+          :disabled="disabled"
+        />
+      </template>
+      <template v-else-if="item.type === 'number'">
+        <Stepper
           ref="itemRef"
           :modelValue="model"
           @update:modelValue="onValueChanged"
-          v-bind="(params as any as PickerFieldProps)"
+          v-bind="params"
         />
-      </view>
-    </template>
-    <template v-else-if="item.type === 'rate'">
-      <Rate
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any as RateProps)"
-      />
-    </template>
-    <template v-else-if="item.type === 'uploader'">
-      <UploaderField
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any as UploaderFieldProps)"
-      />
-    </template>
-    <template v-else-if="item.type === 'select-id'">
-      <PickerIdField 
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any as PickerIdFieldProps)"
-      />
-    </template>
-    <template v-else-if="item.type === 'select-city'">
-      <PickerCityField
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params as any)"
-      />
-    </template>
-    <template v-else-if="item.type === 'select-lonlat'">
-      <PickerLonlat
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="(v:any) => onValueChanged(v)"
-        v-bind="params"
-      />
-    </template>
-    <template v-else-if="item.type === 'check-box'">
-      <CheckBox
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="params"
-      />
-    </template>
-    <template v-else-if="item.type === 'check-box-list'"> 
-      <CheckBoxList
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="(params)"
-      />
-    </template>
-    <template v-else-if="item.type === 'check-box-int'"> 
-      <CheckBoxToInt
-        ref="itemRef"
-        :modelValue="model"
-        @update:modelValue="onValueChanged"
-        v-bind="params"
-      />
-    </template>
-
-    <template v-else-if="item.type === 'datetime'">
-      <view>
-        <DateTimePickerField
+      </template>
+      <template v-else-if="item.type === 'switch'">
+        <Switch
           ref="itemRef"
-          :value="model"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
           v-bind="params"
-          @update:modelValue="(e: any) => onValueChanged(e)"
         />
-      </view>
-    </template>
-    <template v-else-if="item.type === 'time'">
-      <view>
-        <TimePickerField
+      </template>
+      <template v-else-if="item.type === 'radio-value'">
+        <RadioValue
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any as RadioValueProps)"
+        />
+      </template>
+      <template v-else-if="item.type === 'radio-id'">
+        <RadioIdField
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any as RadioIdFieldProps)"
+        />
+      </template>
+      <template v-else-if="item.type === 'select'">
+        <view>
+          <NaPickerField 
+            ref="itemRef"
+            :modelValue="model"
+            @update:modelValue="onValueChanged"
+            v-bind="(params as any as PickerFieldProps)"
+          />
+        </view>
+      </template>
+      <template v-else-if="item.type === 'rate'">
+        <Rate
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any as RateProps)"
+        />
+      </template>
+      <template v-else-if="item.type === 'uploader'">
+        <UploaderField
           ref="itemRef"
-          :value="model"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any as UploaderFieldProps)"
+        />
+      </template>
+      <template v-else-if="item.type === 'select-id'">
+        <PickerIdField 
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any as PickerIdFieldProps)"
+        />
+      </template>
+      <template v-else-if="item.type === 'select-city'">
+        <PickerCityField
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params as any)"
+        />
+      </template>
+      <template v-else-if="item.type === 'select-lonlat'">
+        <PickerLonlat
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="(v:any) => onValueChanged(v)"
           v-bind="params"
-          @update:modelValue="(e: any) => onValueChanged(e)"
         />
-      </view>
-    </template>
-    <template v-else-if="item.type === 'date'">
-      <view>
-        <DatePickerField
+      </template>
+      <template v-else-if="item.type === 'check-box'">
+        <CheckBox
           ref="itemRef"
-          :value="model"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
           v-bind="params"
-          @update:modelValue="(e: any) => onValueChanged(e)"
         />
-      </view>
-    </template>
-    <ComponentRender v-else
-      ref="itemRef"
-      :modelValue="model"
-      @update:modelValue="onValueChanged"
-      :params="params"
-      :item="item"
-      :isLast="isLast"
-    />
+      </template>
+      <template v-else-if="item.type === 'check-box-list'"> 
+        <CheckBoxList
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="(params)"
+        />
+      </template>
+      <template v-else-if="item.type === 'check-box-int'"> 
+        <CheckBoxToInt
+          ref="itemRef"
+          :modelValue="model"
+          @update:modelValue="onValueChanged"
+          v-bind="params"
+        />
+      </template>
+      <template v-else-if="item.type === 'datetime'">
+        <view>
+          <DateTimePickerField
+            ref="itemRef"
+            :modelValue="model"
+            v-bind="params"
+            @update:modelValue="(e: any) => onValueChanged(e)"
+          />
+        </view>
+      </template>
+      <template v-else-if="item.type === 'time'">
+        <view>
+          <TimePickerField
+            ref="itemRef"
+            :modelValue="model"
+            v-bind="params"
+            @update:modelValue="(e: any) => onValueChanged(e)"
+          />
+        </view>
+      </template>
+      <template v-else-if="item.type === 'date'">
+        <view>
+          <DatePickerField
+            ref="itemRef"
+            :modelValue="model"
+            v-bind="params"
+            @update:modelValue="(e: any) => onValueChanged(e)"
+          />
+        </view>
+      </template>
+      <ComponentRender v-else
+        ref="itemRef"
+        :modelValue="model"
+        @update:modelValue="onValueChanged"
+        :params="params"
+        :item="item"
+        :name="name"
+        :isLast="isLast"
+      />
+    </slot>
   </Field>
 </template>
 
@@ -254,6 +244,12 @@ export interface FormCeilProps {
   additionalProps: Record<string, unknown>,
 }
 
+const filedInternalTypes = [
+  'text',
+  'textarea',
+  'text-tag',
+]
+
 const props = defineProps({	
   item: {
     type: Object as PropType<IDynamicFormItem>,
@@ -303,6 +299,7 @@ const props = defineProps({
   },
 });
 
+const emit = defineEmits([ 'update:model' ]);
 const formItemRef = ref();
 const finalOptions = inject<Ref<IDynamicFormOptions>>('finalOptions'); 
 const globalParams = inject<Ref<IDynamicFormObject>>('globalParams');
@@ -346,7 +343,6 @@ const params = computed(() => {
 const label = computed(() => evaluateCallback(props.item.label) as string)
 
 const itemRef = ref();
-const emit = defineEmits([ 'update:model' ]);
  
 function onValueChanged(v: any) {
   props.item.watch?.(props.model, v, props.rawModel, getComponentRef());

+ 15 - 3
src/components/dynamic/group/FormGroup.vue

@@ -9,7 +9,10 @@
   ]">
     <h5 v-if="title" @click="collapsible ? collapsed = !collapsed : null">
       <span>{{ title }}</span>
-      <IconDown v-if="collapsible" class="collapsible-icon" />
+      <view>
+        <span v-if="collapsed">点击展开更多</span>
+        <IconDown v-if="collapsible" class="collapsible-icon" />
+      </view>
     </h5>
     <Row v-if="!collapsed" :justify="(justify as any)" :gutter="gutter">
       <slot />
@@ -79,8 +82,6 @@ const collapsed = ref(props.collapsed);
   border-radius: var(--dynamic-form-border-radius);
 
   &.collapsed {
-    padding: 0;
-
     .collapsible-icon {
       transform: rotate(0deg);
     }
@@ -105,6 +106,17 @@ const collapsed = ref(props.collapsed);
     color: var(--dynamic-form-text-color);
     margin: 0;
     margin-bottom: 12px;
+
+    view {
+      display: flex;
+      align-items: center;
+
+      span {
+        font-size: 11px;
+        margin-right: 10rpx;
+        color: var(--dynamic-form-secondary-color);
+      }
+    }
   }
 
   &.plain {

+ 1 - 9
src/components/dynamic/nest/DynamicFormItemContainer.vue

@@ -345,7 +345,6 @@ const props = defineProps({
 
 const containerTypes = ['object', 'object-group', 'array-single','group-array','flat-simple','flat-group'];
 const isContainer = computed(() => props.item.type && containerTypes.includes(props.item.type));
-const showContainerEmptyNote = computed(() => isContainer.value && (!props.item.children || props.item.children.length === 0));
 
 defineEmits([	'update:model' ]);
 defineSlots<{
@@ -368,14 +367,7 @@ const editmode = inject('editmode', false);
 const formName = inject('formName', '')
 
 //判断是否显示当前条目
-const isShow = computed(() => {
-  if (props.item.show !== undefined) {
-    const show = evaluateCallback(props.item.show);
-    if (show === false)
-      return false;
-  }
-  return true;
-});
+const isShow = computed(() => props.item.show === undefined || evaluateCallback(props.item.show));
 
 //处理默认值
 const finalModel = computed(() => {

+ 2 - 2
src/components/dynamic/wrappers/CheckBoxToInt.vue

@@ -1,7 +1,7 @@
 <template>
   <CheckBox
-    :checked="checked"
-    @update:checked="(v: boolean) => $emit('update:modelValue', v ? checkedValue : uncheckedValue)"
+    :modelValue="checked"
+    @update:modelValue="(v: boolean) => $emit('update:modelValue', v ? checkedValue : uncheckedValue)"
     :disabled="disabled"
     :text="text"
   />

+ 10 - 0
src/components/dynamic/wrappers/PickerIdField.vue

@@ -2,6 +2,7 @@
   <PickerField 
     :columns="[ loader.data.value || [] ]"
     v-bind="$attrs"
+    @update:modelValue="handleUpdateModelValue"
   />
 </template>
 
@@ -11,6 +12,7 @@ import type { PickerIdFieldProps, PickerIdFieldOption } from './PickerIdField';
 import { useDataLoader } from '@/components/composeabe/DataLoader';
 
 const props = defineProps<PickerIdFieldProps>();
+const emit = defineEmits(['update:modelValue']);
 const loader = useDataLoader<PickerIdFieldOption[]>(async () => {
   const res = await props.loadData();
   return ([{
@@ -21,6 +23,14 @@ const loader = useDataLoader<PickerIdFieldOption[]>(async () => {
   immediate: true,
 });
 
+function handleUpdateModelValue(value: (string|number)[]) {
+  if (value.length === 1) {
+    emit('update:modelValue', value[0]);
+    return;
+  }
+  emit('update:modelValue', value);
+}
+
 defineExpose({
   reload() {
     loader.load();

+ 87 - 8
src/components/form/Field.vue

@@ -51,8 +51,40 @@
         <slot name="leftButton" />
         <slot name="control" />
         <slot>
+          <FlexRow v-if="tags" wrap align="center" :gap="10">
+            <Tag 
+              v-for="(tag, index) in tagSplited"
+              :key="index"
+              :text="tag"
+              color="primary"
+              size="small"
+              closeable
+              @close="onTagDelete(tag)"
+            />
+            <input 
+              ref="inputRef"
+              :style="{
+                ...themeStyles.input.value,
+                ...inputStyle,
+                ...(focused ? activeInputStyle : {}),
+                color: themeContext.resolveThemeColor(disabled ? inputDisableColor : (error ? errorTextColor : inputColor)),
+              }"
+              :value="tagInputString"
+              :placeholder="placeholder"
+              :placeholder-style="`color: ${themeContext.resolveThemeColor(error ? errorTextColor : placeholderTextColor)}`"
+              confirm-type="done"
+              type="text"
+              :maxlength="200"
+              :disabled="disabled"
+              :readonly="readonly"
+              @focus="onFocus"
+              @blur="onBlur"
+              @input="onTagInputInput"
+              @confirm="onTagInputConfirm"
+            />
+          </FlexRow>
           <textarea 
-            v-if="multiline"
+            v-else-if="multiline"
             ref="inputRef"
             :style="{
               ...themeStyles.input.value,
@@ -171,9 +203,10 @@ import Icon from '../basic/Icon.vue';
 import IconButton from '../basic/IconButton.vue';
 import FlexCol from '../layout/FlexCol.vue';
 import FlexRow from '../layout/FlexRow.vue';
-import FlexView from '../layout/FlexView.vue';
 import Touchable from '../feedback/Touchable.vue';
 import type { TextProps } from '../basic/Text.vue';
+import type { RuleItem } from 'async-validator';
+import Tag from '../display/Tag.vue';
 
 
 export interface FieldInstance {
@@ -433,6 +466,21 @@ export interface FieldProps {
    * 右边箭头的自定义属性
    */
   rightArrowProps?: IconProps;
+  /**
+   * 当前条目的校验规则
+   */
+  rules?: RuleItem[],
+
+  /**
+   * 是否显示输入标签模式
+   * @default false
+   */
+  tags?: boolean,
+  /**
+   * 输入标签模式下,标签之间的连接符
+   * @default ','
+   */
+  tagJoinType?: string,
 
   requireChildRef?: () => any,
 }
@@ -484,6 +532,8 @@ const props = withDefaults(defineProps<FieldProps>(), {
   autoHeight: false,
   maxLength: 100,
   modelValue: undefined,
+  tags: false,
+  tagJoinType: ',',
   errorIcon: () => propGetThemeVar('FieldErrorIcon', 'prompt'),
   errorIconProps: () => propGetThemeVar('FieldErrorIconProps', propGetFormContext()?.fieldProps.value?.errorIconProps ?? {}),
   extraMessage: '',
@@ -496,6 +546,10 @@ const props = withDefaults(defineProps<FieldProps>(), {
 
 const formContextProps = inject<FormContext>('formContext', null as any);
 const error = ref<string|null>(null);
+const finalErrorMessage = computed(() => {
+  return props.errorMessage || error.value || '';
+})
+const errorState = computed(() => Boolean(finalErrorMessage.value));
 const childOnClickListener = ref<(() => void)|undefined>(undefined);
 
 //Context for parent
@@ -503,6 +557,7 @@ const formItemInternalContext : FormItemInternalContext = {
   getExpectedRef: () => {
     return props.requireChildRef?.() ?? fieldInstance;
   },
+  getItemRules: () => props.rules ?? [],
   getValidateTrigger: () => props.validateTrigger || formContextProps?.validateTrigger.value || 'submit',
   getFieldName: () => props.name ?? '',
   setErrorState(errorMessage) { error.value = errorMessage; },
@@ -515,6 +570,7 @@ const formItemInternalContext : FormItemInternalContext = {
 };
 //Context for custom children
 const formItemContext : FormItemContext = {
+  errorState: errorState,
   getFieldName: () => {
     return props.name ?? uniqueId;
   },
@@ -623,13 +679,8 @@ const wordLimitText = computed(() => {
   else
     wordString += `字`;
   return wordString
-})
-const finalErrorMessage = computed(() => {
-  return props.errorMessage || error.value || '';
-})
-
+});
 const inputValue = ref();
-
 const focused = ref(false);
 const inputRef = ref();
 
@@ -695,6 +746,34 @@ function onClick() {
     fieldInstance.clearValidate();
 }
 
+//#region 标签输入框事件处理
+
+const tagInputString = ref('');
+const tagSplited = computed<string[]>(() => {
+  return (props.modelValue  as string|| '').split(props.tagJoinType).filter(x => x);
+});
+
+function onTagDelete(tag: string) {
+  const tags = tagSplited.value.concat();
+  const index = tags.indexOf(tag);
+  if (index !== -1) {
+    tags.splice(index, 1);
+    emitChangeText(tags.join(props.tagJoinType));
+  }
+}
+function onTagInputInput(e: any) {
+  focused.value = true;
+  tagInputString.value = e.detail.value;
+}
+function onTagInputConfirm() {
+  if (tagInputString.value && !tagSplited.value.includes(tagInputString.value)) {
+    emitChangeText(props.modelValue + props.tagJoinType + tagInputString.value);
+    tagInputString.value = '';
+  }
+}
+
+//#endregion 
+
 const fieldInstance : FieldInstance = {
   focus() {
     inputRef.value?.focus();

+ 11 - 5
src/components/form/Form.vue

@@ -10,7 +10,7 @@
 import { onMounted, provide, ref, toRefs } from 'vue';
 import { ObjectUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 import Schema from 'async-validator';
-import type { Rule, Rules, ValidateError } from 'async-validator';
+import type { Rule, RuleItem, Rules, ValidateError } from 'async-validator';
 import type { ViewStyle } from '../theme/ThemeDefine';
 import type { FieldProps } from './Field.vue';
 import type { FormContext, FormItemInternalContext } from './FormContext';
@@ -208,7 +208,7 @@ const formContext : FormContext = {
     return accessFormModel(item.getFieldName(), false, undefined);
   },
   getItemRequieed: (item: FormItemInternalContext) => {
-    return checkRuleRequired(item.getFieldName());
+    return checkRuleRequired(getItemRule(item.getFieldName()));
   },
   clearValidate: (item: FormItemInternalContext) => {
     clearValidate(item.getFieldName());
@@ -226,8 +226,15 @@ const formContext : FormContext = {
   fieldProps,
 };
 
-function checkRuleRequired(name: string) {
+function getItemRule(name: string) {
   const rule = props.rules?.[name];
+  const itemRules = formItems.value.get(name)?.getItemRules() || [];
+  return [
+    ...itemRules, 
+    ...(rule instanceof Array ? rule : [rule])
+  ].filter((r): r is RuleItem => r !== undefined) as RuleItem[];
+}
+function checkRuleRequired(rule: Rule|undefined) {
   if (rule instanceof Array)
     return rule.find((r) => r.required === true) !== undefined;
   else if (rule)
@@ -290,7 +297,7 @@ function validate(name?: string|string[]) {
 
   //筛选需要验证的字段
   formItems.value.forEach((_, key) => {
-    const rule = rules.value ? rules.value[key] : undefined;
+    const rule = getItemRule(key);
     if (rule) {
       if (typeof name === 'string') {
         if (name === key) filteredRules[key] = rule;
@@ -303,7 +310,6 @@ function validate(name?: string|string[]) {
   
   //获取当前参数
   const nowValues = model.value;
-
   //开始验证
   return new Promise<void>((resolve, reject) => {
     const validator = new Schema(filteredRules as Rules);

+ 4 - 1
src/components/form/FormContext.ts

@@ -1,5 +1,6 @@
-import { type InjectionKey, inject, provide, type Ref, computed, ref, watch } from "vue";
+import { type InjectionKey, inject, provide, type Ref, computed, ref, watch, type ComputedRef } from "vue";
 import type { FieldProps } from "./Field.vue";
+import type { RuleItem } from "async-validator";
 import { useCellContext } from "../basic/CellContext";
 
 /**
@@ -14,6 +15,7 @@ export type ValidTrigger = "blur" | "change" | "submit";
  * 表单项上下文
  */
 export type FormItemContext = {
+  errorState: ComputedRef<boolean>,
   getFieldName: () => string,
   /**
    * 触发表单条目获得焦点事件
@@ -53,6 +55,7 @@ export type FormItemInternalContext = {
    * @returns 
    */
   getExpectedRef: () => any,
+  getItemRules: () => RuleItem[],
   getFieldName: () => string,
   getValidateTrigger: () => ValidTrigger;
   getUniqueId: () => string,

+ 5 - 0
src/components/index.scss

@@ -44,3 +44,8 @@ wx-action-sheet-item {
   --dynamic-form-shadow-error-color: rgba(232, 74, 74, 0.2);
   --dynamic-form-item-nest-margin: 20px;
 }
+
+.form-static-text {
+  color: var(--dynamic-form-secondary-color);
+  margin: 10px 0;
+}

+ 1 - 1
src/pages/dig/forms/common.vue

@@ -41,7 +41,7 @@ const formModel = ref(new CommonInfoModel()) as Ref<CommonInfoModel>;
 const formDefine = ref<IDynamicFormOptions>({
   formItems: [],
   formAdditionaProps: {
-    labelWidth: '200rpx',
+    labelWidth: '220rpx',
     labelAlign: 'left',
     innerStyle: {
       borderRadius: '20rpx',

+ 162 - 54
src/pages/dig/forms/data/common.ts

@@ -3,61 +3,169 @@ import type { IDynamicFormOptions, IDynamicFormRef } from "@/components/dynamic"
 import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
 import type { Ref } from "vue";
 
-export const villageCommonContent : (model: Ref<IDynamicFormRef>) => IDynamicFormOptions = (model) => ({
-  formItems: [
-    {
-      name: 'a',
-      type: 'flat-group',
-      childrenColProps: { span: 24 },
-      children: [
-        {
-          label: '文章标题',
-          name: 'title',
-          type: 'text',
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入标题',
+export function villageCommonContent (model: Ref<IDynamicFormRef>, options: { 
+  title: string,
+  showTitle?: boolean,
+  showContent?: boolean,
+  contentKey?: string,
+} = {
+  title: '文章',
+  showTitle: true,
+}) : IDynamicFormOptions {
+  return {
+    formItems: [
+      {
+        name: 'a',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          ...(options.showTitle ? [{
+            label: `${options.title}标题`,
+            name: 'title',
+            type: 'text',
+            defaultValue: '',
+            additionalProps: {
+              placeholder: `请输入${options.title}标题`,
+            },
+            rules: [{
+              required: true,
+              message: `请输入${options.title}标题`,
+            }]
+          }]: []),
+          ...(options.showContent !== false ? [{
+            label: `${options.title}介绍`,
+            name: options.contentKey || 'content',
+            type: 'richtext',
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入介绍内容正文',
+              maxLength: 1000,
+            },
+            rules: [{
+              required: true,
+              message: '请输入介绍内容',
+            }]
+          }]: []),
+          {
+            label: '来源(可选)',
+            name: 'source',
+            type: 'text',
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '如果是转载文章可以输入来源',
+            },
           },
-          rules: [{
-            required: true,
-            message: '请输入文章标题',
-          }]
-        },
-        {
-          label: '主要内容',
-          name: 'content',
-          type: 'richtext',
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入介绍内容',
-            maxLength: 1000,
+        ],
+      },
+      {
+        name: 'b',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+            
+          {
+            label: `${options.title}相关图片(可选)`,
+            name: 'images',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/common'),
+              maxFileSize: 1024 * 1024 * 20,
+              maxUploadCount: 20,
+            } as UploaderFieldProps,
+            rules: [],
+            formProps: {
+              extraMessage: '建议分辨率:1920*1080以上',
+            },
+          },
+          {
+            label: `${options.title}介绍视频(可选)`,
+            name: 'video',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/video'),
+              maxFileSize: 1024 * 1024 * 100,
+              single: true,
+              chooseType: 'video',
+            } as UploaderFieldProps,
+            formProps: {
+              extraMessage: '您还可以上传介绍视频,建议使用MP4格式1080P分辨率',
+            },
+            rules: []
           },
-          rules: [{
-            required: true,
-            message: '请输入内容',
-          }]
+
+        ],
+      },
+      {
+        name: 'c',
+        type: 'flat-group',
+        label: '额外信息(可选)',
+        formProps: {
+          showLabel: false,
         },
-        {
-          label: '相关图片(可选)',
-          name: 'images',
-          type: 'uploader',
-          defaultValue: '',
-          additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/common'),
-            maxFileSize: 1024 * 1024 * 20,
-            maxUploadCount: 20,
-          } as UploaderFieldProps,
-          rules: []
+        additionalProps: {
+          collapsible: true,
+          collapsed: true,
         },
-      ],
-    },
-    {
-      name: 'b',
-      type: 'flat-group',
-      childrenColProps: { span: 24 },
-      children: [
-          
-      ],
-    },
-  ]
-});
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '',
+            name: 'extra',
+            type: 'static-text',
+            defaultValue: '',
+            additionalProps: {
+              text: '额外信息用于采集系统内部处理,可以不填写',
+            },
+          },
+          {
+            label: `${options.title}附件(可选)`,
+            name: 'archives',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/archives'),
+              maxFileSize: 1024 * 1024 * 20,
+              maxUploadCount: 20,
+            } as UploaderFieldProps,
+            rules: [],
+            formProps: {
+              extraMessage: '建议上传项目相关文件,如项目合同、项目计划等',
+            },
+          },
+          {
+            label: `${options.title}其他附件(可选)`,
+            name: 'annex',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/annex'),
+              maxFileSize: 1024 * 1024 * 20,
+              maxUploadCount: 20,
+            } as UploaderFieldProps,
+            rules: [],
+            formProps: {
+              extraMessage: '建议上传项目相关文件,如项目合同、项目计划等',
+            },
+          },
+          {
+            label: `关键字`,
+            name: 'keywords',
+            type: 'text-tag',
+            defaultValue: '',
+            rules: [],
+            additionalProps: {
+              placeholder: '可以输入关键字',
+              tagJoinType: ';',
+            },
+            formProps: {
+              extraMessage: '用于系统优化关键字搜索',
+            },
+          },
+        ],
+      }
+    ],
+
+  }
+}

+ 118 - 61
src/pages/dig/forms/data/cultural.ts

@@ -1,69 +1,126 @@
-import VillageInfoApi, { VillageBulidingInfo } from "@/api/inhert/VillageInfoApi";
-import type { SingleForm } from "../forms";
+import VillageInfoApi, { CommonInfoModel, VillageBulidingInfo } from "@/api/inhert/VillageInfoApi";
+import type { GroupForm, SingleForm } from "../forms";
 import type { IDynamicFormItemCallbackAdditionalProps } from "@/components/dynamic";
 import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
 import type { FieldProps } from "@/components/form/Field.vue";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { UploaderInstance } from "@/components/form/Uploader.vue";
+import type { UploaderFieldProps, UploaderFieldInstance } from "@/components/form/UploaderField.vue";
+import { villageCommonContent } from "./common";
 
-export const villageInfoFolkCultureForm : SingleForm = [VillageBulidingInfo, () => ({
-  formItems: [
-    {
-      label: '名称',
-      name: 'name',
-      type: 'text',
-      defaultValue: '',
-      additionalProps: {
-        placeholder: '请输入名称',
+export function villageInfoFolkCultureForm(title: string) : SingleForm {
+  return [VillageBulidingInfo, (m) => ({
+    formItems: [
+      {
+        label: title + '名称',
+        name: 'name',
+        type: 'text',
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入名称',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入名称',
+        }]
+      }, 
+      {
+        label: '村落非遗项目', 
+        name: 'ichId',
+        type: 'select-id', 
+        defaultValue: null,
+        additionalProps: {
+          loadData: {
+            callback: (m, r, p, i) => async () => 
+              (await VillageInfoApi.getList(
+                'ich', undefined, undefined,
+                i.formGlobalParams.villageId,
+                i.formGlobalParams.villageVolunteerId
+              )).map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+          },
+        } as IDynamicFormItemCallbackAdditionalProps<PickerIdFieldProps>,
+        formProps: { showRightArrow: true } as FieldProps,
+        rules: [],
+      },
+      ...villageCommonContent(m, {
+        title: title,
+        showTitle: false,
+        contentKey: 'details'
+      }).formItems
+    ]
+  })];
+}
+
+export const villageInfoCulture : GroupForm = {
+  [1]: [CommonInfoModel, (m) => villageCommonContent(m, {
+    title: '建村历史',
+    showTitle: true
+  })],
+  [2]: [CommonInfoModel, (m) => villageCommonContent(m, {
+    title: '历史事件',
+    showTitle: true
+  })],  
+  [3]: [CommonInfoModel, (m) => ({
+    formItems: [
+      ...(villageCommonContent(m, {
+        title: '历史文献',
+        showTitle: true
+      }).formItems.slice(0, 1)),
+      {
+        label: '扫描件或图片',
+        name: 'images',
+        type: 'uploader',
+        defaultValue: '',
+        additionalProps: {
+          upload: useAliOssUploadCo('xiangyuan/cultural/scan'),
+          maxFileSize: 1024 * 1024 * 20,
+          maxUploadCount: 20,
+        } as UploaderFieldProps,
+        rules: [{
+          required: true,
+          message: '请上传扫描件或图片',
+        }]
       },
-      rules:  [{
-        required: true,
-        message: '请输入名称',
-      }]
-    }, 
-    {
-      label: '详情',
-      name: 'details',
-      type: 'richtext',
-      defaultValue: '',
-      additionalProps: {
-        placeholder: '请输入详情',
-        maxLength: 200,
-        showWordLimit: true, 
+    ],
+  })],
+  [4]: [CommonInfoModel, (m) => ({
+    formItems: [
+      ...villageCommonContent(m, {
+        title: '口述历史',
+        showTitle: true
+      }).formItems.slice(0, 1),
+      {
+        label: '口述历史视频/录音',
+        name: 'video',
+        type: 'uploader',
+        defaultValue: '',
+        additionalProps: {
+          upload: useAliOssUploadCo('xiangyuan/cultural/video'),
+          chooseType: 'video',
+          maxFileSize: 1024 * 1024 * 20,
+          single: true,
+        } as UploaderFieldProps,
+        formProps: {
+          extraMessage: '您可以上传已经录制好的口述历史视频/录音,也可以点击下方按钮录制新的音频。但小程序中录音时长最长10分钟,如需更长时间,请使用系统相机拍摄。',
+        } as FieldProps,
       },
-      rules:  [{
-        required: true,
-        message: '请输入详情',
-      }]
-    }, 
-    {
-      label: '村落非遗项目', 
-      name: 'ichId',
-      type: 'select-id', 
-      defaultValue: null,
-      additionalProps: {
-        loadData: {
-          callback: (m, r, p, i) => async () => 
-            (await VillageInfoApi.getList(
-              'ich', undefined, undefined,
-              i.formGlobalParams.villageId,
-              i.formGlobalParams.villageVolunteerId
-            )).map((p) => ({
-              value: p.id,
-              text: p.title,
-            }))
+      {
+        label: '',
+        name: 'video1',
+        type: 'recorder',
+        defaultValue: '',
+        additionalProps: {
+          onRecordDone: (path: string) => {
+            (m.value.getFormItemControlRef<UploaderFieldInstance>('video')?.getUploaderRef() as UploaderInstance).addItemAndUpload({
+              filePath: path,
+              state: 'notstart',
+            });
+          }
         },
-      } as IDynamicFormItemCallbackAdditionalProps<PickerIdFieldProps>,
-      formProps: { showRightArrow: true } as FieldProps,
-      rules: [],
-    },
-    {
-      label: '文化资源关联内容ID',
-      name: 'details',
-      type: 'text',
-      defaultValue: '',
-      additionalProps: {
-        placeholder: '输入文化资源关联内容ID',
       },
-      rules:  []
-    }, 
-  ]
-})];
+    ],
+  })],
+}

+ 101 - 0
src/pages/dig/forms/data/environment.ts

@@ -0,0 +1,101 @@
+import { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export const villageInfoEnvironmentForm : SingleForm= [CommonInfoModel, (r) => ({
+  formItems: [
+    { 
+      label: '名称', 
+      name: 'name', 
+      type: 'text', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入名称',
+      },
+      rules:  [{
+        required: true,
+        message: '请输入名称',
+      }] 
+    }, 
+    { 
+      label: '自然环境', 
+      name: 'natural', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入自然环境',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入自然环境',
+      }] 
+    }, 
+    { 
+      label: '选址', 
+      name: 'siteSelection', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入选址',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入选址',
+      }] 
+    }, 
+    { 
+      label: '格局', 
+      name: 'structure', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入格局',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入格局',
+      }] 
+    }, 
+    { 
+      label: '整体风貌', 
+      name: 'overallStyle', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入整体风貌',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入整体风貌',
+      }] 
+    }, 
+    { 
+      label: '农业遗产', 
+      name: 'agriculturalHeritage', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入农业遗产',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入农业遗产',
+      }] 
+    }, 
+    ...villageCommonContent(r, {
+      title: '环境格局',
+      showContent: false,
+      showTitle: false,
+    }).formItems
+  ] 
+})]

+ 245 - 0
src/pages/dig/forms/data/ich.ts

@@ -0,0 +1,245 @@
+import VillageInfoApi, { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import type { CheckBoxToIntProps } from "@/components/dynamic/wrappers/CheckBoxToInt";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export const ichFormItems : SingleForm = [CommonInfoModel, (m) => ({
+  formItems: [
+    {
+      label: '非遗基础信息', 
+      name: 'a', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '名称及管理编号', 
+          name: 'name', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入名称',
+          },
+          rules:  [{
+            required: true,
+            message: '请输入名称',
+          }] 
+        }, 
+        {
+          label: '编号', 
+          name: 'code', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入编号',
+          },
+          rules:  [{
+            required: true,
+            message: '请输入编号',
+          }] 
+        },
+        {
+          label: '级别', 
+          name: 'ichLevel',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(111))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择级别',
+          }],
+        },
+        {
+          label: '类型', 
+          name: 'ichType',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(4))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择类型',
+          }],
+        },
+      ]
+    },
+    ...villageCommonContent(m, {
+      title: '非遗',
+      showTitle: false,
+    }).formItems,
+    {
+      label: '传承人信息', 
+      name: 'b', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '是否确定传承人', 
+          name: 'isInheritor', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '是',
+          } as CheckBoxToIntProps ,
+          rules:  [{
+            required: true,
+            message: '请选择是否确定传承人',
+          }] 
+        },
+        {
+          label: '传承人情况',
+          name: 'inheritor',
+          type: 'richtext',
+          defaultValue: '',
+          show: { callback: (_, m) => m.isInheritor === 1 },
+          additionalProps: {
+            placeholder: '请输入传承人情况',
+            maxLength: 200,
+            showWordLimit: true, 
+          },
+          rules:  [{
+            required: true,
+            message: '请输入输入传承人情况',
+          }]
+        },
+        {
+          label: '加入时间', 
+          name: 'joinAt',
+          type: 'datetime', 
+          additionalProps: {
+            type: 'datetime',
+          },
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择加入时间',
+          }],
+        },
+      ]
+    },
+    
+    {
+      label: '详细信息', 
+      name: 'c', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '项目续存情况', 
+          name: 'ichExistenceStatus',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(120))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择项目续存情况',
+          }],
+        },
+        {
+          label: '与村落依存程度', 
+          name: 'ichDependenceDegree',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(124))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择与村落依存程度',
+          }],
+        },
+        {
+          label: '活动规模', 
+          name: 'activityScale',
+          type: 'select-id',
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(142))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择活动规模',
+          }],
+        },
+        {
+          label: '传承时间', 
+          name: 'inheritanceTime',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(147))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择传承时间',
+          }],
+        },
+        {
+          label: '具体传承时间', 
+          name: 'otherInheritanceTime',
+          type: 'datetime', 
+          show: { callback(model, rawModel) {
+            console.log(model, rawModel);
+            return (rawModel.inheritanceTime === 150);
+          } },
+          additionalProps: {
+            type: 'datetime',
+          },
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择具体传承时间',
+          }],
+        },
+      ]
+    },
+  ] 
+})]

+ 527 - 0
src/pages/dig/forms/data/overview.ts

@@ -0,0 +1,527 @@
+import VillageInfoApi, { CommonInfoModel, VillageEnvInfo } from "@/api/inhert/VillageInfoApi";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { IDynamicFormItem } from "@/components/dynamic";
+import type { CheckBoxListProps } from "@/components/dynamic/wrappers/CheckBoxList.vue";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { RadioIdFieldProps } from "@/components/dynamic/wrappers/RadioIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import type { RowProps } from "@/components/layout/grid/Row.vue";
+import type { GroupForm } from "../forms";
+
+export const villageInfoOverviewForm : GroupForm = {
+  [1]: [CommonInfoModel, (form) => ({
+    formItems: [
+      { 
+        label: '村落名称', 
+        name: 'name', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入您的村落名称,例如:后埔',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村落名称',
+        }] 
+      }, 
+      { 
+        label: '村落编码', 
+        name: 'code', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入村落编码,例如330106',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村落编码',
+        }] 
+      }, 
+      { 
+        label: '村落地址', 
+        name: 'cityAddress', 
+        type: 'select-city', 
+        defaultValue: () => [],
+        additionalProps: {
+          placeholder: '请点击这里选择村落地址或右侧者从地图选择',
+          onSelectedTownship: (v: string, code: string) => {
+            form.value.setValueByPath('township', v);
+            form.value.setValueByPath('code', code);
+          }
+        },
+        rules:  [{
+          required: true,
+          message: '请选择村落地址',
+        }],
+      },
+      { 
+        label: '村落乡镇', 
+        name: 'township', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入村落所在乡镇',
+        },
+        rules: [{
+          required: true,
+          message: '请输入村落所在乡镇',
+        }] 
+      }, 
+      { 
+        label: '村落类型', 
+        name: 'villageType',
+        type: 'radio-id', 
+        additionalProps: {
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(94))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+          })),
+        } as RadioIdFieldProps,
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      },
+    ]
+  })],
+  [2]: [VillageEnvInfo, () => ({
+    formItems: [
+      { 
+        label: '经纬度', 
+        name: 'lonlat', 
+        type: 'select-lonlat', 
+        defaultValue: '',
+        additionalProps: {},
+        formProps: { showRightArrow: true } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入村落经纬度',
+        }]
+      }, 
+      { 
+        label: '海拔', 
+        name: 'altitude', 
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          placeholder: '请输入村落海拔',
+          min: -1000,
+          max: 10000,
+          step: 10,
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村落海拔',
+        }] 
+      }, 
+      { 
+        label: '地形地貌特征(多选)', 
+        name: 'landforms',
+        type: 'check-box-list', 
+        additionalProps: {
+          multiple: true,
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(97))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+            }))
+          ,
+        } as CheckBoxListProps,
+        defaultValue: [],
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      },
+      { 
+        label: '村域面积(平方公里)', 
+        name: 'area', 
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          placeholder: '请输入村域面积',
+          min: 0,
+          max: 10000,
+          step: 1,
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村域面积',
+        }] 
+      }, 
+      { 
+        label: '村庄占地面积(亩)', 
+        name: 'villageArea', 
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          placeholder: '请输入村庄占地面积',
+          min: 0,
+          max: 10000,
+          step: 1,
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村庄占地面积',
+        }] 
+      }, 
+      { 
+        label: '村落形成年代', 
+        name: 'age',
+        type: 'select-id', 
+        additionalProps: {
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(103))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+            }))
+          ,
+        } as PickerIdFieldProps,
+        formProps: { showRightArrow: true } as FieldProps,
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      },
+    ]
+  })],
+  [3]: [CommonInfoModel, () => ({
+    formItems: [
+      { 
+        label: '非遗最高级别', 
+        name: 'ichLevel', 
+        type: 'select-id', 
+        defaultValue: null,
+        additionalProps: {
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(111))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+            }))
+          ,
+        } as PickerIdFieldProps,
+        formProps: { showRightArrow: true } as FieldProps,
+        rules: [{
+          required: true,
+          message: '请选择非遗最高级别',
+        }],
+      }, 
+      { 
+        label: '传统建筑数量', 
+        name: 'traditionalBuildings', 
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          min: 0,
+          max: 10000,
+          step: 1,
+        },
+        rules:  [{
+          required: true,
+          message: '请输入传统建筑数量',
+        }] 
+      }, 
+      { 
+        label: '列入历史文化名村级别', 
+        name: 'historyLevel',
+        type: 'select-id', 
+        additionalProps: {
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(151))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+            }))
+          ,
+        } as PickerIdFieldProps,
+        formProps: { showRightArrow: true } as FieldProps,
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      },
+      { 
+        label: '列入特色景观旅游名村级别', 
+        name: 'touristLevel',
+        type: 'select-id', 
+        additionalProps: {
+          loadData: async () => 
+          (await VillageInfoApi.getCategoryChildList(151))
+            .map((p) => ({
+              value: p.id,
+              text: p.title,
+            }))
+          ,
+        } as PickerIdFieldProps,
+        formProps: { showRightArrow: true } as FieldProps,
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      },
+      { 
+        label: '列入少数民族特色村寨试点示范', 
+        name: 'isFeaturedVillage', 
+        type: 'check-box-int', 
+        defaultValue: '',
+        additionalProps: {},
+        rules: [{
+          required: true,
+          message: '请选择类型',
+        }],
+      }, 
+      { 
+        label: '其他认定级别', 
+        name: 'other', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '(可选)输入村落其他认定级别',
+        },
+        rules:  [] 
+      }, 
+    ]
+  })],
+  [4]: [CommonInfoModel, () => ({ 
+    formItems: [
+      {
+        name: 'a',
+        label: '人口与收入',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          { 
+            label: '主要民族', 
+            name: 'nationlity', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入主要民族',
+            },
+            rules: [{
+              required: true,
+              message: '请输入主要民族',
+            }]
+          }, 
+          ...[
+            {
+              label: '户籍人口',
+              name: 'registeredPopulation',
+            },
+            {
+              label: '常住人口',
+              name: 'permanentPopulation',
+            },
+            {
+              label: '人均年收入',
+              name: 'personalAnnualIncome',
+            },
+            {
+              label: '集体年收入',
+              name: 'villageAnnualIncome',
+            },
+          ].map((it) => ({
+            name: '',
+            label: it.label,
+            type: 'flat-group',
+            rowProps: { 
+              align: 'center',
+            }  as RowProps,
+            children: [
+              { 
+                label: '', 
+                name: it.name + 'Year', 
+                type: 'number', 
+                defaultValue: () => new Date().getFullYear(),
+                additionalProps: {
+                  min: 1900,
+                  max: 2100,
+                  step: 10,
+                },
+                formProps: {
+                  labelWidth: '0rpx',
+                  labelPosition: 'left',
+                  showBottomBorder: false,
+                },
+                rules:  [{
+                  required: true,
+                  message: '请输入',
+                }] 
+              },
+              { 
+                label: '', 
+                name: 'aa', 
+                type: 'static-text', 
+                additionalProps: { text: '年' },
+                formProps: {
+                  labelWidth: '0rpx',
+                  showBottomBorder: false,
+                },
+              },
+              { 
+                label: '', 
+                name: it.name, 
+                type: 'number', 
+                defaultValue: 0,
+                additionalProps: {
+                  min: 0,
+                  step: 10,
+                },
+                formProps: {
+                  labelWidth: '0rpx',
+                  labelPosition: 'left',
+                  showBottomBorder: false,
+                },
+                rules:  [{
+                  required: true,
+                  message: '请输入人口',
+                }] 
+              },
+            ]
+          } as IDynamicFormItem)),
+        ]
+      },
+      {
+        name: '',
+        label: '主要产业',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          ...[
+            {
+              label: '农业',
+              name: 'agriculture',
+              pid: 128,
+            },
+            {
+              label: '林业',
+              name: 'forestry',
+              pid: 194,
+            },
+            {
+              label: '畜牧业',
+              name: 'animal',
+              pid: 200,
+            },
+            {
+              label: '渔业',
+              name: 'fishing',
+              pid: 205,
+            },
+            {
+              label: '制造业(含手工)',
+              name: 'manufacturing',
+              pid: 208,
+            },
+            {
+              label: '建筑业',
+              name: 'construction',
+              pid: 227,
+            },
+            {
+              label: '批发和零售业',
+              name: 'retail',
+              pid: 230,
+            },
+            {
+              label: '服务业',
+              name: 'service',
+              pid: 233,
+            },
+            {
+              label: '其他',
+              name: 'otherIndustries',
+              pid: 238,
+            },
+          ].map((it) => ({
+            label: it.label, 
+            name: it.name,
+            type: 'select-id', 
+            additionalProps: {
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(it.pid))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as PickerIdFieldProps,
+            formProps: { showRightArrow: true } as FieldProps,
+            rules: [],
+          })),
+          { 
+            label: '其他服务业', 
+            name: 'otherService', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '(可选)输入其他服务业',
+            },
+            rules:  [] 
+          }, 
+          { 
+            label: '其他农业', 
+            name: 'otherAgriculture', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '(可选)输入其他农业',
+            },
+            rules:  [] 
+          }, 
+        ]
+      },
+    ] 
+  })],
+  [5]: [CommonInfoModel, () => ({
+    formItems: [
+      {
+        name: '',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '概括',
+            name: 'overview',
+            type: 'richtext',
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入村落整体概括信息',
+              maxLength: 300,
+              showWordLimit: true, 
+            } as FieldProps,
+            rules: [{
+              required: true,
+              message: '请输入概括',
+            }]
+          }
+        ]
+      },
+      {
+        name: '',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '相关视频(可选)',
+            name: 'video',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/cultural/video'),
+              chooseType: 'video',
+              maxFileSize: 1024 * 1024 * 20,
+              single: true,
+            } as UploaderFieldProps,
+          },
+        ]
+      },
+    ] 
+  })],
+}

+ 17 - 884
src/pages/dig/forms/forms.ts

@@ -15,588 +15,20 @@ import type { NewDataModel } from "@imengyu/js-request-transform";
 import type { Ref } from "vue";
 import { villageCommonContent } from "./data/common";
 import { villageInfoBuildingForm } from "./data/building";
-import { villageInfoFolkCultureForm } from "./data/cultural";
+import { villageInfoCulture, villageInfoFolkCultureForm } from "./data/cultural";
 import { villageInfoFoodProductsForm } from "./data/food";
+import { villageInfoOverviewForm } from "./data/overview";
+import { ichFormItems } from "./data/ich";
+import { villageInfoEnvironmentForm } from "./data/environment";
+
+export type SingleForm = [NewDataModel, (formRef: Ref<IDynamicFormRef>) => IDynamicFormOptions]
+export type GroupForm = Record<number, SingleForm>
 
-export type SingleForm = [NewDataModel, (model: Ref<IDynamicFormRef>) => IDynamicFormOptions]
- 
 //TODO: 关联的文化资源ID
 
-const villageInfoForm : Record<string, Record<number, SingleForm>> = {
-  'overview': {
-    [1]: [CommonInfoModel, (form) => ({
-      formItems: [
-        { 
-          label: '村落名称', 
-          name: 'name', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入您的村落名称,例如:后埔',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入村落名称',
-          }] 
-        }, 
-        { 
-          label: '村落编码', 
-          name: 'code', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入村落编码,例如330106',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入村落编码',
-          }] 
-        }, 
-        { 
-          label: '村落地址', 
-          name: 'cityAddress', 
-          type: 'select-city', 
-          defaultValue: () => [],
-          additionalProps: {
-            placeholder: '请点击这里选择村落地址或右侧者从地图选择',
-            onSelectedTownship: (v: string, code: string) => {
-              form.value.setValueByPath('township', v);
-              form.value.setValueByPath('code', code);
-            }
-          },
-          rules:  [{
-            required: true,
-            message: '请选择村落地址',
-          }],
-        },
-        { 
-          label: '村落乡镇', 
-          name: 'township', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入村落所在乡镇',
-          },
-          rules: [{
-            required: true,
-            message: '请输入村落所在乡镇',
-          }] 
-        }, 
-        { 
-          label: '村落类型', 
-          name: 'villageType',
-          type: 'radio-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(94))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-            })),
-          } as RadioIdFieldProps,
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-      ]
-    })],
-    [2]: [VillageEnvInfo, () => ({
-      formItems: [
-        { 
-          label: '经纬度', 
-          name: 'lonlat', 
-          type: 'select-lonlat', 
-          defaultValue: '',
-          additionalProps: {},
-          formProps: { showRightArrow: true } as FieldProps,
-          rules:  [{
-            required: true,
-            message: '请输入村落经纬度',
-          }]
-        }, 
-        { 
-          label: '海拔', 
-          name: 'altitude', 
-          type: 'number', 
-          defaultValue: 0,
-          additionalProps: {
-            placeholder: '请输入村落海拔',
-            min: -1000,
-            max: 10000,
-            step: 10,
-          },
-          rules:  [{
-            required: true,
-            message: '请输入村落海拔',
-          }] 
-        }, 
-        { 
-          label: '地形地貌特征(多选)', 
-          name: 'landforms',
-          type: 'check-box-list', 
-          additionalProps: {
-            multiple: true,
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(97))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as CheckBoxListProps,
-          defaultValue: [],
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-        { 
-          label: '村域面积(平方公里)', 
-          name: 'area', 
-          type: 'number', 
-          defaultValue: 0,
-          additionalProps: {
-            placeholder: '请输入村域面积',
-            min: 0,
-            max: 10000,
-            step: 1,
-          },
-          rules:  [{
-            required: true,
-            message: '请输入村域面积',
-          }] 
-        }, 
-        { 
-          label: '村庄占地面积(亩)', 
-          name: 'villageArea', 
-          type: 'number', 
-          defaultValue: 0,
-          additionalProps: {
-            placeholder: '请输入村庄占地面积',
-            min: 0,
-            max: 10000,
-            step: 1,
-          },
-          rules:  [{
-            required: true,
-            message: '请输入村庄占地面积',
-          }] 
-        }, 
-        { 
-          label: '村落形成年代', 
-          name: 'age',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(103))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-      ]
-    })],
-    [3]: [CommonInfoModel, () => ({
-      formItems: [
-        { 
-          label: '非遗最高级别', 
-          name: 'ichLevel', 
-          type: 'select-id', 
-          defaultValue: null,
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(111))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择非遗最高级别',
-          }],
-        }, 
-        { 
-          label: '传统建筑数量', 
-          name: 'traditionalBuildings', 
-          type: 'number', 
-          defaultValue: 0,
-          additionalProps: {
-            min: 0,
-            max: 10000,
-            step: 1,
-          },
-          rules:  [{
-            required: true,
-            message: '请输入传统建筑数量',
-          }] 
-        }, 
-        { 
-          label: '列入历史文化名村级别', 
-          name: 'historyLevel',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(151))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-        { 
-          label: '列入特色景观旅游名村级别', 
-          name: 'touristLevel',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(151))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-        { 
-          label: '列入少数民族特色村寨试点示范', 
-          name: 'isFeaturedVillage', 
-          type: 'check-box-int', 
-          defaultValue: '',
-          additionalProps: {},
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        }, 
-        { 
-          label: '其他认定级别', 
-          name: 'other', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '(可选)输入村落其他认定级别',
-          },
-          rules:  [] 
-        }, 
-      ]
-    })],
-    [4]: [CommonInfoModel, () => ({ 
-      formItems: [
-        {
-          name: 'a',
-          label: '人口与收入',
-          type: 'flat-group',
-          childrenColProps: { span: 24 },
-          children: [
-            { 
-              label: '主要民族', 
-              name: 'nationlity', 
-              type: 'text', 
-              defaultValue: '',
-              additionalProps: {
-                placeholder: '请输入主要民族',
-              },
-              rules: [{
-                required: true,
-                message: '请输入主要民族',
-              }]
-            }, 
-            ...[
-              {
-                label: '户籍人口',
-                name: 'registeredPopulation',
-              },
-              {
-                label: '常住人口',
-                name: 'permanentPopulation',
-              },
-              {
-                label: '人均年收入',
-                name: 'personalAnnualIncome',
-              },
-              {
-                label: '集体年收入',
-                name: 'villageAnnualIncome',
-              },
-            ].map((it) => ({
-              name: '',
-              label: it.label,
-              type: 'flat-group',
-              rowProps: { 
-                align: 'center',
-              }  as RowProps,
-              children: [
-                { 
-                  label: '', 
-                  name: it.name + 'Year', 
-                  type: 'number', 
-                  defaultValue: () => new Date().getFullYear(),
-                  additionalProps: {
-                    min: 1900,
-                    max: 2100,
-                    step: 10,
-                  },
-                  formProps: {
-                    labelWidth: '0rpx',
-                    labelPosition: 'left',
-                    showBottomBorder: false,
-                  },
-                  rules:  [{
-                    required: true,
-                    message: '请输入',
-                  }] 
-                },
-                { 
-                  label: '', 
-                  name: 'aa', 
-                  type: 'static-text', 
-                  additionalProps: { text: '年' },
-                  formProps: {
-                    labelWidth: '0rpx',
-                    showBottomBorder: false,
-                  },
-                },
-                { 
-                  label: '', 
-                  name: it.name, 
-                  type: 'number', 
-                  defaultValue: 0,
-                  additionalProps: {
-                    min: 0,
-                    step: 10,
-                  },
-                  formProps: {
-                    labelWidth: '0rpx',
-                    labelPosition: 'left',
-                    showBottomBorder: false,
-                  },
-                  rules:  [{
-                    required: true,
-                    message: '请输入人口',
-                  }] 
-                },
-              ]
-            } as IDynamicFormItem)),
-          ]
-        },
-        {
-          name: '',
-          label: '主要产业',
-          type: 'flat-group',
-          childrenColProps: { span: 24 },
-          children: [
-            ...[
-              {
-                label: '农业',
-                name: 'agriculture',
-                pid: 128,
-              },
-              {
-                label: '林业',
-                name: 'forestry',
-                pid: 194,
-              },
-              {
-                label: '畜牧业',
-                name: 'animal',
-                pid: 200,
-              },
-              {
-                label: '渔业',
-                name: 'fishing',
-                pid: 205,
-              },
-              {
-                label: '制造业(含手工)',
-                name: 'manufacturing',
-                pid: 208,
-              },
-              {
-                label: '建筑业',
-                name: 'construction',
-                pid: 227,
-              },
-              {
-                label: '批发和零售业',
-                name: 'retail',
-                pid: 230,
-              },
-              {
-                label: '服务业',
-                name: 'service',
-                pid: 233,
-              },
-              {
-                label: '其他',
-                name: 'otherIndustries',
-                pid: 238,
-              },
-            ].map((it) => ({
-              label: it.label, 
-              name: it.name,
-              type: 'select-id', 
-              additionalProps: {
-                loadData: async () => 
-                (await VillageInfoApi.getCategoryChildList(it.pid))
-                  .map((p) => ({
-                    value: p.id,
-                    text: p.title,
-                  }))
-                ,
-              } as PickerIdFieldProps,
-              formProps: { showRightArrow: true } as FieldProps,
-              rules: [],
-            })),
-            { 
-              label: '其他服务业', 
-              name: 'otherService', 
-              type: 'text', 
-              defaultValue: '',
-              additionalProps: {
-                placeholder: '(可选)输入其他服务业',
-              },
-              rules:  [] 
-            }, 
-            { 
-              label: '其他农业', 
-              name: 'otherAgriculture', 
-              type: 'text', 
-              defaultValue: '',
-              additionalProps: {
-                placeholder: '(可选)输入其他农业',
-              },
-              rules:  [] 
-            }, 
-          ]
-        },
-      ] 
-    })],
-    [5]: [CommonInfoModel, () => ({
-      formItems: [
-        {
-          name: '',
-          type: 'flat-group',
-          childrenColProps: { span: 24 },
-          children: [
-            {
-              label: '概括',
-              name: 'overview',
-              type: 'richtext',
-              defaultValue: '',
-              additionalProps: {
-                placeholder: '请输入村落整体概括信息',
-                maxLength: 300,
-                showWordLimit: true, 
-              } as FieldProps,
-              rules: [{
-                required: true,
-                message: '请输入概括',
-              }]
-            }
-          ]
-        },
-        {
-          name: '',
-          type: 'flat-group',
-          childrenColProps: { span: 24 },
-          children: [
-            {
-              label: '相关视频(可选)',
-              name: 'video',
-              type: 'uploader',
-              defaultValue: '',
-              additionalProps: {
-                upload: useAliOssUploadCo('xiangyuan/cultural/video'),
-                chooseType: 'video',
-                maxFileSize: 1024 * 1024 * 20,
-                single: true,
-              } as UploaderFieldProps,
-            },
-          ]
-        },
-      ] 
-    })],
-  },
-  'cultural': {
-    [1]: [CommonInfoModel, villageCommonContent],
-    [2]: [CommonInfoModel, villageCommonContent],
-    [3]: [CommonInfoModel, (m) => ({
-      formItems: [
-        ...(villageCommonContent(m).formItems.slice(0, 1)),
-        {
-          label: '扫描件或图片',
-          name: 'images',
-          type: 'uploader',
-          defaultValue: '',
-          additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/cultural/scan'),
-            maxFileSize: 1024 * 1024 * 20,
-            maxUploadCount: 20,
-          } as UploaderFieldProps,
-          rules: [{
-            required: true,
-            message: '请上传扫描件或图片',
-          }]
-        },
-      ],
-    })],
-    [4]: [CommonInfoModel, (m) => ({
-      formItems: [
-        ...villageCommonContent(m).formItems.slice(0, 1),
-        {
-          label: '口述历史视频/录音',
-          name: 'video',
-          type: 'uploader',
-          defaultValue: '',
-          additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/cultural/video'),
-            chooseType: 'video',
-            maxFileSize: 1024 * 1024 * 20,
-            single: true,
-          } as UploaderFieldProps,
-          formProps: {
-            extraMessage: '您可以上传已经录制好的口述历史视频/录音,也可以点击下方按钮录制新的音频。但小程序中录音时长最长10分钟,如需更长时间,请使用系统相机拍摄。',
-          } as FieldProps,
-        },
-        {
-          label: '',
-          name: 'video1',
-          type: 'recorder',
-          defaultValue: '',
-          additionalProps: {
-            onRecordDone: (path: string) => {
-              (m.value.getFormItemControlRef<UploaderFieldInstance>('video')?.getUploaderRef() as UploaderInstance).addItemAndUpload({
-                filePath: path,
-                state: 'notstart',
-              });
-            }
-          },
-        },
-      ],
-    })],
-  },
+const villageInfoForm : Record<string, GroupForm> = {
+  'overview': villageInfoOverviewForm,
+  'cultural': villageInfoCulture,
   'story': {
     [0]: [CommonInfoModel, () => ({
       formItems: [
@@ -829,96 +261,7 @@ const villageInfoForm : Record<string, Record<number, SingleForm>> = {
     })]
   },
   'environment': {
-    [0]: [CommonInfoModel, () => ({
-      formItems: [
-        { 
-          label: '名称', 
-          name: 'name', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入名称',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入名称',
-          }] 
-        }, 
-        { 
-          label: '自然环境', 
-          name: 'natural', 
-          type: 'richtext', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入自然环境',
-            maxLength: 200,
-            showWordLimit: true, 
-          },
-          rules:  [{
-            required: true,
-            message: '请输入自然环境',
-          }] 
-        }, 
-        { 
-          label: '选址', 
-          name: 'siteSelection', 
-          type: 'richtext', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入选址',
-            maxLength: 200,
-            showWordLimit: true, 
-          },
-          rules:  [{
-            required: true,
-            message: '请输入选址',
-          }] 
-        }, 
-        { 
-          label: '格局', 
-          name: 'structure', 
-          type: 'richtext', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入格局',
-            maxLength: 200,
-            showWordLimit: true, 
-          },
-          rules:  [{
-            required: true,
-            message: '请输入格局',
-          }] 
-        }, 
-        { 
-          label: '整体风貌', 
-          name: 'overallStyle', 
-          type: 'richtext', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入整体风貌',
-            maxLength: 200,
-            showWordLimit: true, 
-          },
-          rules:  [{
-            required: true,
-            message: '请输入整体风貌',
-          }] 
-        }, 
-        { 
-          label: '农业遗产', 
-          name: 'agriculturalHeritage', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入农业遗产',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入农业遗产',
-          }] 
-        }, 
-      ] 
-    })]
+    [0]: villageInfoEnvironmentForm
   },
   'building': {
     [1]: villageInfoBuildingForm,
@@ -1197,224 +540,14 @@ const villageInfoForm : Record<string, Record<number, SingleForm>> = {
     })],
   },
   'folk_culture': {
-    [1]: villageInfoFolkCultureForm,
-    [2]: villageInfoFolkCultureForm,
-    [3]: villageInfoFolkCultureForm,
-    [4]: villageInfoFolkCultureForm,
-    [5]: villageInfoFolkCultureForm,
+    [1]: villageInfoFolkCultureForm('节庆活动'),
+    [2]: villageInfoFolkCultureForm('祭祀崇礼'),
+    [3]: villageInfoFolkCultureForm('婚丧嫁娶'),
+    [4]: villageInfoFolkCultureForm('地方方言'),
+    [5]: villageInfoFolkCultureForm('特色文化'),
   },
   'ich': {
-    [0]: [CommonInfoModel, () => ({
-      formItems: [
-        {
-          label: '名称及管理编号', 
-          name: 'name', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入名称',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入名称',
-          }] 
-        }, 
-        {
-          label: '编号', 
-          name: 'code', 
-          type: 'text', 
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入编号',
-          },
-          rules:  [{
-            required: true,
-            message: '请输入编号',
-          }] 
-        },
-        {
-          label: '文化资源关联内容ID',
-          name: 'inheritor',
-          type: 'text',
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '输入文化资源关联内容ID',
-          },
-          rules:  []
-        }, 
-        {
-          label: '级别', 
-          name: 'ichLevel',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(111))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择级别',
-          }],
-        },
-        {
-          label: '类型', 
-          name: 'ichType',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(4))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择类型',
-          }],
-        },
-        {
-          label: '是否确定传承人', 
-          name: 'isInheritor', 
-          type: 'check-box-int', 
-          defaultValue: 0,
-          additionalProps: {
-            text: '是',
-          } as CheckBoxToIntProps ,
-          rules:  [{
-            required: true,
-            message: '请选择是否确定传承人',
-          }] 
-        },
-        {
-          label: '传承人情况',
-          name: 'inheritor',
-          type: 'richtext',
-          defaultValue: '',
-          additionalProps: {
-            placeholder: '请输入传承人情况',
-            maxLength: 200,
-            showWordLimit: true, 
-          },
-          rules:  [{
-            required: true,
-            message: '请输入输入传承人情况',
-          }]
-        }, 
-        {
-          label: '项目续存情况', 
-          name: 'ichExistenceStatus',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(120))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择项目续存情况',
-          }],
-        },
-        {
-          label: '与村落依存程度', 
-          name: 'ichDependenceDegree',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(124))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择与村落依存程度',
-          }],
-        },
-        {
-          label: '活动规模', 
-          name: 'activityScale',
-          type: 'select-id',
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(142))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择活动规模',
-          }],
-        },
-        {
-          label: '传承时间', 
-          name: 'inheritanceTime',
-          type: 'select-id', 
-          additionalProps: {
-            loadData: async () => 
-            (await VillageInfoApi.getCategoryChildList(147))
-              .map((p) => ({
-                value: p.id,
-                text: p.title,
-              }))
-            ,
-          } as PickerIdFieldProps,
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择传承时间',
-          }],
-        },
-        {
-          label: '具体传承时间', 
-          name: 'otherInheritanceTime',
-          type: 'datetime', 
-          show: { callback(model, rawModel) {
-            return (rawModel.inheritanceTime === 150);
-          } },
-          additionalProps: {
-            type: 'datetime',
-          },
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择具体传承时间',
-          }],
-        },
-        {
-          label: '加入时间', 
-          name: 'joinAt',
-          type: 'datetime', 
-          additionalProps: {
-            type: 'datetime',
-          },
-          formProps: { showRightArrow: true } as FieldProps,
-          rules: [{
-            required: true,
-            message: '请选择加入时间',
-          }],
-        },
-      ] 
-    })],
+    [0]: ichFormItems,
   },
   'travel_guide': {
     [0]: [CommonInfoModel, () => ({

+ 1 - 1
src/pages/dig/task/custom.vue

@@ -1,7 +1,7 @@
 <template>
   <FlexCol :padding="30" :gap="10">
     <Image
-      src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_custom.png"
+      src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_custom.jpg"
       :radius="20"
       mode="widthFix"
       :width="690"