快乐的梦鱼 преди 5 дни
родител
ревизия
73d82fecb4

+ 0 - 248
.tmp-showdoc-1.html

@@ -1,248 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8" />
-    <meta
-      name="viewport"
-      content="width=device-width,viewport-fit=cover, initial-scale=1.0"
-    />
-    <meta name="renderer" content="webkit" />
-    <meta http-equiv="X-UA-Compatible" content="edge" />
-    <meta http-equiv="Expires" content="0" />
-    <meta http-equiv="Pragma" content="no-cache" />
-    <meta
-      http-equiv="Cache-control"
-      content="no-cache, no-store, must-revalidate"
-    />
-    <meta http-equiv="Cache" content="no-cache" />
-    <link rel="apple-touch-icon" href="https://showdoc-server.cdn.dfyun.com.cn/logo/w_64.png" />
-    <link rel="apple-touch-icon-precomposed" href="https://showdoc-server.cdn.dfyun.com.cn/logo/w_64.png" />
-    <link rel="icon" href="https://showdoc-server.cdn.dfyun.com.cn/favicon.ico" />
-    <title>ShowDoc</title>
-    <meta name="keywords" content="在线API文档 技术文档 数据字典 在线手册" />
-    <meta
-      name="description"
-      content="一个非常适合IT团队的在线API文档、技术文档工具。你可以使用Showdoc来编写在线API文档、技术文档、数据字典、在线手册"
-    />
-    <script src="https://showdoc-server.cdn.dfyun.com.cn/server-cdn-status.js?v=3"></script>
-    <script src="https://showdoc.cdn.dfyun.com.cn/cdn-status.js?v=2"></script>
-    <script type="text/javascript">
-      // ========================================
-      // 环境检测(优先执行)
-      // ========================================
-      var host = window.location.host
-      var port = window.location.port
-      
-      // 开发环境:Vite dev server(端口 8081)
-      var isDevEnv = (host.indexOf('localhost') > -1 || host.indexOf('127.0.0.1') > -1) && port === '8081'
-      
-      // 测试环境:构建后的代码 + 测试域名(不使用 CDN)
-      var isTestEnv = !isDevEnv && (
-        host.indexOf('wu.com') > -1 ||
-        host.indexOf('gaoyixia.com') > -1 ||
-        host.indexOf('test.showdoc') > -1 ||
-        host.indexOf('local.showdoc') > -1 ||
-        host.indexOf('sd.gaoyixia.com') > -1 ||
-        host.indexOf('127.0.0.1') > -1 ||
-        host.indexOf('192.168.') > -1 ||
-        host.indexOf('localhost') > -1
-      )
-
-      // ========================================
-      // 全局配置
-      // ========================================
-      if (isDevEnv) {
-        // 开发环境
-        window.DocConfig = {
-          server: '/server/index.php?s=',
-          originalServer: '/server/index.php?s=',
-          lang: 'zh-cn',
-          staticPath: '/'
-        }
-      } else if (isTestEnv) {
-        // 测试环境:使用本地资源(根目录)
-        window.DocConfig = {
-          server: '/server/index.php?s=',
-          originalServer: '/server/index.php?s=',
-          lang: 'zh-cn',
-          staticPath: '/'
-        }
-      } else {
-        // 生产环境(默认使用 CDN)
-        window.DocConfig = {
-          server: 'https://showdoc-server.cdn.dfyun.com.cn/server/index.php?s=',
-          originalServer: '/server/index.php?s=',
-          lang: 'zh-cn',
-          staticPath: 'https://showdoc.cdn.dfyun.com.cn/'
-        }
-        
-        // 生产环境 CDN 容灾
-        // Server CDN 容灾
-        if (typeof window.SHOWDOC_SERVER_CDN_STASTUS == 'undefined') {
-          window.DocConfig.server = '/server/index.php?s='
-        }
-        // 静态资源 CDN 容灾
-        if (typeof window.SHOWDOC_CDN_STASTUS == 'undefined') {
-          window.DocConfig.staticPath = '/'
-        }
-      }
-    </script>
-    <script type="module" crossorigin src="https://showdoc.cdn.dfyun.com.cn/assets/index-bf750cc3.js"></script>
-    <link rel="modulepreload" crossorigin href="https://showdoc.cdn.dfyun.com.cn/assets/vendor-775275a9.js">
-    <link rel="modulepreload" crossorigin href="https://showdoc.cdn.dfyun.com.cn/assets/vendor-vue-dc600416.js">
-    <link rel="modulepreload" crossorigin href="https://showdoc.cdn.dfyun.com.cn/assets/components-79c21c71.js">
-    <link rel="modulepreload" crossorigin href="https://showdoc.cdn.dfyun.com.cn/assets/core-9aca1148.js">
-    <link rel="modulepreload" crossorigin href="https://showdoc.cdn.dfyun.com.cn/assets/assets-7d25f5ec.js">
-    <link rel="stylesheet" href="https://showdoc.cdn.dfyun.com.cn/assets/vendor-49dc2278.css">
-    <link rel="stylesheet" href="https://showdoc.cdn.dfyun.com.cn/assets/vendor-vue-e0182f4b.css">
-    <link rel="stylesheet" href="https://showdoc.cdn.dfyun.com.cn/assets/components-2a251b15.css">
-    <link rel="stylesheet" href="https://showdoc.cdn.dfyun.com.cn/assets/assets-c462eb69.css">
-    <link rel="stylesheet" href="https://showdoc.cdn.dfyun.com.cn/assets/index-726c1082.css">
-  </head>
-
-  <body class="grey-bg">
-    <div class="index-item-block">
-      <div>INDEX_HTML</div>
-      <div>ITEM_HTML</div>
-    </div>
-    <div id="app"></div>
-
-    <!-- CDN 容灾脚本 -->
-    <script>
-      // CDN 容灾:如果 CDN 不可用,替换所有资源为本地路径
-      if (typeof window.SHOWDOC_CDN_STASTUS == 'undefined') {
-        // 把全局变量中的静态资源路径改为本地
-        window.DocConfig.staticPath = '/'
-        changeCDNToRoot()
-        setTimeout(() => {
-          changeCDNToRoot()
-        }, 500)
-      }
-
-      // 修改资源路径从 CDN 到本地,并重新加载
-      function changeCDNToRoot() {
-        const loadScript = src => {
-          return new Promise((resolve, reject) => {
-            const scriptTag = document.createElement('script')
-            scriptTag.src = src
-            scriptTag.type = 'module'
-            scriptTag.crossOrigin = 'anonymous'
-            scriptTag.onload = resolve
-            scriptTag.onerror = reject
-            document.head.appendChild(scriptTag)
-          })
-        }
-
-        const loadStylesheet = href => {
-          return new Promise((resolve, reject) => {
-            const linkTag = document.createElement('link')
-            linkTag.href = href
-            linkTag.rel = 'stylesheet'
-            linkTag.onload = resolve
-            linkTag.onerror = reject
-            document.head.appendChild(linkTag)
-          })
-        }
-
-        const updateLinkTags = async () => {
-          // 转换为数组,避免 HTMLCollection live 更新问题
-          const links = Array.from(document.getElementsByTagName('link'))
-          
-          for (const linkTag of links) {
-            const href = linkTag.getAttribute('href')
-
-            if (href && href.includes('dfyun')) {
-              const newHref = href.replace('https://showdoc.cdn.dfyun.com.cn/', '/')
-              await loadStylesheet(newHref)
-              
-              // 检查 parentNode 是否存在
-              if (linkTag.parentNode) {
-                linkTag.parentNode.removeChild(linkTag)
-              }
-            }
-          }
-        }
-
-        const updateScriptTags = async () => {
-          // 转换为数组,避免 HTMLCollection live 更新问题
-          const scripts = Array.from(document.getElementsByTagName('script'))
-          
-          for (const scriptTag of scripts) {
-            const src = scriptTag.getAttribute('src')
-
-            if (src && src.includes('dfyun') && !src.includes('showdoc-server')) {
-              const newSrc = src.replace('https://showdoc.cdn.dfyun.com.cn/', '/')
-              await loadScript(newSrc)
-              
-              // 检查 parentNode 是否存在
-              if (scriptTag.parentNode) {
-                scriptTag.parentNode.removeChild(scriptTag)
-              }
-            }
-          }
-        }
-
-        const updateImgTags = () => {
-          const imgs = Array.from(document.getElementsByTagName('img'))
-          
-          for (const imgTag of imgs) {
-            let src = imgTag.getAttribute('src')
-
-            if (src && src.includes('dfyun')) {
-              src = src.replace('https://showdoc.cdn.dfyun.com.cn/', '/')
-              imgTag.setAttribute('src', src)
-            }
-          }
-        }
-
-        updateLinkTags()
-          .then(() => {
-            return updateScriptTags()
-          })
-          .then(() => {
-            console.log('[CDN] 已切换到本地资源')
-            updateImgTags()
-          })
-          .catch(error => {
-            console.error('[CDN] 切换失败:', error)
-          })
-      }
-    </script>
-
-
-    <!-- 防止被镜像站 -->
-    <script language="JavaScript">
-      var host = window.location.host
-      if (
-        host.indexOf('localhost') === -1 &&
-        host.indexOf('wu') === -1 &&
-        host.indexOf('showdoc') === -1 &&
-        host.indexOf('star7th.com') === -1 &&
-        host.indexOf('192.168.') === -1 &&
-        host.indexOf('gaoyixia.com') === -1 &&
-        host.indexOf('dongjingyu.cn') === -1 &&
-        host.indexOf('127.0.0.1') === -1
-      ) {
-        var href = window.location.href
-        var j = href.replace(new RegExp(host, 'g'), 'www.showdoc.com.cn')
-        window.location.href = j
-      }
-    </script>
-
-    <!-- 百度统计 -->
-    <script>
-      if (window.location.host == 'www.showdoc.com.cn') {
-        var _hmt = _hmt || []
-        ;(function() {
-          var hm = document.createElement('script')
-          hm.src = 'https://hm.baidu.com/hm.js?84e82fa31c8ca8671f6b6f972b7e54fb'
-          var s = document.getElementsByTagName('script')[0]
-          s.parentNode.insertBefore(hm, s)
-        })()
-      }
-    </script>
-
-    <!-- 主应用入口 -->
-    
-  </body>
-</html>

