浏览代码

📦 表单优化

快乐的梦鱼 2 月之前
父节点
当前提交
68eb3671ac

+ 8 - 10
package-lock.json

@@ -8,8 +8,8 @@
       "name": "minnan-collect-web",
       "version": "0.0.0",
       "dependencies": {
-        "@imengyu/imengyu-utils": "^0.0.17",
-        "@imengyu/imengyu-web-shared": "^0.0.1",
+        "@imengyu/imengyu-utils": "^0.0.20",
+        "@imengyu/imengyu-web-shared": "^0.0.2",
         "@imengyu/js-request-transform": "^0.3.7",
         "@imengyu/vue-dynamic-form": "^0.1.3",
         "@imengyu/vue-scroll-rect": "^0.1.3",
@@ -1041,28 +1041,26 @@
       }
     },
     "node_modules/@imengyu/imengyu-utils": {
-      "version": "0.0.17",
-      "resolved": "https://registry.npmmirror.com/@imengyu/imengyu-utils/-/imengyu-utils-0.0.17.tgz",
-      "integrity": "sha512-cs2eB17qjIKnkQeb2j9FO2U/aevyohOr5IAIInCxr2/8QhOeHdWd+4bDmMdpxmEYEx0W+Hth8C5YUhMCJifONQ==",
+      "version": "0.0.20",
+      "resolved": "https://registry.npmmirror.com/@imengyu/imengyu-utils/-/imengyu-utils-0.0.20.tgz",
+      "integrity": "sha512-pPI5fObKE9QBMZtrRrR9U/fht2boPoaC61e4tPoULkLqt8W1Mm1yk11pQgM04HbFMe9TNggoPQJc9ctR3xCOXA==",
       "license": "MIT",
       "dependencies": {
         "@imengyu/js-request-transform": "^0.3.6"
       }
     },
     "node_modules/@imengyu/imengyu-web-shared": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmmirror.com/@imengyu/imengyu-web-shared/-/imengyu-web-shared-0.0.1.tgz",
-      "integrity": "sha512-o9qPEPEt0Z7yV7xm5Z7aP4+CkCaz7xuWBFmuDAotck97lXrfi2+7SOcyHHifY00/38kotPLuGSwP5peYv8tdMg==",
+      "version": "0.0.2",
+      "resolved": "https://registry.npmmirror.com/@imengyu/imengyu-web-shared/-/imengyu-web-shared-0.0.2.tgz",
+      "integrity": "sha512-v3STWPwgQK8jkPZGDXcfTsk4J4nHIiMSyGoRY89C2rd9JswbU6h+2n3p1Hck4S6Mw3182F2VL9P2AieIPWtSdQ==",
       "license": "MIT",
       "dependencies": {
         "@imengyu/imengyu-utils": "^0.0.16",
         "@imengyu/vue-dynamic-form": "^0.1.2",
         "@imengyu/vue-scroll-rect": "^0.1.7",
-        "@tinymce/tinymce-vue": "^6.3.0",
         "@vuemap/vue-amap": "^2.1.17",
         "ant-design-vue": "^4.2.6",
         "nprogress": "^0.2.0",
-        "tinymce": "^8.1.2",
         "vue": "^3.5.22",
         "vue-esign": "^1.1.4",
         "vue-router": "^4.6.3",

+ 4 - 3
package.json

@@ -15,10 +15,11 @@
     "updater": "node src/scripts/UpdateScript/index.mjs"
   },
   "dependencies": {
-    "@imengyu/imengyu-utils": "^0.0.17",
-    "@imengyu/imengyu-web-shared": "^0.0.1",
+    "@imengyu/imengyu-utils": "^0.0.20",
+    "@imengyu/imengyu-web-shared": "^0.0.2",
     "@imengyu/js-request-transform": "^0.3.7",
-    "@imengyu/vue-dynamic-form": "^0.1.3",
+    "@imengyu/vue-dynamic-form": "^0.1.5",
+    "@imengyu/vue-dynamic-form-ant": "^0.0.1",
     "@imengyu/vue-scroll-rect": "^0.1.3",
     "@tinymce/tinymce-vue": "^6.3.0",
     "@vuemap/vue-amap": "^2.1.12",

+ 4 - 10
src/App.vue

@@ -8,12 +8,8 @@
     }"
     :componentSize="'large'"
   >
-    <NavBar v-if="hasNav" />
-    <MobileNav v-else />
-    <main :class="{
-      'has-nav': hasNav,
-      'mobile-nav': !hasNav,
-    }">
+    <NavBar />
+    <main class="has-nav">
       <RouterView />
       <!-- <RouterView v-slot="{ Component }">
         <KeepAlive>
@@ -22,12 +18,12 @@
         <component :is="Component" v-if="!route.meta.keepAlive" />
       </RouterView> -->
     </main>
-    <FooterSmall v-if="hasNav" />
+    <FooterSmall />
   </a-config-provider>
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, provide, watch } from 'vue';
+import { onMounted, watch } from 'vue';
 import { RouterView, useRoute } from 'vue-router'
 import { useAuthStore } from './stores/auth';
 import NavBar from './components/NavBar.vue';
@@ -35,12 +31,10 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN';
 import { useRedirectLoginPage } from './common/LoginPageRedirect';
 import FooterSmall from './components/FooterSmall.vue';
 import Colors from './assets/scss/vueexp.module.scss';
-import MobileNav from './components/MobileNav.vue';
 
 const authStore = useAuthStore();
 const route = useRoute();
 const { checkAndRedirectLoginPage } = useRedirectLoginPage();
