浏览代码

📦 修改首页和细节问题

快乐的梦鱼 1 周之前
父节点
当前提交
3710e8544e

+ 1 - 0
src/App.vue

@@ -6,6 +6,7 @@
         colorPrimary: '#bd4b36',
       },
     }"
+    :componentSize="'large'"
   >
     <NavBar />
     <main>

+ 20 - 0
src/api/RequestModules.ts

@@ -207,7 +207,26 @@ export function reportError<T extends DataModel>(instance: RequestCoreInstance<T
       title: '抱歉',
       content: errMsg,
     });
+  }
 }
+function responseErrorHandler<T extends DataModel>(err: Error, instance: RequestCoreInstance<T>, apiName: string | undefined) : RequestApiError {
+  if (err instanceof TypeError) {
+    let errorMessage = '';
+    if (err.message.indexOf('Failed to fetch') >= 0)
+      errorMessage = '连接网络失败,请检查您的网络连接';
+    else if (err.message.toLowerCase().indexOf('timeout') >= 0)
+      errorMessage = '请求超时,请稍后重试';
+    else if (err.message.indexOf('CORS') >= 0)
+      errorMessage = '跨域请求失败';
+    else if (err.message.indexOf('abort') >= 0)
+      errorMessage = '请求已取消';
+    else if (err.message.indexOf('Invalid URL') >= 0)
+      errorMessage = '无效URL';
+    else 
+      errorMessage = err.message;
+    return new RequestApiError('networkError', errorMessage, '', 0, null, null, null, apiName, '');
+  }
+  return new RequestApiError('networkError', err.message, '', 0, null, null, null, apiName, '');
 }
 
 /**
@@ -220,6 +239,7 @@ export class AppServerRequestModule<T extends DataModel> extends RequestCoreInst
     this.config.errCodes = []; //
     this.config.requestInceptor = requestInceptor;
     this.config.responseDataHandler = responseDataHandler;
+    this.config.responseErrorHandler = responseErrorHandler;
     this.config.responseErrReoprtInceptor = responseErrReoprtInceptor;
     this.config.reportError = reportError;
   }

+ 7 - 5
src/api/inheritor/InheritorContent.ts

@@ -369,6 +369,7 @@ export class SeminarInfo extends CommonInfo<SeminarInfo> {
       ...this._convertTable,
       lonlat: { serverSide: 'undefined' },
       visit: { clientSide: 'number' },
+      ichSiteType: { clientSide: 'number', serverSide: 'number' },
     };
     this._convertKeyType = (key, direction) => {
       if (key.endsWith('Text') || key.endsWith('_text')) {
@@ -556,6 +557,11 @@ export class InheritorContentApi extends AppServerRequestModule<DataModel> {
   async saveExpandInfo<T extends DataModel>(dataModel: T) {
     return (await this.post('/ich/inheritor/saveExpand', dataModel.toServerSide(), '扩展内容表采集(非遗,传承人,传习所)'));
   }
+  async saveWorkInfo(dataModel: InheritorWorkInfo) {
+    return (await this.post('/ich/inheritor/saveWork', {
+      ...dataModel.toServerSide(),
+    }, '保存传承人作品信息'));
+  }
   async savePlanInfo(dataModel: PlanInfo) {
     return (await this.post('/ich/inheritor/savePlans', dataModel.toServerSide(), '保存项目五年计划'));
   }
@@ -581,10 +587,6 @@ export class InheritorContentApi extends AppServerRequestModule<DataModel> {
      */
     collectType: 'content'|'ich',
     /**
-     * 采集模型:2=非遗项目,7=传承人,17=传习所
-     */
-    modelId: 2|7|17,
-    /**
      * 提交用户ID
      */
     userId?: number,