+ 1 - 1
src/api/collect/AssessmentContent.ts

@@ -505,7 +505,7 @@ export class AssessmentContentApi extends AppServerRequestModule<DataModel> {
    */
   async getAgreementDetail(id: number) {
     const res = await this.post('/ich/check/agreementDetail', '传承协议详情', { id });
-    return transformDataModel(AgreementDetail, (res.data ?? {}) as KeyValue);
+    return transformDataModel<AgreementDetail>(AgreementDetail, (res.data ?? {}) as KeyValue);
   }
 }
 

+ 2 - 1
src/components/composeabe/loader/LoaderCommon.ts

@@ -1,4 +1,4 @@
-import type { Ref } from "vue";
+import type { ComputedRef, Ref } from "vue";
 
 export type LoaderLoadType = 'loading' | 'finished' | 'nomore' | 'error' | 'empty';
 
@@ -10,6 +10,7 @@ export type LoaderLoadType = 'loading' | 'finished' | 'nomore' | 'error' | 'empt
 export interface ILoaderCommon<P> {
   error: Ref<string>;
   status: Ref<LoaderLoadType>;
+  isFinished: ComputedRef<boolean>;
   load: (refresh?: boolean, params?: P) => Promise<void>;
   reload: () => Promise<void>;
 }

+ 4 - 1
src/components/composeabe/loader/SimpleDataLoader.ts