-const hasNav = computed(() => authStore.loginFromEmbed === false);
 
 onMounted(async () => {
   await authStore.loadLoginState(route);

+ 8 - 4
src/assets/scss/fix.scss

@@ -48,11 +48,10 @@
 }
 
 .dynamic-form-group {
-  background-color: transparent;
   margin-bottom: 10px;
-  padding: 0 0 0 10px;
+  padding: 35px;
 
-  h5 {
+  .title {
     display: inline-block;
     border-radius: 10px;
     background-color: $border-active-color;
@@ -66,9 +65,14 @@
   .ant-form-item {
     margin-bottom: 24px !important;
   }
+  .dynamic-form-group {
+    padding: 25px;
+  }
 }
 @media screen and (max-width: 425px) {
-
+  .dynamic-form-group {
+    padding: 10px;
+  }
 }
 
 //utils-fix

+ 75 - 0
src/common/components/dynamicf/TextTag.vue

@@ -0,0 +1,75 @@
+<template>
+  <a-select
+    v-model:value="tagList"
+    mode="tags"
+    placeholder="输入标签后按回车或逗号确认"
+    :max-tag-count="tagMaxCount > 0 ? tagMaxCount : undefined"
+    @change="handleTagChange"
+    :style="{ width: '100%' }"
+    v-bind="$attrs"
+  >
+    <a-select-option
+      v-for="tag in tagList"
+      :key="tag"
+      :value="tag"
+      :class="tagColorMaker(tag)"
+    >
+      {{ tag }}
+    </a-select-option>
+  </a-select>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { message } from 'ant-design-vue';
+
+const props = defineProps({
+  /**
+   * 已经合并的标签文本
+   */
+  modelValue: {
+    type: String,
+    default: '',
+  },
+  /**
+   * 标签分隔符
+   */
+  tagJoinType: {
+    type: String,
+    default: ';',
+  },
+  /**
+   * 标签最大数量
+   */
+  tagMaxCount: {
+    type: Number,
+    default: 0,
+  },
+  /**
+   * 标签颜色生成器
+   * @param tag 标签文本
+   * @returns 标签颜色类名
+   */
+  tagColorMaker: {
+    type: Function,
+    default: (tag: string) => '',
+  },
+
+})
+const emit = defineEmits(['update:modelValue']);
+const tagList = ref<string[]>([]);
+const parseModelValue = (value: string) => {
+  if (!value) return [];
+  return value.split(props.tagJoinType).filter(tag => tag.trim());
+};
+
+watch(() => props.modelValue, (newValue) => {
+  tagList.value = parseModelValue(newValue);
+}, { immediate: true });
+
+const handleTagChange = (tags: string[]) => {
+  tagList.value = tags;
+  const joinedTags = tags.join(props.tagJoinType);
+  emit('update:modelValue', joinedTags);
+};
+</script>

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

@@ -1,4 +1,4 @@
-import type { AntUploadRequestOption, UploadCoInterface } from "@/components/dynamicf/UploadImageFormItem";
+import type { AntUploadRequestOption, UploadCoInterface } from "@imengyu/vue-dynamic-form-ant";
 import { RandomUtils, StringUtils } from "@imengyu/imengyu-utils";
 import OSS from 'ali-oss';
 

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

@@ -1,5 +1,5 @@
 import CommonContent from "@/api/CommonContent";
-import type { AntUploadRequestOption, UploadCoInterface } from "@imengyu/imengyu-web-shared";
+import type { AntUploadRequestOption, UploadCoInterface } from "@imengyu/vue-dynamic-form-ant";
 
 export function useImageSimpleUploadCo(additionData?: Record<string, any>) : UploadCoInterface {
 

+ 8 - 2
src/main.ts

@@ -1,22 +1,26 @@
 import 'vue3-carousel/carousel.css'
 import '@imengyu/imengyu-web-shared/lib/imengyu-web-shared.css'
-import '@imengyu/vue-dynamic-form/dist/style.css'
+import '@imengyu/vue-dynamic-form/dist/vue-dynamic-form.css'
+import '@imengyu/vue-dynamic-form-ant/lib/vue-dynamic-form-ant.css'
 import '@vueup/vue-quill/dist/vue-quill.snow.css';
 import 'tinymce/tinymce';
 import 'tinymce/themes/silver/theme';
 import 'tinymce/icons/default';
 
-import { createApp } from 'vue'
+import { createApp, markRaw } from 'vue'
 import { createPinia } from 'pinia'
 
 import App from './App.vue'
 import router from './router'
 import NProgress from 'nprogress';
 import ImengyuCommon from '@imengyu/imengyu-web-shared';
+import VueDynamicFormAnt from '@imengyu/vue-dynamic-form-ant';
 import { registryConvert } from '@/common/ConvertRgeistry'
 import { initAMapApiLoader } from '@vuemap/vue-amap';
 import { QuillEditor } from '@vueup/vue-quill'
 import { configDynamicForm } from './components/dynamicf';
+import { DynamicFormItemRegistry } from '@imengyu/vue-dynamic-form';
+import TextTag from './common/components/dynamicf/TextTag.vue';
 
 initAMapApiLoader({
   key: '212b86dc49a5116a34e945d31da7ad14',
@@ -29,9 +33,11 @@ const app = createApp(App)
 app.use(createPinia())
 app.use(router)
 app.use(ImengyuCommon, {})
+app.use(VueDynamicFormAnt, {})
 app.component('QuillEditor', QuillEditor);
 app.mount('#app').$nextTick(() => {
   configDynamicForm();
+  DynamicFormItemRegistry.register('text-tag', markRaw(TextTag), {}, 'modelValue');
 });
 
 router.beforeEach((to, from, next) => {

+ 1 - 0
src/pages/composeable/TaskEntryForm.ts

@@ -11,6 +11,7 @@ export function useTaskEntryForm() {
     router.push({
       path: '../forms/' + type, 
       query: {
+        id: type === 'common' ? 1 : undefined,
         villageId: querys.value.villageId,  
         villageVolunteerId: querys.value.villageVolunteerId,  
         subType,

+ 2 - 5
src/pages/details.vue

@@ -5,14 +5,12 @@
     <!-- 表单 -->
     <section class="main-section">
       <div class="content">
-        <div v-if="hasNav" class="title left-right">
+        <div class="title left-right">
           <a-button :icon="h(ArrowLeftOutlined)" class="mb-3" @click="router.back()">返回</a-button>
           <h2>{{ querys.name }}</h2>
           <span style="width:50px;"></span>
         </div>
-        <div v-else class="title">
-          <h2>{{ querys.name }}</h2>
-        </div>
+               
         <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_1.jpg"/>
  
         <div class="mt-2 p-2 bg-primary radius-s rounded-3 d-flex flex-row align-center">
@@ -164,7 +162,6 @@ const nextPageData = computed(() => ({
   villageId: querys.value.id,  
   villageVolunteerId: querys.value.villageVolunteerId,
 }));
-const hasNav = computed(() => authStore.loginFromEmbed === false);
 const { isAdmin } = useAuthStore();
 const { canCollect, isEmpty } = useCollectStore();
 const { goForm } = useTaskEntryForm();

+ 6 - 2
src/pages/forms/common.vue

@@ -37,7 +37,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, h, reactive } from 'vue';
+import { ref, h, reactive, nextTick } from 'vue';
 import { getVillageInfoForm } from './forms';
 import { RequestApiError, waitTimeOut } from '@imengyu/imengyu-utils';
 import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
@@ -126,7 +126,7 @@ const { querys } = useLoadQuerys({
   try {
     const [model, forms] = getVillageInfoForm(querys.subType, querys.subId);
     formModel.value = new model();
-    formOptions.value = forms;
+    formOptions.value = forms(formRef);
     if (querys.id !== -1) {
       formData = await VillageInfoApi.getInfo(
         querys.subType, 
@@ -150,6 +150,10 @@ const { querys } = useLoadQuerys({
   if (formData) {
     formModel.value = reactive(formData);
   }
+    
+  await nextTick();
+
+  formRef.value.initDefaultValuesToModel();
 });
 
 function showError(e: any, title?: string, callback?: () => void) {

+ 565 - 0
src/pages/forms/data/building.ts

@@ -0,0 +1,565 @@
+import VillageInfoApi, { CommonInfoModel, VillageBulidingInfo } from "@/api/inhert/VillageInfoApi";
+import type { CheckBoxListProps } from "@/components/dynamic/wrappers/CheckBoxList.vue";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { SingleForm } from "../forms";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import { villageCommonContent } from "./common";
+
+export function villageInfoBuildingForm(title: string) : SingleForm  {
+  return [VillageBulidingInfo, (r) => ({
+    formItems: [
+      
+      {
+        label: '基础信息',
+        name: 'baseInfo',
+        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: 'ownership',
+            type: 'select-id', 
+            additionalProps: {
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(152))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as PickerIdFieldProps,
+            formProps: { showRightArrow: true } as FieldProps,
+            rules: [{
+              required: true,
+              message: '请选择产权归属',
+            }],
+          },
+          {
+            label: '位置', 
+            name: 'position', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入位置',
+            },
+            rules:  [{
+              required: true,
+              message: '请输入位置',
+            }] 
+          },
+          {
+            label: '建筑类型', 
+            name: 'buildingType',
+            type: 'select-id', 
+            additionalProps: {
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(163))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as PickerIdFieldProps,
+            formProps: { showRightArrow: true } as FieldProps,
+            rules: [{
+              required: true,
+              message: '请选择建筑类型',
+            }],
+          },
+          {
+            label: '其他建筑类型', 
+            name: 'otherBuildingType', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: { placeholder: '其他建筑类型' },
+            show: { callback: (_, m) => m.buildingType == 170 },
+            rules: [{
+              required: true,
+              message: '请输入其他建筑类型',
+            }]
+          },
+        ]
+      },
+      {
+        label: '简介和故事',
+        name: 'extraInfo',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '建筑中的故事', 
+            name: 'story', 
+            type: 'richtext', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入建筑中的故事',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules:  [{
+              required: true,
+              message: '请输入建筑中的故事',
+            }] 
+          },
+          {
+            label: '功能特点', 
+            name: 'funcFeatures', 
+            type: 'richtext', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入功能特点',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules:  [] 
+          },
+          ...villageCommonContent(r, {
+            title: title,
+            showContent: false,
+            showTitle: false,
+          }).formItems
+        ]
+      },
+      {
+        label: '详细信息',
+        name: 'detailsInfo',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+
+
+          {
+            label: '保护级别', 
+            name: 'protectionLevel',
+            type: 'select-id', 
+            additionalProps: {
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(171))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as PickerIdFieldProps,
+            formProps: { showRightArrow: true } as FieldProps,
+            rules: [{
+              required: true,
+              message: '请选择建筑类型',
+            }],
+          },
+          {
+            label: '其他保护级别', 
+            name: 'otherProtectionLevel', 
+            type: 'text', 
+            defaultValue: '',
+            show: { callback: (_, m) => m.protectionLevel == 177 },
+            additionalProps: { placeholder: '其他保护级别' },
+            rules: [{
+              required: true,
+              message: '请输入其他保护级别',
+            }]
+          },
+          {
+            label: '总占地面积', 
+            name: 'area', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入总占地面积',
+            },
+            rules:  [{
+              required: true,
+              message: '请输入总占地面积',
+            }] 
+          },
+          {
+            label: '建筑面积', 
+            name: 'buildingArea', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: {
+              placeholder: '请输入建筑面积',
+            },
+            rules:  [{
+              required: true,
+              message: '请输入建筑面积',
+            }] 
+          },
+          {
+            label: '建筑层数', 
+            name: 'floor', 
+            type: 'number', 
+            defaultValue: 0,
+            additionalProps: {
+              min: 0,
+              max: 1000,
+            },
+            rules:  [{
+              required: true,
+              message: '请输入建筑层数',
+            }] 
+          },
+          {
+            label: '所含建筑幢数', 
+            name: 'num', 
+            type: 'number', 
+            defaultValue: 0,
+            additionalProps: {
+              min: 0,
+              max: 1000,
+            },
+            rules:  [{
+              required: true,
+              message: '请输入所含建筑幢数',
+            }] 
+          },
+          {
+            label: '始建时间(年)', 
+            name: 'age', 
+            type: 'number', 
+            defaultValue: 2025,
+            additionalProps: {
+              min: -50000,
+              max: 2000,
+            },
+            rules:  [{
+              required: true,
+              message: '请输入始建时间',
+            }] 
+          },
+          {
+            label: '承重结构(多选)', 
+            name: 'bearingType', 
+            type: 'check-box-list', 
+            additionalProps: {
+              multiple: true,
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(246))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as CheckBoxListProps,
+            defaultValue: [],
+            rules: [{
+              required: true,
+              message: '请选择类型',
+            }],
+          },
+          {
+            label: '其他承重结构类型', 
+            name: 'otherBearing', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: { placeholder: '其他承重结构类型' },
+            rules: []
+          },
+          {
+            label: '居民建筑类型', 
+            name: 'residentialBuildingType', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: { placeholder: '居民建筑类型' },
+            rules: []
+          },
+          {
+            label: '修缮过程', 
+            name: 'repairProcess', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '修缮过程',
+              maxLength: 200,
+              showWordLimit: true, 
+             },
+            rules: []
+          },
+          {
+            label: '建筑风貌', 
+            name: 'architecturalStyle', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '建筑风貌',
+              maxLength: 200,
+              showWordLimit: true, 
+             },
+            rules: []
+          },
+          {
+            label: '院落布局', 
+            name: 'layout', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '院落布局',
+              maxLength: 200,
+              showWordLimit: true, 
+             },
+            rules: []
+          },
+        ]
+      },
+      
+      {
+        label: '建筑做法',
+        name: 'detailsInfo',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '屋面形式(多选)', 
+            name: 'roofForm', 
+            type: 'check-box-list', 
+            additionalProps: {
+              multiple: true,
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(264))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as CheckBoxListProps,
+            defaultValue: [],
+            rules: [],
+          },
+          {
+            label: '屋面形式说明', 
+            name: 'roofDescribe', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '屋面形式说明',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules: []
+          },
+          {
+            label: '围护墙体(多选)', 
+            name: 'wallType', 
+            type: 'check-box-list', 
+            additionalProps: {
+              multiple: true,
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(271))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as CheckBoxListProps,
+            defaultValue: [],
+            rules: [],
+          },
+          {
+            label: '围护墙体说明', 
+            name: 'otherBuildingType', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '围护墙体说明',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules: []
+          },
+          {
+            label: '地面做法(多选)', 
+            name: 'floorType', 
+            type: 'check-box-list', 
+            additionalProps: {
+              multiple: true,
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(258))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as CheckBoxListProps,
+            defaultValue: [],
+            rules: [],
+          },
+          {
+            label: '地面做法说明', 
+            name: 'floorDescribe', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '地面做法说明',
+              maxLength: 200,
+              showWordLimit: true, 
+             },
+            rules: []
+          },
+          {
+            label: '特殊工艺做法', 
+            name: 'specialProcess', 
+            type: 'text', 
+            defaultValue: '',
+            additionalProps: { placeholder: '特殊工艺做法' },
+            rules: []
+          },
+        ]
+      },
+      {
+        label: '建筑用途',
+        name: 'detailsInfo',
+        type: 'flat-group',
+        childrenColProps: { span: 24 },
+        children: [
+          {
+            label: '历史功能', 
+            name: 'funcHistory', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '历史功能',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules: []
+          },
+          {
+            label: '现状用途(多选)', 
+            name: 'purpose', 
+            type: 'check-box-list', 
+            additionalProps: {
+              multiple: true,
+              loadData: async () => 
+              (await VillageInfoApi.getCategoryChildList(252))
+                .map((p) => ({
+                  value: p.id,
+                  text: p.title,
+                }))
+              ,
+            } as CheckBoxListProps,
+            defaultValue: [],
+            rules: [],
+          },
+          {
+            label: '其他现状用途', 
+            name: 'otherPurpose', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '其他现状用途',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules: []
+          },
+          {
+            label: '改扩建情况及维修状况', 
+            name: 'repair', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '改扩建情况及维修状况',
+              maxLength: 200,
+              showWordLimit: true, 
+            },
+            rules: []
+          },
+          {
+            label: '改扩建情况及维修状况说明', 
+            name: 'repairDescribe', 
+            type: 'textarea', 
+            defaultValue: '',
+            additionalProps: { 
+              placeholder: '改扩建情况及维修状况说明',
+              maxLength: 200,
+              showWordLimit: true, 
+             },
+            rules: []
+          },
+        ]
+      },
+    ] 
+  })]
+}
+
+export const villageInfoDistributionForm : SingleForm = [CommonInfoModel, (r) => ({
+  formItems: [
+    {
+      label: '建筑数量', 
+      name: 'num', 
+      type: 'number', 
+      defaultValue: '',
+      additionalProps: {
+        min: 0,
+        max: 100,
+      },
+      rules:  [{
+        required: true,
+        message: '请输入建筑数量',
+      }] 
+    }, 
+    {
+      label: '分布图', 
+      name: 'distribution', 
+      type: 'uploader', 
+      defaultValue: '',
+      additionalProps: {
+        upload: useAliOssUploadCo('xiangyuan/distribution'),
+        maxFileSize: 1024 * 1024 * 20,
+        single: true,
+      } as UploaderFieldProps,
+      rules:  [{
+        required: true,
+        message: '请上传分布图',
+      }] 
+    }, 
+    {
+      label: '营造智慧', 
+      name: 'wisdom', 
+      type: 'richtext', 
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入营造智慧',
+        maxLength: 200,
+        showWordLimit: true, 
+      },
+      rules:  [{
+        required: true,
+        message: '请输入营造智慧',
+      }] 
+    }, 
+    ...villageCommonContent(r, {
+      title: '建筑分布',
+      showContent: false,
+      showTitle: false,
+    }).formItems
+  ] 
+})];

