Просмотр исходного кода

Merge branch 'feature/new-ui-26'

快乐的梦鱼 1 неделя назад
Родитель
Сommit
a2c31e1efd
100 измененных файлов с 3251 добавлено и 2258 удалено
  1. 1481 641
      package-lock.json
  2. 24 25
      package.json
  3. 3 1
      src/App.vue
  4. 29 3
      src/api/BaseAppServerRequestModule.ts
  5. 19 2
      src/api/CommonContent.ts
  6. 2 0
      src/api/fusion/CalendarContent.ts
  7. 1 1
      src/api/introduction/CustomContent.ts
  8. 1 1
      src/api/introduction/FeatureContent.ts
  9. 1 1
      src/api/introduction/SeaContent.ts
  10. 10 0
      src/api/research/DiscussContent.ts
  11. 10 0
      src/api/research/ExpertContent.ts
  12. 10 0
      src/api/research/IndexTeamsContent.ts
  13. 10 0
      src/api/research/InnovationContent.ts
  14. 10 0
      src/api/research/ProjectContent.ts
  15. 10 0
      src/api/research/ResultContent.ts
  16. 10 0
      src/api/research/TeamsContent.ts
  17. 1 1
      src/common/components/RequireLogin.vue
  18. 7 8
      src/common/components/tabs/tabbar.vue
  19. 14 0
      src/common/composeabe/ErrorDisplay.ts
  20. 2 1
      src/common/composeabe/SimpleDataLoader.ts
  21. 2 1
      src/common/composeabe/SimplePageContentLoader.ts
  22. 3 1
      src/common/composeabe/SimplePageListLoader.ts
  23. 9 0
      src/common/composeabe/TabControl.ts
  24. 4 0
      src/common/scss/common.scss
  25. 1 0
      src/common/scss/define/colors.scss
  26. 4 4
      src/common/scss/define/size.scss
  27. 1 1
      src/common/scss/global/color.scss
  28. 1 0
      src/common/scss/global/shadow.scss
  29. 0 8
      src/common/style/commonParserStyle.ts
  30. 6 6
      src/components/README.md
  31. 3 3
      src/components/basic/Button.vue
  32. 26 8
      src/components/basic/IconButton.vue
  33. 1 2
      src/components/basic/Image.vue
  34. 9 19
      src/components/basic/Text.vue
  35. 35 0
      src/components/display/Divider.vue
  36. 70 8
      src/components/display/TextEllipsis.vue
  37. 17 0
      src/components/display/block/IconTextBlock.vue
  38. 9 3
      src/components/display/block/ImageBlock2.vue
  39. 7 1
      src/components/display/block/ImageBlock3.vue
  40. 25 0
      src/components/display/loading/Loadmore.vue
  41. 0 90
      src/components/dynamic/DynamicFormCate.vue
  42. 0 85
      src/components/dynamic/DynamicFormCateInner.vue
  43. 2 4
      src/components/dynamic/DynamicFormControl.vue
  44. 0 3
      src/components/dynamic/Images/IconAdd.vue
  45. 0 3
      src/components/dynamic/Images/IconCheck.vue
  46. 0 3
      src/components/dynamic/Images/IconDelete.vue
  47. 0 3
      src/components/dynamic/Images/IconDown.vue
  48. 0 1
      src/components/dynamic/Images/IconError.svg
  49. 0 3
      src/components/dynamic/Images/IconUp.vue
  50. 0 1
      src/components/dynamic/Images/IconWarning.svg
  51. 0 1
      src/components/dynamic/group/FormArrayGroup.vue
  52. 34 50
      src/components/dynamic/group/FormGroup.vue
  53. 22 3
      src/components/dynamic/wrappers/CheckBoxTreeListItem.vue
  54. 93 19
      src/components/feedback/SwipeRow.vue
  55. 3 0
      src/components/form/CalendarField.vue
  56. 2 0
      src/components/form/CascadePickerField.vue
  57. 2 0
      src/components/form/CascaderField.vue
  58. 2 0
      src/components/form/DatePickerField.vue
  59. 2 0
      src/components/form/DateTimePickerField.vue
  60. 34 0
      src/components/form/Picker.ts
  61. 1 5
      src/components/form/Picker.vue
  62. 3 0
      src/components/form/PickerField.vue
  63. 65 4
      src/components/form/SearchBar.vue
  64. 2 0
      src/components/form/TimePickerField.vue
  65. 4 6
      src/components/form/Uploader.vue
  66. 0 2
      src/components/form/UploaderField.vue
  67. 3 1
      src/components/layout/BaseView.ts
  68. 1 1
      src/components/layout/FlexView.vue
  69. 11 1
      src/components/nav/NavBar.vue
  70. 47 3
      src/components/nav/Pagination.vue
  71. 2 2
      src/components/theme/ThemeDefine.ts
  72. 1 1
      src/components/typography/HorizontalScrollText.vue
  73. 2 2
      src/manifest.json
  74. 64 11
      src/pages.json
  75. 145 0
      src/pages/article/common/CommonCategoryHome.vue
  76. 63 13
      src/pages/article/common/CommonContent.ts
  77. 128 61
      src/pages/article/common/CommonListPage.vue
  78. 34 16
      src/pages/article/common/DetailTabPage.vue
  79. 3 1
      src/pages/article/common/list.vue
  80. 79 29
      src/pages/article/details.vue
  81. 1 2
      src/pages/article/editor/preview.vue
  82. 33 0
      src/pages/article/index.vue
  83. 1 1
      src/pages/article/list.vue
  84. 0 345
      src/pages/discover.vue
  85. 44 0
      src/pages/document/details.vue
  86. 159 209
      src/pages/home.vue
  87. 7 8
      src/pages/home/introduction.vue
  88. 20 2
      src/pages/home/laws.vue
  89. 0 436
      src/pages/inhert.vue
  90. 24 19
      src/pages/inhert/artifact/details.vue
  91. 13 0
      src/pages/inhert/artifact/list.vue
  92. 39 0
      src/pages/inhert/index.vue
  93. 18 14
      src/pages/inhert/inheritor/details.vue
  94. 0 1
      src/pages/inhert/inheritor/list.vue
  95. 35 27
      src/pages/inhert/intangible/DetailsCommon.vue
  96. 30 12
      src/pages/inhert/intangible/list.vue
  97. 0 2
      src/pages/inhert/language/list.vue
  98. 77 0
      src/pages/inhert/seminar/list.vue
  99. 8 2
      src/pages/inhert/unit/list.vue
  100. 0 0
      src/pages/inhert/village/details.vue

Разница между файлами не показана из-за своего большого размера
+ 1481 - 641
package-lock.json


+ 24 - 25
package.json