@@ -605,7 +607,7 @@ export class InheritorContentApi extends AppServerRequestModule<DataModel> {
   }) {
     return this.post('/ich/inheritor/collectList', {
       collect_type: data.collectType,
-      model_id: data.modelId,
+      model_id: new dataModel().modelId,
       user_id: data.userId,
       progress: data.progress,
       review_id: data.reviewId,

二进制
src/assets/images/LogoIconDark.png


文件差异内容过多而无法显示
+ 0 - 1
src/assets/images/Welecome.svg


+ 2 - 1
src/assets/scss/colors.scss

@@ -11,7 +11,8 @@ $background-color: rgb(249, 246, 237);
 $text-content-color: #654A38;
 $text-content-second-color: rgba(101, 74, 56, 0.6);
 
-$selection-max-width: 1250px;
+$selection-max-width: 880px;
+$selection-max-width-large: 1280px;
 
 $border-split-color:rgb(236, 236, 236);
 $border-grey-color: #b3b3b3;

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

@@ -9,13 +9,34 @@
   --vc-pgn-active-color: var(--vc-clr-primary)
 }
 .dynamic-form-group {
-  padding: 40px;
-  background-color: rgba(#eee, 0.6);
-  border-radius: 5px;
+  padding: 90px;
+  background-color: #fff;
+  border-radius: 15px;
+
+  > h3 {
+    text-align: center;
+  }
+}
+.ant-form-item {
+  margin-bottom: 48px !important;
+
+ .ant-form-item-label > label {
+  font-weight: 600;
+  font-size: 16px;
+  line-height: 24px;
+ }
 }
 
 @media screen and (max-width: 768px) {
   .dynamic-form-group {
-    padding: 20px;
+    padding: 45px;
+  }
+  .ant-form-item {
+    margin-bottom: 24px !important;
+  }
+}
+@media screen and (max-width: 425px) {
+  .dynamic-form-group {
+    padding: 30px;
   }
 }

+ 9 - 0
src/assets/scss/main.scss

@@ -185,6 +185,12 @@ $small-banner-height: 445px;
       margin-right: 10px;
     }
   }
+
+  &.large {
+    .content {
+      max-width: $selection-max-width-large;
+    }
+  }
   
   .content {
     max-width: $selection-max-width;
@@ -314,6 +320,9 @@ $small-banner-height: 445px;
       padding-top: 20px;
       padding-bottom: 20px;
     }
+    .content .title.left-right {
+      flex-direction: column;
+    }
   }
   .main-stats {
     h4 {

+ 73 - 21
src/components/dynamicf/Map/AddressSercher.vue

@@ -2,6 +2,8 @@
 import { ref, computed, nextTick, watch, type Ref } from 'vue';
 import { SearchOutlined } from '@ant-design/icons-vue';
 import type { SelectProps } from 'ant-design-vue';
+import { waitTimeOut } from '@imengyu/imengyu-utils';
+import { ScrollRect } from '@imengyu/vue-scroll-rect';
 
 // 定义Props
 const props = defineProps<{
@@ -25,8 +27,8 @@ export interface AddressItem {
 // 状态管理
 const inputValue = ref(props.modelValue);
 const addressList = ref<Array<AddressItem>>([]);
-const showDropdown = ref(false);
 const loading = ref(false);
+const openList = ref(false);
 
 // 监听modelValue变化
 watch(() => props.modelValue, (newValue) => {
@@ -39,6 +41,7 @@ watch(() => props.modelValue, (newValue) => {
 async function searchAddress() {
   if (!inputValue.value?.trim()) return;
   loading.value = true;
+  openList.value = true;
 
   try {
     const apiKey = '8fd09264c33678141f609588c432df0e';
@@ -54,18 +57,12 @@ async function searchAddress() {
         lat: parseFloat(item.location.split(',')[1]),
         address: item.address || item.name
       }));
-      
-      // 显示下拉框
-      await nextTick();
-      showDropdown.value = true;
     } else {
       addressList.value = [];
-      showDropdown.value = false;
     }
   } catch (error) {
     console.error('搜索地址失败:', error);
     addressList.value = [];
-    showDropdown.value = false;
   } finally {
     loading.value = false;
   }
@@ -75,6 +72,9 @@ async function searchAddress() {
 function handleSelectAddress(address: any) {
   // 更新输入值
   inputValue.value = address.name;
+  // 关闭下拉列表
+  openList.value = false;
+
   emit('update:modelValue', address.name);
   
   // 发送详细地址信息
@@ -84,9 +84,6 @@ function handleSelectAddress(address: any) {
     lat: address.lat,
     address: address.address
   });
-  
-  // 关闭下拉框
-  showDropdown.value = false;
 }
 
 // 处理输入变化
@@ -111,28 +108,83 @@ const selectOptions = computed<SelectProps['options']>(() => {
 </script>
 
 <template>
-  <div style="position: relative; display: flex; align-items: center; gap: 8px;">
-    <div style="position: relative; flex: 1;">
-      <a-select
+  <div class="address-searcher">
+    <div class="input-wrapper">
+      <a-input
         v-model:value="inputValue"
         mode="combobox"
         :options="selectOptions"
         :disabled="props.disabled"
         :show-search="true"
-        :open="showDropdown"
         :default-active-first-option="false"
-        :show-arrow="false"
         :filter-option="false"
-        :not-found-content="null"
+        :not-found-content="'暂无匹配的可选地址,可修改关键字扩大搜索范围'"
         placeholder="请输入地址"
         style="width: 100%;"
         @select="(value: string, option: any) => handleSelectAddress(option?.data)"
         @search="handleInputChange"
       />
     </div>
-    <a-button :disabled="props.disabled" type="primary" :loading="loading" @click="handleSearch">
-      <SearchOutlined />
-      搜索
-    </a-button>
+    <a-popover placement="bottomRight" trigger="click" v-model:open="openList" >
+      <template #content>
+        <ScrollRect scroll="vertical">
+          <a-list class="list" size="small" itemLayout="vertical" :data-source="selectOptions">
+            <template #renderItem="{ item }">
+              <div class="list-item" @click="handleSelectAddress(item.data)">
+                <div class="list-item-content">
+                  <div class="list-item-title">{{ item.label }}</div>
+                  <div class="list-item-desc">{{ item.value }}</div>
+                </div>
+              </div>
+            </template>
+          </a-list>
+        </ScrollRect>
+      </template>
+      <template #title>
+        <span>模糊地址查询</span>
+      </template>
+      <a-button :disabled="props.disabled" type="primary" :loading="loading" @click="handleSearch">
+        <SearchOutlined />
+        搜索
+      </a-button>
+    </a-popover>
   </div>
-</template>
+</template>
+
+<style scoped lang="scss">
+.address-searcher {
+  position: relative;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  .input-wrapper {
+    flex: 1;
+  }
+}
+.list {
+  width: 50vw;
+  max-width: 600px;
+  max-height: 70vh;
+
+  .list-item {
+    padding: 8px 12px;
+    cursor: pointer;
+    &:hover {
+      background-color: #f5f5f5;
+    }
+  }
+  .list-item-content {
+    display: flex;
+    flex-direction: column;
+  }
+  .list-item-title {
+    font-size: 14px;
+    font-weight: 500;
+  }
+  .list-item-desc {
+    font-size: 12px;
+    color: #999;
+  }
+}
+</style>

+ 1 - 0
src/components/dynamicf/Map/MapConfig.ts

@@ -0,0 +1 @@
+export const defaultCenter = [118.1476536, 24.503791];

+ 20 - 7
src/components/dynamicf/Map/MapPointPicker.vue

@@ -4,7 +4,7 @@
     <span class="lonlat">经纬度:{{ Number(center[0]).toFixed(6) }}, {{ Number(center[1]).toFixed(6) }}</span>
     <el-amap
       style="width: 100%"
-      v-model:center="center"
+      :center="center"
       :zoom="zoom"
       :dragEnable="!disabled"
       :zoomEnable="!disabled"
@@ -13,6 +13,7 @@
       :doubleClickZoom="!disabled"
       :scrollWheel="!disabled"
       @init="handleInit"
+      @update:center="handleUpdateCenter"
       v-bind="$attrs"
     >
     </el-amap>
@@ -20,7 +21,8 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, type PropType } from 'vue';
+import { onMounted, ref, watch, type PropType } from 'vue';
+import { defaultCenter } from './MapConfig';
 
 const props = defineProps({
   disabled: {
@@ -29,7 +31,7 @@ const props = defineProps({
   },
   modelValue: {
     type: Object as PropType<(number|string)[]>,
-    default: () => ([121.59996, 31.197646])
+    default: () => defaultCenter.concat()
   },
   zoom: {
     type: Number,
@@ -46,18 +48,28 @@ const props = defineProps({
 });
 
 const emit = defineEmits(['update:modelValue' ])
-const center = ref(props.modelValue);
+const center = ref(defaultCenter);
 let map: any = null;
 
 function handleInit(mapRef: any) {
   map = mapRef;
 }
 
+function loadCenter() {
+  center.value = [Number(props.modelValue[0]), Number(props.modelValue[1])];
+  if (isNaN(center.value[0])) center.value[0] = defaultCenter[0];
+  if (isNaN(center.value[1])) center.value[1] = defaultCenter[1];
+}
+
 watch(() => props.modelValue, (newVal) => {
-  center.value = newVal;
+  loadCenter()
 })
-watch(center, (newVal) => {
+function handleUpdateCenter(newVal: (number|string)[]) {
   emit('update:modelValue', newVal);
+}
+
+onMounted(() => {
+  loadCenter();
 })
 
 defineExpose({
@@ -75,6 +87,7 @@ defineExpose({
 div {
   position: relative;
   border: 1px solid #ccc;
+  border-radius: 10px;
 }
 .lonlat {
   position: absolute;
@@ -94,6 +107,6 @@ img {
   width: 30px;
   height: 30px;
   pointer-events: none;
-  z-index: 100;
+  z-index: 2;
 }
 </style>

+ 25 - 1
src/components/dynamicf/UploadImageFormItem.vue

@@ -8,7 +8,8 @@
     :max-count="maxCount"
     :show-upload-list="!single"
     :customRequest="handleUpload"
-    :before-upload="beforeUpload"
+    :beforeUpload="handleBeforeUpload"
+    @reject="handleUploadSubImgReject"
     @change="handleUploadSubImgChange"
   >
     <template v-if="single && !disabled">
@@ -136,15 +137,38 @@ onMounted(() => {
   }
 });
 
+const needRemoveItem : string[] = [];
+
 watch(() => props.value, () => {
   if (!props.single) {
     uploadSubImgList.value = stringUrlsToUploadedItems(props.value instanceof Array ? (props.value as string[] || []) : [])
   }
 });
+watch(uploadSubImgList, (e) => {
+  setTimeout(() => {
+    if (needRemoveItem.length > 0) {
+      //移除不允许上传的项目
+      for (let i = 0; i < e.length; i++) {
+        if (needRemoveItem.includes(e[i].uid)) 
+          uploadSubImgList.value.splice(i, 1);
+      }
+      needRemoveItem.length = 0;
+    }
+  }, 300)
+});
 
+function handleBeforeUpload(file: FileItem) {
+  const result = props.beforeUpload?.(file) ?? true;
+  if (!result)
+    needRemoveItem.push(file.uid);
+  return result;
+}
 function handleUpload(requestOption: AntUploadRequestOption) {
   props.uploadCo?.uploadRequest(requestOption);
 }
+function handleUploadSubImgReject(e: FileInfo) {
+  console.log(e);
+}
 function handleUploadSubImgChange(info: FileInfo) {
   if (info.file.status === 'uploading') {
     uploadingSubImg.value = true;

+ 26 - 1
src/components/dynamicf/UploadVideoFormItem.vue

@@ -7,8 +7,9 @@
     :class="uploadClass"
     :max-count="single ? 1 : maxCount"
     :customRequest="handleUpload"
-    :before-upload="beforeUpload"
+    :beforeUpload="handleBeforeUpload"
     @change="handleUploadSubImgChange"
+    @reject="handleUploadSubImgReject"
   >
     <a-button v-if="!disabled">
       <upload-outlined></upload-outlined>
@@ -119,13 +120,37 @@ onMounted(() => {
   setTimeout(loadValue, 400);
 });
 
+const needRemoveItem : string[] = [];
+
 watch(() => props.value, () => {
   loadValue();
 });
+watch(uploadSubImgList, (e) => {
+  setTimeout(() => {
+    if (needRemoveItem.length > 0) {
+      //移除不允许上传的项目
+      for (let i = 0; i < e.length; i++) {
+        if (needRemoveItem.includes(e[i].uid)) 
+          uploadSubImgList.value.splice(i, 1);
+      }
+      needRemoveItem.length = 0;
+    }
+  }, 300)
+});
 
+function handleBeforeUpload(file: FileItem) {
+  const result = props.beforeUpload?.(file) ?? true;
+  if (!result)
+    needRemoveItem.push(file.uid);
+  return result;
+}
 function handleUpload(requestOption: AntUploadRequestOption) {
   props.uploadCo?.uploadRequest(requestOption);
 }
+function handleUploadSubImgReject(e: FileInfo) {
+  console.log(e);
+  message.error('上传失败!' + e.file.response);
+}
 function handleUploadSubImgChange(info: FileInfo) {
   if (info.file.status === 'uploading') {
     uploadingSubImg.value = true;

+ 32 - 26
src/components/parts/EmptyToRecord.vue

@@ -1,8 +1,12 @@
 <script setup lang="ts">
+import type { PropType } from 'vue';
+import SimplePageContentLoader from '../content/SimplePageContentLoader.vue';
+import type { ISimpleDataLoader } from '@/composeable/SimpleDataLoader';
+
 const emit = defineEmits([ 'edit' ]);
 defineProps({
-  model: {
-    type: null,
+  loader: {
+    type: Object as PropType<ISimpleDataLoader<any, any>>,
     default: undefined
   },
   title: {
@@ -29,29 +33,31 @@ defineProps({
 </script>
 
 <template>
-  <a-result
-    v-if="!model"
-    status="404"
-    :title="`${title}信息`"
-    :subTitle="emptyText || `暂无${title}信息,快去补充`"
-  > 
-    <template #extra>
-      <a-button v-if="showAdd" type="primary" @click="emit('edit')">{{ buttonText }}</a-button>
-    </template>
-  </a-result>
-  <div v-else>
-    <a-alert
-      v-if="showEdited"
-      :message="`点击这里可以修改 ${title} 信息`"
-      type="info"
-      show-icon
-    >
-      <template #action>
-        <a-space>
-          <a-button size="small" type="primary" @click="emit('edit')">去修改</a-button>
-        </a-space>
+  <SimplePageContentLoader :loader="loader">
+    <a-result
+      v-if="!loader.content.value"
+      status="404"
+      :title="`${title}信息`"
+      :subTitle="emptyText || `暂无${title}信息,快去补充`"
+    > 
+      <template #extra>
+        <a-button v-if="showAdd" type="primary" @click="emit('edit')">{{ buttonText }}</a-button>
       </template>
-    </a-alert>
-    <slot></slot>
-  </div>
+    </a-result>
+    <div v-else>
+      <a-alert
+        v-if="showEdited"
+        :message="`点击这里可以修改 ${title} 信息`"
+        type="info"
+        show-icon
+      >
+        <template #action>
+          <a-space>
+            <a-button size="small" type="primary" @click="emit('edit')">去修改</a-button>
+          </a-space>
+        </template>
+      </a-alert>
+      <slot></slot>
+    </div>
+  </SimplePageContentLoader>
 </template>

+ 58 - 0
src/pages/admin-works.vue

@@ -0,0 +1,58 @@
+<template>
+  <!-- 传承人作品 -->
+  <div class="about main-background main-background-type0">
+    <div class="nav-placeholder">
+    </div>
+    <!-- 表单 -->
+    <section class="main-section large">
+      <div class="content">
+        <div class="title">
+          <h2>传承人作品管理</h2>
+        </div>
+
+        <EmptyToRecord title="作品" :loader="worksData" :showEdited="false" :showAdd="false">
+          <a-list item-layout="horizontal" :data-source="worksData?.content.value || []">
+            <template #renderItem="{ item }">
+              <a-list-item>
+                <a-list-item-meta
+                  :title="item.title"
+                  :description="item.desc"
+                >
+                  <template #avatar>
+                    <a-avatar :src="item.image" />
+                  </template>
+                </a-list-item-meta>
+                <template #actions>
+                  <a key="list-loadmore-edit" @click="handleGoWork(item)">编辑</a>
+                </template>
+              </a-list-item>
+            </template>
+          </a-list>
+        </EmptyToRecord>
+      </div>
+    </section>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRoute, useRouter } from 'vue-router';
+import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
+import EmptyToRecord from '@/components/parts/EmptyToRecord.vue';
+import InheritorContent, { InheritorWorkInfo } from '@/api/inheritor/InheritorContent';
+
+const router = useRouter();
+const route = useRoute();
+
+const worksData = useSimpleDataLoader(async () => {
+  const inheritorId = route.query.inheritorId ? parseFloat(route.query.inheritorId as string) : undefined;
+  return (await InheritorContent.getInheritorInfo(inheritorId)).works;
+})
+
+function handleGoWork(item: InheritorWorkInfo) {
+  router.push({ name: 'FormWork', query: { 
+    inheritorId: route.query.inheritorId,
+    id: item.id 
+  } })
+}
+
+</script>

+ 49 - 60
src/pages/admin.vue

@@ -4,7 +4,7 @@
     <div class="nav-placeholder">
     </div>
     <!-- 表单 -->
-    <section class="main-section ">
+    <section class="main-section large">
       <div class="content">
         <div class="title">
           <h2>管理员管理</h2>
@@ -12,67 +12,50 @@
        
         <a-tabs v-model:activeKey="activeKey" centered>
           <a-tab-pane key="1" tab="传承人列表">
-            <EmptyToRecord 
-              title="传承人"
-              emptyText="暂无数据"
-              :model="inheritorData"
-              :showEdited="false"
-              :showAdd="false"
-              :pageSize="16"
+            <!-- <div class="d-flex justify-content-end">
+              <a-button type="primary" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
+            </div> -->
+            <CommonListBlock 
+              :showTotal="true"
+              :rowCount="1"
+              :rowType="5"
+              :dropDownNames="[{
+                options: categoryData.content.value ?? [],
+                label: '分类',
+                defaultSelectedValue: 0,
+              }]"
+              :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadInheritorData(page, pageSize, dropDownValues, searchText)"  
+              :showDetail="(item) => router.push({ name: 'FormInheritor', query: { id: item.id } })"
             >
-              <!-- <div class="d-flex justify-content-end">
-                <a-button type="primary" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
-              </div> -->
-              <CommonListBlock 
-                :showTotal="true"
-                :rowCount="1"
-                :rowType="5"
-                :dropDownNames="[{
-                  options: categoryData.content.value ?? [],
-                  label: '分类',
-                  defaultSelectedValue: 0,
-                }]"
-                :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadInheritorData(page, pageSize, dropDownValues, searchText)"  
-                :showDetail="(item) => router.push({ name: 'FormInheritor', query: { id: item.id } })"
-              >
-                <template #itemRight="{ item }">
-                  <AdminItemState :item="item" />
-                  <a-button type="link">编辑</a-button>
-                  <a-button type="link" @click.stop="handleCopyAccount(item)">传承人账号</a-button>
-                </template>
-              </CommonListBlock>
-            </EmptyToRecord>
+              <template #itemRight="{ item }">
+                <AdminItemState :item="item" />
+                <a-button type="link">编辑</a-button>
+                <a-button type="link" @click.stop="handleCopyAccount(item)">传承人账号</a-button>
+                <a-button type="link" @click.stop="handleGoWorks(item)">传承人作品</a-button>
+              </template>
+            </CommonListBlock>
           </a-tab-pane>
           <a-tab-pane key="2" tab="非遗项目列表">
-            <EmptyToRecord 
-              title="非遗项目"
-              emptyText="暂无数据"
-              :model="inheritorData"
-              :showEdited="false"
-              :showAdd="false"
-              :pageSize="16"
-            >
-              <!-- <div class="d-flex justify-content-end">
-                <a-button type="primary" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
-              </div> -->
-              <CommonListBlock 
-                :showTotal="true"
-                :rowCount="1"
-                :rowType="5"
-                :dropDownNames="[{
-                  options: categoryData.content.value ?? [],
-                  label: '分类',
-                  defaultSelectedValue: 0,
-                }]"
-                :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadIchData(page, pageSize, dropDownValues, searchText)"
-                :showDetail="(item) => router.push({ name: 'FormIch', query: { id: item.id } })"
-               >
-                <template #itemRight="{ item }">
-                  <AdminItemState :item="item" />
-                  <a-button type="link" @click.stop="router.push({ name: 'FormIch', query: { id: item.id } })">编辑</a-button>
-                </template>
-              </CommonListBlock>
-            </EmptyToRecord>
+            <!-- <div class="d-flex justify-content-end">
+              <a-button type="primary" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
+            </div> -->
+            <CommonListBlock 
+              :showTotal="true"
+              :rowCount="1"
+              :rowType="5"
+              :dropDownNames="[{
+                options: categoryData.content.value ?? [],
+                label: '分类',
+                defaultSelectedValue: 0,
+              }]"
+              :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadIchData(page, pageSize, dropDownValues, searchText)"
+              :showDetail="(item) => router.push({ name: 'FormIch', query: { id: item.id } })"
+              >
+              <template #itemRight="{ item }">
+                <AdminItemState :item="item" />
+                <a-button type="link" @click.stop="router.push({ name: 'FormIch', query: { id: item.id } })">编辑</a-button>
+              </template>
+            </CommonListBlock>
           </a-tab-pane>
         </a-tabs>
       </div>
@@ -81,7 +64,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue';
+import { h, ref, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useAuthStore } from '@/stores/auth';
 import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
@@ -201,4 +184,10 @@ async function handleCopyAccount(item: GetContentListItem) {
   }
 
 }
+
+function handleGoWorks(item: GetContentListItem) {
+  router.push({ name: 'AdminWorks', query: { 
+    inheritorId: item.id 
+  } })
+}
 </script>

+ 19 - 19
src/pages/forms/form.vue

@@ -14,13 +14,11 @@
         <template v-else>   
           <a-tabs centered>
             <a-tab-pane key="1" :tab="basicTabText">
-              <ScrollRect scroll="vertical" style="height: 70vh">
-                <DynamicForm
-                  ref="formBase"
-                  :model="(formModel as any)" 
-                  :options="formOptions"
-                />
-              </ScrollRect>
+              <DynamicForm
+                ref="formBase"
+                :model="(formModel as any)" 
+                :options="formOptions"
+              />
               <div class="d-flex flex-column mt-3">
                 <div class="d-flex flex-row w-100 align-items-center justify-content-between">
                   <span>
@@ -104,12 +102,11 @@ import { useWindowOnUnLoadConfirm } from '@/composeable/WindowOnUnLoad';
 import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@imengyu/vue-dynamic-form';
 import { message, Modal, type FormInstance } from 'ant-design-vue';
 import { ArrowLeftOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
-import { ScrollRect } from '@imengyu/vue-scroll-rect';
 import { useAuthStore } from '@/stores/auth';
 import type { DataModel } from '@imengyu/js-request-transform';
-import InheritorContent from '@/api/inheritor/InheritorContent';
+import InheritorContent, { InheritorWorkInfo } from '@/api/inheritor/InheritorContent';
 import CommonListBlock from '@/components/content/CommonListBlock.vue';
-import { DateUtils, waitTimeOut } from '@imengyu/imengyu-utils';
+import { waitTimeOut } from '@imengyu/imengyu-utils';
 
 const props = defineProps({
   title: {
@@ -145,12 +142,12 @@ const props = defineProps({
     default: () => Promise.resolve()
   },
   save: {
-    type: Function as PropType<(model: T) => Promise<void>>,
-    default: () => Promise.resolve()
+    type: Function as PropType<(model: T) => Promise<DataModel>>,
+    default: (a: any) => Promise.resolve(a)
   },
   saveExtend: {
-    type: Function as PropType<(model: U) => Promise<void>>,
-    default: () => Promise.resolve()
+    type: Function as PropType<(model: U) => Promise<DataModel>>,
+    default: (a: any) => Promise.resolve(a)
   },
 })
 
@@ -189,8 +186,13 @@ async function handleSubmitBase() {
     return;
   }
   try {
-    const result = await InheritorContent.saveBaseInfo(formModel.value);
-    await props.save(formModel.value);
+
+    let result = null;
+    const data = await props.save(formModel.value);
+    if (formModel.value instanceof InheritorWorkInfo)
+      result = await InheritorContent.saveWorkInfo(data as InheritorWorkInfo);
+    else
+      result = await InheritorContent.saveBaseInfo(data);
     Modal.success({
       title: '提交成功',
       content: result.message,
@@ -221,7 +223,7 @@ async function handleSubmitExtend() {
     return;
   }
   try {
-    const result = await InheritorContent.saveExpandInfo(formModel.value);
+    const result = await InheritorContent.saveExpandInfo(await props.saveExtend(formModel.value));
     await props.save(formModel.value);
     Modal.success({
       title: '提交成功',
@@ -256,7 +258,6 @@ async function loadHistoryData(page: number, pageSize: number, dropDownValues: n
   const res = (await InheritorContent.getCollectList(props.model, {
     contentId: Number(route.query.id || formModel.value.contentId),
     collectType: 'content',
-    modelId: 2,
     userId: authStore.userInfo?.id,
     page,
     pageSize
@@ -276,7 +277,6 @@ async function handleShowHistory(item: any) {
   showHistoryLoading.value = true;
   showHistory.value = true;
   await waitTimeOut(100);
-   // showHistoryModel.value = await InheritorContent.getCollectListInfo(props.model, item.id);
   showHistoryModel.value = item;
   showHistoryLoading.value = false;
 }

+ 4 - 2
src/pages/forms/ich.vue

@@ -109,14 +109,14 @@ const formOptions = ref<IDynamicFormOptions>({
           },
         },
         
-        {
+        /* {
           type: 'simple-flat', label: '', name: 'map',
           childrenColProps: { span: 12 },
           children: [
             { label: '平面坐标X', name: 'mapX', type: 'number', additionalProps: { placeholder: '请输入平面坐标X' } },
             { label: '平面坐标Y', name: 'mapY', type: 'number', additionalProps: { placeholder: '请输入平面坐标Y' } },
           ]
-        },
+        }, */
         { label: '保护单位(多个保护单位请用逗号隔开)', name: 'unit', type: 'text', additionalProps: { placeholder: '请输入保护单位' } },
         //{ label: '非遗编号', name: 'code', type: 'text', additionalProps: { placeholder: '请输入非遗编号' } },
         //{ label: '流行地区', name: 'popularRegion', type: 'text', additionalProps: { placeholder: '请输入流行地区' } },
@@ -131,6 +131,7 @@ const formOptions = ref<IDynamicFormOptions>({
             placeholder: '请上传图片',
             maxCount: 20,
             name: 'file',
+            accept: 'image/*',
             beforeUpload: useBeforeUploadImageChecker(),
             uploadCo: useAliOssUploadCo('ich/images'),
           } as UploadImageFormItemProps,
@@ -140,6 +141,7 @@ const formOptions = ref<IDynamicFormOptions>({
           //hidden: { callback: (_, model) => (model as IchInfo).type !== 3 },
           additionalProps: {
             placeholder: '请上传视频',
+            accept: 'video/*',
             name: 'file',
             beforeUpload: useBeforeUploadVideoChecker(),
             uploadCo: useAliOssUploadCo('ich/video'),

+ 10 - 2
src/pages/forms/inheritor.vue

@@ -87,6 +87,7 @@ const formOptions = ref<IDynamicFormOptions>({
             placeholder: '请上传图片',
             maxCount: 20,
             name: 'file',
+            accept: 'image/*',
             beforeUpload: useBeforeUploadImageChecker(),
             uploadCo: useAliOssUploadCo('inheritor/images'),
           } as UploadImageFormItemProps,
@@ -126,6 +127,7 @@ const formOptions = ref<IDynamicFormOptions>({
           additionalProps: {
             placeholder: '请上传视频',
             name: 'file',
+            accept: 'video/*',
             beforeUpload: useBeforeUploadVideoChecker(),
             uploadCo: useAliOssUploadCo('inheritor/video'),
           } as UploadImageFormItemProps,  
@@ -199,6 +201,7 @@ const formOptions = ref<IDynamicFormOptions>({
           additionalProps: {
             placeholder: '请上传图片',
             name: 'file',
+            accept: 'image/*',
             beforeUpload: useBeforeUploadImageChecker(),
             uploadCo: useAliOssUploadCo('inheritor/images'),
           } as UploadImageFormItemProps,
@@ -218,6 +221,7 @@ const formOptions = ref<IDynamicFormOptions>({
             placeholder: '请上传图片',
             maxCount: 20,
             name: 'file',
+            accept: 'image/*',
             beforeUpload: useBeforeUploadImageChecker(),
             uploadCo: useAliOssUploadCo('inheritor/images'),
           } as UploadImageFormItemProps,
@@ -228,6 +232,7 @@ const formOptions = ref<IDynamicFormOptions>({
           additionalProps: {
             placeholder: '请上传音频',
             name: 'file',
+            accept: 'audio/*',
             beforeUpload: useBeforeUploadAudioChecker(),
             uploadCo: useAliOssUploadCo('inheritor/audios'),
           } as UploadImageFormItemProps,
@@ -324,6 +329,7 @@ const formExtendOptions = ref<IDynamicFormOptions>({
       type: 'single-image',
       additionalProps: {
         placeholder: '请上传证件照',
+        accept: 'image/*',
         beforeUpload: useBeforeUploadImageChecker(),
         uploadCo: useAliOssUploadCo('inheritor/idcards'),
       } as UploadImageFormItemProps
@@ -541,7 +547,8 @@ const formExtendOptions = ref<IDynamicFormOptions>({
       type: 'mulit-image',
       additionalProps: {
         placeholder: '请上传图片资源',
-            beforeUpload: useBeforeUploadImageChecker(),
+        accept: 'image/*',
+        beforeUpload: useBeforeUploadImageChecker(),
         uploadCo: useAliOssUploadCo('inheritor/images'),
       }
     },
@@ -634,7 +641,8 @@ const formExtendOptions = ref<IDynamicFormOptions>({
       type: 'single-image',
       additionalProps: {
         placeholder: '请上传被推荐人身份证复印件',
-            beforeUpload: useBeforeUploadImageChecker(),
+        accept: 'image/*',
+        beforeUpload: useBeforeUploadImageChecker(),
         uploadCo: useAliOssUploadCo('inheritor/idcards'),
       } as UploadImageFormItemProps
     },

+ 16 - 14
src/pages/forms/seminar.vue

@@ -13,16 +13,14 @@
 
 <script setup lang="ts">
 import { ref, type Ref } from 'vue';
-import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
 import Form from './form.vue';
 import InheritorContent, { SeminarExpandInfo, SeminarInfo } from '@/api/inheritor/InheritorContent';
 import CommonContent from '@/api/CommonContent';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@imengyu/vue-dynamic-form';
-import type { SelectProps } from 'ant-design-vue';
-import type { UploadImageFormItemProps } from '@/components/dynamicf/UploadImageFormItem';
 import type { AddressItem } from '@/components/dynamicf/Map/AddressSercher.vue';
 import { useAuthStore } from '@/stores/auth';
 import { useAliOssUploadCo } from '@/common/upload/AliOssUploadCo';
+import { useBeforeUploadImageChecker, type UploadImageFormItemProps } from '@/components/dynamicf/UploadImageFormItem';
 
 const authStore = useAuthStore();
 const formRef = ref();
@@ -57,6 +55,15 @@ const formOptions = ref<IDynamicFormOptions>({
             placeholder: '请选择传习所级别',
             loadData: async () => (await CommonContent.getCategoryList(2)).map(p => ({ label: p.title, value: p.id, raw: p }))
           },
+        },{ 
+          label: '图片', name: 'image', type: 'single-image',
+          additionalProps: {
+            placeholder: '请上传图片',
+            name: 'file',
+            accept: 'image/*',
+            beforeUpload: useBeforeUploadImageChecker(),
+            uploadCo: useAliOssUploadCo('seminar/images')
+          } as UploadImageFormItemProps,
         },
         { label: '传习所介绍', name: 'content', type: 'richtext', additionalProps: { placeholder: '请输入内容' } },
         
@@ -72,14 +79,14 @@ const formOptions = ref<IDynamicFormOptions>({
         },
         { label: '地图坐标', name: 'lonlat', type: 'map-pick-point' },
         
-        {
+        /* {
           type: 'simple-flat', label: '', name: 'map',
           childrenColProps: { span: 12 },
           children: [
             { label: '平面坐标X', name: 'mapX', type: 'number', additionalProps: { placeholder: '请输入平面坐标X' } },
             { label: '平面坐标Y', name: 'mapY', type: 'number', additionalProps: { placeholder: '请输入平面坐标Y' } },
           ]
-        },
+        }, */
         
         { label: '联系人', name: 'contact', type: 'text', additionalProps: { placeholder: '请输入联系人' } },
         { label: '联系电话', name: 'mobile', type: 'text', additionalProps: { placeholder: '请输入联系电话' } },
@@ -149,15 +156,7 @@ const formOptions = ref<IDynamicFormOptions>({
               { text: '数字档案', value: 5 }]
             },  
         },
-        { 
-          label: '图片', name: 'image', type: 'single-image',
-          additionalProps: {
-            placeholder: '请上传图片',
-            name: 'file',
-            beforeUpload: useBeforeUploadImageChecker(),
-            uploadCo: useAliOssUploadCo('seminar/images')
-          } as UploadImageFormItemProps,
-        },
+        
         { 
           label: '图片说明', name: 'imageDesc', type: 'text',
           additionalProps: { placeholder: '请输入图片说明' }
@@ -173,6 +172,7 @@ const formOptions = ref<IDynamicFormOptions>({
             placeholder: '请上传图片',
             maxCount: 20,
             name: 'file',
+            accept: 'image/*',
             beforeUpload: useBeforeUploadImageChecker(),
             uploadCo: useAliOssUploadCo('seminar/images'),
           } as UploadImageFormItemProps,
@@ -183,6 +183,7 @@ const formOptions = ref<IDynamicFormOptions>({
           additionalProps: {
             placeholder: '请上传音频',
             name: 'file',
+            accept: 'audio/*',
             beforeUpload: useBeforeUploadAudioChecker(),
             uploadCo: useAliOssUploadCo('seminar/audios')
           } as UploadImageFormItemProps,
@@ -193,6 +194,7 @@ const formOptions = ref<IDynamicFormOptions>({
           additionalProps: {
             placeholder: '请上传视频',
             name: 'file',
+            accept: 'video/*',
             beforeUpload: useBeforeUploadVideoChecker(),
             uploadCo: useAliOssUploadCo('seminar/videos')
           } as UploadImageFormItemProps,  

+ 11 - 2
src/pages/forms/works.vue

@@ -6,6 +6,7 @@
     :load="loadData"
     :model="InheritorWorkInfo"
     basicTabText="作品/产品信息"
+    :save="handleSave"
   />
 </template>
 
@@ -57,8 +58,9 @@ const formOptions = ref<IDynamicFormOptions>({
             hidden: { callback: (_, model) => (model as InheritorWorkInfo).type !== 4 },
             additionalProps: { 
               placeholder: '请上传组图', 
+              accept: 'image/*',
               beforeUpload: useBeforeUploadImageChecker(),
-              uploadCo: useAliOssUploadCo('inheritor/images'), name: 'file', accept: 'image/*', maxCount: 20 
+              uploadCo: useAliOssUploadCo('inheritor/images'), name: 'file', maxCount: 20 
             } as UploadImageFormItemProps 
           },
           { label: '作品/产品介绍', name: 'content', type: 'richtext', additionalProps: { placeholder: '请输入内容介绍' } },
@@ -67,6 +69,7 @@ const formOptions = ref<IDynamicFormOptions>({
             hidden: { callback: (_, model) => (model as InheritorWorkInfo).type !== 2 },
             additionalProps: { 
               placeholder: '请上传音频', 
+              accept: 'audio/*',
               beforeUpload: useBeforeUploadAudioChecker(),
               uploadCo: useAliOssUploadCo('inheritor/audios'), 
               name: 'file' 
@@ -153,11 +156,17 @@ const formOptions = ref<IDynamicFormOptions>({
 const route = useRoute();
 
 async function loadData() {
+  const inheritorId = route.query.inheritorId ? parseFloat(route.query.inheritorId as string) : undefined;
   const id = parseFloat(route.query.id as string);
   if (id) {
-    const works = await InheritorContent.getInheritorInfo(undefined);
+    const works = await InheritorContent.getInheritorInfo(inheritorId);
     formModel.value = works.works.find((item) => item.id === id) || new InheritorWorkInfo();
   }
 }
+async function handleSave(data: InheritorWorkInfo) {
+  data.ichId = route.query.id ? parseFloat(route.query.id as string) : undefined;
+  data.inheritorId = route.query.inheritorId ? parseFloat(route.query.inheritorId as string) : undefined;
+  return data;
+}
 
 </script>

+ 221 - 31
src/pages/index.vue

@@ -1,28 +1,84 @@
 <template>
   <div class="main-background main-background-type0 index">
     <div class="nav-placeholder"></div>
-    <div class="empty-page">
-      <img src="@/assets/images/Welecome.svg" />
-      <h1>欢迎您 {{authStore.userInfo?.nickname}}!</h1>
-      <p></p>
-      
-      <RouterLink v-if="authStore.isLogged && authStore.loginType === 1" to="/admin">
-        <a-button size="large" type="primary">进入管理员页面</a-button>
-      </RouterLink>
-      <RouterLink v-else-if="authStore.isLogged" to="/inheritor">
-        <a-button size="large" type="primary">进入非遗数字化资源信息校对</a-button>
-      </RouterLink>
-      <RouterLink v-else to="/login">
-        <a-button size="large" type="primary">去登录</a-button>
-      </RouterLink>
+    <div class="hero-section">
+      <!-- 大标题区域 -->
+      <div class="hero-header">
+        <img src="@/assets/images/LogoIconDark.png" class="hero-logo" />
+        <h1 class="main-title">{{TITLE}}</h1>
+        <p class="welcome-text">欢迎您 {{authStore.userInfo?.nickname}}!</p>
+      </div>
+      <div class="action-buttons">
+        <RouterLink v-if="authStore.isLogged && authStore.loginType === 1" to="/admin" class="button-link">
+          <a-button size="large" type="primary" class="action-button">进入管理员页面</a-button>
+        </RouterLink>
+        <RouterLink v-else-if="authStore.isLogged" to="/inheritor" class="button-link">
+          <a-button size="large" type="primary" class="action-button">进入非遗数字化资源信息校对</a-button>
+        </RouterLink>
+        <RouterLink v-else to="/login" class="button-link">
+          <a-button size="large" type="primary" class="action-button">去登录</a-button>
+        </RouterLink>
+      </div>
     </div>
+
+    <!--介绍内容区域-->
+    <section class="main-section large small-h">
+      <div class="content">
+        <div class="title">
+          <h2>平台优势</h2>
+        </div>
+        <div class="features-grid">
+          <div class="feature-card">
+            <div class="feature-icon">📸</div>
+            <h3>数字化采集</h3>
+            <p>高效便捷地采集闽南文化资源,支持图片、音频、视频等多种格式</p>
+          </div>
+          <div class="feature-card">
+            <div class="feature-icon">📋</div>
+            <h3>智能管理</h3>
+            <p>系统化管理非遗项目信息,实现快速检索和数据统计分析</p>
+          </div>
+          <div class="feature-card">
+            <div class="feature-icon">🔍</div>
+            <h3>精准校对</h3>
+            <p>专业的信息校对流程,确保数据准确性和文化传承质量</p>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!--常见问题-->
+    <section class="main-section large small-h">
+      <div class="content">
+        <div class="title">
+          <h2>常见问题</h2>
+        </div>
+
+        <a-collapse class="faq-collapse" v-model:activeKey="activeKey" accordion>
+          <a-collapse-panel key="1" header="如何使用">
+            <p>用户可通过注册账号登录系统,提交非遗项目,传承人,传习所信息。</p>
+          </a-collapse-panel>
+          <a-collapse-panel key="2" header="是免费使用的吗?">
+            <p>是的,传承人可登录系统进行资源信息提交和修改。</p>
+          </a-collapse-panel>
+          <a-collapse-panel key="3" header="如果我需要帮助,可以获得支持吗?">
+            <p>欢迎致电:18649931391 获取电话支持服务。</p>
+          </a-collapse-panel>
+        </a-collapse>
+        
+      </div>
+    </section>
   </div>
 </template>
 
 <script setup lang="ts">
+import { TITLE } from '@/common/ConstStrings';
 import { useAuthStore } from '@/stores/auth';
+import { ref } from 'vue';
+import { RouterLink } from 'vue-router';
 
 const authStore = useAuthStore();
+const activeKey = ref('1');
 </script>
 
 <style lang="scss">
@@ -31,32 +87,166 @@ const authStore = useAuthStore();
 .index {
   min-height: calc(100vh - 50px);
 }
-.empty-page {
+
+.hero-section {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
   text-align: center;
-  font-family: SourceHanSerifCNBold;
-  min-height: 66vh;
+  padding: 40px 20px;
+  min-height: 80vh;
+}
+.hero-header {
+  margin-bottom: 40px;
+}
+.hero-logo {
+  width: 60px;
+  height: 60px;
+  margin-top: 64px;
+  margin-bottom: 24px;
+  transition: transform 0.3s ease;
 
-  img {
-    width: 150px;
-    height: 150px;
-    margin-bottom: 20px;
+  &:hover {
+    transform: scale(1.05);
   }
-  h1 {
-    font-size: 2rem;
+}
+
+.main-title {
+  font-size: 2.8rem;
+  font-weight: 700;
+  color: #333;
+  margin-bottom: 16px;
+  letter-spacing: 1px;
+  font-family: 'SourceHanSerifCNBold', sans-serif;
+}
+
+.welcome-text {
+  font-size: 1.4rem;
+  color: #666;
+  margin-bottom: 32px;
+}
+.action-buttons {
+  display: flex;
+  gap: 20px;
+  flex-wrap: wrap;
+  justify-content: center;
+  margin-bottom: 60px;
+}
+.button-link {
+  text-decoration: none;
+}
+.action-button {
+  padding: 12px 24px;
+  font-size: 1.1rem;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  min-width: 200px;
+
+  &:hover {
+    transform: translateY(-3px);
+    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
   }
-  a {
-    text-decoration: none;
-    font-size: 1.2rem;
-    cursor: pointer;
+}
+
+.features-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  gap: 30px;
+}
+.feature-card {
+  background: white;
+  border-radius: 12px;
+  padding: 30px;
+  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s ease;
+
+  h3 {
+    text-align: center;
+  }
+
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
+  }
+
+  .feature-icon {
+    font-size: 2.5rem;
+    margin-bottom: 20px;
+    height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  h3 {
+    font-size: 1.3rem;
     color: #333;
+    margin-bottom: 16px;
+    font-weight: 600;
+  }
 
-    &:hover {
-      color: $primary-color;
-    }
+  p {
+    color: #666;
+    line-height: 1.6;
+  }
+}
+
+.faq-collapse {
+  border-radius: 0!important;
+  border: none!important;
+  background-color: transparent;
+
+  .ant-collapse-item {
+    background-color: #fff!important;
+    border-radius: 0.75rem!important;
+    border: 1px solid #e5e7eb!important;
+    margin-top: 1.5rem;
+    overflow: hidden;
+  }
+  .ant-collapse-item-active {
+    border-radius: 0.75rem!important;
+  }
+  .ant-collapse-header {
+    display: flex;
+    flex-direction: row;
+    align-items: center !important;
+    font-size: 1.125rem!important;
+    line-height: 1.75rem!important;
+    font-weight: 600;
+    padding: 1.5rem !important;
+
+  }
+  .ant-collapse-content {
+    border: none!important;
+    padding-left: 1.5rem;
+    padding-right: 1.5rem;
+    font-size: 1rem!important;
+  }
+  .ant-collapse-content-box {
+    padding: 0!important;
+  }
+}
+
+@media (max-width: 768px) {
+  .main-title {
+    font-size: 2.2rem;
+  }
+  .welcome-text {
+    font-size: 1.2rem;
+  }
+  .action-buttons {
+    flex-direction: column;
+    align-items: center;
+    gap: 16px;
+  } 
+  .hero-logo {
+    width: 150px;
+    height: 150px;
+  }
+  .features-grid {
+    grid-template-columns: 1fr;
   }
 }
-</style>
+</style>

+ 84 - 92
src/pages/inheritor.vue

@@ -6,127 +6,128 @@
     <!-- 表单 -->
     <section class="main-section ">
       <div class="content">
-        <div class="title">
+        <div class="title left-right">
+          <span style="width:160px;"></span>
           <h2>非遗数字化资源信息校对</h2>
+          <small class="text-secondary">技术支持:18649931391</small>
         </div>
        
         <a-tabs v-model:activeKey="activeKey" centered>
           <a-tab-pane key="1" tab="非遗项目">
-            <EmptyToRecord title="非遗项目" :model="ichData" @edit="router.push({ name: 'FormIch' })">
-              <a-alert v-if="ichData?.progress == -1" message="提交的信息被退回,您可以去修改" type="error" class="mt-3" showIcon>
+            <EmptyToRecord title="非遗项目" :loader="ichData" @edit="router.push({ name: 'FormIch' })">
+              <a-alert v-if="ichData.content.value!.progress == -1" message="提交的信息被退回,您可以去修改" type="error" class="mt-3" showIcon>
                 <template #action>
                   <a-button size="small" type="primary" @click="router.push({ name: 'FormIch' })">去修改</a-button>
                 </template>
               </a-alert>
-              <a-descriptions class="mt-3" title="非遗项目信息" v-if="ichData" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
-                <a-descriptions-item label="标题"><ShowValueOrNull :value="ichData.title" /></a-descriptions-item>
-                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ ichData.intro ]" /></a-descriptions-item>
-                <a-descriptions-item label="描述" :span="3"><SimpleRichHtml :contents="[ ichData.description ]" /></a-descriptions-item>
-                <a-descriptions-item label="类别"><ShowValueOrNull :value="ichData.ichTypeText" /></a-descriptions-item>
-                <a-descriptions-item label="级别"><ShowValueOrNull :value="ichData.levelText" /></a-descriptions-item>
-                <a-descriptions-item label="级别"><ShowValueOrNull :value="ichData.levelText" /></a-descriptions-item>
-                <a-descriptions-item label="批次"><ShowValueOrNull :value="ichData.batchText" /></a-descriptions-item>
-                <a-descriptions-item label="区域"><ShowValueOrNull :value="ichData.regionText" /></a-descriptions-item>
-                <a-descriptions-item label="保护单位"><ShowValueOrNull :value="ichData.unit" /></a-descriptions-item>
-                <a-descriptions-item v-if="ichData.image" label="图片">
-                  <a-image :src="ichData.image" style="max-width:300px;" />
+              <a-descriptions class="mt-3" title="非遗项目信息" v-if="ichData.content.value" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
+                <a-descriptions-item label="标题"><ShowValueOrNull :value="ichData.content.value.title" /></a-descriptions-item>
+                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ ichData.content.value.intro ]" /></a-descriptions-item>
+                <a-descriptions-item label="类别"><ShowValueOrNull :value="ichData.content.value.ichTypeText" /></a-descriptions-item>
+                <a-descriptions-item label="级别"><ShowValueOrNull :value="ichData.content.value.levelText" /></a-descriptions-item>
+                <a-descriptions-item label="级别"><ShowValueOrNull :value="ichData.content.value.levelText" /></a-descriptions-item>
+                <a-descriptions-item label="批次"><ShowValueOrNull :value="ichData.content.value.batchText" /></a-descriptions-item>
+                <a-descriptions-item label="区域"><ShowValueOrNull :value="ichData.content.value.regionText" /></a-descriptions-item>
+                <a-descriptions-item label="保护单位"><ShowValueOrNull :value="ichData.content.value.unit" /></a-descriptions-item>
+                <a-descriptions-item v-if="ichData.content.value.image" label="图片">
+                  <a-image :src="ichData.content.value.image" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="ichData.video" label="视频">
-                  <video controls :src="ichData.video" style="max-width:300px;" />
+                <a-descriptions-item v-if="ichData.content.value.video" label="视频">
+                  <video controls :src="ichData.content.value.video" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="ichData.audio" label="音频">
-                  <audio controls :src="ichData.audio" style="max-width:300px;" />
+                <a-descriptions-item v-if="ichData.content.value.audio" label="音频">
+                  <audio controls :src="ichData.content.value.audio" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item label="类型"><ShowValueOrNull :value="ichData.typeText" /></a-descriptions-item>
-                <a-descriptions-item v-if="ichData.latitude && ichData.longitude" label="地图">
-                  <SimplePointedMap :longitude="ichData.longitude" :latitude="ichData.latitude" :zoom="15" height="300px"  />
+                <a-descriptions-item label="类型"><ShowValueOrNull :value="ichData.content.value.typeText" /></a-descriptions-item>
+                <a-descriptions-item v-if="ichData.content.value.latitude && ichData.content.value.longitude" label="地图">
+                  <SimplePointedMap :longitude="ichData.content.value.longitude" :latitude="ichData.content.value.latitude" :zoom="15" height="300px"  />
                 </a-descriptions-item>
               </a-descriptions>
             </EmptyToRecord>
           </a-tab-pane>
           <a-tab-pane key="2" tab="传承人">
-            <EmptyToRecord title="传承人" :model="inheritorData" @edit="router.push({ name: 'FormInheritor' })">
-              <a-alert v-if="inheritorData?.progress == -1" message="提交的信息被退回,您可以去修改" type="error" class="mt-3" showIcon>
+            <EmptyToRecord title="传承人" :loader="inheritorData" @edit="router.push({ name: 'FormInheritor' })">
+              <a-alert v-if="inheritorData.content.value!.progress == -1" message="提交的信息被退回,您可以去修改" type="error" class="mt-3" showIcon>
                 <template #action>
                   <a-button size="small" type="primary" @click="router.push({ name: 'FormInheritor' })">去修改</a-button>
                 </template>
               </a-alert>
-              <a-descriptions class="mt-3" title="传承人信息" v-if="inheritorData" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
-                <a-descriptions-item label="名字"><ShowValueOrNull :value="inheritorData.title" /></a-descriptions-item>
-                <a-descriptions-item v-if="inheritorData.image" label="头像">
-                  <a-avatar :src="inheritorData.image" size="large" />
+              <a-descriptions class="mt-3" title="传承人信息" v-if="inheritorData.content.value" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
+                <a-descriptions-item label="名字"><ShowValueOrNull :value="inheritorData.content.value.title" /></a-descriptions-item>
+                <a-descriptions-item v-if="inheritorData.content.value.image" label="头像">
+                  <a-avatar :src="inheritorData.content.value.image" size="large" />
                 </a-descriptions-item>
-                <a-descriptions-item label="传承人等级"><ShowValueOrNull :value="inheritorData.levelText" /></a-descriptions-item>
-                <a-descriptions-item label="传承人批次"><ShowValueOrNull :value="inheritorData.batchText" /></a-descriptions-item>
-                <a-descriptions-item label="别称"><ShowValueOrNull :value="inheritorData.alsoName" /></a-descriptions-item>
-                <a-descriptions-item label="时代"><ShowValueOrNull :value="inheritorData.age" /></a-descriptions-item>
-                <a-descriptions-item label="出生地"><ShowValueOrNull :value="inheritorData.birthplace" /></a-descriptions-item>
-                <a-descriptions-item label="民族"><ShowValueOrNull :value="inheritorData.nation" /></a-descriptions-item>
-                <a-descriptions-item label="出生日期"><ShowValueOrNull :value="inheritorData.dateBirth" /></a-descriptions-item>
-                <a-descriptions-item label="逝世日期"><ShowValueOrNull :value="inheritorData.deathBirth" /></a-descriptions-item>
-                <a-descriptions-item label="单位"><ShowValueOrNull :value="inheritorData.unit" /></a-descriptions-item>
-                <a-descriptions-item label="区域"><ShowValueOrNull :value="inheritorData.regionText" /></a-descriptions-item>
+                <a-descriptions-item label="传承人等级"><ShowValueOrNull :value="inheritorData.content.value.levelText" /></a-descriptions-item>
+                <a-descriptions-item label="传承人批次"><ShowValueOrNull :value="inheritorData.content.value.batchText" /></a-descriptions-item>
+                <a-descriptions-item label="别称"><ShowValueOrNull :value="inheritorData.content.value.alsoName" /></a-descriptions-item>
+                <a-descriptions-item label="时代"><ShowValueOrNull :value="inheritorData.content.value.age" /></a-descriptions-item>
+                <a-descriptions-item label="出生地"><ShowValueOrNull :value="inheritorData.content.value.birthplace" /></a-descriptions-item>
+                <a-descriptions-item label="民族"><ShowValueOrNull :value="inheritorData.content.value.nation" /></a-descriptions-item>
+                <a-descriptions-item label="出生日期"><ShowValueOrNull :value="inheritorData.content.value.dateBirth" /></a-descriptions-item>
+                <a-descriptions-item label="逝世日期"><ShowValueOrNull :value="inheritorData.content.value.deathBirth" /></a-descriptions-item>
+                <a-descriptions-item label="单位"><ShowValueOrNull :value="inheritorData.content.value.unit" /></a-descriptions-item>
+                <a-descriptions-item label="区域"><ShowValueOrNull :value="inheritorData.content.value.regionText" /></a-descriptions-item>
 
-                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ inheritorData.intro ]" /></a-descriptions-item>
-                <a-descriptions-item label="描述" :span="3"><SimpleRichHtml :contents="[ inheritorData.content! ]" /></a-descriptions-item>
-                <a-descriptions-item label="奖项/成就" :span="3"><SimpleRichHtml :contents="[ inheritorData.prize ]" /></a-descriptions-item>
+                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ inheritorData.content.value.intro ]" /></a-descriptions-item>
+                <a-descriptions-item label="描述" :span="3"><SimpleRichHtml :contents="[ inheritorData.content.value.content! ]" /></a-descriptions-item>
+                <a-descriptions-item label="奖项/成就" :span="3"><SimpleRichHtml :contents="[ inheritorData.content.value.prize ]" /></a-descriptions-item>
                       
-                <a-descriptions-item v-if="inheritorData.typicalImages" label="代表性图片">
-                  <ImageGrid :data="inheritorData.typicalImages" />
+                <a-descriptions-item v-if="inheritorData.content.value.images" label="代表性图片">
+                  <ImageGrid :data="inheritorData.content.value.images" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="inheritorData.video" label="视频">
-                  <video controls :src="inheritorData.video" style="max-width:300px;" />
+                <a-descriptions-item v-if="inheritorData.content.value.video" label="视频">
+                  <video controls :src="inheritorData.content.value.video" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="inheritorData.audio" label="音频">
-                  <audio controls :src="inheritorData.audio" style="max-width:300px;" />
+                <a-descriptions-item v-if="inheritorData.content.value.audio" label="音频">
+                  <audio controls :src="inheritorData.content.value.audio" style="max-width:300px;" />
                 </a-descriptions-item>
               </a-descriptions>
             </EmptyToRecord>
           </a-tab-pane>
           <a-tab-pane key="3" tab="传习所">
-            <EmptyToRecord title="传习所" :model="seminarData" @edit="router.push({ name: 'FormSeminar' })">
-              <a-alert v-if="seminarData?.progress == -1" message="提交的信息被退回,您可以去修改" type="warning" showIcon></a-alert>
-              <a-descriptions class="mt-3" title="传习所信息" v-if="seminarData" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
-                <a-descriptions-item label="标题"><ShowValueOrNull :value="seminarData.title" /></a-descriptions-item>
-                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ seminarData.desc as string ]" /></a-descriptions-item>
-                <a-descriptions-item label="介绍" :span="3"><SimpleRichHtml :contents="[ seminarData.content as string ]" /></a-descriptions-item>
-                <a-descriptions-item v-if="seminarData.latitude && seminarData.longitude" label="地图">
-                  <SimplePointedMap :longitude="seminarData.longitude" :latitude="seminarData.latitude" :zoom="15" height="300px"  />
+            <EmptyToRecord title="传习所" :loader="seminarData" @edit="router.push({ name: 'FormSeminar' })">
+              <a-alert v-if="seminarData.content.value?.progress == -1" message="提交的信息被退回,您可以去修改" type="warning" showIcon></a-alert>
+              <a-descriptions class="mt-3" title="传习所信息" v-if="seminarData.content.value" bordered :column="{ xs: 1, sm: 1, md: 1, lg: 2 }">
+                <a-descriptions-item label="标题"><ShowValueOrNull :value="seminarData.content.value.title" /></a-descriptions-item>
+                <a-descriptions-item label="简介" :span="3"><SimpleRichHtml :contents="[ seminarData.content.value.desc as string ]" /></a-descriptions-item>
+                <a-descriptions-item label="介绍" :span="3"><SimpleRichHtml :contents="[ seminarData.content.value.content as string ]" /></a-descriptions-item>
+                <a-descriptions-item v-if="seminarData.content.value.latitude && seminarData.content.value.longitude" label="地图">
+                  <SimplePointedMap :longitude="seminarData.content.value.longitude" :latitude="seminarData.content.value.latitude" :zoom="15" height="300px"  />
                 </a-descriptions-item>
-                <a-descriptions-item label="地址"><ShowValueOrNull :value="seminarData.address" /></a-descriptions-item>
-                <a-descriptions-item label="批次"><ShowValueOrNull :value="seminarData.batchText" /></a-descriptions-item>
-                <a-descriptions-item label="级别"><ShowValueOrNull :value="seminarData.levelText" /></a-descriptions-item>
+                <a-descriptions-item label="地址"><ShowValueOrNull :value="seminarData.content.value.address" /></a-descriptions-item>
+                <a-descriptions-item label="批次"><ShowValueOrNull :value="seminarData.content.value.batchText" /></a-descriptions-item>
+                <a-descriptions-item label="级别"><ShowValueOrNull :value="seminarData.content.value.levelText" /></a-descriptions-item>
                 
-                <a-descriptions-item label="联系人"><ShowValueOrNull :value="seminarData.contact" /></a-descriptions-item>
-                <a-descriptions-item label="联系电话"><ShowValueOrNull :value="seminarData.mobile" /></a-descriptions-item>
+                <a-descriptions-item label="联系人"><ShowValueOrNull :value="seminarData.content.value.contact" /></a-descriptions-item>
+                <a-descriptions-item label="联系电话"><ShowValueOrNull :value="seminarData.content.value.mobile" /></a-descriptions-item>
                 
-                <a-descriptions-item label="单位类型"><ShowValueOrNull :value="seminarData.ichSiteTypeText" /></a-descriptions-item>
-                <a-descriptions-item label="单位"><ShowValueOrNull :value="seminarData.unit" /></a-descriptions-item>
+                <a-descriptions-item label="单位类型"><ShowValueOrNull :value="seminarData.content.value.ichSiteTypeText" /></a-descriptions-item>
+                <a-descriptions-item label="单位"><ShowValueOrNull :value="seminarData.content.value.unit" /></a-descriptions-item>
                 
-                <a-descriptions-item v-if="seminarData.latitude && seminarData.longitude" label="地图">
-                  <SimplePointedMap :longitude="seminarData.longitude" :latitude="seminarData.latitude" :zoom="15" height="300px"  />
+                <a-descriptions-item v-if="seminarData.content.value.latitude && seminarData.content.value.longitude" label="地图">
+                  <SimplePointedMap :longitude="seminarData.content.value.longitude" :latitude="seminarData.content.value.latitude" :zoom="15" height="300px"  />
                 </a-descriptions-item>
 
-                <a-descriptions-item v-if="seminarData.image" label="图片">
-                  <a-image :src="seminarData.image" style="max-width:300px;" />
+                <a-descriptions-item v-if="seminarData.content.value.image" label="图片">
+                  <a-image :src="seminarData.content.value.image" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="seminarData.video" label="视频">
-                  <video controls :src="seminarData.video" style="max-width:300px;" />
+                <a-descriptions-item v-if="seminarData.content.value.video" label="视频">
+                  <video controls :src="seminarData.content.value.video" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item v-if="seminarData.audio" label="音频">
-                  <audio controls :src="seminarData.audio" style="max-width:300px;" />
+                <a-descriptions-item v-if="seminarData.content.value.audio" label="音频">
+                  <audio controls :src="seminarData.content.value.audio" style="max-width:300px;" />
                 </a-descriptions-item>
-                <a-descriptions-item label="类型"><ShowValueOrNull :value="seminarData.typeText" /></a-descriptions-item>
+                <a-descriptions-item label="类型"><ShowValueOrNull :value="seminarData.content.value.typeText" /></a-descriptions-item>
 
               </a-descriptions>
             </EmptyToRecord>
           </a-tab-pane>
           <a-tab-pane key="4" tab="作品">
-            <EmptyToRecord title="作品" buttonText="新增作品" :model="inheritorData?.works" :showEdited="false" @edit="router.push({ name: 'FormWork' })">
+            <EmptyToRecord title="作品" buttonText="新增作品" :loader="inheritorData" :showEdited="false" @edit="router.push({ name: 'FormWork' })">
               <div class="d-flex justify-content-end">
                 <a-button type="primary" @click="router.push({ name: 'FormWork' })">+ 新增</a-button>
               </div>
-              <a-list item-layout="horizontal" :data-source="inheritorData?.works || []">
+              <a-list item-layout="horizontal" :data-source="inheritorData?.content.value?.works || []">
                 <template #renderItem="{ item }">
                   <a-list-item>
                     <a-list-item-meta
@@ -193,35 +194,26 @@ import SimpleRichHtml from '@/components/display/SimpleRichHtml.vue';
 import ShowValueOrNull from '@/components/dynamicf/Display/ShowValueOrNull.vue';
 import EmptyToRecord from '@/components/parts/EmptyToRecord.vue';
 import { SettingsUtils } from '@imengyu/imengyu-utils';
+import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 
 const router = useRouter();
 const route = useRoute();
 const activeKey = ref(route.query.tab as string || '1');
-const ichData = ref<IchInfo>();
-const inheritorData = ref<InheritorInfo>();
-const seminarData = ref<SeminarInfo>();
 const planData = ref<PlanInfo[]>([]);
 const showInMessage = ref(false);
 
 watch(activeKey, (newValue) => {
   router.replace({ query: { tab: newValue } });
-})
-onMounted(() => {
-  InheritorContent.getIchInfo(undefined).then(data => {
-    ichData.value = data;
-    InheritorContent.getPlanList(ichData.value.id).then(data => {
-      planData.value = data;
-    });
-  });
-  InheritorContent.getInheritorInfo(undefined).then(data => {
-    inheritorData.value = data;
-    openInMessage();
-  });
-  InheritorContent.getSeminarInfo(undefined).then(data => {
-    seminarData.value = data;
-  });
+});
+
+const ichData = useSimpleDataLoader(async () => {
+  const data = await InheritorContent.getIchInfo(undefined);
+  planData.value = await InheritorContent.getPlanList(data.id);
+  return data;
+});
+const inheritorData = useSimpleDataLoader(async () => await InheritorContent.getInheritorInfo(undefined));
+const seminarData = useSimpleDataLoader(async () => await InheritorContent.getSeminarInfo(undefined));
 
-})
 
 function openInMessage() {
   const lastTime = new Date(parseInt('' + SettingsUtils.getSettings('inheritorShowInMessageLastTime', 0)));

+ 5 - 0
src/router/index.ts

@@ -41,6 +41,11 @@ const router = createRouter({
       component: () => import('@/pages/admin.vue'),
     },
     {
+      path: '/admin-works',
+      name: 'AdminWorks',
+      component: () => import('@/pages/admin-works.vue'),
+    },
+    {
       path: '/change-password',
       name: 'ChangePassword',
       component: () => import('@/pages/change-password.vue'),