+ 171 - 0
src/pages/forms/data/common.ts

@@ -0,0 +1,171 @@
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { IDynamicFormOptions, IDynamicFormRef } from "@/components/dynamic";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import type { Ref } from "vue";
+
+export function villageCommonContent (ref: 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: '如果是转载文章可以输入来源',
+            },
+          }]: []),
+        ],
+      },
+      {
+        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: {
+              extra: '建议分辨率: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: {
+              extra: '您还可以上传介绍视频,建议使用MP4格式1080P分辨率',
+            },
+            rules: []
+          },
+
+        ],
+      },
+      {
+        name: 'c',
+        type: 'flat-group',
+        label: '额外信息(可选)',
+        formProps: {
+          showLabel: false,
+        },
+        additionalProps: {
+          collapsible: true,
+          collapsed: true,
+        },
+        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: {
+              extra: '建议上传项目相关文件,如项目合同、项目计划等',
+            },
+          },
+          {
+            label: `${options.title}其他附件(可选)`,
+            name: 'annex',
+            type: 'uploader',
+            defaultValue: '',
+            additionalProps: {
+              upload: useAliOssUploadCo('xiangyuan/annex'),
+              maxFileSize: 1024 * 1024 * 20,
+              maxUploadCount: 20,
+            } as UploaderFieldProps,
+            rules: [],
+            formProps: {
+              extra: '建议上传项目相关文件,如项目合同、项目计划等',
+            },
+          },
+          {
+            label: `关键字`,
+            name: 'keywords',
+            type: 'text-tag',
+            defaultValue: '',
+            rules: [],
+            additionalProps: {
+              placeholder: '可以输入关键字',
+              tagJoinType: ';',
+            },
+            formProps: {
+              extra: '用于系统优化关键字搜索',
+            },
+          },
+        ],
+      }
+    ],
+
+  }
+}