@@ -35,38 +35,37 @@
     "type-check": "vue-tsc --noEmit"
   },
   "dependencies": {
-    "@dcloudio/uni-app": "3.0.0-4070620250821001",
-    "@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
-    "@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
-    "@dcloudio/uni-components": "3.0.0-4070620250821001",
-    "@dcloudio/uni-h5": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-alipay": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-baidu": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-harmony": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-jd": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-kuaishou": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-lark": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-qq": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-toutiao": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001",
-    "@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
-    "@imengyu/imengyu-utils": "^0.0.24",
+    "@dcloudio/uni-app": "3.0.0-4080720251210001",
+    "@dcloudio/uni-app-harmony": "3.0.0-4080720251210001",
+    "@dcloudio/uni-app-plus": "3.0.0-4080720251210001",
+    "@dcloudio/uni-components": "3.0.0-4080720251210001",
+    "@dcloudio/uni-h5": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-alipay": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-baidu": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-harmony": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-jd": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-kuaishou": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-lark": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-qq": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-toutiao": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001",
+    "@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001",
+    "@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001",
+    "@imengyu/imengyu-utils": "^0.0.26",
     "@imengyu/js-request-transform": "^0.3.7",
     "async-validator": "^4.2.5",
     "pinia": "^3.0.1",
     "tslib": "^2.8.1",
-    "vue": "^3.4.21",
-    "vue-i18n": "^9.1.9"
+    "vue": "3.5.27",
+    "vue-i18n": "9.14.4"
   },
   "devDependencies": {
     "@dcloudio/types": "3.4.19",
-    "@dcloudio/uni-automator": "3.0.0-4070620250821001",
-    "@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
-    "@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
-    "@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
-    "@uni-helper/uni-app-types": "^1.0.0-alpha.6",
-    "@vue/runtime-core": "^3.4.21",
+    "@dcloudio/uni-automator": "3.0.0-4080720251210001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
+    "@vue/runtime-core": "3.5.27",
     "@vue/tsconfig": "^0.1.3",
     "sass": "^1.86.0",
     "typescript": "^5.8.3",

+ 3 - 1
src/App.vue

@@ -4,6 +4,7 @@ import { useAuthStore } from './store/auth'
 import { configTheme } from './components/theme/ThemeDefine';
 import { RequestApiConfig } from '@imengyu/imengyu-utils';
 import ApiCofig from './common/config/ApiCofig';
+import { isDev } from './common/config/AppCofig';
 
 const authStore = useAuthStore();
 
@@ -26,13 +27,14 @@ onLaunch(() => {
 
 RequestApiConfig.setConfig({
   ...RequestApiConfig.getConfig(),
-  BaseUrl: ApiCofig.serverProd,
+  BaseUrl: isDev ? ApiCofig.server.Dev : ApiCofig.server.Prod,
 })
 
 configTheme(false, (theme, darkTheme) => {
   theme.colorConfigs.default.primary = '#d9492e';
   theme.colorConfigs.pressed.primary = '#882d1d';
   theme.colorConfigs.background.primary = '#ffcfc6';
+  theme.colorConfigs.background.page = '#f6f2e7';
   return [theme, darkTheme];
 });
 </script>

+ 29 - 3
src/api/BaseAppServerRequestModule.ts

@@ -1,4 +1,5 @@
 import BugReporter from "@/common/BugReporter";
+import type { MpErrorInfo } from "@/common/composeabe/ErrorDisplay";
 import ApiCofig from "@/common/config/ApiCofig";
 import AppCofig, { isDev } from "@/common/config/AppCofig";
 import { RequestCoreInstance, RequestApiError, UniappImplementer, StringUtils, appendGetUrlParams, appendPostParams, RequestResponse, RequestOptions, type RequestApiInfoStruct, RequestApiResult, type RequestApiErrorType, defaultResponseDataGetErrorInfo, defaultResponseDataHandlerCatch } from "@imengyu/imengyu-utils";
@@ -79,7 +80,7 @@ export class BaseAppServerRequestModule<T extends DataModel> extends RequestCore
     this.config.baseUrl = baseUrl;
     this.config.errCodes = [];
     //请求拦截器
-    this.config.requestInceptor = (url, req) => {
+    this.config.requestInterceptor = (url, req) => {
       //获取app中的token,追加到头;
       const app = getApp();
       if (StringUtils.isNullOrEmpty((req.headers as KeyValue).token as string)) {
@@ -191,7 +192,7 @@ export class BaseAppServerRequestModule<T extends DataModel> extends RequestCore
             errCodeStr = res.errCodeStr;
           }
 
-          return new RequestApiError(
+          const res = new RequestApiError(
             errType,
             errString,
             errCodeStr,
@@ -201,6 +202,8 @@ export class BaseAppServerRequestModule<T extends DataModel> extends RequestCore
             response.headers,
             apiInfo
           );
+          console.log(res);
+          return res;
         }
       } catch (err) {
         if (err instanceof RequestApiError) {
@@ -212,8 +215,31 @@ export class BaseAppServerRequestModule<T extends DataModel> extends RequestCore
         });
       }
     };
+    //响应错误处理函数
+    this.config.responseErrorHandler = (err, instance, apiInfo) => {
+      if (err instanceof RequestApiError)
+        throw err;
+      function createError(type: RequestApiErrorType, message: string) {
+        return new RequestApiError(type, message, '', -1, undefined, err, undefined, apiInfo);
+      }
+      if (err instanceof RequestApiError)
+        return err;
+      if (err instanceof Error)
+        return createError('unknow', err.message);
+      if (typeof err === 'object' && (err as MpErrorInfo)?.errMsg) {
+        const message = (err as MpErrorInfo).errMsg;
+        if (message.includes('timeout') || message.includes('aborted without reason'))
+          return createError('networkError', '请求超时或被取消');
+        if (message.includes('request:fail') && message.includes('ssl'))
+          return createError('networkError', '网络异常,可能是服务器过期或您的本地时间设置不正确');
+        if (message.includes('Network Error') || message.includes('request:fail'))
+          return createError('networkError', '网络异常,请检查网络设置');
+        return createError('unknow', '未知异常: ' + message);
+      }
+      return createError('unknow', '未知异常: ' + JSON.stringify(err));
+    };
     //错误报告处理
-    this.config.responseErrReoprtInceptor = (instance, response) => {
+    this.config.responseErrorReportInterceptor = (instance, response) => {
       return (
         (response.errorType !== 'businessError' && response.errorType !== 'networkError') ||
         matchNotReportMessage(response.code, response.errorMessage) === true

+ 19 - 2
src/api/CommonContent.ts

@@ -89,6 +89,7 @@ export class GetContentListParams extends DataModel<GetContentListParams> {
   static TYPE_AUDIO = 2;
   static TYPE_VIDEO = 3;
   static TYPE_IMAGE = 4;
+  static TYPE_ARCHIVE = 5;
 
   modelId ?: number;
   /**
@@ -285,6 +286,7 @@ export class GetContentDetailItem extends DataModel<GetContentDetailItem> {
   images = [] as string[];
   audio = '';
   video = '';
+  archives = '';
   publishVideo?: string;
   desc = '';
   flag ?: string[];
@@ -498,6 +500,19 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
     }, modelClassCreator);
     return res.data as T;
   }
+  /**
+   * 获取可投稿栏目
+   * @returns 
+   */
+  async getContributeColumnList(querys?: QueryParams) {
+    const res = await this.get('/content/main_body_column/getColumnList', `获取可投稿栏目`, {
+      main_body_id: this.mainBodyId,
+      model_id: 11,
+      iscontribute: 1,
+      ...querys,
+    }, undefined);
+    return res.data as unknown as GetColumContentList[];
+  }
 
   
   /**
@@ -514,11 +529,13 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
         data: data,
         headers: {},
       }
-      if (this.config.requestInceptor) {
-        const { newReq, newUrl } = this.config.requestInceptor(url, req);
+      if (this.config.requestInterceptor) {
+        const { newReq, newUrl } = this.config.requestInterceptor(url, req);
         url = newUrl;
         data = newReq;
       }
+      console.log(url);
+      
       uni.uploadFile({
         url: url,
         name,

+ 2 - 0
src/api/fusion/CalendarContent.ts

@@ -15,6 +15,7 @@ export class CalendarListItem extends GetContentListItem {
       this.dateObj = date;
       this.dateYear = date.getFullYear();
       this.dateMonth = date.getMonth() + 1;
+      this.dateDay = date.getDate();
     }
   }
   date = '';
@@ -22,6 +23,7 @@ export class CalendarListItem extends GetContentListItem {
   dateObj = new Date();
   dateYear = 0;
   dateMonth = 0;
+  dateDay = 0;
 }
 
 export class CalendarContentApi extends CommonContentApi {

+ 1 - 1
src/api/introduction/CustomContent.ts

@@ -3,7 +3,7 @@ import { CommonContentApi } from '../CommonContent';
 export class CustomContentApi extends CommonContentApi {
 
   constructor() {
-    super(undefined, 4, "闽南文化-民间习俗");
+    super(undefined, 4, "闽南文化-民间习俗", 245);
   }
 }
 

+ 1 - 1
src/api/introduction/FeatureContent.ts

@@ -3,7 +3,7 @@ import { CommonContentApi } from '../CommonContent';
 export class FeatureContentApi extends CommonContentApi {
 
   constructor() {
-    super(undefined, 3, "闽南文化-艺术特色");
+    super(undefined, 3, "闽南文化-艺术特色", 243);
   }
 }
 

+ 1 - 1
src/api/introduction/SeaContent.ts

@@ -3,7 +3,7 @@ import { CommonContentApi } from '../CommonContent';
 export class SeaContentApi extends CommonContentApi {
 
   constructor() {
-    super(undefined, 3, "闽南文化-海洋文化", 252);
+    super(undefined, 3, "闽南文化-海洋文化", 254);
   }
 }
 

+ 10 - 0
src/api/research/DiscussContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class DiscussContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 19, "理论研讨", 266);
+  }
+}
+
+export default new DiscussContentApi();

+ 10 - 0
src/api/research/ExpertContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ExpertContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 7, "专家学者", 263);
+  }
+}
+
+export default new ExpertContentApi();

+ 10 - 0
src/api/research/IndexTeamsContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class IndexTeamsContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 17, "研究团队", 264);
+  }
+}
+
+export default new IndexTeamsContentApi();

+ 10 - 0
src/api/research/InnovationContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class InnovationContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 18, "文化新视角", 362);
+  }
+}
+
+export default new InnovationContentApi();

+ 10 - 0
src/api/research/ProjectContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ProjectContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 19, "研究项目", 265);
+  }
+}
+
+export default new ProjectContentApi();

+ 10 - 0
src/api/research/ResultContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ResultContetApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 19, "研究成果", 269);
+  }
+}
+
+export default new ResultContetApi();

+ 10 - 0
src/api/research/TeamsContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class TeamsContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 17, "研究团队", 264);
+  }
+}
+
+export default new TeamsContentApi();

+ 1 - 1
src/common/components/RequireLogin.vue

@@ -23,6 +23,6 @@ defineProps({
 })
 
 function goLogin() {
-  navTo('user/login');
+  navTo('/pages/user/login');
 }
 </script>

+ 7 - 8
src/common/components/tabs/tabbar.vue

@@ -12,9 +12,9 @@
     }"
   >
     <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_home_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_home_on.png" text="首页" />
-    <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_discover_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_discover_on.png" text="发现" />
-    <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_inhert_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_inhert_on.png" hump :humpHeight="[0,0]" :humpSpace="[20,20]" :iconSize="140" text="传承" />
-    <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_shop_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_shop_on.png" text="文旅" />
+    <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_discover_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_discover_on.png" text="资讯" />
+    <TabBarItem icon="https://mn.wenlvti.net/app_static/minnan/images/tabs/icon_inhert_off.png" activeIcon="https://mn.wenlvti.net/app_static/minnan/images/tabs/icon_inhert_on.png" hump :humpHeight="[0,0]" :humpSpace="[20,20]" :iconSize="140" text="传承" />
+    <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_shop_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_shop_on.png" text="乐游" />
     <TabBarItem icon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_profile_off.png" activeIcon="https://mncdn.wenlvti.net/app_static/minnan/images/tabs/icon_profile_on.png" text="我的" />
   </TabBar>
 </template>
@@ -22,7 +22,6 @@
 <script setup lang="ts">
 import TabBar from '@/components/nav/TabBar.vue';
 import TabBarItem from '@/components/nav/TabBarItem.vue';
-import { watch } from 'vue';
 
 const props = defineProps({
   current: {
@@ -33,16 +32,16 @@ const props = defineProps({
 function changeTab(newVal: number) {
   switch(newVal) {
     case 0:
-      switchTab('/pages/home', 0);
+      switchTab('/pages/home/index', 0);
       break;
     case 1:
-      switchTab('/pages/discover', 1);
+      switchTab('/pages/article/index', 1);
       break;
     case 2:
-      switchTab('/pages/inhert', 2);
+      switchTab('/pages/inhert/index', 2);
       break;
     case 3:
-      switchTab('/pages/travel', 3);
+      switchTab('/pages/travel/index', 3);
       break;
     case 4:
       switchTab('/pages/user/index', 4);

+ 14 - 0
src/common/composeabe/ErrorDisplay.ts

@@ -1,3 +1,5 @@
+import { RequestApiError } from "@imengyu/imengyu-utils";
+
 export function showError(e: any, title = '糟糕,出错了', callback?: () => void) {
   console.log('showError', e);
   let message = '';
@@ -15,3 +17,15 @@ export function showError(e: any, title = '糟糕,出错了', callback?: () =>
   })
   uni.hideLoading();
 }
+
+export interface MpErrorInfo {
+  errMsg: string;
+}
+export function formatError(e: any) {
+  if (e instanceof RequestApiError) 
+    return e.errorMessage + (e.errorCodeMessage ? ` (${e.errorCodeMessage})` : '');
+  if (e instanceof Error) 
+    return e.message;
+  else 
+    return '' + (e ?? '未知错误');
+}

+ 2 - 1
src/common/composeabe/SimpleDataLoader.ts

@@ -1,5 +1,6 @@
 import { onMounted, ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
+import { formatError } from "./ErrorDisplay";
 
 export interface ISimpleDataLoader<T, P> extends ILoaderCommon<P> {
   content: Ref<T|null>;
@@ -36,7 +37,7 @@ export function useSimpleDataLoader<T, P = any>(
         loadStatus.value = 'finished';
       loadError.value = '';
     } catch(e) {
-      loadError.value = '' + e;
+      loadError.value = formatError(e);
       loadStatus.value = 'error';
       console.log(e);
       

+ 2 - 1
src/common/composeabe/SimplePageContentLoader.ts

@@ -1,5 +1,6 @@
 import { ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
+import { formatError } from "./ErrorDisplay";
 
 export interface ISimplePageContentLoader<T, P> extends ILoaderCommon<P> {
   content: Ref<T|null>;
@@ -26,7 +27,7 @@ export function useSimplePageContentLoader<T, P = any>(
       loadStatus.value = 'finished';
       loadError.value = '';
     } catch(e) {
-      loadError.value = '' + e;
+      loadError.value = formatError(e);
       loadStatus.value = 'error';
     }
   }

+ 3 - 1
src/common/composeabe/SimplePageListLoader.ts

@@ -1,6 +1,7 @@
 import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
 import { ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
+import { formatError } from "./ErrorDisplay";
 
 export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
   list: Ref<T[]>;
@@ -47,7 +48,8 @@ export function useSimplePageListLoader<T, P = any>(
       loadError.value = '';
       loading = false;
     } catch(e) {
-      loadError.value = '' + e;
+      console.log(e);
+      loadError.value = formatError(e);
       loadStatus.value = 'error';
       loading = false;
     } finally {

+ 9 - 0
src/common/composeabe/TabControl.ts

@@ -5,6 +5,15 @@ export interface TabControlItem {
   [key: string]: any,
 }
 
+export function useTabId(options: {
+  idStart?: number,
+}) {
+  let idStart = options.idStart ?? 0
+  return {
+    nextId: () => idStart++,
+  }
+}
+
 export function useTabControl(options: {
   tabs?: TabControlItem[],
   onTabChange?: (tab: number, tabId: number) => void,

+ 4 - 0
src/common/scss/common.scss

@@ -39,6 +39,10 @@
   top: -2px;
   z-index: 10;
 }
+.text-nowrap {
+  flex-shrink: 0;
+  white-space: nowrap;
+}
 
 //Fix
 

+ 1 - 0
src/common/scss/define/colors.scss

@@ -10,6 +10,7 @@ $colors: (
   light-blue: #0059ff,
   light-primary: #d9492e35,
   light-light-primary: #d9492e15,
+  light-page: #f7f4ec,
   success: #09ad32,
   warning: #FFD666,
   error: #ec4545,

+ 4 - 4
src/common/scss/define/size.scss

@@ -9,10 +9,10 @@ $font-sizes: (
   base: 30rpx,
   l: 32rpx,
   ll: 36rpx,
-  lll: 50rpx,
-  xl: 75rpx,
-  xxl: 100rpx,
-  xxxl: 150rpx,
+  lll: 44rpx,
+  xl: 55rpx,
+  xxl: 70rpx,
+  xxxl: 100rpx,
 );
 
 $image-sizes: (

+ 1 - 1
src/common/scss/global/color.scss

@@ -11,7 +11,7 @@
 .color {
 	@each $key, $color in $colors {
 		&-#{''+$key} {
-			color: $color;
+			color: $color!important;
 		}
 	}
 }

+ 1 - 0
src/common/scss/global/shadow.scss

@@ -8,6 +8,7 @@
   box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.055) !important;
 }
 
+.shadow-base,
 .shadow {
   box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.075) !important;
 }

+ 0 - 8
src/common/style/commonParserStyle.ts

@@ -1,8 +0,0 @@
-export default {
-  p: 'line-height:1.76;font-size:30rpx;margin-bottom:37rpx;color:#111111;text-align:justify;',
-  div: 'line-height:1.76;font-size:30rpx;margin-bottom:37rpx;color:#111111;text-align:justify;',
-  img: 'display:block;max-width:100%;height:auto;margin-bottom:30rpx;',
-  h2:'margin-bottom:30rpx;font-size:32rpx;line-height:1.7;',
-  h1:'margin-bottom:30rpx;font-size:36rpx;line-height:1.7;',
-  h3:'margin-bottom:30rpx;font-size:30rpx;line-height:1.7;',
-} as Record<string, string>;

+ 6 - 6
src/components/README.md

@@ -6,11 +6,11 @@ NaEasy UI 是一款简单的 UniApp 移动端UI组件库。
 
 ## 版本
 
-当前版本:INDEV 0.0.4-25121701
+当前版本:1.0.8-26012501
 
 ## 版权说明
 
-© 2024 imengyu. 保留所有权利。
+© 2026 imengyu. 保留所有权利。
 
 本组件库基于 MIT 许可证开源。您可以自由地使用、修改和分发本组件库,但必须保留原始的版权声明和许可证文本。
 
@@ -38,7 +38,7 @@ SOFTWARE.
 
 ### 使用注意事项
 
-1. 请确保在您的项目中适当标注本组件库的来源
-2. 对于商用项目,请确保遵守相关法律法规
-3. 如对组件库进行了修改或扩展,请在文档中明确说明
-4. 我们不对使用本组件库可能产生的任何损失或问题承担责任
+1. 请确保在您的项目中适当标注本组件库的来源
+2. 对于商用项目,请确保遵守相关法律法规
+3. 如对组件库进行了修改或扩展,请注意备份,防止更新库后被覆盖丢失。
+4. 我们不对使用本组件库可能产生的任何损失或问题承担责任

+ 3 - 3
src/components/basic/Button.vue

@@ -70,7 +70,7 @@ import Touchable from '../feedback/Touchable.vue';
 import type { ButtonGroupContext } from './ButtonGroup.vue';
 import { useChildLinkChild } from '../composeabe/ChildItem';
 
-export type ButtomType = 'default'|'primary'|'success'|'warning'|'danger'|'custom'|'text';
+export type ButtonType = 'default'|'primary'|'success'|'warning'|'danger'|'custom'|'text';
 export type ButtomSizeType = 'small'|'medium'|'large'|'larger'|'mini';
 
 export interface ButtonProp {
@@ -82,7 +82,7 @@ export interface ButtonProp {
    * 按钮支持 default、primary、success、warning、danger、custom 自定义 六种类型
    * @default 'default'
    */
-  type?: ButtomType,
+  type?: ButtonType,
   /**
    * 占满父级主轴
    * @default false
@@ -328,7 +328,7 @@ const themeStyles = themeContext.useThemeStyles({
 
 //按钮样式生成
 const currentStyle = computed(() => {
-  const colorStyle = selectStyleType<ViewStyle, ButtomType>(props.type, 'default', 
+  const colorStyle = selectStyleType<ViewStyle, ButtonType>(props.type, 'default', 
     selectStyleType(props.scheme, 'default', {
       default: {
         default: themeStyles.buttonDefault.value,

+ 26 - 8
src/components/basic/IconButton.vue

@@ -2,7 +2,7 @@
   <Touchable
     :pressedColor="pressedBackgroundColor"
     :innerStyle="style"
-    touchable
+    :touchable="touchable"
     @click="(e) => emit('click', e)"
     v-bind="$attrs"
   >
@@ -19,7 +19,14 @@ import type { IconProps } from './Icon.vue';
 import Icon from './Icon.vue';
 import Touchable from '../feedback/Touchable.vue';
 
-export type IconButtonShapeType = 'round'|'square-full'|'custom';
+/**
+ * 图标按钮形状预设
+ * * round: 圆角按钮
+ * * round-full: 圆角按钮,宽度和高度相等
+ * * square-full: 方角按钮,宽度和高度相等
+ * * custom: 自定义按钮形状
+ */
+export type IconButtonShapeType = 'round'|'round-full'|'square-full'|'custom';
 
 export interface IconButtonProps extends IconProps {
   /**
@@ -32,6 +39,11 @@ export interface IconButtonProps extends IconProps {
    */
   padding?: number,
   /**
+   * 是否可点击
+   * @default true
+   */
+  touchable?: boolean|undefined;
+  /**
    * 按钮形状预设
    * @default round
    */
@@ -46,9 +58,9 @@ export interface IconButtonProps extends IconProps {
    */
   buttonStyle?: ViewStyle;
   /**
-   * 按钮大小
+   * 按钮大小。可以是一个数字,也可以是一个数组,数组的第一个元素是宽度,第二个元素是高度。
    */
-  buttonSize?: number|string;
+  buttonSize?: number|((number|string)[])|string;
   /**
    * 按钮背景颜色
    */
@@ -59,7 +71,8 @@ const emit = defineEmits(['click']);
 const theme = useTheme();
 const props = withDefaults(defineProps<IconButtonProps>(), {
   shape: 'custom',
-  pressedBackgroundColor: () => propGetThemeVar('IconButtonPressedColor', 'pressed.white')
+  pressedBackgroundColor: () => propGetThemeVar('IconButtonPressedColor', 'pressed.white'),
+  touchable: true,
 });
 const style = computed(() => {
   return {
@@ -67,12 +80,17 @@ const style = computed(() => {
     justifyContent: 'center',
     alignItems: 'center',
     padding: props.padding,
-    width: theme.resolveThemeSize(props.buttonSize),
-    height: theme.resolveThemeSize(props.buttonSize),
+    width: Array.isArray(props.buttonSize) ? theme.resolveThemeSize(props.buttonSize[0]) : theme.resolveThemeSize(props.buttonSize),
+    height: Array.isArray(props.buttonSize) ? theme.resolveThemeSize(props.buttonSize[1]) : theme.resolveThemeSize(props.buttonSize),
     backgroundColor: theme.resolveThemeColor(props.backgroundColor),
     ...selectStyleType(props.shape, 'round', {
       "round": {
-        borderRadius: theme.resolveThemeSize('IconButtonRoundBorderRadius', 50),
+        borderRadius: theme.resolveThemeSize('IconButtonRoundBorderRadius', 20),
+      },
+      "round-full": {
+        height: '100%',
+        aspectRatio: 1,
+        borderRadius: '50%',
       },
       "custom": {},
       "square-full": {

+ 1 - 2
src/components/basic/Image.vue

@@ -137,7 +137,7 @@ const props = withDefaults(defineProps<ImageProps>(), {
   loadingSize: () => propGetThemeVar('ImageLoadingSize', 50),
   touchable: false,
   round: () => propGetThemeVar('ImageRound', false),
-  radius: () => propGetThemeVar('ImageRadius', '0'),
+  radius: () => propGetThemeVar('ImageRadius', ''),
 })
 const emit = defineEmits([ 'click' ]);
 const showBackgroundEffect = computed(() => props.showBackgroundEffect && props.mode === 'aspectFit');
@@ -153,7 +153,6 @@ const style = computed(() => {
     borderRadius: props.round ? '50%' : themeContext.resolveThemeSize(props.radius), 
     backgroundColor: isErrorState.value || props.showGrey ? themeContext.resolveThemeColor('background.imageBox') : 'transparent',
     overflow: 'hidden',
-    flexShrink: 0,
     width: themeContext.resolveThemeSize(props.width),
     height: themeContext.resolveThemeSize(props.height),
     ...props.innerStyle,

+ 9 - 19
src/components/basic/Text.vue

@@ -169,29 +169,19 @@ function onClick(e: any) {
 const style = computed(() => {
   const o : Record<string, any> = {
   }
-  let color = props.color;
-  let backgroundColor = props.backgroundColor;
-  let fontSize = props.fontSize;
-  let fontWeight = props.fontWeight;
-  let fontFamily = props.fontFamily;
-  let fontStyle = props.fontStyle as any;
-  if (props.fontConfig) {
-    const style = getText(props.fontConfig, {});
-    color = props.color ?? style.color as string;
-    backgroundColor = props.backgroundColor ?? style.backgroundColor as string;
-    fontSize = props.fontSize ?? style.fontSize as string;
-    fontWeight = props.fontWeight ?? style.fontWeight as string;
-    fontFamily = props.fontFamily ?? style.fontFamily as string;
-    fontStyle = props.fontStyle ?? style.fontStyle as object;
-  }
+  const style = props.fontConfig ? getText(props.fontConfig) : undefined;
+  const color = props.color ?? style?.color as string;
+  const backgroundColor = props.backgroundColor ?? style?.backgroundColor as string;
+  const fontSize = props.fontSize ?? style?.fontSize as string;
+  const fontWeight = props.fontWeight ?? style?.fontWeight as string;
+  const fontFamily = props.fontFamily ?? style?.fontFamily as string;
+  const fontStyle = props.fontStyle ?? style?.fontStyle as string;
+
   if (color) o.color = resolveThemeColor(color, "text");
   if (backgroundColor) o.background = resolveThemeColor(backgroundColor);
   if (fontSize) o.fontSize = resolveThemeSize(fontSize);
   if (fontWeight) o.fontWeight = fontWeight;
-  if (fontStyle) {
-    for(const k in fontStyle)
-      o.fontStyle = k;
-  }
+  if (fontStyle) o.fontStyle = fontStyle;
   if (!props.wrap) o.whiteSpace = 'nowrap';
 
   if (fontFamily) o.fontFamily = fontFamily;

+ 35 - 0
src/components/display/Divider.vue

@@ -21,6 +21,10 @@
     </template>
     <view v-else class="line" :style="lineStyle">
       <view class="bar" :style="barStyle" />
+      <view v-if="centerDot" class="dot" :style="{
+        ...dotStyle,
+        ...centerDotStyle,
+      }" />
     </view>
   </view>
 </template>
@@ -63,6 +67,20 @@ export interface DividerProps {
    */
   type?: 'horizontal' | 'vertical';
   /**
+   * 是否在线中间添加一个点
+   * @default false
+   */
+  centerDot?: boolean;
+  /**
+   * 分割线中间点的大小
+   * @default 26
+   */
+  centerDotSize?: number;
+  /**
+   * 分割线中间点的样式
+   */
+  centerDotStyle?: object,
+  /**
    * 分割线上面的文字(仅水平状态有效)
    */
   text?: string,
@@ -89,6 +107,7 @@ const props = withDefaults(defineProps<DividerProps>(), {
   backgroundColor: () => propGetThemeVar('DividerBackgroundColor', undefined)!,
   width: () => propGetThemeVar('DividerWidth', 2),
   size: () => propGetThemeVar('DividerSize', 36),
+  centerDotSize: () => propGetThemeVar('DividerCenterDotSize', 26),
   type: 'horizontal',
   orientation: 'center',
 });
@@ -98,6 +117,13 @@ const outStyle = computed(() => {
     backgroundColor: theme.resolveThemeColor(props.backgroundColor),
   }
 })
+const dotStyle = computed(() => {
+  return {
+    width: theme.resolveThemeSize(props.centerDotSize),
+    height: theme.resolveThemeSize(props.centerDotSize),
+    backgroundColor: theme.resolveThemeColor(props.color),
+  }
+})
 const lineStyle = computed(() => {
   return {
     fontSize: '22rpx',
@@ -125,6 +151,14 @@ const barStyle = computed(() => {
   align-items: center;
   align-self: stretch;
 
+  .dot {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    border-radius: 50%;
+  }
+  
   .line {
     position: relative;
     display: flex;
@@ -152,6 +186,7 @@ const barStyle = computed(() => {
 
   &.vertical {
     flex-direction: column;
+    height: 100%;
   }
   &.horizontal {
     flex-direction: row;

+ 70 - 8
src/components/display/TextEllipsis.vue

@@ -3,13 +3,55 @@ import { computed, ref } from 'vue';
 import type { TextProps } from '../basic/Text.vue';
 import Text from '../basic/Text.vue';
 import FlexCol from '../layout/FlexCol.vue';
+import BackgroundBox from './block/BackgroundBox.vue';
 
 export interface TextEllipsisProps extends TextProps {
+  /**
+   * 显示的行数
+   * @default 1
+   */
   lines?: number;
+  /**
+   * 是否可展开
+   * @default true
+   */
   expandable?: boolean;
+  /**
+   * 是否默认展开
+   * @default false
+   */
   startOpen?: boolean;
+  /**
+   * 展开按钮文字
+   * @default '展开'
+   */
   openText?: string;
+  /**
+   * 收起按钮文字
+   * @default '收起'
+   */ 
   closeText?: string;
+  /**
+   * 是否显示遮罩层,当定义了该属性时,折叠时将会显示一个遮罩层。
+   * @default false
+   */
+  showMask?: boolean;
+  /**
+   * 遮罩层颜色
+   * @default white
+   */
+  maskColor?: string;
+  /**
+   * 折叠时的高度
+   * @default 140
+   */
+  collapsedHeight?: number;
+  /**
+   * 是否自定义内容,当定义了该属性时,可以通过slot自定义内容渲染,
+   * 折叠时将会设置高度为collapsedHeight。
+   * @default false
+   */
+  customContent?: boolean;
 }
 
 
@@ -17,7 +59,11 @@ const emit = defineEmits(['expand', 'collapse']);
 const props = withDefaults(defineProps<TextEllipsisProps>(), {
   lines: 1,
   startOpen: false,
-  expandable: false,
+  expandable: true,
+  customContent: false,
+  showMask: false,
+  maskColor: 'white',
+  collapsedHeight: 140,
   openText: '展开',
   closeText: '收起',
 });
@@ -43,16 +89,32 @@ defineOptions({
 </script>
 
 <template>
-  <FlexCol>
-    <Text 
-      :ellipsis="true" 
-      :lines="currentLines"
-      v-bind="$attrs"
+  <FlexCol position="relative">
+    <FlexCol 
+      :height="customContent && !open ? collapsedHeight : undefined"
+      overflow="hidden"
     >
       <slot>
-        {{ text }}
+        <Text 
+          :ellipsis="true" 
+          :lines="currentLines"
+          v-bind="$attrs"
+          :text="text"
+        />
       </slot>
-    </Text>
+      <BackgroundBox 
+        v-if="!open && (showMask || customContent)"
+        :height="collapsedHeight"
+        color1="transparent"
+        :color2="maskColor"
+        :innerStyle="{ 
+          position: 'absolute',
+          bottom: '36rpx',
+          left: 0,
+          right: 0,
+        }"
+      />
+    </FlexCol>
     <slot v-if="expandable" name="button" :onClick="handleClick">
       <Text 
         innerClass="nana-text-ellipsis-expand"

+ 17 - 0
src/components/display/block/IconTextBlock.vue

@@ -53,6 +53,10 @@ defineProps({
     type: [String,Number],
     default: '',
   },
+  extraMpSlotState: {
+    type: Boolean,
+    default: true,
+  },
   /**
    * 标题文字属性
    */
@@ -89,10 +93,23 @@ defineProps({
         <Text fontConfig="subText" :text="desc" v-bind="descProps" />
       </FlexCol>
     </FlexRow>
+    <!-- #ifndef MP -->
     <slot name="extra">
       <FlexRow :flexShrink="0">
         <Text fontConfig="subText" :text="extra" v-bind="extraProps" />
       </FlexRow>
     </slot>
+    <!-- #endif -->
+    <!-- #ifdef MP -->
+    <slot v-if="extraMpSlotState" name="extra">
+      <FlexRow :flexShrink="0">
+        <Text fontConfig="subText" :text="extra" v-bind="extraProps" />
+      </FlexRow>
+    </slot>
+    <FlexRow v-else :flexShrink="0">
+      <Text fontConfig="subText" :text="extra" v-bind="extraProps" />
+    </FlexRow>
+    <!-- #endif -->
+
   </FlexRow>
 </template>

+ 9 - 3
src/components/display/block/ImageBlock2.vue

@@ -3,15 +3,16 @@
     touchable
     v-bind="props"
     :flexShrink="0"
-    :innerStyle="{ borderRadius: theme.resolveThemeSize(imageRadius), overflow: 'hidden', }"
+    :innerStyle="{ borderRadius: theme.resolveThemeSize(radius), overflow: 'hidden', }"
     :width="width"
     @click="$emit('click')"
   >
     <Image 
       :src="src" 
-      width="100%"
       :height="imageHeight"
       :radius="imageRadius"
+      width="100%"
+      round
       mode="aspectFill"
     />
     <slot name="desc">
@@ -20,7 +21,12 @@
           :title="title"
           :desc="desc"
           :extra="extra"
-        />
+          :extraMpSlotState="Boolean($slots.extra)"
+        >
+          <template v-if="$slots.extra" #extra>
+            <slot name="extra" />
+          </template>
+        </IconTextBlock>
       </FlexCol>
     </slot>
   </Touchable>

+ 7 - 1
src/components/display/block/ImageBlock3.vue

@@ -20,8 +20,14 @@
           :title="title"
           :desc="desc"
           :extra="extra"
-        />
+          :extraMpSlotState="Boolean($slots.extra)"
+        >
+          <template v-if="$slots.extra" #extra>
+            <slot name="extra" />
+          </template>
+        </IconTextBlock>
       </slot>
+
     </FlexView>
   </Touchable>
 </template>

+ 25 - 0
src/components/display/loading/Loadmore.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import ActivityIndicator from '@/components/basic/ActivityIndicator.vue';
+import Button from '@/components/basic/Button.vue';
 import Text from '@/components/basic/Text.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import type { FlexProps } from '@/components/layout/FlexView.vue';
@@ -39,6 +40,16 @@ export interface LoadMoreProps extends FlexProps {
    * @default '点击加载更多'
    */ 
   loadmoreText?: string,
+  /**
+   * 重试按钮文字
+   * @default '重试'
+   */
+  retryText?: string,
+  /**
+   * 是否显示重试按钮
+   * @default true
+   */
+  showRetry?: boolean,
 }
 
 const props = withDefaults(defineProps<LoadMoreProps>(), {
@@ -47,7 +58,12 @@ const props = withDefaults(defineProps<LoadMoreProps>(), {
   errorText: '加载失败',
   nomoreText: '没有更多了',
   loadmoreText: '点击加载更多',
+  retryText: '重试',
+  showRetry: true,
 });
+const emit = defineEmits<{
+  (e: 'retry'): void;
+}>();
 
 const themeContext = useTheme();
 
@@ -76,5 +92,14 @@ const text = computed(() => {
       <Width :size="20" />
     </template>
     <Text :text="text" fontConfig="subText" />
+    <template v-if="props.showRetry && props.status === 'error'">
+      <Width :size="20" />
+      <Button
+        type="text"
+        textColor="primary"
+        :text="props.retryText"
+        @click="emit('retry')"
+      />
+    </template>
   </FlexRow>
 </template>

+ 0 - 90
src/components/dynamic/DynamicFormCate.vue

@@ -1,90 +0,0 @@
-<template>
-  <view 
-    v-if="formDefine.type === 'group'" 
-    :class="`form-group ${formDefine.props.type}`"
-  >
-    <text class="form-group-title" v-if="formDefineParentLabel">
-      {{ formDefineParentLabel }}
-    </text>
-    <DynamicFormCateInner
-      :formDefine="formDefine"
-      :formModel="formModel" 
-      :groupType="formDefine.props.type"
-    />
-  </view>
-  <DynamicFormCateInner
-    v-else
-    :formDefine="formDefine"
-    :formModel="formModel" 
-  />
-</template>
-
-<script setup lang="ts">
-import type { PropType } from 'vue';
-import type { FormDefine } from '.';
-import DynamicFormCateInner from './DynamicFormCateInner.vue';
-
-export interface FormGroupProps {
-  type: 'row' | 'column' | 'block';
-}
-
-const props = defineProps({
-  formDefineParentLabel: {
-    type: null,
-    default: '' 
-  },
-  formDefineParentKey: {
-    type: String,
-    default: '' 
-  },
-  formModel: {
-    type: Object,
-    default: () => ({})
-  },
-  formDefine: {
-    type: Object as PropType<FormDefine>,
-    default: () => ({})
-  },
-})
-</script>
-
-<style lang="scss">
-.form-group {
-  display: flex;
-  flex-direction: column;
-
-  &.block {
-    margin-bottom: 32rpx;
-    padding: 24rpx 0;
-    background: #fff;
-    border-radius: 10rpx;
-
-    .form-group-title {
-      display: block;
-      font-size: 28rpx;
-      color: #333;
-      margin-bottom: 16rpx;
-    }
-  }
-
-  .form-group-title {
-    display: block;
-    flex-shrink: 0;
-    font-size: 28rpx;
-    color: #333;
-    margin-bottom: 16rpx;
-    margin-left: 26rpx;
-  }
-
-  &.row {
-    flex-direction: row;
-    justify-content: space-between;
-    align-items: center;
-
-    .form-group-title {
-      display: inline-block;
-      margin-left: 30rpx;
-    }
-  }
-}
-</style>

+ 0 - 85
src/components/dynamic/DynamicFormCateInner.vue

@@ -1,85 +0,0 @@
-<template>
-  <view 
-    v-for="(item, key) in formDefine.items"
-    :key="key"
-    :class="[
-      'form-cate-inner',
-      groupType
-    ]"
-  >
-    <DynamicFormCate
-      v-if="item.children"
-      :formDefine="item.children"
-      :formModel="children" 
-      :formDefineParentKey="item.name"
-      :formDefineParentLabel="item.label"
-      :parentModel="formModel"
-      :topModel="topModel"
-    />
-    <DynamicFormControl
-      v-else
-      :modelValue="formModel[item.name] ?? null"
-      :formDefineItem="item"
-      :parentModel="formModel"
-      :topModel="topModel"
-      :isLast="key === formDefine.items.length - 1"
-      @update:modelValue="(v: any) => formModel[item.name] = v"
-    />
-  </view>
-</template>
-
-<script setup lang="ts">
-import { computed, type PropType } from 'vue';
-import type { FormDefine } from '.';
-import DynamicFormControl from './DynamicFormControl.vue';
-import DynamicFormCate from './DynamicFormCate.vue';
-
-const props = defineProps({	
-  topModel: {
-    type: Object,
-    default: () => ({})
-  },
-  parentModel: {
-    type: null,
-  },
-  formModel: {
-    type: Object,
-    default: () => ({})
-  },
-  formDefineParentKey: {
-    type: String,
-    default: ''
-  },
-  formDefine: {
-    type: Object as PropType<FormDefine>,
-    default: () => ({})
-  },
-  groupType: {
-    type: String,
-    default: '' 
-  }
-})
-
-const children = computed(() => {
-  if (props.formDefineParentKey && props.formDefine.propNestType == 'nest')
-    return props.formModel[props.formDefineParentKey];
-  return props.formModel;
-});
-
-</script>
-
-<style lang="scss">
-.form-cate-inner {
-  display: flex;
-  flex-direction: column;
-
-  &.row {
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-  }
-}
-.form-static-text {
-  margin: 0 10rpx 20rpx 10px;
-}
-</style>

+ 2 - 4
src/components/dynamic/DynamicFormControl.vue

@@ -324,12 +324,10 @@ const props = defineProps({
     default: false
   },
   model: {
-    type: null,
-    default: null
+    type: null
   },
   parentModel: {
-    type: null,
-    default: null
+    type: null
   },
   rawModel: {
     type: Object as PropType<Record<string, unknown>>,

Разница между файлами не показана из-за своего большого размера
+ 0 - 3
src/components/dynamic/Images/IconAdd.vue


+ 0 - 3
src/components/dynamic/Images/IconCheck.vue

@@ -1,3 +0,0 @@
-<template>
-  <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M369.792 704.32L930.304 128 1024 223.616 369.984 896l-20.288-20.864-0.128 0.128L0 516.8 96.128 423.68l273.664 280.64z"></path></svg>
-</template>

Разница между файлами не показана из-за своего большого размера
+ 0 - 3
src/components/dynamic/Images/IconDelete.vue


+ 0 - 3
src/components/dynamic/Images/IconDown.vue

@@ -1,3 +0,0 @@
-<template>
-  <svg  viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M904 332c0-8.189-3.124-16.379-9.372-22.628-12.497-12.496-32.759-12.496-45.256 0L512 646.745 174.628 309.372c-12.497-12.496-32.758-12.496-45.255 0-12.497 12.498-12.497 32.758 0 45.256l360 360c12.497 12.496 32.758 12.496 45.255 0l360-360C900.876 348.379 904 340.189 904 332z"></path></svg>
-</template>

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
src/components/dynamic/Images/IconError.svg


+ 0 - 3
src/components/dynamic/Images/IconUp.vue

@@ -1,3 +0,0 @@
-<template>
-  <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M904 692c0 8.189-3.124 16.379-9.372 22.628-12.497 12.496-32.759 12.496-45.256 0L512 377.255 174.628 714.628c-12.497 12.496-32.758 12.496-45.255 0-12.497-12.498-12.497-32.758 0-45.256l360-360c12.497-12.496 32.758-12.496 45.255 0l360 360C900.876 675.621 904 683.811 904 692z"></path></svg>
-</template>

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
src/components/dynamic/Images/IconWarning.svg


+ 0 - 1
src/components/dynamic/group/FormArrayGroup.vue

@@ -1,5 +1,4 @@
 <template>
-  <!--TODO: 请修改为统一样式 -->
   <div class="dynamic-form-array-group">
     <!--列表-->
     <div :class="['list', direction ]">

+ 34 - 50
src/components/dynamic/group/FormGroup.vue

@@ -1,48 +1,29 @@
 <template>
-  <FlexCol 
-    :innerClass="[
-      'dynamic-form-group', 
-      {
-        'collapsed': collapsed,
-        'collapsible': collapsible,
-        'plain': plain,
-      }
-    ]"
-    :innerStyle="dynamicFormGroupStyle"
-  >
-    <Touchable
-      v-if="title" 
-      innerClass="title" 
-      direction="row"
-      :innerStyle="dynamicFormGroupTitleWraperStyle"
-      @click="collapsible ? collapsed = !collapsed : null"
-    >
-      <span :style="dynamicFormGroupTitleStyle">{{ title }}</span>
-      <FlexRow align="center">
-        <text :style="dynamicFormGroupTitleStyle" v-show="collapsed">点击展开更多</text>
-        <Icon 
-          v-if="collapsible"
-          :size="theme.resolveThemeSize('DynamicFormGroupIconSize', 22)" 
-          icon="arrow-down"
-          innerClass="collapsible-icon"
-        />
-      </FlexRow>
-    </Touchable>
+  <div :class="[
+    'dynamic-form-group', 
+    {
+      'collapsed': collapsed,
+      'collapsible': collapsible,
+      'plain': plain,
+    }
+  ]">
+    <h5 v-if="title" class="title" @click="collapsible ? collapsed = !collapsed : null">
+      <span class="title-text">{{ title }}</span>
+      <view class="title-right">
+        <span class="title-right-text" v-show="collapsed">点击展开更多</span>
+        <Icon :size="22" icon="arrow-down" v-if="collapsible" innerClass="collapsible-icon" />
+      </view>
+    </h5>
     <Row v-if="!collapsed" :justify="(justify as any)" :gutter="gutter">
       <slot />
     </Row>
-  </FlexCol>
+  </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from "vue";
+import { ref } from "vue";
 import Row from "@/components/layout/grid/Row.vue";
 import Icon from "@/components/basic/Icon.vue";
-import { useTheme } from "@/components/theme/ThemeDefine";
-import FlexRow from "@/components/layout/FlexRow.vue";
-import Touchable from "@/components/feedback/Touchable.vue";
-import FlexCol from "@/components/layout/FlexCol.vue";
-
 const props = defineProps({
   /**
    * 标题
@@ -87,22 +68,8 @@ const props = defineProps({
     default: false,
   },
 });
-const theme = useTheme();
 
 const collapsed = ref(props.collapsed);
-const dynamicFormGroupStyle = computed(() => ({
-  backgroundColor: theme.resolveThemeColor('DynamicFormGroupBackgroundColor', 'white'),
-  borderRadius: theme.resolveThemeSize('DynamicFormGroupBorderRadius', 10),
-  padding: `${theme.resolveThemeSize('DynamicFormGroupPaddingVertical', 10)} ${theme.resolveThemeSize('DynamicFormGroupPaddingHorizontal', 0)}`,
-  marginBottom: theme.resolveThemeSize('DynamicFormGroupMarginBottom', 12),
-})); 
-const dynamicFormGroupTitleWraperStyle = computed(() => ({
-  marginBottom: theme.resolveThemeSize('DynamicFormGroupTitleMarginBottom', 12),
-})); 
-const dynamicFormGroupTitleStyle = computed(() => ({
-  fontSize: theme.resolveThemeSize('DynamicFormGroupTitleFontSize', 25),
-  color: theme.resolveThemeColor('DynamicFormGroupTitleColor', 'text.second')
-})); 
 
 defineOptions({
   options: {
@@ -115,6 +82,10 @@ defineOptions({
 
 <style lang="scss">
 .dynamic-form-group {
+  padding: 10px 0;
+  background-color: var(--dynamic-form-background-color);
+  border-radius: var(--dynamic-form-border-radius);
+
   &.collapsed {
     .collapsible-icon {
       transform: rotate(0deg);
@@ -125,19 +96,32 @@ defineOptions({
       cursor: pointer;
     }
   }
+
   .collapsible-icon {
     transform: rotate(180deg);
     transition: transform 0.3s ease-in-out;
+    width: 16px;
+    height: 16px;
   }
+
   .title {
     display: flex;
     align-items: center;
     justify-content: space-between;
+    color: var(--dynamic-form-text-color);
+    margin: 0;
+    margin-bottom: 12px;
 
     .title-right {
       display: flex;
       flex-direction: row;
       align-items: center;
+
+      .title-right-text {
+        font-size: 11px;
+        margin-right: 10rpx;
+        color: var(--dynamic-form-secondary-color);
+      }
     }
   }
 

+ 22 - 3
src/components/dynamic/wrappers/CheckBoxTreeListItem.vue

@@ -17,6 +17,12 @@
     </FlexRow>
     <CollapseBox :open="open" :anim="false">
       <FlexCol :padding="[10,0,10,30]">
+        <Loadmore 
+          v-if="loading || loadError"
+          :status="loading ? 'loading' : 'error'"
+          :errorText="loadError"
+          @retry="loadChildren"
+        />
         <CheckBoxTreeListItemWrapper
           v-for="child in item.children"
           :key="child.value"
@@ -36,20 +42,33 @@ import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import IconButton from '@/components/basic/IconButton.vue';
 import CheckBoxTreeListItemWrapper from './CheckBoxTreeListItemWrapper.vue';
+import Loadmore from '@/components/display/loading/Loadmore.vue';
 
 const props = defineProps<{
   item: CheckBoxTreeListItem,
 }>();
 
 const open = ref(false);
+const loading = ref(false);
+const loadError = ref('');
 const loadData = inject('loadData') as (pid?: number) => Promise<CheckBoxTreeListItem[]>;
 
 function toggleOpen() {
   open.value = !open.value;
   if (open.value && (!props.item.children || props.item.children.length === 0))
-    loadData(props.item.value).then((children) => {
-      props.item.children = children;
-    });
+    loadChildren();
+}
+function loadChildren() {
+  loading.value = true;
+  loadData(props.item.value).then((children) => {
+    props.item.children = children;
+    loadError.value = '';
+  }).catch((err) => {
+    props.item.children = [];
+    loadError.value = '' + err;
+  }).finally(() => {
+    loading.value = false;
+  });
 }
 </script>
 

+ 93 - 19
src/components/feedback/SwipeRow.vue

@@ -18,22 +18,40 @@
     >
       <view 
         :style="{
-          width: `${props.leftWidth}px`,
-          marginLeft: `-${props.leftWidth}px`,
+          width: leftWidth ? `${leftWidth}px` : 'auto',
+          marginLeft: `-${leftWidth}px`,
         }"
         class="left"
+        id="left"
       >
-        <slot name="left" />
+        <slot name="left">
+          <Button 
+            v-for="action in leftActions" 
+            :key="action.text" shape="square" 
+            :type="action.type" 
+            :text="action.text" 
+            @click="action.onClick" 
+          />
+        </slot>
       </view>
       <slot />
       <view 
+        id="right"
         :style="{
-          width: `${props.rightWidth}px`,
-          marginRight: `-${props.rightWidth}px`,
+          width: rightWidth ? `${rightWidth}px` : 'auto',
+          marginRight: `-${rightWidth}px`,
         }"
         class="right"
       >
-        <slot name="right" />
+        <slot name="right">
+          <Button 
+            v-for="action in rightActions" 
+            :key="action.text" shape="square" 
+            :type="action.type" 
+            :text="action.text" 
+            @click="action.onClick" 
+          />
+        </slot>
       </view>
     </view>
   </view>
@@ -41,21 +59,45 @@
 
 <script setup lang="ts">
 
-import { nextTick, ref } from 'vue';
+import { computed, getCurrentInstance, nextTick, onMounted, onUpdated, ref } from 'vue';
 import { useTheme } from '../theme/ThemeDefine';
 import { RandomUtils } from '@imengyu/imengyu-utils';
+import Button, { type ButtonType } from '../basic/Button.vue';
 
 const themeContext = useTheme();
 
+export interface SwipeRowAction {
+  /**
+   * 操作按钮的文本
+   */
+  text: string,
+  /**
+   * 操作按钮的类型
+   */
+  type: ButtonType,
+  /**
+   * 操作按钮的点击事件
+   */
+  onClick?: (e: any) => void,
+}
+
 export interface SwipeRowProps {
   /**
-   * 左侧宽度
+   * 手动设置左侧宽度,单位为px
    */
-  leftWidth?: number,
+  leftWidth?: number|undefined,
   /**
-   * 右侧宽度
+   * 左侧操作按钮
    */
-  rightWidth?: number,
+  leftActions?: SwipeRowAction[],
+  /**
+   * 手动设置右侧宽度,单位为px
+   */
+  rightWidth?: number|undefined,
+  /**
+   * 右侧操作按钮
+   */
+  rightActions?: SwipeRowAction[],
   /**
    * 是否禁用
    */
@@ -77,6 +119,31 @@ const props = withDefaults(defineProps<SwipeRowProps>(), {
 const themeStyles = themeContext.useThemeStyles({
 });
 
+const instance = getCurrentInstance();
+const measuredLeftWidth = ref(0);
+const measuredRightWidth = ref(0);
+
+function measureWidth() {
+  function measuredEle(id: string) {
+    uni.createSelectorQuery()
+      .in(instance)
+      .select(`#${id}`)
+      .boundingClientRect()
+      .exec((res) => {
+        if (res[0]) {
+          if (id === 'left')
+            measuredLeftWidth.value = res[0].width;
+          else if (id === 'right')
+            measuredRightWidth.value = res[0].width;
+        }
+      });
+  }
+  nextTick(() => {
+    measuredEle('left');
+    measuredEle('right');
+  });
+}
+
 const currentAnim = ref(false);
 const currentOffset = ref(0);
 
@@ -86,15 +153,16 @@ let pressed = false;
 let startOffset = 0;
 let lastMovedSize = 0;
 
+const leftWidth = computed(() => props.leftWidth && props.leftWidth > 0 ? props.leftWidth : measuredLeftWidth.value);
+const rightWidth = computed(() => props.rightWidth && props.rightWidth > 0 ? props.rightWidth : measuredRightWidth.value);
+
 function handleDrag(x: number) {
   const movedSize = x - startX;
   lastMovedSize = movedSize;
   if (movedSize > 0) {
-    if (props.leftWidth > 0)
-      currentOffset.value = Math.min(startOffset + movedSize, props.leftWidth);
+    currentOffset.value = Math.min(startOffset + movedSize, leftWidth.value);
   } else {
-    if (props.rightWidth > 0)
-      currentOffset.value = Math.max(startOffset + movedSize, -props.rightWidth);
+    currentOffset.value = Math.max(startOffset + movedSize, -rightWidth.value);
   }
 }
 
@@ -121,10 +189,10 @@ function handleTouchEnd(e: any) {
   currentAnim.value = true;
   nextTick(() => {
     if (Math.abs(lastMovedSize) > 10) {
-      if (currentOffset.value > props.leftWidth / 2)
-        currentOffset.value = props.leftWidth;
-      else if (currentOffset.value < -props.rightWidth / 2)
-        currentOffset.value = -props.rightWidth;
+      if (currentOffset.value > leftWidth.value / 2)
+        currentOffset.value = leftWidth.value;
+      else if (currentOffset.value < -rightWidth.value / 2)
+        currentOffset.value = -rightWidth.value;
       else 
         currentOffset.value = 0;
     } else if (props.autoClose)
@@ -132,6 +200,12 @@ function handleTouchEnd(e: any) {
   });
 }
 
+onMounted(() => {
+  measureWidth();
+});
+onUpdated(() => {
+  measureWidth();
+});
 </script>
 
 <style lang="scss">

+ 3 - 0
src/components/form/CalendarField.vue

@@ -45,6 +45,7 @@ import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSh
 import Calendar from './Calendar.vue';
 import Height from '../layout/space/Height.vue';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface CalendarFieldProps extends Omit<CalendarProps, 'modelValue'> {
   
@@ -167,6 +168,8 @@ watch(tempValue, (v) => {
   }
 })
 
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
+  
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 2 - 0
src/components/form/CascadePickerField.vue

@@ -37,6 +37,7 @@ import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSh
 import CascadePicker from './CascadePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface CascadePickerFieldProps extends Omit<CascadePickerProps, 'value'> {
   /**
@@ -120,6 +121,7 @@ const {
   popupShow,
 );
 
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 2 - 0
src/components/form/CascaderField.vue

@@ -45,6 +45,7 @@ import Height from '../layout/space/Height.vue';
 import Text, { type TextProps } from '../basic/Text.vue';
 import PopupTitle from '../dialog/PopupTitle.vue';
 import { getCascaderText } from './CascaderUtils';
+import { usePickerFieldInstance } from './Picker';
 
 export interface CascaderFieldProps extends Omit<CascaderProps, 'modelValue'> {
   /**
@@ -158,6 +159,7 @@ watch(tempValue, (v) => {
 defineExpose({
   confirm: onConfirm,
   cancel: onCancel,
+  ...usePickerFieldInstance(popupShow),
 })
 defineOptions({
   options: {

+ 2 - 0
src/components/form/DatePickerField.vue

@@ -37,6 +37,7 @@ import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSh
 import DatePicker from './DatePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface DatePickerFieldProps extends Omit<DatePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -120,6 +121,7 @@ const {
   popupShow,
 );
 
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 2 - 0
src/components/form/DateTimePickerField.vue

@@ -37,6 +37,7 @@ import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSh
 import DateTimePicker from './DateTimePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface DateTimePickerFieldProps extends Omit<DateTimePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -122,6 +123,7 @@ const {
   undefined,
   popupShow,
 );
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
 
 defineOptions({
   options: {

+ 34 - 0
src/components/form/Picker.ts

@@ -0,0 +1,34 @@
+import type { Ref } from "vue";
+
+export interface PickerItem {
+  /**
+   * 显示文本
+   */
+  text: string;
+  /**
+   * 选中值
+   */
+  value: string|number;
+}
+
+export interface PickerFieldInstance {
+  /**
+   * 显示
+   */
+  show(): void;
+  /**
+   * 隐藏
+   */
+  hide(): void;
+}
+
+export function usePickerFieldInstance(show: Ref<boolean>) : PickerFieldInstance {
+  return {
+    show() {
+      show.value = true;
+    },
+    hide() {
+      show.value = false;
+    }
+  }
+}

+ 1 - 5
src/components/form/Picker.vue

@@ -25,14 +25,10 @@
 import { nextTick, onMounted, ref, watch } from 'vue';
 import { useTheme } from '../theme/ThemeDefine';
 import Empty from '../feedback/Empty.vue';
+import type { PickerItem } from './Picker';
 
 const themeContext = useTheme();
 
-export interface PickerItem {
-  text: string;
-  value: string|number;
-}
-
 export interface PickerProps {
   /**
    * 选择器列

+ 3 - 0
src/components/form/PickerField.vue

@@ -37,6 +37,7 @@ import Popup from '../dialog/Popup.vue';
 import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSheetTitle.vue';
 import Picker from './Picker.vue';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface PickerFieldProps extends Omit<PickerProps, 'value'> {
   modelValue?: (number|string)[];
@@ -123,6 +124,8 @@ const {
   popupShow,
 );
 
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
+
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 65 - 4
src/components/form/SearchBar.vue

@@ -3,7 +3,8 @@
     center 
     :innerStyle="{
       ...themeStyles.view.value, 
-      ...innerStyle
+      ...innerStyle,
+      background: theme.resolveThemeColor(inputBackgroundColor),
     }"
     :flexShrink="0"
     overflow="hidden"
@@ -43,7 +44,6 @@
       />
       <slot name="rightIcon" />
     </FlexRow>
-    <slot name="rightButton" :onCustomButtonClick="onCustomButtonClick" />
     <slot 
       v-if="(
         cancelState === 'show' || 
@@ -64,6 +64,24 @@
         @click="onCancelPressed"
       />
     </slot>
+    <slot
+      v-if="(
+        searchState === 'show' || 
+        (inputFocus && (searchState === 'show-active' || searchState === 'show-active-or-no-empty')) || 
+        (valueTemp !== '' && (searchState === 'show-no-empty' || searchState === 'show-active-or-no-empty'))
+      )"
+      name="searchButton" 
+      :onSearchButtonClick="onSearchButtonClick"
+    >
+      <IconButton
+        :icon="searchIcon"
+        :size="searchIconSize"
+        :color="searchIconColor"
+        v-bind="searchIconProps"
+        @click="onSearchButtonClick"
+      />
+    </slot>
+    <slot name="rightButton" :onCustomButtonClick="onCustomButtonClick"/>
   </FlexRow>
 </template>
 
@@ -73,8 +91,9 @@ import type { IconProps } from '../basic/Icon.vue';
 import { type ViewStyle, type TextStyle, useTheme, propGetThemeVar } from '../theme/ThemeDefine';
 import FlexRow from '../layout/FlexRow.vue';
 import Button from '../basic/Button.vue';
-import { DynamicColor, DynamicSize } from '../theme/ThemeTools';
+import { DynamicSize } from '../theme/ThemeTools';
 import Icon from '../basic/Icon.vue';
+import IconButton from '../basic/IconButton.vue';
 
 export interface SearchBarProp {
   /**
@@ -87,6 +106,11 @@ export interface SearchBarProp {
    */
   inputColor?: string;
   /**
+   * 输入框的背景颜色
+   * @default white
+   */
+  inputBackgroundColor?: string;
+  /**
    * 输入框的placeholder
    */
   placeholder?: string;
@@ -137,6 +161,35 @@ export interface SearchBarProp {
    */
   cancelState?: 'hidden'|'show'|'show-active'|'show-no-empty'|'show-active-or-no-empty';
   /**
+   * 搜索按钮显示
+   * * hidden 不显示
+   * * show 一直显示
+   * * show-active 当输入框激活时显示
+   * * show-no-empty 当输入框有文字时显示
+   * * show-active-or-no-empty 当输入框激活或者有文字时显示
+   * @default 'hidden'
+   */
+  searchState?: 'hidden'|'show'|'show-active'|'show-no-empty'|'show-active-or-no-empty';
+  /**
+   * 搜索按钮图标
+   * @default 'search'
+   */
+  searchIcon?: string;
+  /**
+   * 搜索按钮图标大小
+   * @default 40
+   */
+  searchIconSize?: string|number;
+  /**
+   * 搜索按钮图标颜色
+   * @default text.content
+   */
+  searchIconColor?: string;
+  /**
+   * 图标自定义属性
+   */
+  searchIconProps?: Partial<IconProps>;
+  /**
    * 自定义样式
    */
   innerStyle?: ViewStyle;
@@ -164,6 +217,11 @@ const theme = useTheme();
 
 const props = withDefaults(defineProps<SearchBarProp>(), {
   inputColor: () => propGetThemeVar('SearchBarTextColor', 'text.content'),
+  inputBackgroundColor: () => propGetThemeVar('SearchBarBackgroundColor', 'white'),
+  searchState: 'hidden',
+  searchIcon: () => propGetThemeVar('SearchBarSearchIcon', 'search'),
+  searchIconSize: () => propGetThemeVar('SearchBarSearchIconSize', 40),
+  searchIconColor: () => propGetThemeVar('SearchBarSearchIconColor', 'text.content'),
   placeholder: '',
   placeholderTextColor: () => propGetThemeVar('SearchBarCancelTextColor', 'text.second'),
   cancelText: '取消',
@@ -189,7 +247,6 @@ function updateValue(v: string) {
 const themeStyles = theme.useThemeStyles({
   view: {
     position: 'relative',
-    backgroundColor: DynamicColor('SearchBarBackgroundColor', 'white'),
     borderRadius: DynamicSize('SearchBarBorderRadius', 40),
     paddingHorizontal: DynamicSize('SearchBarPaddingHorizontal', 28),
     paddingVertical: DynamicSize('SearchBarPaddingVertical', 16),
@@ -231,6 +288,10 @@ function onCancelPressed() {
     updateValue('');
   emit('cancel');
 }
+function onSearchButtonClick() {
+  emit('search', valueTemp.value);
+  uni.hideKeyboard()
+}
 function onSumit() {
   emit('search', valueTemp.value);
   uni.hideKeyboard()

+ 2 - 0
src/components/form/TimePickerField.vue

@@ -37,6 +37,7 @@ import ActionSheetTitle, { type ActionSheetTitleProps } from '../dialog/ActionSh
 import TimePicker from './TimePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
+import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
 
 export interface TimePickerFieldProps extends Omit<TimePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -115,6 +116,7 @@ const {
   undefined,
   popupShow,
 );
+defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
 
 defineOptions({
   options: {

+ 4 - 6
src/components/form/Uploader.vue

@@ -52,7 +52,7 @@
           </slot>
           <!-- #endif -->
         </template>
-        <slot v-if="currentUpladList.length < maxUploadCount && !disabled" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
+        <slot v-if="currentUpladList.length < maxUploadCount && !disabled && !readonly" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
           <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
         </slot>
         <slot v-if="readonly && currentUpladList.length === 0">
@@ -292,16 +292,14 @@ const currentUpladList = ref<UploaderItem[]>(props.intitalItems?.concat() || [])
 
 //上传按钮点击
 function onUploadPress() {
-  if (props.disabled) {
+  if (props.disabled || props.readonly)
     return;
-  }
   if (props.maxUploadCount > 1 && props.maxUploadCount - currentUpladList.value.length <= 0) {
     props.onOverCount ?
       props.onOverCount(props.maxUploadCount, currentUpladList.value.length) :
       toast.value?.text(`最多上传 ${props.maxUploadCount} 个文件哦!`);
     return;
   }
-
   const items = props.onPickImage ?
     props.onPickImage() :
     new Promise<UploaderItem[]>((resolve, reject) => {
@@ -336,9 +334,10 @@ function onUploadPress() {
             },
             {
               name: '从微信聊天中选择',
-              subname: '可在录音机或者WPS文档分享给文件传输助手\n然后选择任意文档文件',
+              subname: '可在录音机或者WPS文档分享给文件传输助手\n然后选择任意文档/文件',
             },
           ],
+          showCancel: true,
           onSelect(index, name) {
           },
         }).then((index) => {
@@ -359,7 +358,6 @@ function onUploadPress() {
             });
           }
         });
-
       } else {
         chooseLocal();
       }

+ 0 - 2
src/components/form/UploaderField.vue

@@ -80,8 +80,6 @@ onMounted(() => {
 
 defineExpose({
   getUploaderRef: () => {
-    console.log('getUploaderRef', uploaderRef.value);
-    
     return uploaderRef.value
   },
 })

+ 3 - 1
src/components/layout/BaseView.ts

@@ -22,7 +22,9 @@ export function useBaseViewStyleBuilder(props: FlexProps) {
       backgroundColor: themeContext.resolveThemeColor(props.backgroundColor),
       width: themeContext.resolveThemeSize(props.width),
       height: themeContext.resolveThemeSize(props.height),
-      gap: themeContext.resolveThemeSize(props.gap),
+      gap: !Array.isArray(props.gap) ? themeContext.resolveThemeSize(props.gap) : props.gap,
+      rowGap: Array.isArray(props.gap) ? themeContext.resolveSize(props.gap[0]) : undefined,
+      columnGap: Array.isArray(props.gap) ? themeContext.resolveSize(props.gap[1]) : undefined,
       borderRadius: themeContext.resolveThemeSize(props.radius),
       overflow: props.overflow,
       border: props.border && !props.border.includes(' ') ? themeContext.getVar('border.' + props.border, undefined) : props.border,

+ 1 - 1
src/components/layout/FlexView.vue

@@ -105,7 +105,7 @@ export interface FlexProps {
   /**
    * 间距
    */
-  gap?: number|string,
+  gap?: number|number[]|string,
   /**
    * 背景颜色
    */

+ 11 - 1
src/components/nav/NavBar.vue

@@ -1,6 +1,6 @@
 <template>
   <view 
-    class="nana-nav-title"
+    :class="['nana-nav-title', innerClass]"
     :style="{
       backgroundColor: theme.resolveThemeColor(backgroundColor),
       height: theme.resolveThemeSize(height),
@@ -33,6 +33,7 @@
           paddingLeft: 0,
           paddingRight: 0,
         }"
+        :innerClass="titleClass"
         :color="textColor"
         :textAlign="align"
         :text="title"
@@ -44,6 +45,7 @@
           ...props.titleStyle,
           flex: 1,
         }"
+        :innerClass="titleClass"
         :textAlign="align"
         :color="textColor"
         :text="title"
@@ -139,6 +141,10 @@ export interface NavBarProps {
    */
   titleStyle?: object;
   /**
+   * 自定义标题文字样式
+   */
+  titleClass?: any;
+  /**
    * 标题文字超出时,是否自动滚动
    * @default true
    */
@@ -147,6 +153,10 @@ export interface NavBarProps {
    * 图标透传样式
    */
   iconProps?: IconProps;
+  /**
+   * 自定义类名
+   */
+  innerClass?: any;
 }
 
 function getButton(type: NavBarButtonTypes) {

+ 47 - 3
src/components/nav/Pagination.vue

@@ -10,10 +10,27 @@
       />
     </slot>
     <slot v-if="simple" name="simple" :text="`${currentPage + 1}/${props.pageCount}`"">
+      <PaginationItem
+        v-if="showFirstAndLast"
+        v-bind="$props"
+        :text="firstText"
+        :touchable="canPrev"
+        :active="false"
+        @click="onFirstPress"
+      />
       <text :style="themeStyles.simpleText.value">{{ `${currentPage + 1}/${props.pageCount}` }}</text>
+      <PaginationItem
+        v-if="showFirstAndLast"
+        v-bind="$props"
+        :text="lastText"
+        :touchable="canNext"
+        :active="false"
+        @click="onLastPress"
+      />
     </slot>
-    <template v-else>
-      <template  v-for="index in items" :key="index">
+    <slot v-else name="items" :items="items" :currentPage="currentPage" :onChange="emitChange">
+      <template v-for="index in items" :key="index">
+        <!-- #ifndef MP -->
         <slot
           name="item"
           :text="(index + 1).toString()"
@@ -21,6 +38,7 @@
           :active="index === currentPage"
           :onClick="() => emitChange(index)"
         >
+        <!-- #endif -->
           <PaginationItem
             v-bind="$props"
             :key="index"
@@ -29,9 +47,11 @@
             :touchable="true"
             @click="() => emitChange(index)"
           />
+        <!-- #ifndef MP -->
         </slot>
+        <!-- #endif -->
       </template>
-    </template>
+    </slot>
     <slot v-if="showNextPrev" name="next" :onClick="onNextPress" :touchable="canNext">
       <PaginationItem 
         v-bind="$props"
@@ -63,10 +83,17 @@ export interface PaginationProps {
   pageCount: number;
   /**
    * 是否显示上一页下一页按钮,默认是
+   * @default true
    */
   showNextPrev?: boolean;
   /**
+   * 是否显示第一页和最后一页按钮,默认是
+   * @default true
+   */
+  showFirstAndLast?: boolean;
+  /**
    * 同时显示的页面按钮数量
+   * @default 5
    */
   showPageCount?: number;
   /**
@@ -75,13 +102,25 @@ export interface PaginationProps {
   simple?: boolean;
   /**
    * 上一页按钮文字
+   * @default '上一页'
    */
   prevText?: string;
   /**
    * 下一页按钮文字
+   * @default '下一页'
    */
   nextText?: string;
   /**
+   * 第一页按钮文字,仅在 simple 模式下显示
+   * @default '首页'
+   */
+  firstText?: string;
+  /**
+   * 最后一页按钮文字,仅在 simple 模式下显示
+   * @default '末页'
+   */
+  lastText?: string;
+  /**
    * 按钮按下颜色
    */
   pressedColor?: string;
@@ -123,6 +162,9 @@ const props = withDefaults(defineProps<PaginationProps>(), {
   simple: false,
   prevText: '上一页',
   nextText: '下一页',
+  firstText: '首页',
+  lastText: '末页',
+  showFirstAndLast: true,
   pressedColor: () => propGetThemeVar('PaginationPressedColor', 'pressed.white'),
   pressedTextColor: () => propGetThemeVar('PaginationPressedTextColor', 'text.second'),
   activeColor: () => propGetThemeVar('PaginationActiveColor', 'primary'),
@@ -159,6 +201,8 @@ function emitChange(num: number) {
 }
 const onPrevPress = () => emitChange(props.currentPage - 1);
 const onNextPress = () => emitChange(props.currentPage + 1);
+const onFirstPress = () => emitChange(0);
+const onLastPress = () => emitChange(props.pageCount - 1);
 
 const themeContext = useTheme();
 const themeStyles = themeContext.useThemeStyles({

+ 2 - 2
src/components/theme/ThemeDefine.ts

@@ -164,10 +164,10 @@ export function useTheme() {
       v = theme.value.varOverrides[key];
     return resolveSize(v ?? defaultValue);
   }
-  function getText(key: string, defaultValue: Record<string, string>) {
+  function getText(key: string, defaultValue?: Record<string, string>) {
     return theme.value.textConfigs[key]?? defaultValue;
   }
-  function getVar<T>(key: string, defaultValue: T) : T {
+  function getVar<T>(key: string, defaultValue?: T) : T {
     let rs = undefined;
     let type = '';
     if (key.includes('.'))

+ 1 - 1
src/components/typography/HorizontalScrollText.vue

@@ -114,6 +114,7 @@ defineOptions({
   white-space: nowrap;
   display: flex;
   flex-direction: row;
+  align-items: center;
 
   .placeholder {
     visibility: hidden;
@@ -124,7 +125,6 @@ defineOptions({
   }
   .inner {
     position: absolute;
-    top: 0;
 
     &.scroll {
       animation: 25000ms linear 0s infinite normal none running horizontalScrollText;

+ 2 - 2
src/manifest.json

@@ -1,7 +1,7 @@
 {
-	"name": "mingnan",
+	"name": "minnan",
 	"appid": "__UNI__971AA7D",
-	"description": "",
+	"description": "闽南文化生态保护区(厦门市)",
 	"versionName": "1.0.0",
 	"versionCode": "100",
 	"transformPx": false,

+ 64 - 11
src/pages.json

@@ -1,7 +1,7 @@
 {
   "pages": [
     {
-      "path": "pages/home",
+      "path": "pages/home/index",
       "style": {
         "navigationBarTitleText": "闽南文化生态保护-首页",
         "navigationStyle": "custom"
@@ -27,6 +27,39 @@
       }
     },
     {
+      "path": "pages/introduction/news",
+      "style": {
+        "navigationBarTitleText": "闽南新鲜事",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/introduction/communicate",
+      "style": {
+        "navigationBarTitleText": "世界走透透",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/introduction/explore",
+      "style": {
+        "navigationBarTitleText": "闽南百科",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/introduction/inhert",
+      "style": {
+        "navigationBarTitleText": "遗产报你知"
+      }
+    },
+    {
+      "path": "pages/introduction/travel",
+      "style": {
+        "navigationBarTitleText": "乐游闽南"
+      }
+    },
+    {
       "path": "pages/introduction/character/list",
       "style": {
         "navigationBarTitleText": "历史人物列表",
@@ -54,14 +87,14 @@
       }
     },
     {
-      "path": "pages/travel",
+      "path": "pages/travel/index",
       "style": {
         "navigationBarTitleText": "闽南文化生态保护-文旅",
         "navigationStyle": "custom"
       }
     },
     {
-      "path": "pages/travel/calendar",
+      "path": "pages/travel/calendar/index",
       "style": {
         "navigationBarTitleText": "节庆日历",
         "navigationStyle": "custom"
@@ -82,14 +115,14 @@
       }
     },
     {
-      "path": "pages/discover",
+      "path": "pages/article/index",
       "style": {
         "navigationBarTitleText": "闽南文化生态保护-发现",
         "navigationStyle": "custom"
       }
     },
     {
-      "path": "pages/inhert",
+      "path": "pages/inhert/index",
       "style": {
         "navigationBarTitleText": "闽南文化生态保护-传承",
         "navigationStyle": "custom"
@@ -147,6 +180,13 @@
       }
     },
     {
+      "path": "pages/inhert/seminar/list",
+      "style": {
+        "navigationBarTitleText": "传习所列表",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
       "path": "pages/inhert/seminar/details",
       "style": {
         "navigationBarTitleText": "传习所详情"
@@ -194,7 +234,8 @@
     {
       "path": "pages/article/details",
       "style": {
-        "navigationBarTitleText": "新闻详情"
+        "navigationBarTitleText": "新闻详情",
+        "navigationStyle": "custom"
       }
     },
     {
@@ -287,6 +328,18 @@
       "style": {
         "navigationBarTitleText": "投稿"
       }
+    },
+    {
+      "path": "pages/research/index",
+      "style": {
+        "navigationBarTitleText": "文化新视角"
+      }
+    },
+    {
+      "path": "pages/document/details",
+      "style": {
+        "navigationBarTitleText": "文档详情"
+      }
     }
   ],
   "globalStyle": {
@@ -300,19 +353,19 @@
     "custom": true,
     "list": [
       {
-        "pagePath": "pages/home",
+        "pagePath": "pages/home/index",
         "text": "首页"
       },
       {
-        "pagePath": "pages/discover",
-        "text": "发现"
+        "pagePath": "pages/article/index",
+        "text": "资讯"
       },
       {
-        "pagePath": "pages/inhert",
+        "pagePath": "pages/inhert/index",
         "text": "传承"
       },
       {
-        "pagePath": "pages/travel",
+        "pagePath": "pages/travel/index",
         "text": "文旅"
       },
       {

+ 145 - 0
src/pages/article/common/CommonCategoryHome.vue

@@ -0,0 +1,145 @@
+<template>
+  <!--通用内容首页小分块组件-->
+  <FlexCol width="100%">
+    <!-- 分类 -->
+    <template v-for="category in categoryDatas" :key="category.title">
+      <HomeTitle 
+        :title="category.title"
+        :showMore="category.showMore !== false"
+        moreText="更多"
+        @clickMore="category.morePage" 
+      />
+      <SimplePageContentLoader :loader="category.data" >
+        <FlexCol>
+          <template v-if="category.type === 'article'">
+            <Box2LineRightShadow
+              v-for="(item, i) in category.data.content.value" 
+              :key="i" 
+              :title="item.title"
+              :desc="item.desc"
+              :tags="(item.bottomTags as string[])"
+              @click="category.detailPage(item)"
+            />
+          </template>
+          <template v-else-if="category.type === 'large-image2'">
+            <scroll-view scroll-x>
+              <FlexRow>
+                <Box2LineLargeImageUserShadow
+                  v-for="(item, i) in category.data.content.value"
+                  classNames="width-2-3 mr-2"
+                  titleColor="title-text"
+                  fixSize
+                  title1
+                  :key="i"
+                  :title="item.title"
+                  :desc="item.desc"
+                  :image="item.image"
+                  :tags="(item.bottomTags as string[])"
+                  @click="category.detailPage(item)"
+                />
+              </FlexRow>
+            </scroll-view>
+          </template>
+          <template v-else>
+            <Box2LineImageRightShadow
+              v-for="(item, i) in category.data.content.value"
+              titleColor="title-text"
+              fixSize
+              :key="i"
+              :title="item.title"
+              :desc="item.desc"
+              :image="item.image"
+              :tags="(item.bottomTags as string[])"
+              @click="category.detailPage(item)"
+            />
+          </template>
+        </FlexCol>
+      </SimplePageContentLoader>  
+    </template>
+  </FlexCol>
+</template>
+
+<script setup lang="ts">;
+import HomeTitle from '@/pages/parts/HomeTitle.vue';
+import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import Box2LineImageRightShadow from '@/pages/parts/Box2LineImageRightShadow.vue';
+import Box2LineRightShadow from '@/pages/parts/Box2LineRightShadow.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import type { PropType } from 'vue';
+import { CommonContentApi, GetContentListItem, GetContentListParams } from '@/api/CommonContent';
+import { navCommonDetail, navCommonList, resolveCommonContentFormData, resolveCommonContentGetPageDetailUrlAuto, type IHomeCommonCategoryBlock } from './CommonContent';
+import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { navTo } from '@/components/utils/PageAction';
+import { DateUtils } from '@imengyu/imengyu-utils';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Box2LineLargeImageUserShadow from '@/pages/parts/Box2LineLargeImageUserShadow.vue';
+
+export interface CategoryDefine {
+  title: string;
+  content: CommonContentApi|IHomeCommonCategoryBlock;
+  type?: 'article'|'large-image2'|''|undefined;
+  detailPage?: string;
+  morePage?: string;
+  noFrom?: boolean;
+  showMore?: boolean;
+}
+
+const props = defineProps({
+  categoryDefine: {
+    type: Array as PropType<CategoryDefine[]>,
+    default: () => [],
+  }
+});
+
+const categoryDatas = props.categoryDefine.map(item => ({
+  ...item,
+  detailPage: (dataItem: GetContentListItem) => {
+    const id = dataItem.id;
+    if (item.content instanceof CommonContentApi) {
+      if (item.detailPage) {
+        if (item.detailPage === 'byContent')
+          navTo(resolveCommonContentGetPageDetailUrlAuto(dataItem), { id });
+        else
+          navTo(item.detailPage, { id });
+      } else {
+        navCommonDetail({
+          id,
+          mainBodyColumnId: item.content.mainBodyColumnId,
+          modelId: item.content.modelId,
+        })
+      }
+    } else {
+      item.content.goDetail(dataItem);
+    }
+  },
+  morePage: () => {
+    if (item.content instanceof CommonContentApi) {
+      if (item.morePage) {
+        navTo(item.morePage, {});
+      } else {
+        navCommonList({
+          title: item.title,
+          mainBodyColumnId: item.content.mainBodyColumnId,
+          modelId: item.content.modelId,
+          detailsPage: item.detailPage,
+        })
+      }
+    } else {
+      item.content.goList();
+    }
+  },
+  data: item.content instanceof CommonContentApi ? useSimpleDataLoader(async () => {
+    let res = (await (item.content as CommonContentApi)
+      .getContentList(new GetContentListParams(), 1, 3))
+      .list;
+    if (!item.noFrom)
+      res = resolveCommonContentFormData(res);
+    else
+      res.forEach(p => {
+        if (!p.desc)
+          p.desc = DateUtils.formatDate(p.publishAt, 'YYYY-MM-dd');
+      })
+    return res;
+  }) : item.content.loader,
+}));
+</script>

+ 63 - 13
src/pages/article/common/CommonContent.ts

@@ -2,13 +2,29 @@ import CommonContent, { GetContentListItem, GetContentListParams } from "@/api/C
 import { useSimpleDataLoader, type ISimpleDataLoader } from "@/common/composeabe/SimpleDataLoader";
 import { navTo } from "@/components/utils/PageAction";
 
-export interface IHomePageMiniCommonListGoMoreAndGoDetail {
+/**
+ * 通用内容首页小列表控制代码组合
+ */
+
+export interface IHomeCommonCategoryBlock {
   loader: ISimpleDataLoader<GetContentListItem[], any>;
-  goDetail: (id: number) => void;
+  goDetail: (i: GetContentListItem) => void;
   goList: () => void; 
 }
 
-export function navHomePageMiniCommonListGo(p: {
+export function navCommonDetail(p: {
+  id: number,
+  title?: string,
+  mainBodyColumnId?: string|number|number[],
+  modelId?: number,
+}) {
+  navTo('/pages/article/details', {
+    mainBodyColumnId: p.mainBodyColumnId,
+    modelId: p.modelId,
+    id: p.id,
+  }) 
+}
+export function navCommonList(p: {
   title?: string,
   mainBodyColumnId?: string|number|number[],
   modelId?: number,
@@ -26,12 +42,39 @@ export function navHomePageMiniCommonListGo(p: {
   }) 
 }
 
+export function resolveCommonContentFormData(item: GetContentListItem[]) {
+  item.forEach(it => {
+    it.desc = `来源:${it.from || '暂无'}\n` 
+      + (it.desc || '');
+    it.bottomTags = it.keywords?.length ? it.keywords as string[] : [ it.mainBodyColumnName ];
+  })
+  return item;
+}
+export function resolveCommonContentMakeDoubleSpace(item: GetContentListItem[]) {
+  const result: GetContentListItem[] = [];
+  item.forEach((it) => {
+    const cloned = it.clone();
+    cloned.title = '\u00A0';
+    cloned.desc = '\u00A0';
+    result.push(it, cloned);
+  });
+  return result;
+}
+
+export function resolveCommonContentGetPageDetailUrlAuto(item: GetContentListItem) {
+  if (item.type === GetContentListParams.TYPE_VIDEO || item.video)
+    return '/pages/video/details';
+  if (item.type === GetContentListParams.TYPE_ARCHIVE && item.archive)
+    return '/pages/document/details';
+  return '/pages/article/details';
+}
+
 /**
  * 专用于通用内容的首页小列表控制代码组合
  * @param p 
  * @returns 
  */
-export function useHomePageMiniCommonListGoMoreAndGoDetail(p: {
+export function useHomeCommonCategoryBlock(p: {
   title?: string,
   mainBodyColumnId?: number|number[],
   modelId?: number,
@@ -39,16 +82,20 @@ export function useHomePageMiniCommonListGoMoreAndGoDetail(p: {
   detailsPage: string,
   count?: number,
   params?: Record<string, any>,
-}) : IHomePageMiniCommonListGoMoreAndGoDetail {
-  function goDetail(id: number) {
-    navTo(p.detailsPage, {
+  resolveData?: (item: GetContentListItem[]) => GetContentListItem[],
+}) : IHomeCommonCategoryBlock {
+  function goDetail(i: GetContentListItem) {
+    navTo(p.detailsPage === 'byContent' ? 
+      resolveCommonContentGetPageDetailUrlAuto(i): 
+      p.detailsPage, 
+    {
       mainBodyColumnId: p.mainBodyColumnId,
       modelId: p.modelId,
-      id,
+      id: i.id,
     }) 
   }
   function goList() {
-    navHomePageMiniCommonListGo({
+    navCommonList({
       title: p.title,
       mainBodyColumnId: typeof p.mainBodyColumnId == 'object' ? 
         p.mainBodyColumnId.join(',') : 
@@ -59,13 +106,16 @@ export function useHomePageMiniCommonListGoMoreAndGoDetail(p: {
     }) 
   }
 
-  const loader = useSimpleDataLoader(async () => 
-    (await CommonContent.getContentList(new GetContentListParams().setSelfValues({
+  const loader = useSimpleDataLoader(async () => {
+    let res = (await CommonContent.getContentList(new GetContentListParams().setSelfValues({
       mainBodyColumnId: p.mainBodyColumnId,
       modelId: p.modelId,
       ...p.params,
-    }), 1, p.count ?? 4)).list
-  );
+    }), 1, p.count ?? 4)).list;
+    if (p.resolveData)
+      res = p.resolveData(res);
+    return res;
+  });
 
   return {
     loader,

+ 128 - 61
src/pages/article/common/CommonListPage.vue

@@ -18,7 +18,7 @@
       />
     </view>
     <!-- 搜索 -->
-    <view v-if="showSearch" class="d-flex flex-col">
+    <view v-if="showSearch && showList" class="d-flex flex-col">
       <SearchBar
         v-model="searchValue"
         :placeholder="`输入关键词搜索${title}`" 
@@ -36,7 +36,7 @@
     >
       <template v-for="(drop, k) in dropDownNames" :key="k" >
         <SimpleDropDownPicker 
-          v-if="!drop.activeTab || drop.activeTab.includes(tabCurrentIndex)"
+          v-if="!drop.activeTab || drop.activeTab.includes(tabCurrentId)"
           :modelValue="dropDownValues[k]"
           :columns="drop.options"
           :style="{maxWidth: `${100/dropDownNames.length}%`}"
@@ -45,7 +45,7 @@
       </template>
       <view 
         v-if="(showTotal && dropDownVisibleCount < 3 && dropDownVisibleCount != 0)" 
-        class="d-flex flex-row align-center mt-3 size-s color-primary text-bold"
+        class="d-flex flex-row align-center pt-3 pb-3 size-s color-primary text-bold"
       >
         <text>总共有 {{ listLoader.total }} 个</text>
       </view>
@@ -58,62 +58,79 @@
     </view>
     
     <!-- 列表 -->
+    <slot name="list" :tabId="tabCurrentId" />
     <view class="position-relative d-flex flex-row flex-wrap justify-between align-stretch mt-3">
-      <slot name="list" />
-      <view
-        v-for="(item, i) in listLoader.list.value"
-        :key="item.id"
-        :class="[
-          'position-relative d-flex flex-grow-1',
-          itemType.endsWith('-2') ? 'width-1-2' : 'w-100'
-        ]"
-      >
-        <Box2LineLargeImageUserShadow 
-          v-if="itemType.startsWith('image-large')"
-          class="w-100"
-          titleColor="title-text"
-          :classNames="getItemClass(i)"
-          :image="getImage(item)"
-          :titleBox="item.titleBox"
-          :titlePrefix="item.titlePrefix"
-          :title="item.title"
-          :desc="item.desc"
-          :tags="item.bottomTags"
-          :badge="item.badge"
-          @click="goDetails(item, item.id)"
-        />
-        <Box2LineImageRightShadow 
-          v-else-if="itemType.startsWith('article-common')"
-          class="w-100"
-          titleColor="title-text"
-          :titleBox="item.titleBox"
-          :titlePrefix="item.titlePrefix"
-          :classNames="getItemClass(i)"
-          :image="getImage(item)"
-          :title="item.title"
-          :desc="item.desc"
-          :tags="item.bottomTags"
-          :badge="item.badge"
-          :wideImage="true"
-          @click="goDetails(item, item.id)"
-        />
-        <Box2LineImageRightShadow 
-          v-else-if="itemType.startsWith('article-character')"
-          class="w-100"
-          :classNames="getItemClass(i)"
-          :image="getImage(item)"
-          titleColor="title-text"
-          :title="item.title"
-          :titlePrefix="item.titlePrefix"
-          :titleBox="item.titleBox"
-          :tags="item.bottomTags || item.keywords"
-          :desc="item.desc"
-          :badge="item.badge"
-          @click="goDetails(item, item.id)"
-        />
+      <template v-if="showList">
+        <view
+          v-for="(item, i) in listLoader.list.value"
+          :key="item.id"
+          :class="[
+            'position-relative d-flex flex-grow-1',
+            itemType.endsWith('-2') ? 'width-1-2' : 'w-100'
+          ]"
+        >
+          <Box2LineLargeImageUserShadow 
+            v-if="itemType.startsWith('image-large')"
+            class="w-100"
+            titleColor="title-text"
+            :classNames="getItemClass(i)"
+            :image="getImage(item)"
+            :titleBox="item.titleBox"
+            :titlePrefix="item.titlePrefix"
+            :title="item.title"
+            :desc="item.desc"
+            :tags="item.bottomTags"
+            :badge="item.badge"
+            @click="goDetails(item, item.id)"
+          />
+          <Box2LineImageRightShadow 
+            v-else-if="itemType.startsWith('article-common')"
+            class="w-100"
+            titleColor="title-text"
+            :titleBox="item.titleBox"
+            :titlePrefix="item.titlePrefix"
+            :classNames="getItemClass(i)"
+            :image="getImage(item)"
+            :title="item.title"
+            :desc="item.desc"
+            :tags="item.bottomTags"
+            :badge="item.badge"
+            :wideImage="true"
+            @click="goDetails(item, item.id)"
+          />
+          <Box2LineImageRightShadow 
+            v-else-if="itemType.startsWith('article-character')"
+            class="w-100"
+            :classNames="getItemClass(i)"
+            :image="getImage(item)"
+            titleColor="title-text"
+            :title="item.title"
+            :titlePrefix="item.titlePrefix"
+            :titleBox="item.titleBox"
+            :tags="item.bottomTags || item.keywords"
+            :desc="item.desc"
+            :badge="item.badge"
+            @click="goDetails(item, item.id)"
+          />
+          <Box2LineImageRightShadow 
+            v-else-if="itemType.startsWith('simple-text')"
+            class="w-100"
+            :classNames="getItemClass(i)"
+            titleColor="title-text"
+            :border="false"
+            :showImage="false"
+            :title="item.title"
+            :titlePrefix="item.titlePrefix"
+            :titleBox="item.titleBox"
+            :tags="item.bottomTags || item.keywords"
+            :desc="item.desc"
+            :badge="item.badge"
+            @click="goDetails(item, item.id)"
+          />
 
-      </view>
-      <view v-if="itemType.endsWith('-2') && listLoader.list.value.length % 2 != 0" class="width-1-2" />
+        </view>
+        <view v-if="itemType.endsWith('-2') && listLoader.list.value.length % 2 != 0" class="width-1-2" />
+      </template>
     </view>
     <SimplePageListLoader :loader="listLoader" />
   </view>
@@ -139,8 +156,17 @@ function getItemClass(index: number) {
 }
 
 export interface DropDownNames {
+  /**
+   * 下拉框选项
+   */
   options: SimpleDropDownPickerItem[],
+  /**
+   * 默认选中值
+   */
   defaultSelectedValue: number|string,
+  /**
+   * 显示Tab的ID
+   */
   activeTab?: number[],
 }
 export interface CommonListItem extends Record<string, any>  {
@@ -196,6 +222,13 @@ const props = defineProps({
     default: false,
   },
   /**
+   * 显示列表的Tab ID。默认所有Tab都显示列表。
+   */
+  showListTabIds: {
+    type: Array as PropType<number[]>,
+    default: () => ([]),
+  },
+  /**
    * 下拉框选项控制
    */
   dropDownNames: {
@@ -206,7 +239,7 @@ const props = defineProps({
    * 列表项类型
    */
   itemType: {
-    type: String as PropType<'image-large-2'|'image-large'|'article-common'|'article-character'>,
+    type: String as PropType<'image-large-2'|'image-large'|'article-common'|'article-character'|'simple-text'>,
     default: 'article-common',
   },
   /**
@@ -235,12 +268,21 @@ const props = defineProps({
   },
   /**
    * 点击详情跳转页面路径
+   * 可以是字符串路径,也可以是对象数组,每个对象包含路径和参数
+   * * 特殊值:byContent 表示根据 detailsPageByContentCallback 函数返回值跳转。
    */
   detailsPage: {
     type: [String,Object],
     default: '/pages/article/details'
   },
   /**
+   * 根据内容项返回跳转路径的回调函数
+   */
+  detailsPageByContentCallback: {
+    type: Function as PropType<(item: any) => string>,
+    default: true,
+  },
+  /**
    * 详情跳转页面参数
    */
   detailsParams: {
@@ -266,7 +308,7 @@ const emit = defineEmits([ 'goCustomDetails' ])
 const dropDownVisibleCount = computed(() => {
   let c = 0;
   for (const element of props.dropDownNames) {
-    if (!element.activeTab || element.activeTab.includes(tabCurrentIndex.value))
+    if (!element.activeTab || element.activeTab.includes(tabCurrentId.value))
       c++;
   }
   return c;
@@ -282,6 +324,8 @@ const listLoader = useSimplePageListLoader(props.pageSize, async (page, pageSize
   )
 });
 const tabCurrentIndex = ref(0)
+const tabCurrentId = computed(() => props.tabs?.[tabCurrentIndex.value]?.id ?? -1)
+const showList = computed(() => props.showListTabIds.length == 0 || props.showListTabIds.includes(tabCurrentId.value))
 
 function handleChangeDropDownValue(index: number, value: number) {
   dropDownValues.value[index] = value;
@@ -307,7 +351,25 @@ function goDetails(item: any, id: number) {
     emit('goCustomDetails', item, id)
     return;
   }
+  if (props.detailsPage == 'byContent') {
+    if (handleByContent())
+      return;
+  }
+  function handleByContent() {
+    const page = props.detailsPageByContentCallback(item);
+    if (page) {
+      navTo(page, { 
+        ...props.detailsParams, 
+        id 
+      })
+      return true; 
+    }
+    return false;
+  }
+
   if (typeof props.detailsPage === 'object' && typeof props.detailsPage[0] === 'string') {
+    if (props.detailsPage[tabCurrentIndex.value] == 'byContent' && handleByContent())
+      return;
     navTo(props.detailsPage[tabCurrentIndex.value], { 
       ...props.detailsParams, 
       id 
@@ -315,6 +377,8 @@ function goDetails(item: any, id: number) {
     return; 
   }
   if (typeof props.detailsPage == 'object' && typeof props.detailsPage[0] === 'object') {
+    if (props.detailsPage[tabCurrentIndex.value].page == 'byContent' && handleByContent())
+      return;
     const item = props.detailsPage[tabCurrentIndex.value];
     navTo(item.page, { 
       ...item.params, 
@@ -360,8 +424,11 @@ onMounted(() => {
   if (props.title)
     uni.setNavigationBarTitle({ title: props.title, })
   loadDropDownValues();
-  if (props.loadMounted)
-    listLoader.loadData(undefined, true);
+  if (props.loadMounted) {
+    setTimeout(() => {
+      listLoader.loadData(undefined, true);
+    }, 500);
+  }
 });
 </script>
 

+ 34 - 16
src/pages/article/common/DetailTabPage.vue

@@ -42,21 +42,19 @@
 
           <view class="d-flex flex-col radius-l bg-light p-25 mt-3" style="min-height:70vh">
             <!-- 简介 -->
-            <template v-if="tabCurrentId == 0">
+            <template v-if="tabCurrentId == TAB_ID_INTRO">
               <Parse
                 v-if="loader.content.value.intro"
                 :content="loader.content.value.intro"
-                :tagStyle="commonParserStyle"
               />
               <Parse
                 v-if="loader.content.value.content"
                 :content="loader.content.value.content"
-                :tagStyle="commonParserStyle"
               />
               <text v-if="emptyContent">暂无简介</text>
             </template>
             <!-- 图片 -->
-            <template v-else-if="tabCurrentId == 1">
+            <template v-else-if="tabCurrentId == TAB_ID_IMAGES">
               <slot name="imagesPrefix" />
               <ImageGrid
                 :images="loader.content.value.images"
@@ -66,7 +64,7 @@
               />
             </template>
             <!-- 视频 -->
-            <template v-else-if="tabCurrentId == 2">
+            <template v-else-if="tabCurrentId == TAB_ID_VIDEO">
               <video
                 v-if="loader.content.value.video"
                 class="w-100 video"
@@ -77,7 +75,7 @@
               />
             </template>
             <!-- 音频 -->
-            <template v-else-if="tabCurrentId == 3">
+            <template v-else-if="tabCurrentId == TAB_ID_AUDIO">
               <video 
                 v-if="loader.content.value.audio"
                 class="w-100 video"
@@ -106,20 +104,28 @@ import type { GetContentDetailItem } from "@/api/CommonContent";
 import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
 import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
 import { useTabControl, type TabControlItem } from "@/common/composeabe/TabControl";
+import { requireNotNull } from "@imengyu/imengyu-utils";
 import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
 import ImageGrid from "@/pages/parts/ImageGrid.vue";
 import ImageSwiper from "@/pages/parts/ImageSwiper.vue";
 import ContentNote from "@/pages/parts/ContentNote.vue";
-import commonParserStyle from "@/common/style/commonParserStyle";
 import { computed, type PropType, type Ref } from "vue";
 import Parse from "@/components/display/parse/Parse.vue";
 import Tabs from "@/components/nav/Tabs.vue";
 import LikeFooter from "@/pages/parts/LikeFooter.vue";
 import ArticleCorrect from "@/pages/parts/ArticleCorrect.vue";
 
+export interface DetailTabPageTabsArray {
+  tabsArray: Ref<TabControlItem[]>,
+  getTabById(id: number): TabControlItem | undefined;
+}
+
 const props = defineProps({
   load: {
-    type: Function as PropType<(id: number, tabsArray: Ref<TabControlItem[]>) => Promise<GetContentDetailItem>>,
+    type: Function as PropType<(
+      id: number, 
+      tabsArray: DetailTabPageTabsArray
+    ) => Promise<GetContentDetailItem>>,
     default: null,
   },
   extraTabs: {
@@ -140,16 +146,21 @@ const emptyContent = computed(() => {
   return !(loader.content.value?.intro as string || '').trim() && !(loader.content.value?.content || '').trim();
 })
 
+const TAB_ID_INTRO = 0;
+const TAB_ID_IMAGES = 1;
+const TAB_ID_VIDEO = 2;
+const TAB_ID_AUDIO = 3;
+
 const loader = useSimplePageContentLoader<
   GetContentDetailItem, 
   { id: number }
 >(async (params) => {
   if (!params)
     throw new Error("!params");
-  const d = await props.load(params.id, tabsArray);
-  tabsArray.value[1].visible = Boolean(d.images && d.images.length > 1);
-  tabsArray.value[2].visible = Boolean(d.video);
-  tabsArray.value[3].visible = Boolean(d.audio);
+  const d = await props.load(params.id, tabsArrayObject);
+  requireNotNull(tabsArrayObject.getTabById(TAB_ID_IMAGES)).visible = Boolean(d.images && d.images.length > 1);
+  requireNotNull(tabsArrayObject.getTabById(TAB_ID_VIDEO)).visible = Boolean(d.video);
+  requireNotNull(tabsArrayObject.getTabById(TAB_ID_AUDIO)).visible = Boolean(d.audio);
 
   if (d.title)
     uni.setNavigationBarTitle({ title: d.title });
@@ -172,22 +183,22 @@ const {
 } = useTabControl({
   tabs: [
     {
-      id: 0,
+      id: TAB_ID_INTRO,
       text: '简介',
       visible: true,
     },
     {
-      id: 1,
+      id: TAB_ID_IMAGES,
       text: '图片',
       visible: true,
     },
     {
-      id: 2,
+      id: TAB_ID_VIDEO,
       text: '视频',
       visible: true,
     },
     {
-      id: 3,
+      id: TAB_ID_AUDIO,
       text: '音频',
       visible: true,
     },
@@ -198,6 +209,13 @@ const {
   },
 })
 
+const tabsArrayObject : DetailTabPageTabsArray = {
+  tabsArray,
+  getTabById(id: number) {
+    return tabsArray.value.find(t => t.id == id);
+  }
+}
+
 useLoadQuerys({ id : 0 }, (p) => loader.loadData(p));
 
 defineExpose({

+ 3 - 1
src/pages/article/common/list.vue

@@ -3,7 +3,8 @@
     :title="querys.title || undefined"
     :load="loadData"
     :itemType="querys.itemType as any || undefined"
-    :detailsPage="querys.detailsPage || undefined"
+    :detailsPage="querys.detailsPage || 'byContent'"
+    :detailsPageByContentCallback="resolveCommonContentGetPageDetailUrlAuto"
     :detailsParams="{
       mainBodyColumnId: querys.mainBodyColumnId || undefined,
       modelId: querys.modelId || undefined,
@@ -15,6 +16,7 @@
 import { useLoadQuerys, stringDotNumbersToNumbers } from '@/common/composeabe/LoadQuerys';
 import CommonListPage from './CommonListPage.vue';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
+import { resolveCommonContentGetPageDetailUrlAuto } from './CommonContent';
 
 const { querys } = useLoadQuerys({
   mainBodyColumnId: '',

+ 79 - 29
src/pages/article/details.vue

@@ -1,49 +1,48 @@
 <template>
-  <view class="d-flex flex-column bg-base pb-45">
+  <FlexCol backgroundColor="background.page">
+    <FlexCol position="absolute" :top="0" :left="0" :right="0" :zIndex="100">
+      <StatusBarSpace backgroundColor="transparent" />
+      <NavBar leftButton="back" :iconProps="{ color: 'white' }" textColor="white" />
+    </FlexCol>
     <SimplePageContentLoader :loader="loader">
       <template v-if="loader.content.value">
         <view class="d-flex flex-col">
-          <swiper 
+          <ImageSwiper 
             v-if="loader.content.value.images.length > 0"
-            circular 
-            :indicator-dots="true"
-            :autoplay="true"
-            :interval="3000"
-            :duration="1000"
-            class="height-500"
-          >
-            <swiper-item v-for="(item, key) in loader.content.value.images" :key="key">
-              <view class="item">
-                <Image 
-                  :src="item" 
-                  width="100%"
-                  :height="500"
-                  :radius="15"
-                  mode="aspectFill" 
-                  @click="onPreviewImage(key)"
-                />
-              </view>
-            </swiper-item>
-          </swiper>
+            :images="loader.content.value.images"
+            height="500rpx"
+          />
           <Image 
             v-else-if="loader.content.value.image"
             width="100%"
+            height="500rpx"
             :radius="15"
             :src="loader.content.value.image"
+            :defaultImage="AppCofig.defaultImage"
             mode="widthFix"
           />
           <view class="d-flex flex-col p-3">
             <view class="size-ll color-title-text">{{ loader.content.value.title }}</view>
             <view class="d-flex flex-row mt-2">
               <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">来源:{{ loader.content.value.from }}</text>
-              <text class="size-s color-text-content-second">{{ DataDateUtils.formatDate(loader.content.value.publishAt, 'YYYY-MM-dd') }}</text>
+              <text class="size-s color-text-content-second text-nowrap">{{ DataDateUtils.formatDate(loader.content.value.publishAt, 'YYYY-MM-dd') }}</text>
             </view>
           </view>
+          <view v-if="archiveInfo.hasArchive" class="mt-3">
+            <Box2LineImageRightShadow
+              class="w-100"
+              titleColor="title-text"
+              title2
+              :image="archiveInfo.archiveIcon"
+              :title="loader.content.value.title"
+              desc="点击查看完整文档"
+              @click="goArchive(loader.content.value.id)"
+            />
+          </view>
           <view class="p-3 radius-ll bg-light mt-3">
             <Parse
               v-if="loader.content.value.content"
               :content="loader.content.value.content"
-              :tagStyle="commonParserStyle"
             />
             <text v-if="emptyContent">暂无简介</text>
           </view>
@@ -59,7 +58,6 @@
               :image="item.thumbnail || item.image || AppCofig.defaultImage"
               :title="item.title"
               :desc="item.desc"
-              :badge="item.badge"
               :wideImage="true"
               @click="goDetails(item.id)"
             />
@@ -74,7 +72,8 @@
         </view>
       </template>
     </SimplePageContentLoader>
-  </view>
+    <Footer text="到底了~" />
+  </FlexCol>
 </template>
 
 <script setup lang="ts">
@@ -85,7 +84,6 @@ import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageConten
 import { useSwiperImagePreview } from "@/common/composeabe/SwiperImagePreview";
 import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
 import NewsIndexContent from "@/api/news/NewsIndexContent";
-import commonParserStyle from "@/common/style/commonParserStyle";
 import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
 import ContentNote from "../parts/ContentNote.vue";
 import Parse from "@/components/display/parse/Parse.vue";
@@ -98,6 +96,18 @@ import AppCofig from "@/common/config/AppCofig";
 import LikeFooter from "../parts/LikeFooter.vue";
 import Image from "@/components/basic/Image.vue";
 import ArticleCorrect from "../parts/ArticleCorrect.vue";
+import ImageSwiper from "../parts/ImageSwiper.vue";
+import IconExcel from '@/components/images/files/excel.png';
+import IconPowerpoint from '@/components/images/files/powerpoint.png';
+import IconUnknown from '@/components/images/files/unknown.png';
+import IconWord from '@/components/images/files/word.png';
+import IconPdf from '@/components/images/files/pdf.png';
+import { StringUtils } from "@imengyu/imengyu-utils";
+import Icon from "@/components/basic/Icon.vue";
+import FlexCol from "@/components/layout/FlexCol.vue";
+import Footer from "@/components/display/Footer.vue";
+import NavBar from "@/components/nav/NavBar.vue";
+import StatusBarSpace from "@/components/layout/space/StatusBarSpace.vue";
 
 const loader = useSimplePageContentLoader<
   GetContentDetailItem, 
@@ -114,6 +124,37 @@ const loader = useSimplePageContentLoader<
 const { onPreviewImage } = useSwiperImagePreview(() => loader.content.value?.images || [])
 
 const emptyContent = computed(() => (loader.content.value?.content || '').trim() === '')
+const archiveInfo = computed(() => {
+  const hasArchive = Boolean(loader.content.value?.archives);
+  let fileIcon = IconUnknown;
+  const ext = StringUtils.path.getFileExt(loader.content.value?.archives || '');
+  switch (ext) {
+    case 'excel': 
+    case 'xls': 
+    case 'xlsx': 
+      fileIcon = IconExcel;
+      break;
+    case 'powerpoint': 
+    case 'ppt': 
+    case 'pptx': 
+      fileIcon = IconPowerpoint;
+      break;
+    case 'word': 
+    case 'docx': 
+    case 'doc': 
+    case 'txt': 
+    case 'rtf': 
+      fileIcon = IconWord;
+      break;
+    case 'pdf': 
+      fileIcon = IconPdf;
+      break;
+  }
+  return {
+    hasArchive,
+    archiveIcon: hasArchive ? fileIcon : IconUnknown,
+  }
+})
 
 const recommendListLoader = useSimpleDataLoader(async () => {
   if (!querys.value.modelId)
@@ -121,10 +162,19 @@ const recommendListLoader = useSimpleDataLoader(async () => {
   return (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(querys.value.modelId)
     .setMainBodyColumnId(querys.value.mainBodyColumnId)
-  , 1, 10)).list.filter((p) => p.id !== querys.value.id);
+  , 1, 10)).list
+    .filter((p) => p.id !== querys.value.id)
+    .map((p) => ({
+      ...p,
+      desc: `${p.from ? `来源:${p.from}` : ''}\n${p.desc || ''}`,
+    }));
 });
 
-
+function goArchive(id: number) {
+  navTo('/pages/document/details', { 
+    id,
+  });
+}
 function goDetails(id: number) {
   navTo('/pages/article/details', { 
     id, 

+ 1 - 2
src/pages/article/editor/preview.vue

@@ -1,12 +1,11 @@
 <template>
   <Empty v-if="!content" image="default" description="空内容,请先编写内容后再预览" />
   <view v-else class="p-3">
-    <Parse :content="content" :tagStyle="commonParserStyle" />
+    <Parse :content="content" />
   </view>
 </template>
 
 <script setup lang="ts">
-import commonParserStyle from '@/common/style/commonParserStyle';
 import Parse from '@/components/display/parse/Parse.vue';
 import Empty from '@/components/feedback/Empty.vue';
 import { onLoad } from '@dcloudio/uni-app';

+ 33 - 0
src/pages/article/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <FlexCol>
+    <StatusBarSpace backgroundColor="background.page" />
+    <NavBar 
+      title="闽南新鲜事" 
+      :titleScroll="false" 
+      titleClass="size-lll font-songti color-title-text" 
+      align="left" 
+      backgroundColor="background.page" 
+    />
+    <News />
+    <Height :height="140" />
+    <Tabbar :current="1" />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
+import Tabbar from '@/common/components/tabs/Tabbar.vue';
+import News from '../introduction/news.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import NavBar from '@/components/nav/NavBar.vue';
+import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
+import Image from '@/components/basic/Image.vue';
+import Height from '@/components/layout/space/Height.vue';
+
+onShareTimeline(() => {
+  return {}; 
+})
+onShareAppMessage(() => {
+  return {}; 
+})
+</script>

+ 1 - 1
src/pages/article/list.vue

@@ -1,6 +1,6 @@
 <template>
   <CommonListPage
-    title="闽南文化资讯"
+    title="闽南闽南新鲜事"
     :load="loadData"
     itemType="article-common"
     :detailsParams="{

+ 0 - 345
src/pages/discover.vue

@@ -1,345 +0,0 @@
-<template>
-  <view class="home-container page-discover d-flex flex-col bg-base">
-    <image 
-      class="position-absolute title"
-      src="https://mncdn.wenlvti.net/app_static/minnan/images/discover/Title.png"
-      mode="widthFix"
-    />
-    <view class="content d-flex flex-col wing-l">
-      
-      <!-- 文化百科 
-      <HomeTitle title="文化百科" />
-      <scroll-view scroll-x>
-        <view class="d-flex flex-row">
-          <view 
-            v-for="(item, i) in categories" 
-            :key="i"
-            class="width-150 d-flex flex-col align-center flex-shrink-0"
-            @click="item.onClick"
-          >
-            <image :src="item.icon" class="width-100 height-100" />
-            <text class="width-130 text-align-center mt-2 color-primary size-s">{{ item.name }}</text>
-          </view>
-        </view>
-      </scroll-view>-->
-
-      <!-- 闽南语猜猜猜 -->
-      <!-- <view class="home-title">
-        <text>闽南语猜猜猜</text>
-      </view>
-      <Box2LinePlayRightArrow 
-        title="听语音猜词语"
-        desc="每日更新,赢取积分"
-        @click="navTo('/pages/article/web/ewebview', {
-          url: 'https://mncdn.wenlvti.net/assets/addons/yunexamine/h5/#/pages/home/dashboard'
-        })"
-      /> -->
-
-      <!-- 文化挑战 -->
-      <!--<HomeTitle title="文化挑战" />
-       
-      方言配音大赛隐藏
-      <Box2LineRightSlot 
-        title="方言配音大赛"
-        desc="参与人数:1,234"
-        @click="navTo('/pages/answer/index')"
-      >
-        <image class="width-60 height-60 radius-base" src="https://mncdn.wenlvti.net/app_static/minnan/images/discover/IconCup.png" mode="aspectFill" />
-      </Box2LineRightSlot>
-      <Box2LineRightSlot
-        title="非遗百科问答"
-        desc="可获积分:500"
-      >
-        <Button :radius="40" type="primary" size="small" @click="goAnswer">立即报名</Button>
-      </Box2LineRightSlot> -->
-
-      <!-- 老照片
-      <HomeTitle title="老照片" showMore @clickMore="goImagesList" />
-      <SimplePageContentLoader :loader="imagesData">
-        <scroll-view scroll-x>
-          <view class="d-flex flex-row">
-            <view 
-              v-for="(item, i) in imagesData.content.value"
-              :key="i"
-              class="mr-2"
-              @click="goImagesDetail(item.id)"
-            >
-              <Image 
-                class="width-300 height-200 radius-base"
-                :src="item.image"
-                :style="{
-                  backgroundImage: `url('${item.image}')`,
-                  backgroundSize: 'cover',
-                }"
-                mode="aspectFit"
-              />
-            </view>
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-      -->
-      
-      <!-- 闽南文化资讯 -->
-      <HomeTitle title="闽南文化资讯" showMore @clickMore="navTo('/pages/article/list')" />
-      <SimplePageContentLoader :loader="cultureData">
-        <view class="d-flex w-100 flex-row flex-wrap align-stretch justify-between">
-          <Box2LineLargeImageUserShadow 
-            v-for="(item, i) in cultureData.content.value"
-            :key="i"
-            :title="item.title"
-            :desc="item.desc"
-            :image="item.thumbnail || item.image"
-            fixSize
-            @click="goCultureDetail(item.id)"
-          />
-        </view>
-      </SimplePageContentLoader> 
-
-      <!-- 闽南文化百科 -->
-      <view class="d-flex flex-col">
-        <HomeTitle title="闽南文化百科" showMore @clickMore="goTopicsList" />
-        <SimplePageContentLoader :loader="topicsData">
-          <Box2LineRightShadow
-            v-for="(item, i) in topicsData.content.value" 
-            :key="i" 
-            :title="item.title"
-            :desc="item.desc"
-            @click="goTopicsDetail(item.id)"
-          />
-        </SimplePageContentLoader>
-      </view>
-
-      <!-- 传播交流 -->
-      <view class="d-flex flex-col">
-        <HomeTitle title="传播交流" showMore @clickMore="goCommunicationList" />
-        <SimplePageContentLoader :loader="communicationData">
-          <Box2LineLargeImageUserShadow
-            v-for="(item, i) in communicationData.content.value" 
-            :key="i" 
-            :title="item.title"
-            :image="item.thumbnail || item.image"
-            :desc="item.desc"
-            @click="goCommunicationDetail(item.id)"
-          />
-        </SimplePageContentLoader>
-      </view>
-
-      <!-- 理论研究 -->
-      <view class="d-flex flex-col">
-        <HomeTitle title="理论研究" showMore @clickMore="goDiscoveryList" />
-        <SimplePageContentLoader :loader="discoveryData">
-          <Box2LineLargeImageUserShadow
-            v-for="(item, i) in discoveryData.content.value" 
-            :key="i" 
-            :title="item.title"
-            :desc="item.desc"
-            :image="item.thumbnail || item.image"
-            @click="goDiscoveryDetail(item.id)"
-          />
-        </SimplePageContentLoader>
-      </view>
-
-      <!-- 热门话题 -->
-      <!-- <HomeTitle title="热门话题" />
-      <SimplePageContentLoader :loader="topicsData">
-        <view class="d-flex flex-col">
-          <Box2LineRightShadow 
-            v-for="(item, i) in topicsData.content.value" 
-            :key="i" 
-            :title="item.title"
-            :desc="item.desc"
-            :right="item.right"
-          />
-        </view>
-      </SimplePageContentLoader> -->
-
-    </view>
-  </view> 
-  <tabbar :current="1"></tabbar>
-</template>
-
-<script setup lang="ts">
-import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import { useAuthStore } from '@/store/auth';
-import { useHomePageMiniCommonListGoMoreAndGoDetail } from './article/common/CommonContent';
-import { navTo } from '@/components/utils/PageAction';
-import Tabbar from '@/common/components/tabs/tabbar.vue';
-import Box2LineRightSlot from './parts/Box2LineRightSlot.vue';
-import Box2LineLargeImageUserShadow from './parts/Box2LineLargeImageUserShadow.vue';
-import HomeTitle from './parts/HomeTitle.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
-import NewsIndexContent from '@/api/news/NewsIndexContent';
-import Button from '@/components/basic/Button.vue';
-import Image from '@/components/basic/Image.vue';
-import Box2LineRightShadow from './parts/Box2LineRightShadow.vue';
-
-const authStore = useAuthStore();
-
-const CategoryIcon1 = 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon1.png';
-const CategoryIcon2 = 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon2.png';
-const CategoryIcon4 = 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon4.png';
-const CategoryIcon5 = 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon5.png';
-const ImageTest2 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/ImageTest2.jpg';
-const ImageTest3 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/ImageTest3.jpg';
-const ImageTest4 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/ImageTest4.jpg';
-const ImageTest5 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/ImageTest5.jpg';
-const UserHead = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/UserHead.png';
-const categories = [
-  { 
-    name: '戏剧曲艺', 
-    icon: 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon6.png', 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '戏剧曲艺',
-      mainBodyColumnId: '240,241',
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
-  },
-  { 
-    name: '音乐舞蹈', 
-    icon: 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon7.png', 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '音乐舞蹈',
-      mainBodyColumnId: 239,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
-  },
-  { 
-    name: '民间技艺', 
-    icon: 'https://mncdn.wenlvti.net/app_static/minnan/images/discover/CategoryIcon8.png', 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '民间技艺',
-      mainBodyColumnId: 242,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
-  },
-  { 
-    name: '闽南民俗', 
-    icon: CategoryIcon1  , 
-    onClick: () => navTo('/pages/introduction/custom/list') 
-  },
-  { 
-    name: '建筑文化', 
-    icon: CategoryIcon2  , 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '建筑文化',
-      mainBodyColumnId: 252,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
-  },
-  { 
-    name: '闽南美食', 
-    icon: CategoryIcon4 , 
-    onClick: () => navTo('/pages/introduction/food/list')
-  },
-  { 
-    name: '海洋文化', 
-    icon: CategoryIcon5  , 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '海洋文化',
-      mainBodyColumnId: 254,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
-  },
-];
-const {
-  loader: cultureData,
-  goDetail: goCultureDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南文化',
-  mainBodyColumnId: [228/* , 298, 299 */],
-  modelId: NewsIndexContent.modelId,
-  params: {
-    platfrom: 327,
-  },
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-const {
-  loader: imagesData,
-  goList: goImagesList,
-  goDetail: goImagesDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '老照片',
-  mainBodyColumnId: 102,
-  modelId: 8,
-  itemType: 'image-large-2',
-  detailsPage: '/pages/article/details',
-});
-
-const {
-  loader: topicsData,
-  goList: goTopicsList,
-  goDetail: goTopicsDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南文化百科',
-  mainBodyColumnId: 320,
-  modelId: 18,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-const {
-  loader: discoveryData,
-  goList: goDiscoveryList,
-  goDetail: goDiscoveryDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '理论研究',
-  mainBodyColumnId: 266,
-  modelId: 19,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-
-const {
-  loader: communicationData,
-  goList: goCommunicationList,
-  goDetail: goCommunicationDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '传播交流',
-  mainBodyColumnId: [260,261,262],
-  modelId: 18,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-function goAnswer() {
-  navTo('/pages/article/web/ewebview', { url: encodeURIComponent(
-    `https://mn.wenlvti.net/app_static/minnan-answer/index.html?token=${authStore.token}&t=${new Date().getTime()}#/pages/home/dashboard/`
-  ) })
-}
-
-onShareTimeline(() => {
-  return {}; 
-})
-onShareAppMessage(() => {
-  return {}; 
-})
-</script>
-
-<style lang="scss">
-.page-discover {
-  > .content {
-    margin-top: 10vh;
-  }
-  > .title {
-    width: 100rpx;
-  }
-
-  .grid4-item {
-    width: 330rpx;
-  }
-}
-</style>

+ 44 - 0
src/pages/document/details.vue

@@ -0,0 +1,44 @@
+<template>
+  <web-view v-if="loader.content.value" :src="loader.content.value?.archives || ''"  />
+</template>
+
+<script setup lang="ts">
+import type { GetContentDetailItem } from "@/api/CommonContent";
+import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
+import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
+import { onShareAppMessage, onShareTimeline } from "@dcloudio/uni-app";
+import NewsIndexContent from "@/api/news/NewsIndexContent";
+
+const loader = useSimplePageContentLoader<
+  GetContentDetailItem, 
+  { id: number }
+>(async (params) => {
+  if (!params)
+    throw new Error("!params");
+  const res = await NewsIndexContent.getContentDetail(params.id);
+  uni.setNavigationBarTitle({ title: res.title });
+  return res;
+});
+
+function getPageShareData() {
+  if (!loader.content.value)
+    return { title: '文章详情', imageUrl: '' }
+  return {
+    title: loader.content.value.title,
+    imageUrl: loader.content.value.images[0],
+  }
+}
+onShareTimeline(() => {
+  return getPageShareData(); 
+})
+onShareAppMessage(() => {
+  return getPageShareData();
+})
+
+const { querys } = useLoadQuerys({ 
+  id: 0
+}, (t) => loader.loadData(t));
+</script>
+
+<style lang="scss">
+</style>

+ 159 - 209
src/pages/home.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="home-container page-home d-flex flex-col bg-base">
     <Image 
-      innerClass="position-absolute"
+      innerClass="main-banner position-absolute"
       width="100%"
       src="https://mncdn.wenlvti.net/app_static/minnan/images/home/BackgroundBanner5.jpg"
       mode="widthFix"
@@ -9,41 +9,72 @@
     <view class="content d-flex flex-col wing-l">
 
       <!-- 分栏 -->
-      <view class="shadow-l radius-l bg-base p-3">
+      <view class="shadow-base radius-l bg-base p-3">
+        <!--
+          点击介绍
+          @click="navTo('introduction')"
+        -->
         <view 
-          class="main-banner-box"
-          @click="navTo('home/introduction')"
+          class="main-banner-box mb-25"
         >
-          <text class="title">闽南文化生态保护区(厦门市)</text>
-          <text>世界闽南文化交流中心</text>
-          <view class="more">
-            <text>查看详情</text>
-          </view>
+          <text class="title">世界闽南文化交流中心</text>
+          <text>闽南文化生态保护区(厦门市)</text>
           <Image 
             innerClass="footer"
             src="https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBanner2.png"
+            :width="280"
             mode="widthFix"
           />
         </view>
-        <view class="position-relative d-flex flex-row flex-wrap justify-between mt-3">
-
-          <view  
-            v-for="(tab, k) in subTabs" 
-            :key="k"
-            class="d-flex flex-column justify-center align-center width-1-4 mt-2 mb-2"
-            @click="tab.onClick"
-          >
-            <Image width="65%" :src="tab.icon" mode="widthFix" :innerStyle="{ maxHeight: '100rpx' }" />
-            <text class="color-second-text mt-2 size-base text-align-center">{{ tab.name }}</text>
-          </view>
+        
+        <view class="position-relative d-flex flex-row flex-wrap justify-between mt-25 row-gap-sss">
+          <HomeButton
+            title="常识一点通"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconMap.png"
+            :size="50"
+            @click="navTo('/pages/introduction/explore')"
+          />
+          <HomeButton
+            title="闽南新鲜事"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconDoc.png"
+            :size="50"
+            @click="navTo('/pages/introduction/news')"
+          />
+          <HomeButton
+            title="遗产报你知"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconIch.png"
+            :size="50"
+            @click="navTo('/pages/introduction/inhert')"
+          />
+          <HomeButton
+            title="文化新视角"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconReserch.png"
+            :size="50"
+            @click="navTo('/pages/research/index')"
+          />
+          <HomeButton
+            title="世界走透透"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconArtifact.png"
+            :size="50"
+            @click="navTo('/pages/introduction/communicate')"
+          />
+          <HomeButton
+            title="来厦门䢐迌"
+            icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconDiscover.png"
+            :size="50"
+            @click="navTo('/pages/introduction/travel')"
+          />
+        </view>
 
+        <view class="position-relative d-flex flex-row flex-wrap justify-between mt-3">
           <Box1AudioPlay
-            class="w-100 mt-3" 
+            class="w-100" 
             :title="indexAudioPlayer.currentTitle.value"
             :image="indexAudioPlayer.currentItem?.value?.image"
             :playState="indexAudioPlayer.isPlaying.value"
             :playTime="indexAudioPlayer.timeString.value"
             @playPauseClick="indexAudioPlayer.playpause"
+            @arrowClick="handleGoAudioList"
             @nextClick="indexAudioPlayer.next"
             @prevClick="indexAudioPlayer.prev"
             @click="handleGoAudioList"
@@ -53,35 +84,46 @@
 
       <!-- 数据统计 -->
       <SimplePageContentLoader :loader="statsLoader">
-        <view v-if="statsLoader.content.value" class="d-flex flex-col justify-center mt-3 pt-3 b-3">
-          <view class="d-flex flex-row align-center">
-            <view class="d-flex flex-col w-50">
-              <StatsText
-                :title="statsLoader.content.value[0].title" 
-                :data="statsLoader.content.value[0].datas" 
-                :type="statsLoader.content.value[0].type" 
-              />
-              <view class="p-2">
-                <HorizontalScrollText :text="statsText1" :fontSize="26" color="text.second" :outerStyle="{ height: '40rpx' }" />
-              </view>
+        <view v-if="statsLoader.content.value" class="d-flex flex-col justify-center mt-3 pt-3 pb-3 bg-light-page radius-base">
+          <view class="d-flex flex-col">
+            <StatsText
+              :title="statsLoader.content.value[0].title" 
+              :data="statsLoader.content.value[0].datas" 
+              :type="statsLoader.content.value[0].type" 
+            />
+            <view class="p-2">
+              <HorizontalScrollText :text="statsText1" :fontSize="26" color="text.second" :outerStyle="{ height: '40rpx' }" />
             </view>
-            <view class="d-flex flex-col w-50">
-              <StatsText 
-                classNames="border-left-forth"
-                :title="statsLoader.content.value[1].title" 
-                :data="statsLoader.content.value[1].datas" 
-                :type="statsLoader.content.value[1].type" 
-              />
-              <view class="p-2">
-                <HorizontalScrollText :text="statsText2" :fontSize="26" color="text.second" :outerStyle="{ height: '40rpx' }" />
-              </view>
+          </view>
+          <view class="border-top-light-primary pt-2 mt-3"></view>
+          <view class="d-flex flex-col">
+            <StatsText
+              :title="statsLoader.content.value[1].title" 
+              :data="statsLoader.content.value[1].datas" 
+              :type="statsLoader.content.value[1].type" 
+            />
+            <view class="p-2">
+              <HorizontalScrollText :text="statsText2" :fontSize="26" color="text.second" :outerStyle="{ height: '40rpx' }" />
             </view>
           </view>
-          <view class="border-top-forth pt-2 mt-3"></view>
+          <view class="border-top-light-primary pt-2 mt-3"></view>
           <StatsText
+            :title="statsLoader.content.value[2].title" 
             :data="statsLoader.content.value[2].datas" 
             :type="statsLoader.content.value[2].type" 
           />
+          <view class="border-top-light-primary pt-2 mt-3"></view>
+          <StatsText
+            :title="statsLoader.content.value[3].title" 
+            :data="statsLoader.content.value[3].datas" 
+            :type="statsLoader.content.value[3].type" 
+          />
+          <view class="border-top-light-primary pt-2 mt-3"></view>
+          <StatsText
+            :title="statsLoader.content.value[4].title" 
+            :data="statsLoader.content.value[4].datas" 
+            :type="statsLoader.content.value[4].type" 
+          />
         </view>
       </SimplePageContentLoader>
 
@@ -95,7 +137,7 @@
           :enable-zoom="false"
           :enable-scroll="false"
           :scale="15"
-          @click="navTo('inhert/map/index', { tab: mapTab })"
+          @click="navTo('/pages/inhert/map/index', { tab: mapTab })"
         />
         <scroll-view class="map-tags position-absolute" :scroll-x="true">
           <view class="tag-bar d-flex flex-row flex-nowrap">
@@ -150,114 +192,32 @@
       </SimplePageContentLoader>
     </view>
   </view>
-  <tabbar :current="0"></tabbar>
+  <Tabbar :current="0" />
 </template>
 
 <script setup lang="ts">
-const MainBoxIcon1 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon10.png';
-const MainBoxIcon2 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon2.png';
-const MainBoxIcon3 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon3.png';
-const MainBoxIcon4 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon4.png';
-const MainBoxIcon5 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon5.png';
-const MainBoxIcon6 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon6.png';
-const MainBoxIcon7 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon9.png';
-const MainBoxIcon8 = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon8.png';
-const ImageTest = 'https://mncdn.wenlvti.net/app_static/minnan/images/home/ImageTest.jpg';
-
 import { ref, watch } from 'vue';
 import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
 import { navTo } from '@/components/utils/PageAction';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import { useSimpleListAudioPlayer } from '@/common/composeabe/SimpleAudioPlayer';
+import { navCommonList } from '@/pages/article/common/CommonContent';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
 import SeminarContent from '@/api/inheritor/SeminarContent';
 import ProjectsContent from '@/api/inheritor/ProjectsContent';
-import ProductsContent from '@/api/inheritor/ProductsContent';
 import AppCofig from '@/common/config/AppCofig';
 import VillageApi from '@/api/inhert/VillageApi';
 import ScenicSpotContent from '@/api/fusion/ScenicSpotContent';
 import IndexContent from '@/api/introduction/IndexContent';
-import StatsText, { type StatsTextItem } from './parts/StatsText.vue';
+import StatsText, { type StatsTextItem } from '../parts/StatsText.vue';
 import HomeTitle from '@/pages/parts/HomeTitle.vue'; 
-import Tabbar from '@/common/components/tabs/tabbar.vue';
+import Tabbar from '@/common/components/tabs/Tabbar.vue';
 import Box1AudioPlay from '@/pages/parts/Box1AudioPlay.vue';
 import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
 import HorizontalScrollText from '@/components/typography/HorizontalScrollText.vue';
-import { navHomePageMiniCommonListGo } from './article/common/CommonContent';
 import Image from '@/components/basic/Image.vue';
-
-const subTabs = [
-  { 
-    name: '非遗名录', 
-    icon: MainBoxIcon6 , 
-    onClick: () => navTo('/pages/inhert/intangible/list')
-  },
-  { 
-    name: '文物古迹', 
-    icon: MainBoxIcon5 , 
-    onClick: () => navTo('/pages/inhert/artifact/list') 
-  },
-  { 
-    name: '语言文化', 
-    icon: MainBoxIcon1, 
-    onClick: () => navHomePageMiniCommonListGo({
-      title: '语言文化',
-      mainBodyColumnId: 235,
-      modelId: 5,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    })
-  },  { 
-    name: '建筑文化', 
-    icon: MainBoxIcon8 , 
-    onClick: () => navHomePageMiniCommonListGo({
-      title: '建筑文化',
-      mainBodyColumnId: 252,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    })
-  },
-  { name: '历史人物', icon: MainBoxIcon3, onClick: () => navTo('/pages/introduction/character/list') },
-  { 
-    name: '民间习俗', 
-    icon: MainBoxIcon4, 
-    onClick: () => navHomePageMiniCommonListGo({
-      title: '民间习俗',
-      modelId: 4,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    })
-  },
-  { 
-    name: '饮食文化', 
-    icon: MainBoxIcon2, 
-    onClick: () => navHomePageMiniCommonListGo({
-      title: '饮食文化',
-      mainBodyColumnId: 253,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    })
-  },
-  { 
-    name: '海洋文化', 
-    icon: 'https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBoxIcon11.png', 
-    onClick: () => navHomePageMiniCommonListGo({
-      title: '海洋文化',
-      mainBodyColumnId: 254,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    })
-  },
-  /* { 
-    name: '老字号', 
-    icon: MainBoxIcon7 , 
-    onClick: () => navTo('/pages/inhert/old/list') 
-  }, */
-];
+import HomeButton from '../parts/HomeButton.vue';
 
 const mapCtx = uni.createMapContext('map');
 const mapTab = ref(1);
@@ -313,8 +273,8 @@ watch(mapTab, () => mapLoader.loadData(undefined, true));
 const indexAudioPlayer = useSimpleListAudioPlayer(async () => {
   return (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(5)
-    .setMainBodyColumnId(313)
-  , 1, 6)).list.sort(() => Math.random()>0.5?-1:1).map((p) => {
+    .setMainBodyColumnId(321)
+  , 1, 10)).list.sort((a, b) => -1).map((p) => {
     return {
       id: p.id,
       title: p.title,
@@ -339,10 +299,6 @@ const recommendLoader = useSimpleDataLoader(async () => {
     p.itemType = p.type == GetContentListParams.TYPE_VIDEO ? 'video' : 'artifact';
     return p;
   }));
-  /* list.push(...(await ProductsContent.getContentList(new GetContentListParams(), 1, 6)).list.map((p) => {
-    p.itemType = p.type == GetContentListParams.TYPE_VIDEO ? 'video' : 'intangible';
-    return p;
-  })); */
   return list;
 });
 const statsText1 = ref('');
@@ -350,9 +306,6 @@ const statsText2 = ref('');
 const statsLoader = useSimpleDataLoader(async () => {
   const data = (await IndexContent.getStats());
 
-  const semiCount = (await SeminarContent.getContentList(new GetContentListParams(), 1, 6)).total;
-  const unmoveableCount = (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 6)).total;
-  
   let sumInheritor = 0;
   let sumProject = 0;
   const topLevelProject = data.ichData.find((p: any) => p.level_text == '人类非遗')?.total || 0;
@@ -360,9 +313,9 @@ const statsLoader = useSimpleDataLoader(async () => {
   const thirdLevelProject = data.ichData.find((p: any) => p.level_text == '省级')?.total || 0;
   const forthLevelProject = data.ichData.find((p: any) => p.level_text == '市级')?.total || 0;
 
-  const topLevelInheritor = data.ichData.find((p: any) => p.level_text == '国家级')?.total || 0;
-  const secondLevelInheritor = data.ichData.find((p: any) => p.level_text == '省级')?.total || 0;
-  const thirdLevelInheritor = data.ichData.find((p: any) => p.level_text == '市级')?.total || 0;
+  const topLevelInheritor = data.inheritorData.find((p: any) => p.level_text == '国家级')?.total || 0;
+  const secondLevelInheritor = data.inheritorData.find((p: any) => p.level_text == '省级')?.total || 0;
+  const thirdLevelInheritor = data.inheritorData.find((p: any) => p.level_text == '市级')?.total || 0;
 
   const projects = (data.ichData as any[]).filter((p: any) => [ '人类非遗', '国家级', '省级', '市级' ].includes(p.level_text)).map((item: any) => {
     if (item.level_text != '人类非遗')
@@ -370,6 +323,8 @@ const statsLoader = useSimpleDataLoader(async () => {
     return {
       title: item.level_text,
       value: item.total,
+      titleSuffix: '项',
+      type: 'forth',
       onClick: () => navTo('/pages/inhert/intangible/list', { tab: 0, level: item.level }),
     } as StatsTextItem
   });
@@ -378,92 +333,80 @@ const statsLoader = useSimpleDataLoader(async () => {
     return {
       title: item.title,
       value: item.total,
+      titleSuffix: '人',
+      type: 'normal',
       onClick: () => navTo('/pages/inhert/inheritor/list', { level: item.level }),
     }
-  }).concat([
-    {
-      title: '',
-      value: '',
-    }
-  ]);
+  });
 
   statsText1.value = `目前厦门市非遗项目市级以上共有 ${sumProject} 项,其中:国家级 ${secondLevelProject} 项(含 ${topLevelProject} 项为人类非遗)、省级 ${thirdLevelProject} 项、市级 ${forthLevelProject} 项。`;
   statsText2.value = `目前厦门市非遗传承人市级以上共有 ${sumInheritor} 人,其中:国家级 ${topLevelInheritor} 人、省级 ${secondLevelInheritor} 人、市级 ${thirdLevelInheritor} 人。`;
-  
-  /* projects.splice(1, 0, {
-    title: `(其中${topLevelProject}项为人类非遗)`,
-    value: ' ',
-    longTitle: true,
-    onClick: () => navTo('/pages/inhert/intangible/list', { tab: 0, level: 0 }),
-  }) */
 
   return [
     {
-      title: '非遗项目',
+      title: '非物质文化遗产代表性项目',
       datas: projects
     },
     {
-      title: '非遗传承人',
+      title: '非物质文化产代表性传承人',
       datas: inheritors
     },
     {
-      datas: [
-        {
-          title: '非遗传习所',
-          value: semiCount,
-          onClick: () => navTo('/pages/inhert/map/index', { tab: 2 }),
-        },
-        {
-          title: '传统村落',
-          value: data.villageData[0].total,
-          onClick: () => navTo('/pages/inhert/village/list'),
-        },
-        {
-          title: '文物古迹',
-          value: unmoveableCount,
-          onClick: () => navTo('/pages/inhert/artifact/list'),
-        },
-      ],
-    },
-    {
-      title: '不可移动文物',
-      type: 'none',
-      datas: data.crData.map((item: any) => {
-        return {
-          title: item.title,
-          value: item.total
-        }
-      })
-    },
-    {
-      title: '闽南文化重要相关文物古迹',
-      type: 'none',
-      datas: data.minnanCr.map((item: any) => {
+      title: '非物质文化遗产传习中心',
+      datas: data.ichCenter.map((item: any) => {
         return {
           title: item.title,
-          value: item.total
+          value: item.total,
+          titleSuffix: '处',
+          type: 'normal',
+          onClick: () => navTo('/pages/inhert/seminar/list', { region: item.id }),
         }
-      })
+      }),
     },
     {
       title: '重要相关历史风貌区',
-      type: 'none',
       datas: data.historyData.map((item: any) => {
         return {
           title: item.title,
-          value: item.total
+          value: item.total,
+          titleSuffix: '处',
+          onClick: () => {
+            switch (item.title) {
+              case '世界文化遗产':
+                navCommonList({
+                  title: '世界文化遗产',
+                  modelId: 17,
+                  mainBodyColumnId: 310
+                });
+                break;
+              case '传统村落':
+                navTo('/pages/inhert/village/list');
+                break;
+              case '重点区域':
+                navCommonList({
+                  title: '重点区域',
+                  modelId: 17,
+                  mainBodyColumnId: 283
+                });
+                break;
+            }
+          },
         }
-      })
+      }),
     },
     {
-      title: '传习中心',
+      title: '闽南文化重要相关文物古迹',
       type: 'none',
-      datas: data.ichCenter.map((item: any) => {
+      datas: data.minnanCr.map((item: any) => {
         return {
           title: item.title,
-          value: item.total
+          value: item.total,
+          titleSuffix: '处',
+          onClick: () => navTo('/pages/inhert/artifact/list', {
+            level: item.level
+          }),
         }
-      })
+      }),
     },
   ]
 
@@ -496,8 +439,12 @@ onShareAppMessage(() => {
 
 <style lang="scss">
 .page-home {
+
+  .main-banner {
+    top: 0rpx;
+  }
   .content {
-    margin-top: 470rpx;
+    margin-top: 400rpx;
   }
 
   .map-tags {
@@ -546,7 +493,7 @@ onShareAppMessage(() => {
     overflow: hidden;
     border-radius: 15rpx;
     background: linear-gradient(180deg, #E5CDAB 0%, #F0E3D6 100%), #F7F3E8;
-    padding: 20rpx;
+    padding: 30rpx 20rpx;
     font-family: "SongtiSCBlack";
     color: #432A04;
 
@@ -559,11 +506,14 @@ onShareAppMessage(() => {
     }
     .more {
       margin-top: 30rpx;
-      padding: 10rpx 20rpx;
-      width: 150rpx;
-      background-image: url('https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBanner.png');
-      background-size: 100% auto;
-      background-repeat: no-repeat;
+      padding: 10rpx 18rpx;
+      width: 180rpx;
+
+      &.badge {
+        background-image: url('https://mncdn.wenlvti.net/app_static/minnan/images/home/MainBanner.png');
+        background-size: 100% auto;
+        background-repeat: no-repeat;
+      }
 
       text {
         font-family: initial;
@@ -572,9 +522,9 @@ onShareAppMessage(() => {
     }
     .footer {
       position: absolute;
-      right: 0;
-      bottom: 0;
-      width: 370rpx;
+      right: -80rpx;
+      bottom: -10rpx;
+      width: 180rpx;
       z-index: 2;
       height: auto;
     }

+ 7 - 8
src/pages/home/introduction.vue

@@ -11,14 +11,14 @@
 
       <!-- 标题 -->
       <view class="font-songti color-title-text d-flex flex-col align-center justify-center p-3">
-        <text class="size-lll">闽南文化生态保护区<text class="size-ll">(厦门市)</text></text>
-        <text class="size-base mt-2">世界闽南文化交流中心</text>
+        <text class="size-xl">世界闽南文化交流中心</text>
+        <text class="size-lll mt-2">闽南文化生态保护区<text class="size-ll ml-2">(厦门市)</text></text>
       </view>
 
       <!-- 分栏 -->
-      <view class="shadow-l radius-l bg-base p-3">
+      <view class="shadow-base radius-l bg-base p-3 size-base">
         <SimplePageContentLoader :loader="introdData">
-          <Parse :content="introdData.content.value" :tag-style="commonParserStyle" />
+          <Parse :content="introdData.content.value" />
         </SimplePageContentLoader>
 
         <!--保护区建设历程-->
@@ -65,8 +65,7 @@ import CommonContent, { GetColumListParams, GetContentListParams } from '@/api/C
 import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
 import Box2LineImageRightShadow from '@/pages/parts/Box2LineImageRightShadow.vue';
 import NewsIndexContent from '@/api/news/NewsIndexContent';
-import Tabbar from '@/common/components/tabs/tabbar.vue';
-import commonParserStyle from '@/common/style/commonParserStyle';
+import Tabbar from '@/common/components/tabs/Tabbar.vue';
 import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
 import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
 import { DataDateUtils } from '@imengyu/js-request-transform';
@@ -95,7 +94,7 @@ const listLoader = useSimplePageListLoader<{
 }>(8, async (page, pageSize, params) => {
   const res = await CommonContent.getContentList(new GetContentListParams().setSelfValues({
     modelId: 17, 
-    mainBodyColumnId: '255,283',
+    mainBodyColumnId: '255',
   }), page, pageSize);
   return { list: res.list.map((item) => {
     return {
@@ -120,7 +119,7 @@ onMounted(() => {
 <style lang="scss">
 .page-home-introduction {
   .content {
-    margin-top: 15vh;
+    margin-top: 6vh;
   }
 }
 </style>

+ 20 - 2
src/pages/home/laws.vue

@@ -1,10 +1,17 @@
 <template>
   <CommonListPage 
     title="政策法规"
-    itemType="image-large-2"
+    itemType="article-common"
     showTotal
     :dropDownNames="dropdownNames"
     :load="loadData" 
+    :tabs="[{
+      id: 360,
+      text: '国家政策',
+    },{
+      id: 361,
+      text: '地方政策',
+    }]"
   />
   <!--  -->
 </template>
@@ -14,6 +21,7 @@ import { ref } from 'vue';
 import CommonListPage, { type DropDownNames } from '@/pages/article/common/CommonListPage.vue';
 import { GetContentListParams } from '@/api/CommonContent';
 import PolicyContent from '@/api/introduction/PolicyContent';
+import { DateUtils } from '@imengyu/imengyu-utils';
 
 const dropdownNames = ref<DropDownNames[]>([]);
 
@@ -24,6 +32,16 @@ async function loadData(
   dropDownValues: number[],
   tabSelect: number,
 ) {
-  return await PolicyContent.getContentList(new GetContentListParams().setKeywords(searchText), page, pageSize);
+  const res = (await PolicyContent.getContentList(new GetContentListParams()
+    .setKeywords(searchText)
+    .setMainBodyColumnId(tabSelect)
+  , page, pageSize));
+  return { 
+    list: res.list.map((p) => ({ 
+      ...p, 
+      desc: DateUtils.formatDate(p.publishAt, 'yyyy-MM-dd'),
+    })), 
+    total: res.total 
+  }
 }
 </script>

+ 0 - 436
src/pages/inhert.vue

@@ -1,436 +0,0 @@
-<template>
-  <view class="home-container page-inhert d-flex flex-col bg-base">
-    <image 
-      class="position-absolute title"
-      src="https://mncdn.wenlvti.net/app_static/minnan/images/inhert/Title.png"
-      mode="widthFix"
-    />
-    <view class="content d-flex flex-col ">
-
-      <!-- 非遗项目 -->
-      <HomeTitle title="非遗项目" showMore inWing @clickMore="navTo('inhert/intangible/list')" />
-      <SimplePageContentLoader :loader="intangibleData" >
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in intangibleData.content.value"
-              classNames="width-2-3 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.image"
-              :tags="item.bottomTags"
-              @click="navTo('inhert/intangible/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-      
-      <!-- 非遗传承人 -->
-      <HomeTitle title="非遗传承人" showMore inWing @clickMore="navTo('inhert/inheritor/list')" />
-      <SimplePageContentLoader :loader="inheritorData">
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in inheritorData.content.value"
-              classNames="width-2-5 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :image="item.image"
-              :tags="item.bottomTags"
-              :titleBox="item.titleBox"
-              title1
-              @click="navTo('inhert/inheritor/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-
-      <!-- 非遗作品 -->
-      <!-- <HomeTitle title="非遗作品" showMore inWing @clickMore="navTo('inhert/intangible/list', { tab: 1 })" />
-      <SimplePageContentLoader :loader="productsData" >
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in productsData.content.value"
-              classNames="width-2-3 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.image"
-              :bottomLocate="item.bottomLocate"
-              :bottomScore="item.bottomScore"
-              @click="navTo('inhert/product/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader> -->
-
-      <!-- 文物 -->
-      <HomeTitle title="文物古迹" showMore inWing @clickMore="navTo('inhert/artifact/list')" />
-      <SimplePageContentLoader :loader="artifactData">
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in artifactData.content.value"
-              classNames="width-2-5 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :image="item.image"
-              :tags="item.tags"
-              title1
-              @click="navTo('inhert/artifact/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-
-      <!-- 老字号 -->
-      <HomeTitle title="老字号" showMore inWing @clickMore="navTo('/pages/inhert/old/list')" />
-      <SimplePageContentLoader :loader="oldData">
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in oldData.content.value"
-              classNames="width-2-3 mr-2"
-              fixSize
-              titleColor="title-text"
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.thumbnail"
-              :bottomLocate="(item.regionText as string)"
-              :bottomScore="''"
-              @click="navTo('/pages/inhert/intangible/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-      
-
-      <!-- 闽南语在线课程-->
-      <HomeTitle title="闽南语在线课程" :showMore="false" inWing @clickMore="goCourseList" />
-      <SimplePageContentLoader :loader="corseData" >
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in corseData.content.value"
-              classNames="width-2-3 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.thumbnail || item.image"
-              @click="goCourseDetail(item.id)"
-            />
-          </view>
-        </scroll-view>
-        <view class="d-flex flex-col wing-l">
-          <Box1AudioPlay
-            class="w-100 mt-3" 
-            :title="indexAudioPlayer.currentTitle.value"
-            :image="indexAudioPlayer.currentItem?.value?.image"
-            :playState="indexAudioPlayer.isPlaying.value"
-            :playTime="indexAudioPlayer.timeString.value"
-            @playPauseClick="indexAudioPlayer.playpause"
-            @nextClick="indexAudioPlayer.next"
-            @prevClick="indexAudioPlayer.prev"
-            @click="handleGoAudioList"
-          />
-        </view>
-      </SimplePageContentLoader>
-      
-      <!-- 传统村落 -->
-      <HomeTitle title="传统村落" showMore inWing @clickMore="navTo('/pages/inhert/village/list')" />
-      <SimplePageContentLoader :loader="villageData">
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in villageData.content.value"
-              classNames="width-2-3 mr-2"
-              fixSize
-              titleColor="title-text"
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.image"
-              @click="navTo('/pages/inhert/village/details', { id: item.id })"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-
-      <view class="d-flex flex-col wing-l">
-        <!-- 闽南语原创歌曲 
-        <HomeTitle title="闽南语原创歌曲" showMore @clickMore="goMingnanSongsList" />
-
-        <SimplePageContentLoader :loader="mingnanSongsData" >
-          <Box1AudioPlay 
-            v-for="s in mingnanSongsData.content.value" 
-            :key="s.id"
-            :title="s.title" 
-            :image="s.thumbnail || s.image"
-            :showPrev="false"
-            :showNext="false"
-            innerClass="w-100 mt-1" 
-            @playPauseClick="goMingnanSongsDetail(s.id)"
-            @click="goMingnanSongsDetail(s.id)"
-          />
-        </SimplePageContentLoader>
-        -->
-
-        <!-- 闽南语作品库 
-        <HomeTitle title="闽南语作品库" showMore @clickMore="goSongsList" />
-        <SimplePageContentLoader :loader="songsData" >
-          <Box2LinePlayRightArrow 
-            v-for="s in songsData.content.value" 
-            :key="s.id"
-            :title="s.title" 
-            :desc="s.desc"
-            @click="goSongsDetail(s.id)"
-          />
-        </SimplePageContentLoader>
-        -->
-      </view>
-
-      <!-- 童趣记忆 
-      <HomeTitle title="童趣记忆" showMore inWing @clickMore="goMemoryList" />
-      <SimplePageContentLoader :loader="memoryData" >
-        <scroll-view scroll-x>
-          <view class="padding-wing-l pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
-            <Box2LineLargeImageUserShadow
-              v-for="(item, i) in memoryData.content.value"
-              classNames="width-2-3 mr-2"
-              titleColor="title-text"
-              fixSize
-              :key="i"
-              :title="item.title"
-              :desc="item.desc"
-              :image="item.thumbnail || item.image"
-              @click="goMemoryDetail(item.id)"
-            />
-          </view>
-        </scroll-view>
-      </SimplePageContentLoader>
-      -->
-
-      <!-- 闽南文化百科
-      <view class="d-flex flex-col wing-l">
-        <HomeTitle title="闽南文化百科" showMore @clickMore="goTopicsList" />
-        <SimplePageContentLoader :loader="topicsData">
-          <Box2LineRightShadow
-            v-for="(item, i) in topicsData.content.value" 
-            :key="i" 
-            :title="item.title"
-            :desc="item.desc"
-            @click="goTopicsDetail(item.id)"
-          />
-        </SimplePageContentLoader>
-      </view> -->
-
-    </view>
-  </view>
-  <tabbar :current="2"></tabbar>
-</template>
-
-<script setup lang="ts">
-import { navTo } from '@/components/utils/PageAction';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import CommonContent, { GetContentListParams } from '@/api/CommonContent';
-import Tabbar from '@/common/components/tabs/tabbar.vue';
-import HomeTitle from './parts/HomeTitle.vue';
-import Box2LineLargeImageUserShadow from './parts/Box2LineLargeImageUserShadow.vue';
-import Box2LinePlayRightArrow from './parts/Box2LinePlayRightArrow.vue';
-import Box1AudioPlay from './parts/Box1AudioPlay.vue';
-import Box2LineRightShadow from './parts/Box2LineRightShadow.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
-import ProjectsContent from '@/api/inheritor/ProjectsContent';
-import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
-import { useHomePageMiniCommonListGoMoreAndGoDetail } from './article/common/CommonContent';
-import ProductsContent from '@/api/inheritor/ProductsContent';
-import InheritorContent from '@/api/inheritor/InheritorContent';
-import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { useSimpleListAudioPlayer } from '@/common/composeabe/SimpleAudioPlayer';
-import VillageApi from '@/api/inhert/VillageApi';
-
-const artifactData = useSimpleDataLoader(async () => 
-  (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 4)).list.map(p => ({
-    id: p.id,
-    title: p.title, 
-    desc: '', 
-    image: p.thumbnail || p.image,
-    likes: p.likes,
-    tags: [p.levelText, p.district] as string[],
-    comment: p.comments,
-  }))
-);
-const intangibleData = useSimpleDataLoader(async () => 
-  (await ProjectsContent.getContentList(new GetContentListParams(), 1, 4)).list.map(p => ({
-    id: p.id,
-    title: p.title, 
-    desc: p.desc, 
-    image: p.thumbnail || p.image,
-    bottomTags: [
-      p.levelText, 
-      p.ichTypeText, 
-      p.batchText,
-      p.regionText,
-    ] as string[],
-  }))
-);
-const inheritorData = useSimpleDataLoader(async () => 
-  (await InheritorContent.getContentList(new GetContentListParams(), 1, 4)).list.map(p => ({
-    id: p.id,
-    title: p.title, 
-    desc: '', 
-    image: p.thumbnail || p.image,
-    titleBox: Boolean(p.deathBirth),
-    bottomTags: [
-      p.levelText, 
-      p.nation,
-      p.ichName
-    ] as string[],
-  }))
-);
-
-const productsData = useSimpleDataLoader(async () => 
-  (await ProductsContent.getContentList(new GetContentListParams(), 1, 4)).list.map(p => ({
-    id: p.id,
-    title: p.title, 
-    desc: p.desc, 
-    image: p.thumbnail || p.image,
-    bottomLocate: p.area as string,
-    bottomScore: '5.0',
-  }))
-);
-const villageData = useSimpleDataLoader(async () =>   {
-  const res = await CommonContent.getCategoryList(151);
-  const it2 = res.find(p => p.title == '省级');
-  return (await VillageApi.getVallageList(it2?.id)).map(p => ({
-    id: p.id,
-    title: p.title, 
-    desc: '', 
-    image: p.thumbnail || p.image,
-    tags: [p.levelText, p.district] as string[],
-    comment: p.comments,
-  }))
-});
-
-
-
-const indexAudioPlayer = useSimpleListAudioPlayer(async () => {
-  return (await CommonContent.getContentList(new GetContentListParams()
-    .setModelId(5)
-    .setMainBodyColumnId(313)
-  , 1, 6)).list.sort(() => Math.random()>0.5?-1:1).map((p) => {
-    return {
-      id: p.id,
-      title: p.title,
-      image: p.thumbnail || p.image,
-      src: p.audio as string, 
-    }
-  });
-})
-function handleGoAudioList() {
-  navTo('/pages/inhert/language/list') 
-}
-
-const {
-  loader: corseData,
-  goList: goCourseList,
-  goDetail: goCourseDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南语在线课程',
-  mainBodyColumnId: [257/* ,235,237,210 */],
-  modelId: 5,
-  itemType: 'article-common',
-  detailsPage: '/pages/video/details',
-});
-
-const {
-  loader: songsData,
-  goList: goSongsList,
-  goDetail: goSongsDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南语作品库',
-  mainBodyColumnId: 189,
-  modelId: 16,
-  itemType: 'article-common',
-  detailsPage: '/pages/video/details',
-});
-
-const {
-  loader: mingnanSongsData,
-  goList: goMingnanSongsList,
-  goDetail: goMingnanSongsDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南语原创歌曲',
-  mainBodyColumnId: 315,
-  modelId: 16,
-  itemType: 'article-common',
-  detailsPage: '/pages/video/details',
-});
-
-const {
-  loader: memoryData,
-  goList: goMemoryList,
-  goDetail: goMemoryDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '童趣记忆',
-  mainBodyColumnId: 96,
-  modelId: 8,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-const {
-  loader: oldData,
-  goList: goOldList,
-  goDetail: goOldDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '老字号',
-  mainBodyColumnId: 312,
-  modelId: 17,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-const {
-  loader: topicsData,
-  goList: goTopicsList,
-  goDetail: goTopicsDetail,
-} = useHomePageMiniCommonListGoMoreAndGoDetail({
-  title: '闽南文化百科',
-  mainBodyColumnId: 320,
-  modelId: 18,
-  itemType: 'article-common',
-  detailsPage: '/pages/article/details',
-});
-
-onShareTimeline(() => {
-  return {}; 
-})
-onShareAppMessage(() => {
-  return {}; 
-})
-</script>
-
-<style lang="scss">
-.page-inhert {
-  > .content {
-    margin-top: 10vh;
-  }
-  > .title {
-    width: 100rpx;
-  }
-}
-</style>

+ 24 - 19
src/pages/inhert/artifact/details.vue

@@ -4,25 +4,25 @@
     :load="load"
     :extraTabs="[
       {
-        id: 5,
+        id: TAB_ID_VR,
         text: 'VR参观',
         width: 180,
         visible: true,
       },
       {
-        id: 6,
+        id: TAB_ID_PROTECTED_AREA,
         text: '保护范围',
         width: 180,
         visible: true,
       },
       {
-        id: 7,
+        id: TAB_ID_ENVIRONMENT,
         text: '建筑环境',
         width: 180,
         visible: true,
       },
       {
-        id: 8,
+        id: TAB_ID_VALUE,
         text: '价值评估',
         width: 180,
         visible: true,
@@ -30,7 +30,7 @@
     ]"
   >
     <template #extraTabs="{ content, tabCurrentId }">
-      <template v-if="tabCurrentId==5">
+      <template v-if="tabCurrentId==TAB_ID_VR">
         <!-- VR参观 -->
         <view class="d-flex flex-row justify-center p-5">
           <Button @click="handleGoToVr(content.vr as string)">
@@ -39,17 +39,17 @@
           </Button>
         </view>
       </template>
-      <template v-if="tabCurrentId==6">
+      <template v-if="tabCurrentId==TAB_ID_PROTECTED_AREA">
         <!-- 保护范围 -->
-        <Parse :content="content.protectedArea" :tagStyle="commonParserStyle" />
+        <Parse :content="content.protectedArea" />
       </template>
-      <template v-if="tabCurrentId==7">
+      <template v-if="tabCurrentId==TAB_ID_ENVIRONMENT">
         <!-- 建筑环境 -->
-        <Parse :content="content.environment" :tagStyle="commonParserStyle" />
+        <Parse :content="content.environment" />
       </template>
-      <template v-if="tabCurrentId==8">
+      <template v-if="tabCurrentId==TAB_ID_VALUE">
         <!-- 价值评估 -->
-        <Parse :content="content.value" :tagStyle="commonParserStyle" />
+        <Parse :content="content.value" />
       </template>
     </template>
     <template #titleEnd="{ content }">
@@ -92,24 +92,29 @@
   </DetailTabPage>
 </template>
 <script setup lang="ts">
-import type { TabControlItem } from "@/common/composeabe/TabControl";
+import { useTabId, type TabControlItem } from "@/common/composeabe/TabControl";
 import { ref, type Ref } from "vue";
 import { navTo } from "@/components/utils/PageAction";
 import UnmoveableContent from "@/api/inheritor/UnmoveableContent";
-import commonParserStyle from "@/common/style/commonParserStyle";
 import IntroBlock from "@/pages/article/common/IntroBlock.vue";
-import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
+import DetailTabPage, { type DetailTabPageTabsArray } from "@/pages/article/common/DetailTabPage.vue";
 import { onShareTimeline, onShareAppMessage } from "@dcloudio/uni-app";
 import Parse from "@/components/display/parse/Parse.vue";
 import Button from "@/components/basic/Button.vue";
 import Tag from "@/components/display/Tag.vue";
 
-async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+const { nextId } = useTabId({ idStart: 5 });
+const TAB_ID_VR = nextId();
+const TAB_ID_PROTECTED_AREA = nextId();
+const TAB_ID_ENVIRONMENT = nextId();
+const TAB_ID_VALUE = nextId();
+
+async function load(id: number, tabsArray: DetailTabPageTabsArray) {
   const d = await UnmoveableContent.getContentDetail(id);
-  tabsArray.value[4].visible = Boolean(d.vr);
-  tabsArray.value[5].visible = Boolean(d.protectedArea);
-  tabsArray.value[6].visible = Boolean(d.environment);
-  tabsArray.value[7].visible = Boolean(d.value);
+  tabsArray.getTabById(TAB_ID_VR)!.visible = Boolean(d.vr);
+  tabsArray.getTabById(TAB_ID_PROTECTED_AREA)!.visible = Boolean(d.protectedArea);
+  tabsArray.getTabById(TAB_ID_ENVIRONMENT)!.visible = Boolean(d.environment);
+  tabsArray.getTabById(TAB_ID_VALUE)!.visible = Boolean(d.value); 
   return d;
 }
 

+ 13 - 0
src/pages/inhert/artifact/list.vue

@@ -55,6 +55,7 @@ import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
 import AppCofig from '@/common/config/AppCofig';
 import Tabs from '@/components/nav/Tabs.vue';
 import SearchBar from '@/components/form/SearchBar.vue';
+import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
 
 const categoryData = useSimpleDataLoader(async () => 
   [{
@@ -124,7 +125,19 @@ const listLoader = useSimplePageListLoader<{
   }), total: res.total }
 });
 
+useLoadQuerys({
+  region: 0,
+  level: 0,
+}, (querys) => {
+  if (querys.level)
+    selectedLevel.value = querys.level;
+  if (querys.region)
+    selectedRegion.value = querys.region;
+  setTimeout(doSearch, 1000);
+});
+
 watch(selectedLevel, () => {
+    console.log('watchselectedLevel.value', selectedLevel.value);
   listLoader.loadData(undefined, true);
 });
 watch(selectedRegion, () => {

+ 39 - 0
src/pages/inhert/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <FlexCol>
+    <StatusBarSpace backgroundColor="background.page" />
+    <NavBar leftButton="custom" backgroundColor="background.page">
+      <template #left>
+        <Image
+          src="https://mn.wenlvti.net/app_static/minnan/images/inhert/Title.png"
+          mode="widthFix"
+          :width="110"
+          :innerStyle="{ marginLeft: '30rpx', marginTop: '30rpx' }"
+        />
+      </template>
+    </NavBar>
+    <Inhert />
+    <Height :height="150" />
+    <Tabbar :current="2" />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import Tabbar from '@/common/components/tabs/Tabbar.vue';
+import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
+import Inhert from '../introduction/inhert.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
+import NavBar from '@/components/nav/NavBar.vue';
+import Image from '@/components/basic/Image.vue';
+import Height from '@/components/layout/space/Height.vue';
+
+onShareTimeline(() => {
+  return {}; 
+})
+onShareAppMessage(() => {
+  return {}; 
+})
+</script>
+
+<style lang="scss">
+</style>

+ 18 - 14
src/pages/inhert/inheritor/details.vue

@@ -4,19 +4,19 @@
     :load="load"
     :extraTabs="[
       {
-        id: 5,
+        id: TAB_ID_PRIZE,
         text: '荣誉奖项',
         width: 180,
         visible: true,
       },
       {
-        id: 6,
+        id: TAB_ID_ASSOCIATION_ME,
         text: '非遗项目',
         width: 180,
         visible: true,
       },
       {
-        id: 7,
+        id: TAB_ID_ICH_SITES,
         text: '传习所',
         visible: true,
       }
@@ -29,11 +29,11 @@
       </view>
     </template>
     <template #extraTabs="{ content, tabCurrentId }">
-      <template v-if="tabCurrentId==5">
+      <template v-if="tabCurrentId==TAB_ID_PRIZE">
         <!-- 荣誉奖项 -->
-        <Parse :content="content.prize" :tagStyle="commonParserStyle" />
+        <Parse :content="content.prize" />
       </template>
-      <template v-else-if="tabCurrentId==6">
+      <template v-else-if="tabCurrentId==TAB_ID_ASSOCIATION_ME">
         <!-- 非遗项目 -->
         <CommonListPage 
           :showSearch="false"
@@ -46,7 +46,7 @@
           }"
         />
       </template>
-      <template v-else-if="tabCurrentId==6">
+      <template v-else-if="tabCurrentId==TAB_ID_ICH_SITES">
         <!-- 传习所 -->
         <CommonListPage 
           :showSearch="false"
@@ -127,25 +127,29 @@
   </DetailTabPage>
 </template>
 <script setup lang="ts">
-import type { TabControlItem } from "@/common/composeabe/TabControl";
+import { useTabId, type TabControlItem } from "@/common/composeabe/TabControl";
 import { ref, type Ref } from "vue";
 import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
 import RoundTags from "@/pages/parts/RoundTags.vue";
-import commonParserStyle from "@/common/style/commonParserStyle";
 import SeminarContent from "@/api/inheritor/SeminarContent";
 import InheritorContent from "@/api/inheritor/InheritorContent";
 import ProjectsContent from "@/api/inheritor/ProjectsContent";
 import IntroBlock from "@/pages/article/common/IntroBlock.vue";
-import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
+import DetailTabPage, { type DetailTabPageTabsArray } from "@/pages/article/common/DetailTabPage.vue";
 import CommonListPage from "@/pages/article/common/CommonListPage.vue";
 import Parse from "@/components/display/parse/Parse.vue";
 import Image from "@/components/basic/Image.vue";
 
-async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+const { nextId } = useTabId({ idStart: 5 });
+const TAB_ID_PRIZE = nextId();
+const TAB_ID_ASSOCIATION_ME = nextId();
+const TAB_ID_ICH_SITES = nextId();
+
+async function load(id: number, tabsArray: DetailTabPageTabsArray) {
   const d = await InheritorContent.getContentDetail(id);
-  tabsArray.value[4].visible = Boolean(d.prize);
-  tabsArray.value[5].visible = Boolean(d.associationMeList && d.associationMeList.length > 0);
-  tabsArray.value[6].visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
+  tabsArray.getTabById(TAB_ID_PRIZE)!.visible = Boolean(d.prize);
+  tabsArray.getTabById(TAB_ID_ASSOCIATION_ME)!.visible = Boolean(d.associationMeList && d.associationMeList.length > 0);
+  tabsArray.getTabById(TAB_ID_ICH_SITES)!.visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
   d.titleBox = Boolean(d.deathBirth);
   return d;
 }

+ 0 - 1
src/pages/inhert/inheritor/list.vue

@@ -61,7 +61,6 @@ onLoad(async (querys) => {
       id: item.id,
       name: item.title,
     }))),
-    activeTab: [0],
     defaultSelectedValue: querys?.level ?? 0,
   });
   console.log('level', querys?.level);

+ 35 - 27
src/pages/inhert/intangible/DetailsCommon.vue

@@ -4,35 +4,35 @@
     :load="load"
     :extraTabs="[
       {
-        id: 5,
+        id: TAB_ID_ICH_SITES,
         text: '传习所',
         visible: true,
       },
       {
-        id: 6,
+        id: TAB_ID_INHERITORS,
         text: '传承人',
         visible: true,
       },
       {
-        id: 7,
+        id: TAB_ID_PEDIGREE,
         text: '传承谱系',
         width: 180,
         visible: true,
       },
       {
-        id: 8,
+        id: TAB_ID_WORKS,
         text: '非遗作品',
         width: 180,
         visible: true,
       },
       {
-        id: 9,
+        id: TAB_ID_ASSOCIATION_ME,
         text: '相关资讯',
         width: 180,
         visible: true,
       },
       {
-        id: 10,
+        id: TAB_ID_LOCATION,
         text: '地理位置',
         width: 180,
         visible: true,
@@ -40,7 +40,7 @@
     ]"
   >
     <template #extraTabs="{ content, tabCurrentId }">
-      <template v-if="tabCurrentId==5">
+      <template v-if="tabCurrentId==TAB_ID_ICH_SITES">
         <!-- 非遗传习中心 -->
         <CommonListPage 
           :showSearch="false"
@@ -53,7 +53,7 @@
           }"
         />
       </template>
-      <template v-else-if="tabCurrentId==6">
+      <template v-else-if="tabCurrentId==TAB_ID_INHERITORS">
         <!-- 非遗传承人 -->
         <CommonListPage 
           :showSearch="false"
@@ -66,12 +66,13 @@
           }"
         />
       </template>
-      <template v-else-if="tabCurrentId==7">
+      <template v-else-if="tabCurrentId==TAB_ID_PEDIGREE">
+        <!-- 传承谱系 -->
         <view class="d-flex flex-col mt-3 mb-2">
-          <Parse :content="content.pedigree" :tagStyle="commonParserStyle" />
+          <Parse :content="content.pedigree" />
         </view>
       </template>
-      <template v-else-if="tabCurrentId==8">
+      <template v-else-if="tabCurrentId==TAB_ID_WORKS">
         <!-- 非遗作品 -->
         <CommonListPage 
           :showSearch="false"
@@ -84,7 +85,7 @@
           }"
         />
       </template>
-      <template v-else-if="tabCurrentId==9">
+      <template v-else-if="tabCurrentId==TAB_ID_ASSOCIATION_ME">
         <!-- 相关资讯 -->
         <CommonListPage 
           :showSearch="false"
@@ -97,7 +98,7 @@
           }"
         />
       </template>
-      <template v-else-if="tabCurrentId==10">
+      <template v-else-if="tabCurrentId==TAB_ID_LOCATION">
         <!-- 地理位置 -->
         <view class="d-flex flex-col mt-3 mb-2">
          <HomeTitle title="地理位置" />
@@ -213,22 +214,21 @@
   </DetailTabPage>
 </template>
 <script setup lang="ts">
-import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
-import ProjectsContent from "@/api/inheritor/ProjectsContent";
-import CommonListPage from "@/pages/article/common/CommonListPage.vue";
-import IntroBlock from "@/pages/article/common/IntroBlock.vue";
+import { ref } from "vue";
 import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
-import type { TabControlItem } from "@/common/composeabe/TabControl";
-import { h, ref, type Ref } from "vue";
+import { useTabId } from "@/common/composeabe/TabControl";
 import { navTo } from "@/components/utils/PageAction";
 import { StringUtils } from "@imengyu/imengyu-utils";
+import DetailTabPage, { type DetailTabPageTabsArray } from "@/pages/article/common/DetailTabPage.vue";
+import ProjectsContent from "@/api/inheritor/ProjectsContent";
+import CommonListPage from "@/pages/article/common/CommonListPage.vue";
+import IntroBlock from "@/pages/article/common/IntroBlock.vue";
 import InheritorContent from "@/api/inheritor/InheritorContent";
 import ProductsContent from "@/api/inheritor/ProductsContent";
 import SeminarContent from "@/api/inheritor/SeminarContent";
 import ImagesUrls from "@/common/config/ImagesUrls";
 import Tag from "@/components/display/Tag.vue";
 import Parse from "@/components/display/parse/Parse.vue";
-import commonParserStyle from "@/common/style/commonParserStyle";
 
 defineProps({	
   commonRefName : {
@@ -241,18 +241,26 @@ defineProps({
   },
 })
 
-async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+const { nextId } = useTabId({ idStart: 4 });
+const TAB_ID_ICH_SITES = nextId();
+const TAB_ID_INHERITORS = nextId();
+const TAB_ID_PEDIGREE = nextId();
+const TAB_ID_WORKS = nextId();
+const TAB_ID_ASSOCIATION_ME = nextId();
+const TAB_ID_LOCATION = nextId();
+
+async function load(id: number, tabsArray: DetailTabPageTabsArray) {
   const d = await ProjectsContent.getContentDetail(
     id, 
     undefined, 
     querys.value.modelId > 0 ? querys.value.modelId : undefined
   );
-  tabsArray.value[4].visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
-  tabsArray.value[5].visible = Boolean(d.inheritorsList && (d.inheritorsList as any[]).length > 0);
-  tabsArray.value[6].visible = Boolean(d.pedigree);
-  tabsArray.value[7].visible = Boolean(d.worksList && (d.worksList as any[]).length > 0);
-  tabsArray.value[8].visible = Boolean(d.associationMeList && (d.associationMeList as any[]).length > 0);
-  tabsArray.value[9].visible = Boolean(d.longitude && d.latitude);
+  tabsArray.getTabById(TAB_ID_ICH_SITES)!.visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
+  tabsArray.getTabById(TAB_ID_INHERITORS)!.visible = Boolean(d.inheritorsList && (d.inheritorsList as any[]).length > 0);
+  tabsArray.getTabById(TAB_ID_PEDIGREE)!.visible = Boolean(d.pedigree);
+  tabsArray.getTabById(TAB_ID_WORKS)!.visible = false && Boolean(d.worksList && (d.worksList as any[]).length > 0);
+  tabsArray.getTabById(TAB_ID_ASSOCIATION_ME)!.visible = Boolean(d.associationMeList && (d.associationMeList as any[]).length > 0);
+  tabsArray.getTabById(TAB_ID_LOCATION)!.visible = Boolean(d.longitude && d.latitude);
   return d;
 }
 async function loadSubList(page: number, pageSize: number, content: any, subList: string) {

+ 30 - 12
src/pages/inhert/intangible/list.vue

@@ -49,23 +49,41 @@ async function loadData(
   tabSelect: number,
 ) {
   let api;
+  let res;
   switch (tabSelect) {
-    case 0: api = ProjectsContent; break;
+    case 0: 
+      res = (await ProjectsContent.getContentList(new GetContentListParams().setSelfValues({
+        ichType: dropDownValues[0] == 0 ? undefined: dropDownValues[0],
+        level: dropDownValues[1] == 0 ? undefined: dropDownValues[1],
+        region: dropDownValues[2] == 0 ? undefined: dropDownValues[2],
+        pid: 0,
+        keywords: searchText,
+      }), page, pageSize));
+      break;
     case 2: return {
       list: [],
       total: 0,
     };
     default:
-    case 1: api = ProductsContent; break;
-    case 3: api = SeminarContent; break;
-    case 4: api = UnitContent; break;
+    case 1: 
+      res = (await ProductsContent.getContentList(new GetContentListParams().setSelfValues({
+        level: dropDownValues[1] == 0 ? undefined: dropDownValues[1],
+        region: dropDownValues[2] == 0 ? undefined: dropDownValues[2],
+        keywords: searchText,
+      }), page, pageSize));
+    break;
+    case 3:
+      res = (await SeminarContent.getContentList(new GetContentListParams().setSelfValues({
+        region: dropDownValues[2] == 0 ? undefined: dropDownValues[2],
+        keywords: searchText,
+      }), page, pageSize));
+     break;
+    case 4: 
+      res = (await UnitContent.getContentList(new GetContentListParams().setSelfValues({
+        keywords: searchText,
+      }), page, pageSize));
+     break;
   }
-  const res = (await api.getContentList(new GetContentListParams().setSelfValues({
-    ichType: tabSelect !== 0 || dropDownValues[0] == 0 ? undefined: dropDownValues[0],
-    level: (tabSelect !== 0 && tabSelect !== 2) || dropDownValues[1] == 0 ? undefined: dropDownValues[1],
-    region: tabSelect !== 0 || dropDownValues[2] == 0 ? undefined: dropDownValues[2],
-    keywords: searchText,
-  }), page, pageSize));
 
   res.list.forEach((item) => {
     item.bottomTags = [
@@ -102,7 +120,7 @@ onLoad(async (querys) => {
       id: item.id,
       name: item.title,
     }))),
-    activeTab: [0,3],
+    activeTab: [0,2],
     defaultSelectedValue: querys?.level ?? 0,
   });
   dropdownNames.value.push({ 
@@ -113,7 +131,7 @@ onLoad(async (querys) => {
       id: item.id,
       name: item.title,
     }))),
-    activeTab: [0],
+    activeTab: [0,3],
     defaultSelectedValue: 0,
   });
   await waitTimeOut(800);

+ 0 - 2
src/pages/inhert/language/list.vue

@@ -95,8 +95,6 @@ const player = ref();
 const playMode = ref<'loop'|'random'|'list'>(uni.getStorageSync('LanguagePlayMode') || 'list');
 const playAuto = ref(Boolean(uni.getStorageSync('LanguageAutoPlay')));
 
-console.log(playAuto);
-
 const searchValue = ref('');
 const tab = ref(-1)
 const listLoader = useSimplePageListLoader(8, async (page, pageSize) => {

+ 77 - 0
src/pages/inhert/seminar/list.vue

@@ -0,0 +1,77 @@
+<template>
+  <CommonListPage 
+    title="非遗传习所"
+    itemType="article-character"
+    detailsPage="/pages/inhert/seminar/details"
+    showTotal
+    :dropDownNames="dropdownNames"
+    :load="loadData" 
+  />
+</template>
+
+<script setup lang="ts">
+import CommonContent, { GetContentListParams } from '@/api/CommonContent';
+import SeminarContent from '@/api/inheritor/SeminarContent';
+import CommonListPage, { type DropDownNames } from '@/pages/article/common/CommonListPage.vue';
+import { onLoad } from '@dcloudio/uni-app';
+import { onMounted, ref } from 'vue';
+
+const dropdownNames = ref<DropDownNames[]>([]);
+
+async function loadData(
+  page: number, 
+  pageSize: number,
+  searchText: string,
+  dropDownValues: number[]
+) {
+  const res = (await SeminarContent.getContentList(new GetContentListParams().setSelfValues({
+    ichType: dropDownValues[0] == 0 ? undefined: dropDownValues[0],
+    level: dropDownValues[1] == 0 ? undefined: dropDownValues[1],
+    region: dropDownValues[2] == 0 ? undefined: dropDownValues[2],
+    keywords: searchText,
+  }), page, pageSize));
+  res.list.forEach((p) => {
+    p.titleBox = Boolean(p.deathBirth);
+    p.desc = p.ichName as string;
+    p.bottomTags = [
+      p.levelText, 
+      p.nation,
+    ];
+  })
+  return res;
+}
+
+onLoad(async (querys) => {
+  dropdownNames.value.push({ 
+    options: [{
+      id: 0, 
+      name: '全部类别'
+    }].concat((await CommonContent.getCategoryList(4)).map((item) => ({
+      id: item.id,
+      name: item.title,
+    }))),
+    defaultSelectedValue: 0,
+  });
+  const levels = await CommonContent.getCategoryList(2);
+  dropdownNames.value.push({ 
+    options: [{
+      id: 0, 
+      name: '全部级别'
+    }].concat(levels.map((item) => ({
+      id: item.id,
+      name: item.title,
+    }))),
+    defaultSelectedValue: querys?.level ?? 0,
+  });
+  dropdownNames.value.push({  
+    options: [{
+      id: 0, 
+      name: '全部区域'
+    }].concat((await CommonContent.getCategoryList(1)).map((item) => ({
+      id: item.id,
+      name: item.title,
+    }))),
+    defaultSelectedValue: querys?.region ?? 0,
+  });
+})
+</script>

+ 8 - 2
src/pages/inhert/unit/list.vue

@@ -1,7 +1,7 @@
 <template>
   <CommonListPage 
     title="保护单位"
-    itemType="article-common"
+    itemType="simple-text"
     detailsPage="disabled"
     showTotal
     :dropDownNames="dropdownNames"
@@ -31,7 +31,13 @@ async function loadData(
     .setKeywords(searchText)
   , page, pageSize));
   res.list.forEach((p) => {
-    p.desc = p.ichName as string;
+    p.desc = '';
+    p.bottomTags = [
+      p.levelText, 
+      p.ichTypeText, 
+      p.batchText,
+      p.regionText,
+    ] as string[];
   })
   return res;
 }

+ 0 - 0
src/pages/inhert/village/details.vue


Некоторые файлы не были показаны из-за большого количества измененных файлов