@@ -1,4 +1,4 @@
-import { onMounted, ref, type Ref } from "vue";
+import { computed, onMounted, ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
 
 export interface ISimpleDataLoader<T, P> extends ILoaderCommon<P> {
@@ -62,9 +62,12 @@ export function useSimpleDataLoader<T, P = any>(
     }
   })
 
+  const isFinished = computed(() => status.value === 'finished' || status.value === 'nomore');
+
   return {
     content,
     status,
+    isFinished,
     error,
     load,
     reload: () => load(true),

+ 4 - 1
src/components/composeabe/loader/SimplePageListLoader.ts

@@ -1,5 +1,5 @@
 import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
-import { onMounted, ref, type Ref } from "vue";
+import { computed, onMounted, ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
 
 export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
@@ -83,11 +83,14 @@ export function useSimplePageListLoader<T, P = any>(
       load(false, lastParams);
   })
 
+  const isFinished = computed(() => status.value === 'finished' || status.value === 'nomore');
+
   return {
     list,
     total,
     page,
     status,
+    isFinished,
     error,
     load,
     reload: () => load(true),

+ 8 - 0
src/components/theme/Theme.ts

@@ -158,6 +158,14 @@ export const DefaultTheme : ThemeConfig = {
       fontSize: '26rpx',
       fontWeight: 'bold',
     },
+    p: {
+      color: 'text.content',
+      innerStyle: {
+        lineHeight: '1.5',
+        marginBottom: '10rpx',
+      },
+      fontSize: '24rpx',
+    },
     content: {
       color: 'text.content',
       fontSize: '24rpx',

+ 9 - 0
src/components/typography/P.vue

@@ -0,0 +1,9 @@
+<template>
+  <Text fontConfig="p" v-bind="$props">
+    <slot />
+  </Text>
+</template>
+
+<script setup lang="ts">
+import Text from '../basic/Text.vue';
+</script>

+ 246 - 1
src/pages/collect/assessment/argeement-sign.vue

@@ -1,6 +1,251 @@
 <template>
+  <FlexCol padding="space.lg">
+    <SimplePageContentLoader :loader="loader">
+      <template v-if="loader.isFinished.value">
+        <Result
+          v-if="!currentAgreement"
+          status="info"
+          title="您还未签署传承协议"
+          description="请先签署传承协议"
+        >
+          <Height :height="30" />
+          <Button type="primary" @click="createAgreement">签署传承协议</Button>
+        </Result>
+        <FlexCol v-else :gap="'lg'">
+          <Alert type="info" message="请仔细阅读传承协议,确保您已理解内容,并签署传承协议。" />
+
+          <FlexCol
+            :gap="'md'"
+            :innerStyle="articleWrapStyle"
+          >
+            <H3>{{ agreementTitle }}</H3>
+
+            <FlexCol :gap="'sm'">
+              <P>甲方:福建省文化和旅游厅</P>
+              <FlexRow align="center" wrap :gap="'sm'">
+                <Text font-config="p" color="text.content">乙方:</Text>
+                <AgreementPrefillInline
+                  v-model="currentAgreement.partyA"
+                  placeholder="请填写乙方(传承人)姓名"
+                />
+              </FlexRow>
+            </FlexCol>
+
+            <Height :height="8" />
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              为传承弘扬中华优秀传统文化,有效保护和传承非物质文化遗产,鼓励和支持国家级非物质文化遗产代表性传承人开展传承活动,根据《中华人民共和国非物质文化遗产法》《国家级非物质文化遗产代表性传承人认定与管理办法》等有关法律法规,制定协议,并按照下列各项条款签署,甲、乙双方共同遵守。
+            </Text>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              一、甲乙双方应当以习近平新时代中国特色社会主义思想为指导,坚持以人民为中心,弘扬社会主义核心价值观,共同保护传承非物质文化遗产,推动中华优秀传统文化创造性转化、创新性发展。
+            </Text>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              二、甲方按照《国家级非物质文化遗产代表性传承人认定与管理办法》的要求,支持国家级非物质文化遗产代表性传承人开展传承、传播活动。
+            </Text>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              三、甲方按照《国家级非物质文化遗产保护专项资金管理办法》的要求,落实国家给予的代表性传承人的传承补助。
+            </Text>
+
+            <FlexRow align="center" wrap :gap="'sm'" :inner-style="paragraphStyle">
+              <Text font-config="p" color="text.content">四、乙方应积极开展传承活动,培养后继人才,制定传承计划,{{ agreementYear }} 年度带徒</Text>
+              <AgreementPrefillInline
+                v-model="currentAgreement.apprentice"
+                number-mode
+                placeholder="人数"
+                suffix="人。"
+              />
+            </FlexRow>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              五、乙方应妥善保存相关实物、资料情况。主动保存、提供与该项非遗项目有关的原始资料、实物,配合记录工作。
+            </Text>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              六、乙方应主动、及时配合非遗调查,主动向文化和旅游主管部门、非遗保护中心反映非遗项目保护、传承情况和总结材料,并完成文化和旅游主管部门临时交办的非遗工作任务,提出保护的意见、建议。
+            </Text>
+
+            <FlexRow align="center" wrap :gap="'sm'" :inner-style="paragraphStyle">
+              <Text font-config="p" color="text.content">七、乙方应积极、主动参加各级政府组织的非物质文化遗产公益性宣传活动,{{ agreementYear }} 年度完成</Text>
+              <AgreementPrefillInline
+                v-model="currentAgreement.activity"
+                number-mode
+                placeholder="场次"
+                suffix="场。"
+              />
+            </FlexRow>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              八、乙方应合理使用国家级非物质文化遗产代表性传承人补助经费,用于开展非遗项目的传习活动,做好传承补助经费使用记录、支出范围和绩效评价等,不得用于生活补助。
+            </Text>
+
+            <FlexRow align="center" wrap :gap="'sm'" :inner-style="paragraphStyle">
+              <Text font-config="p" color="text.content">九、乙方应积极、主动参加文化和旅游部组织的非物质文化遗产代表性传承人研修班,{{ agreementYear }} 年度完成</Text>
+              <AgreementPrefillInline
+                v-model="currentAgreement.course"
+                number-mode
+                placeholder="场次"
+                suffix="场。"
+              />
+            </FlexRow>
+
+            <Text font-config="p" color="text.content" :inner-style="paragraphStyle">
+              十、乙方应积极参与非物质文化遗产相关理论和实践研究、发表(出版)论文、专著等研究。
+            </Text>
+
+            <Height :height="16" />
+
+            <FlexCol :gap="'md'" :inner-style="signBlockStyle">
+              <Text font-config="p" color="text.content" bold>甲方:福建省文化和旅游厅</Text>
+              <Field
+                label="负责人(代表人)"
+                label-position="top"
+                v-model="currentAgreement.partyASign"
+                placeholder="选填"
+                :show-bottom-border="true"
+              />
+              <Field
+                label="甲方电话"
+                label-position="top"
+                type="tel"
+                v-model="currentAgreement.partyAMobile"
+                placeholder="请填写甲方联系电话"
+              />
+              <AgreementDateWriteBlock
+                v-model="partyAStampDate"
+                hint="(以实际盖章日期为准)"
+              />
+
+              <Height :height="24" />
+
+              <Text font-config="p" color="text.content" bold>乙方:{{ currentAgreement.partyB }}(签名)</Text>
+              <Field
+                label="乙方签名 / 说明"
+                label-position="top"
+                v-model="currentAgreement.partyBSign"
+                placeholder="签名、捺印说明或电子签说明"
+              />
+              <Field
+                label="身份证号"
+                label-position="top"
+                v-model="currentAgreement.idCard"
+                placeholder="请填写身份证号"
+              />
+              <Field
+                label="项目名称"
+                label-position="top"
+                v-model="currentAgreement.ich"
+                placeholder="非遗项目名称"
+              />
+              <Field
+                label="身体状况"
+                label-position="top"
+                v-model="currentAgreement.health"
+                placeholder="请简要填写"
+              />
+              <Field
+                label="乙方电话"
+                label-position="top"
+                type="tel"
+                v-model="currentAgreement.mobile"
+                placeholder="请填写联系电话"
+              />
+              <AgreementDateWriteBlock
+                v-model="partyBSignDate"
+                hint="(以实际签署日期为准)"
+              />
+            </FlexCol>
+          </FlexCol>
+        </FlexCol>
+      </template>
+    </SimplePageContentLoader>
+  </FlexCol>
 </template>
 
 <script setup lang="ts">
+import { computed, ref } from 'vue';
+import { useAuthStore } from '@/store/auth';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import AssessmentContentApi, { AgreementDetail } from '@/api/collect/AssessmentContent';
 import FlexCol from '@/components/layout/FlexCol.vue';
-</script>
+import Result from '@/components/feedback/Result.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
+import Button from '@/components/basic/Button.vue';
+import Height from '@/components/layout/space/Height.vue';
+import H3 from '@/components/typography/H3.vue';
+import Alert from '@/components/feedback/Alert.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import P from '@/components/typography/P.vue';
+import Field from '@/components/form/Field.vue';
+import Text from '@/components/basic/Text.vue';
+import AgreementPrefillInline from './components/AgreementPrefillInline.vue';
+import AgreementDateWriteBlock, { type AgreementYmdParts } from './components/AgreementDateWriteBlock.vue';
+
+const authStore = useAuthStore();
+const currentAgreement = ref<AgreementDetail | null>(null);
+/** 甲方盖章日期(页面填写,保存时可拼为字符串提交) */
+const partyAStampDate = ref<AgreementYmdParts>({ year: '', month: '', day: '' });
+/** 乙方签署日期 */
+const partyBSignDate = ref<AgreementYmdParts>({ year: '', month: '', day: '' });
+
+const articleWrapStyle = {
+  padding: '24rpx 28rpx',
+  borderRadius: '16rpx',
+  backgroundColor: '#fafafa',
+  borderWidth: '1rpx',
+  borderStyle: 'solid',
+  borderColor: '#eeeeee',
+};
+const paragraphStyle = {
+  lineHeight: '1.75',
+  marginBottom: '12rpx',
+};
+const signBlockStyle = {
+  paddingTop: '8rpx',
+};
+
+const agreementYear = computed(() => currentAgreement.value?.year ?? new Date().getFullYear());
+
+const levelTitle = computed(() => {
+  const v = currentAgreement.value?.level;
+  if (v === 23) return '国家级';
+  if (v === 24) return '省级';
+  if (v === 25) return '市级';
+  return '国家级';
+});
+const agreementTitle = computed(
+  () => `${agreementYear.value} 年度${levelTitle.value}非物质文化遗产代表性传承人传承协议`,
+);
+const loader = useSimpleDataLoader(async () => {
+  const list = await AssessmentContentApi.getAgreementList({
+    userId: authStore.userInfo?.id,
+    year: new Date().getFullYear(),
+  });
+  if (list.data.length > 0) {
+    const detail = await AssessmentContentApi.getAgreementDetail(list.data[0].id);
+    currentAgreement.value = detail;
+    partyAStampDate.value = { year: '', month: '', day: '' };
+    partyBSignDate.value = { year: '', month: '', day: '' };
+  } else {
+    currentAgreement.value = null;
+  }
+  return currentAgreement.value;
+});
+
+function createAgreement() {
+  const now = new Date();
+  const u = authStore.userInfo;
+  const nick = u?.nickname || u?.diyname;
+  const detail = new AgreementDetail();
+  detail.userId = authStore.userInfo!.id;
+  detail.year = new Date().getFullYear();
+  detail.partyA = '福建省文化和旅游厅';
+  detail.partyB = nick ?? '';
+  console.log(authStore.userInfo, u, nick);
+  partyAStampDate.value = { year: now.getFullYear().toString(), month: (now.getMonth() + 1).toString(), day: now.getDate().toString() };
+  partyBSignDate.value = { year: now.getFullYear().toString(), month: (now.getMonth() + 1).toString(), day: now.getDate().toString() };
+  currentAgreement.value = detail;
+}
+</script>

+ 105 - 0
src/pages/collect/assessment/components/AgreementDateWriteBlock.vue

@@ -0,0 +1,105 @@
+<template>
+  <FlexCol gap="gap.xs" padding="space.md">
+    <FlexRow align="center" wrap :gap="'sm'" :inner-style="rowOuterStyle">
+      <Text v-if="prefix" font-config="p" color="text.second" :inner-style="prefixStyle">
+        {{ prefix }}
+      </Text>
+      <AgreementPrefillInline
+        :model-value="modelValue.year"
+        placeholder="YYYY"
+        suffix="年"
+        :max-length="4"
+        :field-style="yearFieldStyle"
+        @update:model-value="(v) => patch('year', v)"
+      />
+      <AgreementPrefillInline
+        :model-value="modelValue.month"
+        placeholder="MM"
+        suffix="月"
+        :max-length="2"
+        :field-style="mdFieldStyle"
+        @update:model-value="(v) => patch('month', v)"
+      />
+      <AgreementPrefillInline
+        :model-value="modelValue.day"
+        placeholder="DD"
+        suffix="日"
+        :max-length="2"
+        :field-style="mdFieldStyle"
+        @update:model-value="(v) => patch('day', v)"
+      />
+    </FlexRow>
+    <Text v-if="hint" font-config="p" color="text.second" :inner-style="hintStyle">
+      {{ hint }}
+    </Text>
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import type { ViewStyle, TextStyle } from '@/components/theme/ThemeDefine';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Text from '@/components/basic/Text.vue';
+import AgreementPrefillInline from './AgreementPrefillInline.vue';
+
+/** 年月日拆分填写(均为数字字符串) */
+export type AgreementYmdParts = {
+  year: string;
+  month: string;
+  day: string;
+};
+
+const props = withDefaults(
+  defineProps<{
+    modelValue: AgreementYmdParts;
+    /** 行前前缀,默认「时间:」 */
+    prefix?: string;
+    /** 第二行灰色说明 */
+    hint?: string;
+  }>(),
+  {
+    prefix: '时间:',
+    hint: '',
+  },
+);
+
+const emit = defineEmits<{
+  'update:modelValue': [value: AgreementYmdParts];
+}>();
+
+const rowOuterStyle: ViewStyle = {
+  marginTop: '8rpx',
+};
+
+const prefixStyle: TextStyle = {
+  flexShrink: 0,
+};
+
+const hintStyle: TextStyle = {
+  marginTop: '4rpx',
+};
+
+const yearFieldStyle: ViewStyle = {
+  minWidth: '120rpx',
+  maxWidth: '130rpx',
+};
+
+const mdFieldStyle: ViewStyle = {
+  minWidth: '78rpx',
+  maxWidth: '100rpx',
+};
+
+function digitsOnly(v: string | number, maxLen: number) {
+  const str = String(v ?? '').replace(/\D/g, '');
+  return str.slice(0, maxLen);
+}
+
+function patch(key: keyof AgreementYmdParts, v: string | number) {
+  const max = key === 'year' ? 4 : 2;
+  const next = digitsOnly(v, max);
+  emit('update:modelValue', {
+    ...props.modelValue,
+    [key]: next,
+  });
+}
+</script>

+ 122 - 0
src/pages/collect/assessment/components/AgreementPrefillInline.vue

@@ -0,0 +1,122 @@
+<template>
+  <FlexRow
+    align="center"
+    wrap
+    :gap="gap"
+    :innerStyle="rowStyle"
+  >
+    <Text
+      v-if="label"
+      fontConfig="p"
+      color="text.second"
+      :innerStyle="labelTextStyle"
+    >
+      {{ label }}
+    </Text>
+    <Field
+      :model-value="textValue"
+      :type="numberMode ? 'number' : 'text'"
+      :show-label="false"
+      :show-bottom-border="false"
+      :placeholder="placeholder"
+      :max-length="maxLength"
+      :field-style="mergedFieldStyle"
+      :input-style="mergedInputStyle"
+      :input-flex="1"
+      @update:model-value="onUpdate"
+    />
+    <Text v-if="suffix" fontConfig="p" color="text.content" :innerStyle="suffixTextStyle">
+      {{ suffix }}
+    </Text>
+  </FlexRow>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useTheme, type ViewStyle, type TextStyle } from '@/components/theme/ThemeDefine';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Field from '@/components/form/Field.vue';
+import Text from '@/components/basic/Text.vue';
+
+const props = withDefaults(
+  defineProps<{
+    /** 绑定值:字符串或与 `number-mode` 搭配的数字 */
+    modelValue?: string | number | null;
+    /** 为 true 时对外 `update:modelValue` 发出数字(解析失败为 0) */
+    numberMode?: boolean;
+    placeholder?: string;
+    /** 输入框左侧短说明,如「带徒」 */
+    label?: string;
+    /** 输入框右侧固定文案,如「人」「场」 */
+    suffix?: string;
+    maxLength?: number;
+    /** 主题间距键,默认 sm */
+    gap?: string;
+    fieldStyle?: ViewStyle;
+    inputStyle?: TextStyle;
+  }>(),
+  {
+    modelValue: '',
+    numberMode: false,
+    placeholder: '请填写',
+    label: '',
+    suffix: '',
+    maxLength: 20,
+    gap: 'sm',
+    fieldStyle: () => ({}),
+    inputStyle: () => ({}),
+  },
+);
+
+const emit = defineEmits<{
+  'update:modelValue': [value: string | number];
+}>();
+
+const themeContext = useTheme();
+
+const rowStyle = computed(() => ({
+  flexShrink: 0,
+}));
+
+const labelTextStyle = computed<TextStyle>(() => ({
+  flexShrink: 0,
+  marginRight: '4rpx',
+}));
+
+const suffixTextStyle = computed<TextStyle>(() => ({
+  flexShrink: 0,
+  marginLeft: '4rpx',
+}));
+
+const mergedFieldStyle = computed<ViewStyle>(() => ({
+  paddingVertical: themeContext.resolveThemeSize('AgreementPrefillFieldPaddingV', 6),
+  paddingHorizontal: themeContext.resolveThemeSize('AgreementPrefillFieldPaddingH', 12),
+  borderRadius: themeContext.resolveThemeSize('AgreementPrefillFieldRadius', '10rpx'),
+  backgroundColor: themeContext.resolveThemeColor('AgreementPrefillFieldBg', 'light'),
+  borderWidth: '1rpx',
+  borderStyle: 'solid',
+  borderColor: themeContext.resolveThemeColor('AgreementPrefillFieldBorder', 'border.cell'),
+  minWidth: themeContext.resolveThemeSize('AgreementPrefillFieldMinWidth', '120rpx'),
+  maxWidth: themeContext.resolveThemeSize('AgreementPrefillFieldMaxWidth', '220rpx'),
+  ...props.fieldStyle,
+}));
+
+const mergedInputStyle = computed<TextStyle>(() => ({
+  textAlign: 'center',
+  fontSize: themeContext.resolveThemeSize('AgreementPrefillInputFontSize', 28),
+  ...props.inputStyle,
+}));
+
+const textValue = computed(() =>
+  props.modelValue === null || props.modelValue === undefined ? '' : String(props.modelValue),
+);
+
+function onUpdate(raw: string) {
+  if (props.numberMode) {
+    const n = parseInt(raw.replace(/\D/g, ''), 10);
+    emit('update:modelValue', Number.isFinite(n) ? n : 0);
+  } else {
+    emit('update:modelValue', raw);
+  }
+}
+</script>

+ 1 - 1
src/pages/collect/inheritor.vue

@@ -41,7 +41,7 @@
         <Cell icon="https://mncdn.wenlvti.net/uploads/20250313/66d4665b1da5075e60148312469b2630.png" title="作品" showArrow touchable @click="navTo('works')" />
       </CellGroup>
       <CellGroup round>
-        <Cell icon="https://mncdn.wenlvti.net/uploads/20250313/cbc47d0b9cad7891e6154359952858c6.png" title="返回闽南文化" showArrow touchable @click="back()" />f
+        <Cell icon="https://mncdn.wenlvti.net/app_static/minnan/logo.png" title="返回闽南文化" showArrow touchable @click="back()" />
         <Cell icon="https://mncdn.wenlvti.net/uploads/20250313/cbc47d0b9cad7891e6154359952858c6.png" title="退出登录" showArrow touchable @click="onLogout" />
       </CellGroup>
     </FlexCol>