+ 126 - 0
src/pages/forms/data/cultural.ts

@@ -0,0 +1,126 @@
+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 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: '请上传扫描件或图片',
+        }]
+      },
+    ],
+  })],
+  [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: {
+          extra: '您可以上传已经录制好的口述历史视频/录音来更生动地介绍历史。',
+        } 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',
+            });
+          }
+        },
+      }, */
+    ],
+  })],
+}

+ 259 - 0
src/pages/forms/data/element.ts

@@ -0,0 +1,259 @@
+import VillageInfoApi, { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { StepperProps } from "@/components/form/Stepper.vue";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export const vilElementForm : SingleForm = [CommonInfoModel, (r) => ({
+  formItems: [
+    {
+      label: '基础信息',
+      name: 'baseInfo',
+      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: '(可选)请输入编号',
+          },
+        },
+        {
+          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: '请选择年代',
+          }],
+        },
+        {
+          label: '要素类型', 
+          name: 'elementType',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(178))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择要素类型',
+          }],
+        },
+      ]
+    },
+    {
+      label: '详细信息',
+      name: 'detailInfo',
+      type: 'flat-group',
+      childrenColProps: { span: 24 },
+      children: [
+        {
+          label: '环境特点', 
+          name: 'environment', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入环境特点',
+            maxLength: 500,
+            showWordLimit: true, 
+          } as FieldProps,
+          rules:  [] 
+        },
+        {
+          label: '功能特点', 
+          name: 'funcFeatures', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入功能特点',
+            maxLength: 300,
+            showWordLimit: true, 
+          } as FieldProps,
+          rules:  [] 
+        },
+        {
+          label: '保存状况', 
+          name: 'condition', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入保存状况',
+            maxLength: 500,
+            showWordLimit: true, 
+          } as FieldProps,
+          rules:  [] 
+        },
+        {
+          label: '文化故事', 
+          name: 'story', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入文化故事',
+            maxLength: 1000,
+            showWordLimit: true, 
+          } as FieldProps,
+          rules:  [] 
+        },
+        {
+          label: '图片', 
+          name: 'images', 
+          type: 'uploader', 
+          defaultValue: '',
+          additionalProps: {
+            upload: useAliOssUploadCo('xiangyuan/element'),
+            maxFileSize: 1024 * 1024 * 20,
+            maxUploadCount: 20,
+          } as UploaderFieldProps,
+          rules:  [] 
+        },
+        ...villageCommonContent(r, {
+          title: '环境要素',
+          showContent: false,
+          showTitle: false,
+        }).formItems
+      ]
+    },
+    {
+      label: '位置信息',
+      name: 'locationInfo',
+      type: 'flat-group',
+      childrenColProps: { span: 24 },
+      children: [
+        {
+          label: '位置', 
+          name: 'position', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入位置',
+          },
+          rules:  [{
+            required: true,
+            message: '请输入位置',
+          }] 
+        },
+        {
+          label: '经纬度', 
+          name: 'lonlat', 
+          type: 'select-lonlat', 
+          defaultValue: '',
+          additionalProps: {},
+          formProps: { showRightArrow: true } as FieldProps,
+          rules:  [{
+            required: true,
+            message: '请输入经纬度',
+          }]
+        }, 
+        {
+          label: '地址', 
+          name: 'address', 
+          type: 'select-address', 
+          defaultValue: '',
+          additionalProps: {},
+          rules:  [{
+            required: true,
+            message: '请输入地址',
+          }] 
+        },
+        {
+          label: '方位', 
+          name: 'orientation', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: { placeholder: '方位' },
+          rules: []
+        },
+        {
+          label: '相对距离', 
+          name: 'distance', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: { placeholder: '相对距离' },
+          rules: []
+        },
+        {
+          name: '',
+          label: '平面坐标XY',
+          type: 'flat-simple',
+          children: [
+            { 
+              label: '', 
+              name: 'mapX', 
+              type: 'number', 
+              defaultValue: 0,
+              additionalProps: {
+                min: -250,
+                max: 250,
+              } as StepperProps,
+              formProps: {
+                labelWidth: '0rpx',
+                labelPosition: 'left',
+                showBottomBorder: false,
+              },
+              rules:  [{
+                required: true,
+                message: '请输入X',
+              }] 
+            },
+            { 
+              label: '', 
+              name: 'mapY', 
+              type: 'number', 
+              defaultValue: 0,
+              additionalProps: {
+                min: -250,
+                max: 250,
+              } as StepperProps,
+              formProps: {
+                labelWidth: '0rpx',
+                labelPosition: 'left',
+                showBottomBorder: false,
+              },
+              rules:  [{
+                required: true,
+                message: '请输入Y',
+              }] 
+            },
+          ]
+        },
+      ]
+    },
+  ] 
+})]

+ 101 - 0
src/pages/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
+  ] 
+})]

+ 43 - 0
src/pages/forms/data/food.ts

@@ -0,0 +1,43 @@
+import { VillageBulidingInfo } from "@/api/inhert/VillageInfoApi";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export function villageInfoFoodProductsForm(title: string) : SingleForm {
+  return [VillageBulidingInfo, (r) => ({
+    formItems: [
+      {
+        label: `${title}名称`,
+        name: 'name',
+        type: 'text',
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入名称',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入名称',
+        }]
+      }, 
+      {
+        label: `${title}详情`,
+        name: 'details',
+        type: 'richtext',
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入详情',
+          maxLength: 200,
+          showWordLimit: true, 
+        },
+        rules:  [{
+          required: true,
+          message: '请输入详情',
+        }]
+      },
+      ...(villageCommonContent(r, { 
+        title: title,
+        showContent: false,
+        showTitle: false,
+      })).formItems
+    ]
+  })];
+}

+ 76 - 0
src/pages/forms/data/history.ts

@@ -0,0 +1,76 @@
+import { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+import type { FieldProps } from "@/components/form/Field.vue";
+
+export const villageInfoStoryFormItems: SingleForm = [CommonInfoModel, (r) => ({
+  formItems: [
+    {
+      label: '标题',
+      name: 'title',
+      type: 'text',
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入标题',
+      },
+      rules: [{
+        required: true,
+        message: '请输入标题',
+      }]
+    },
+    {
+      label: '简介',
+      name: 'intro',
+      type: 'richtext',
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入内容',
+      },
+      rules: [{
+        required: true,
+        message: '请输入内容',
+      }]
+    },
+    ...villageCommonContent(r, {
+      title: '掌故轶事',
+      showTitle: false,
+    }).formItems
+  ]
+})]
+
+export const villageInfoFigureFormItems: SingleForm = [CommonInfoModel, (r) => ({
+  formItems: [
+    {
+      label: '历史人物名称',
+      name: 'name',
+      type: 'text',
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入标题',
+      },
+      rules: [{
+        required: true,
+        message: '请输入标题',
+      }]
+    },
+    {
+      label: '历史人物简介',
+      name: 'brief',
+      type: 'richtext',
+      defaultValue: '',
+      additionalProps: {
+        placeholder: '请输入简介',
+        maxLength: 500,
+        showWordLimit: true,
+      } as FieldProps,
+      rules: [{
+        required: true,
+        message: '请输入简介',
+      }]
+    },
+    ...villageCommonContent(r, {
+      title: '历史人物',
+      showTitle: false,
+    }).formItems
+  ]
+})]

+ 244 - 0
src/pages/forms/data/ich.ts

@@ -0,0 +1,244 @@
+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) {
+            return (rawModel.inheritanceTime == 150);
+          } },
+          additionalProps: {
+            type: 'datetime',
+          },
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择具体传承时间',
+          }],
+        },
+      ]
+    },
+  ] 
+})]

+ 545 - 0
src/pages/forms/data/overview.ts

@@ -0,0 +1,545 @@
+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,
+          addonAfter: 'M',
+        },
+        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,
+          addonAfter: '平方公里',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入村域面积',
+        }] 
+      }, 
+      { 
+        label: '村庄占地面积', 
+        name: 'villageArea', 
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          placeholder: '请输入村庄占地面积',
+          min: 0,
+          max: 10000,
+          step: 1,
+          addonAfter: '亩',
+        },
+        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: '请输入概括',
+            }]
+          },
+          {
+            label: '突出价值',
+            name: 'prominent',
+            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,
+          },
+        ]
+      },
+    ] 
+  })],
+}

+ 266 - 0
src/pages/forms/data/relic.ts

@@ -0,0 +1,266 @@
+import VillageInfoApi, { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { StepperProps } from "@/components/form/Stepper.vue";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import type { SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export const villageInfoRelicForm : SingleForm = [CommonInfoModel, (r) => ({
+  formItems: [
+    {
+      label: '基础信息',
+      name: 'baseInfo',
+      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: '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: '请选择年代',
+          }],
+        },
+        {
+          label: '保护级别', 
+          name: 'level',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(158))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择保护级别',
+          }],
+        },
+        {
+          label: '文物类型', 
+          name: 'crType',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(3))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择文物类型',
+          }],
+        },
+      ]
+    },
+    {
+      label: '介绍',
+      name: 'introdInfo',
+      type: 'flat-group',
+      childrenColProps: { span: 24 },
+      children: [
+        {
+          label: '简介', 
+          name: 'intro', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入简介',
+            maxLength: 200,
+            showWordLimit: true, 
+          },
+          rules:  [{
+            required: true,
+            message: '请输入简介',
+          }] 
+        },
+        {
+          label: '描述', 
+          name: 'description', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入简介',
+            maxLength: 200,
+            showWordLimit: true, 
+          },
+          rules:  [{
+            required: true,
+            message: '请输入描述',
+          }] 
+        },
+        {
+          label: '图片', 
+          name: 'images', 
+          type: 'uploader', 
+          defaultValue: '',
+          additionalProps: {
+            upload: useAliOssUploadCo('xiangyuan/relic'),
+            maxFileSize: 1024 * 1024 * 20,
+            maxUploadCount: 20,
+          } as UploaderFieldProps,
+          rules:  [] 
+        },
+        {
+          label: '文化故事', 
+          name: 'content', 
+          type: 'richtext', 
+          defaultValue: '',
+          additionalProps: { 
+            placeholder: '输入关于此文物的历史文化故事',
+            maxLength: 5000,
+            showWordLimit: true, 
+          } as FieldProps,
+          rules: []
+        },
+        ...villageCommonContent(r, {
+          title: '文物古迹',
+          showContent: false,
+          showTitle: false,
+        }).formItems
+      ]
+    },
+    {
+      label: '位置信息',
+      name: 'locationInfo',
+      type: 'flat-group',
+      childrenColProps: { span: 24 },
+      children: [
+        {
+          label: '经纬度', 
+          name: 'lonlat', 
+          type: 'select-lonlat', 
+          defaultValue: '',
+          additionalProps: {},
+          formProps: { showRightArrow: true } as FieldProps,
+          rules:  [{
+            required: true,
+            message: '请输入经纬度',
+          }]
+        }, 
+        {
+          label: '地址', 
+          name: 'address', 
+          type: 'select-address', 
+          defaultValue: '',
+          additionalProps: {},
+          rules:  [{
+            required: true,
+            message: '请输入地址',
+          }] 
+        },
+        {
+          label: '方位', 
+          name: 'orientation', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: { placeholder: '方位' },
+          rules: []
+        },
+        {
+          label: '相对距离', 
+          name: 'distance', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: { placeholder: '相对距离' },
+          rules: []
+        },
+        {
+          name: '',
+          label: '平面坐标XY',
+          type: 'flat-simple',
+          children: [
+            { 
+              label: '', 
+              name: 'mapX', 
+              type: 'number', 
+              defaultValue: 0,
+              additionalProps: {
+                min: -250,
+                max: 250,
+              } as StepperProps,
+              formProps: {
+                labelWidth: '0rpx',
+                labelPosition: 'left',
+                showBottomBorder: false,
+              },
+              rules:  [{
+                required: true,
+                message: '请输入X',
+              }] 
+            },
+            { 
+              label: '', 
+              name: 'mapY', 
+              type: 'number', 
+              defaultValue: 0,
+              additionalProps: {
+                min: -250,
+                max: 250,
+              } as StepperProps,
+              formProps: {
+                labelWidth: '0rpx',
+                labelPosition: 'left',
+                showBottomBorder: false,
+              },
+              rules:  [{
+                required: true,
+                message: '请输入Y',
+              }] 
+            },
+          ]
+        },
+      ]
+    },
+
+
+    
+  ] 
+})];

+ 702 - 0
src/pages/forms/data/travel.ts

@@ -0,0 +1,702 @@
+import VillageInfoApi, { CommonInfoModel } from "@/api/inhert/VillageInfoApi";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import type { PickerIdFieldProps } from "@/components/dynamic/wrappers/PickerIdField";
+import type { FieldProps } from "@/components/form/Field.vue";
+import type { PickerFieldProps } from "@/components/form/PickerField.vue";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import type { GroupForm, SingleForm } from "../forms";
+import { villageCommonContent } from "./common";
+
+export const villageInfoTravelGuideForm : SingleForm = [CommonInfoModel, () => ({
+  formItems: [
+    {
+      label: '交通基本信息', 
+      name: 'transportBasic', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '入村路线', 
+          name: 'villageRoute', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入入村路线',
+          },
+          rules:  [{
+            required: true,
+            message: '请输入入村路线',
+          }] 
+        },
+        {
+          label: '距离县城', 
+          name: 'county', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { min: 0, addonAfter: 'KM' },
+          rules:  [{
+            required: true,
+            message: '请输入距离县城',
+          }] 
+        },
+        {
+          label: '距离镇区中心', 
+          name: 'town', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { min: 0, addonAfter: 'KM' },
+          rules:  [{
+            required: true,
+            message: '请输入距离镇区中心',
+          }] 
+        }, 
+        {
+          label: '距离市中心', 
+          name: 'city', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { min: 0, addonAfter: 'KM' },
+          rules:  [{
+            required: true,
+            message: '请输入距离市中心',
+          }] 
+        }
+      ]
+    },
+    {
+      label: '交通设施信息', 
+      name: 'transportFacilities', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '最近高速收费站名称', 
+          name: 'tollStationName', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入最近高速收费站名称',
+          },
+          rules:  [{
+            required: true,
+            message: '请输入最近高速收费站名称',
+          }] 
+        },
+        {
+          label: '最近高速收费站名称', 
+          name: 'tollStation', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入最近高速收费站名称',
+          },
+          rules:  [] 
+        },
+        {
+          label: '距离最近火车站', 
+          name: 'trainStation', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入距离最近火车站',
+          },
+          rules:  [] 
+        },
+        //有无公交车
+        {
+          label: '有无公交车', 
+          name: 'isBus', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '有',
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无公交车',
+            type: 'number',
+          }] 
+        },
+        {
+          label: '公交车介绍', 
+          name: 'busIntro', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.isBus == 1) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入公交车介绍',
+          },
+          rules: [{
+            required: true,
+            message: '请输入公交车介绍',
+          }] 
+        },
+        {
+          label: '其他交通方式', 
+          name: 'otherBus', 
+          type: 'text', 
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他交通方式',
+          },
+          rules:  [] 
+        }
+      ]
+    },
+    {
+      label: '图片信息', 
+      name: 'imageInfo', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '景区全景图', 
+          name: 'panorama', 
+          type: 'uploader', 
+          defaultValue: '',
+          additionalProps: {
+            upload: useAliOssUploadCo('xiangyuan/travel/panorama'),
+            maxFileSize: 1024 * 1024 * 20,
+            single: true,
+          } as UploaderFieldProps,
+          rules:  [] 
+        },
+        {
+          label: '其他图', 
+          name: 'otherImage', 
+          type: 'uploader', 
+          defaultValue: '',
+          additionalProps: {
+            upload: useAliOssUploadCo('xiangyuan/travel/guide'),
+            maxFileSize: 1024 * 1024 * 20,
+            single: true,
+          } as UploaderFieldProps,
+          rules:  [] 
+        }
+      ]
+    },
+    {
+      label: '标识牌信息', 
+      name: 'signInfo', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        //解说牌
+        {
+          label: '有无解说牌', 
+          name: 'introBoard', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '有',
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无解说牌',
+          }] 
+        },
+        {
+          label: '其他解说牌', 
+          name: 'otherIntroBoard', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.introBoard == 1) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他解说牌',
+          },
+          rules:  [] 
+        },
+        {
+          label: '有无解指示牌', 
+          name: 'indicateBoard', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '有',
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无指示牌',
+          }] 
+        },
+        {
+          label: '其他指示牌', 
+          name: 'otherIndicateBoard', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.indicateBoard == 1) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他指示牌',
+          },
+          rules: [{
+            required: true,
+            message: '请输入其他指示牌',
+          }] 
+        },
+        {
+          label: '有无安全告示牌', 
+          name: 'safeBoard', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '有',
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无安全告示牌',
+          }] 
+        },
+        {
+          label: '其他安全告示牌', 
+          name: 'otherSafeBoard', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.safeBoard == 1) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他安全告示牌',
+          },
+          rules: [{
+            required: true,
+            message: '请输入其他安全告示牌',
+          }] 
+        }
+      ]
+    },
+    {
+      label: '服务设施', 
+      name: 'serviceFacilities', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '有无游客服务中心', 
+          name: 'visitorCenter', 
+          type: 'check-box-int', 
+          defaultValue: 0,
+          additionalProps: {
+            text: '有',
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有有无游客服务中心',
+          }] 
+        },
+        {
+          label: '游客服务中心面积', 
+          name: 'visitorCenterArea', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.visitorCenter == 1) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入游客服务中心面积',
+          },
+          rules: [{
+            required: true,
+            message: '请输入游客服务中心面积',
+          }] 
+        },
+        {
+          label: '商业设施', 
+          name: 'business',
+          type: 'select-id', 
+          additionalProps: {
+            loadData: async () => 
+            (await VillageInfoApi.getCategoryChildList(282))
+              .map((p) => ({
+                value: p.id,
+                text: p.title,
+              }))
+            ,
+          } as PickerIdFieldProps,
+          formProps: { showRightArrow: true } as FieldProps,
+          rules: [{
+            required: true,
+            message: '请选择商业设施',
+          }],
+        },
+        {
+          label: '其他商业设施', 
+          name: 'otherBusiness', 
+          type: 'text', 
+          defaultValue: '',
+          show: { callback: (_, rawModel) => (rawModel.business == 288) },
+          additionalProps: {
+            placeholder: '请输入其他商业设施',
+          },
+          rules: [{
+            required: true,
+            message: '请输入其他商业设施',
+          }] 
+        }
+      ]
+    },
+    {
+      label: '医疗与游览服务', 
+      name: 'medicalTourService', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        //医疗点
+        {
+          label: '有无医疗点', 
+          name: 'medicalPoint', 
+          type: 'select', 
+          defaultValue: 0,
+          additionalProps: {
+            columns: [[
+              { value: 0, text: '无' },
+              { value: 1, text: '有' },
+              { value: 2, text: '其他' }
+            ]],
+            singleValue: true,
+          } as PickerFieldProps,
+          formProps: {
+            showRightArrow: true,
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无医疗点',
+          }] 
+        },
+        {
+          label: '其他医疗点', 
+          name: 'otherMedicalPoint', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.medicalPoint == 2) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他医疗点',
+          },
+          rules: [{
+            required: true,
+            message: '请输入其他医疗点',
+          }] 
+        },
+        //游览车
+        {
+          label: '有无游览车', 
+          name: 'tourBus', 
+          type: 'select', 
+          defaultValue: 0,
+          additionalProps: {
+            columns: [[
+              { value: 0, text: '无' },
+              { value: 1, text: '有' },
+              { value: 2, text: '其他' }
+            ]],
+            singleValue: true,
+          } as PickerFieldProps,
+          formProps: {
+            showRightArrow: true,
+          },
+          rules:  [{
+            required: true,
+            message: '请选择有无游览车',
+          }] 
+        },
+        {
+          label: '其他游览车', 
+          name: 'otherTourBus', 
+          type: 'text', 
+          show: { callback: (_, rawModel) => (rawModel.tourBus == 2) },
+          defaultValue: '',
+          additionalProps: {
+            placeholder: '请输入其他游览车',
+          },
+          rules: [{
+            required: true,
+            message: '请输入其他游览车',
+          }] 
+        }
+      ]
+    },
+    {
+      label: '基础设施', 
+      name: 'infrastructure', 
+      type: 'flat-group', 
+      childrenColProps: {
+        span: 24,
+      },
+      children: [
+        {
+          label: '机动车停车场', 
+          name: 'parkingLot', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { min: 0, addonAfter: '辆' },
+          rules:  [] 
+        },
+        {
+          label: '分类垃圾点', 
+          name: 'garbagePoint', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { 
+            min: 0,
+            addonAfter: '处',
+          },
+          rules:  [] 
+        },
+        {
+          label: '公共卫生间', 
+          name: 'publicToilets', 
+          type: 'number', 
+          defaultValue: 0,
+          additionalProps: { min: 0, addonAfter: '处' },
+          rules:  [] 
+        }
+      ]
+    }
+  ] 
+})]
+
+export const villageInfoRouteForm : GroupForm = {
+  [1]: [CommonInfoModel, (r) => ({
+    formItems: [
+      {
+        label: '游览路线', 
+        name: 'route', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入游览路线',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入游览路线',
+        }] 
+      },
+      {
+        label: '路线名称', 
+        name: 'name', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入路线名称',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入路线名称',
+        }] 
+      },
+      {
+        label: '描述', 
+        name: 'desc', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入描述',
+          maxLength: 200,
+          showWordLimit: true, 
+        },
+        rules:  [{
+          required: true,
+          message: '请输入描述',
+        }] 
+      },
+      {
+        label: '起始点', 
+        name: 'startPoint', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入起始点',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入起始点',
+        }] 
+      },
+      {
+        label: '终止点', 
+        name: 'endPoint', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入终止点',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入终止点',
+        }] 
+      },
+      {
+        label: '预计时长', 
+        name: 'estimate', 
+        type: 'number', 
+        defaultValue: 1,
+        additionalProps: {
+          min: 1,
+          addonAfter: '小时',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入预计时长',
+        }] 
+      },
+      ...villageCommonContent(r, {
+        title: '游览路线',
+        showContent: false,
+        showTitle: false,
+      }).formItems
+    ] 
+  })],
+  [2]: [CommonInfoModel, (r) => ({
+    formItems: [
+      {
+        label: '活动标题', 
+        name: 'activity', 
+        type: 'text', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入活动标题',
+        },
+        rules:  [{
+          required: true,
+          message: '请输入活动标题',
+        }] 
+      },
+      {
+        label: '活动开始时间', 
+        name: 'startTime',
+        type: 'datetime', 
+        defaultValue: '',
+        additionalProps: {
+          type: 'datetime',
+        },
+        rules: [{
+          required: true,
+          message: '请选择活动开始时间',
+        }],
+      },
+      {
+        label: '活动结束时间', 
+        name: 'endTime',
+        type: 'datetime', 
+        defaultValue: '',
+        additionalProps: {
+          type: 'datetime',
+        },
+        rules: [{
+          required: true,
+          message: '请选择活动结束时间',
+        }],
+      },
+      {
+        label: '活动时长', 
+        name: 'duration',
+        type: 'number', 
+        defaultValue: 0,
+        additionalProps: {
+          min: 0,
+          addonAfter: '分钟',
+        },
+        rules: [{
+          required: true,
+          message: '请选择活动时长',
+        }],
+      },
+      ...villageCommonContent(r, {
+        title: '活动时间',
+        showContent: false,
+        showTitle: false,
+      }).formItems
+    ] 
+  })],
+  [3]: [CommonInfoModel, (r) => ({
+    formItems: [
+      {
+        label: '特色', 
+        name: 'advant', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入特色',
+          maxLength: 300,
+          showWordLimit: true, 
+        } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入特色',
+        }] 
+      },
+      {
+        label: '文化背景', 
+        name: 'intro', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入文化背景',
+          maxLength: 300,
+          showWordLimit: true, 
+        } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入文化背景',
+        }] 
+      },
+      {
+        label: '精彩推荐', 
+        name: 'recommend', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入精彩推荐',
+          maxLength: 300,
+          showWordLimit: true, 
+        } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入精彩推荐',
+        }] 
+      },
+      {
+        label: '推荐描述', 
+        name: 'reason', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入推荐描述',
+          maxLength: 300,
+          showWordLimit: true, 
+        } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入推荐描述',
+        }] 
+      },
+      {
+        label: '活动亮点', 
+        name: 'highlight', 
+        type: 'richtext', 
+        defaultValue: '',
+        additionalProps: {
+          placeholder: '请输入活动亮点',
+          maxLength: 300,
+          showWordLimit: true, 
+        } as FieldProps,
+        rules:  [{
+          required: true,
+          message: '请输入活动亮点',
+        }] 
+      },
+      ...villageCommonContent(r, {
+        title: '路线特色',
+        showContent: false,
+        showTitle: false,
+      }).formItems
+    ] 
+  })]
+}

文件差异内容过多而无法显示
+ 89 - 2444
src/pages/forms/forms.ts


+ 6 - 7
src/pages/forms/list.vue

@@ -33,13 +33,12 @@
             </div>
           </div>
         </div>
-        <SimplePageListContentLoader :loader="listLoader" :noEmpty="true">
-          <template #empty>
-            <a-empty description="暂无数据,点击按钮新增数据">
-              <a-button type="primary" @click="newData">+ 新增数据</a-button>
-            </a-empty>
-          </template>
-        </SimplePageListContentLoader>
+        <SimplePageListContentLoader :loader="listLoader" :noEmpty="true" :emptyView="{
+          text: '暂无数据,点击右上方按钮新增数据',
+          button: false,
+          buttonText: '', 
+          buttonClick: () => {}
+        }" />
       </div>
     </section>
   </div>

+ 1 - 3
src/pages/inheritor.vue

@@ -3,11 +3,10 @@
   <div class="about main-background main-background-type0">
     <div class="nav-placeholder">
     </div>
-    <img class="main-top-banner" src="https://mn.wenlvti.net/app_static/xiangan/banner_submit.jpg" />
     <!-- 表单 -->
     <section class="main-section large">
       <div class="content">
-        <div v-if="hasNav" class="title left-right">
+        <div class="title left-right">
           <span style="width:160px;"></span>
           <h2>乡源·乡村文化资源挖掘平台</h2>
           <small class="text-secondary">技术支持:18649931391</small>
@@ -74,7 +73,6 @@ import VillageApi, { VillageListItem } from '@/api/inhert/VillageApi';
 
 const router = useRouter();
 const authStore = useAuthStore();
-const hasNav = computed(() => authStore.loginFromEmbed === false);
 
 const collectStore = useCollectStore();
 const villageListLoader = useSimpleDataLoader(async () => await VillageApi.getClaimedVallageList(), true);

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

@@ -1,5 +1,5 @@
 <template>
-  <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_custom.png" />
+  <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_custom.jpg" />
   <div class="task-list">
     <div class="item">
       <i class="iconfont icon-task-custom-2"></i>

+ 1 - 1
src/pages/task/index.vue

@@ -25,7 +25,7 @@ function handleBack() {
 
 <style lang="scss">
 .tasks .head-img {
-  height: 160px;
+  height: 260px;
   object-fit: cover;
 }
 </style>

+ 1 - 19
src/stores/auth.ts

@@ -13,20 +13,11 @@ export const useAuthStore = defineStore('auth', {
     userId: 0,
     userInfo: null as null|UserInfo,
     loginType: 0,
-    loginFromEmbed: false,
   }),
   actions: {
     async loadLoginState(route: RouteLocationNormalizedLoadedGeneric) {
       try {
-        let res : string|null = null;
-        let saveLoginData = false;
-        const query = route.query.internalAuth as string|undefined;
-        if (query) {
-          res = decodeURIComponent(query);
-          saveLoginData = true;
-        } else {
-          res = localStorage.getItem(STORAGE_KEY);
-        }
+        const res = localStorage.getItem(STORAGE_KEY);
         if (!res)
           throw 'no storage';
         const authInfo = JSON.parse(res);
@@ -35,12 +26,6 @@ export const useAuthStore = defineStore('auth', {
         this.expireAt = authInfo.expireAt;
         this.userInfo = authInfo.userInfo;
         this.loginType = authInfo.loginType;
-        this.loginFromEmbed = authInfo.loginFromEmbed;
-
-        if (saveLoginData) {
-          this.loginFromEmbed = true;
-          this.saveLoginData();
-        }
 
         // 检查token是否过期,如果快要过期,则刷新token
         /*if (canRefresh && Date.now() > this.expireAt + 1000 * 3600 * 5) {
@@ -51,7 +36,6 @@ export const useAuthStore = defineStore('auth', {
         this.token = '';
         this.userId = 0;
         this.userInfo = null;
-
         console.log('loadLoginState', error);
       }
     },
@@ -77,7 +61,6 @@ export const useAuthStore = defineStore('auth', {
       this.userInfo = loginResult.userInfo;
       this.loginType = loginType;
       this.expireAt = (loginResult.expiresIn || 0) + Date.now();
-      this.loginFromEmbed = false;
       this.saveLoginData();
     },
     saveLoginData() {
@@ -88,7 +71,6 @@ export const useAuthStore = defineStore('auth', {
           expireAt: this.expireAt,
           userInfo: this.userInfo,
           loginType: this.loginType,
-          loginFromEmbed: this.loginFromEmbed,
         }) 
       );
     },