Sfoglia il codice sorgente

🚧 升级nuxtjs版本

快乐的梦鱼 2 settimane fa
parent
commit
0888103a99
51 ha cambiato i file con 7970 aggiunte e 10801 eliminazioni
  1. 8 20
      App.vue
  2. 0 1
      README.md
  3. 247 0
      api/BaseAppServerRequestModule.ts
  4. 17 11
      api/CommonContent.ts
  5. 6 214
      api/RequestModules.ts
  6. 3 3
      api/village/VillageApi.ts
  7. 12 2
      common/config/ApiCofig.ts
  8. 0 29
      components/dynamicf/ActionRender.ts
  9. 0 35
      components/dynamicf/ActionRender.vue
  10. 0 50
      components/dynamicf/CascaderFormItem.ts
  11. 0 165
      components/dynamicf/CascaderFormItem.vue
  12. 0 56
      components/dynamicf/CheckBoxToInt.vue
  13. 0 8
      components/dynamicf/CheckBoxValue.ts
  14. 0 51
      components/dynamicf/CheckBoxValue.vue
  15. 0 30
      components/dynamicf/Display/ShowDateOrNull.vue
  16. 0 86
      components/dynamicf/Display/ShowImageList.vue
  17. 0 76
      components/dynamicf/Display/ShowImageOrNull.vue
  18. 0 69
      components/dynamicf/Display/ShowInList.vue
  19. 0 35
      components/dynamicf/Display/ShowMomentOrNull.vue
  20. 0 48
      components/dynamicf/Display/ShowTagList.vue
  21. 0 72
      components/dynamicf/Display/ShowValueOrNull.vue
  22. 0 37
      components/dynamicf/Display/StateRenderer.vue
  23. 0 88
      components/dynamicf/Dropdown/IdAsValueDropdown.ts
  24. 0 253
      components/dynamicf/Dropdown/IdAsValueDropdown.vue
  25. 0 336
      components/dynamicf/Dropdown/IdAsValueTreeDropdown.vue
  26. 0 127
      components/dynamicf/IdAsValueTree.ts
  27. 0 179
      components/dynamicf/IdAsValueTree.vue
  28. 0 69
      components/dynamicf/NumberRange.vue
  29. 0 116
      components/dynamicf/PasswordStrengthMeter.vue
  30. 0 37
      components/dynamicf/PasswordWithStrengthInput.vue
  31. 0 29
      components/dynamicf/RadioValue.ts
  32. 0 77
      components/dynamicf/RadioValue.vue
  33. 0 25
      components/dynamicf/SelectValue.ts
  34. 0 74
      components/dynamicf/SelectValue.vue
  35. 0 99
      components/dynamicf/SimpleEditDynamicStringList.vue
  36. 0 128
      components/dynamicf/SimpleListDynamicForm.vue
  37. 0 42
      components/dynamicf/SimpleSelectFormItem.ts
  38. 0 69
      components/dynamicf/SimpleSelectFormItem.vue
  39. 0 140
      components/dynamicf/UploadImageFormItem.ts
  40. 0 169
      components/dynamicf/UploadImageFormItem.vue
  41. 0 6
      components/dynamicf/WhiteSpace.ts
  42. 0 16
      components/dynamicf/WhiteSpace.vue
  43. 0 28
      components/dynamicf/WrapperRangePicker.vue
  44. 0 24
      components/dynamicf/WrapperTimeRangePicker.vue
  45. 0 91
      components/dynamicf/index.ts
  46. 0 98
      components/error/ErrorReporter.vue
  47. 0 28
      components/error/ErrorReporterIs.ts
  48. 1 1
      composeable/SimplePagerDataLoader.ts
  49. 5 1
      nuxt.config.ts
  50. 7656 7338
      package-lock.json
  51. 15 15
      package.json

+ 8 - 20
App.vue

@@ -1,31 +1,20 @@
 <template>
-  <a-config-provider
-    :locale="zhCN"
-    :theme="{
-      token: {
-        colorPrimary: '#bd4b36',
-      },
-    }"
-  >
-    <NuxtLoadingIndicator />
-    <NavBar v-if="showNavAndFooter" />
-    <main>
-      <NuxtPage />
-    </main>
-    <Footer v-if="showNavAndFooter" />
-  </a-config-provider>
+  <NuxtLoadingIndicator />
+  <NavBar v-if="showNavAndFooter" />
+  <main>
+    <NuxtPage />
+  </main>
+  <Footer v-if="showNavAndFooter" />
 </template>
 
 <script setup lang="ts">
 import { onMounted, watch } from 'vue';
-import { RouterView, useRoute } from 'vue-router'
+import { useRoute } from 'vue-router'
 import NavBar from './components/NavBar.vue';
 import Footer from './components/Footer.vue';
 import { useAuthStore } from './stores/auth';
-import zhCN from 'ant-design-vue/es/locale/zh_CN';
-import VueAMap, {initAMapApiLoader} from '@vuemap/vue-amap';
+import {initAMapApiLoader} from '@vuemap/vue-amap';
 import { registryConvert } from './common/ConvertRgeistry'
-import { registerAllFormComponents } from './components/dynamicf';
 
 const showNavAndFooter = computed(() => {
   return route.path !== '/exportToAiIch';
@@ -36,7 +25,6 @@ if (import.meta.client) {
     key: '212b86dc49a5116a34e945d31da7ad14',
     securityJsCode: '46cae8205a707cfaf5801abcc4301566',
   });
-  registerAllFormComponents();
 } 
 registryConvert();
 

+ 0 - 1
README.md

@@ -25,7 +25,6 @@
 | vue | ^3.5.13 | 核心前端框架 |
 | nuxt | ^3.17.6 | 服务端渲染框架 |
 | pinia | ^3.0.1 | 状态管理库 |
-| ant-design-vue | ^4.2.6 | UI 组件库 |
 | axios | ^1.9.0 | HTTP 客户端 |
 | bootstrap | ^5.3.0 | 响应式布局框架 |
 | @vuemap/vue-amap | ^2.1.12 | 高德地图组件 |

+ 247 - 0
api/BaseAppServerRequestModule.ts

@@ -0,0 +1,247 @@
+import ApiCofig from "@/common/config/ApiCofig";
+import AppCofig, { isDev } from "@/common/config/AppCofig";
+import { useAuthStore } from "@/stores/auth";
+import { RequestCoreInstance, RequestApiError, StringUtils, appendGetUrlParams, appendPostParams, RequestResponse, RequestOptions, type RequestApiInfoStruct, RequestApiResult, type RequestApiErrorType, defaultResponseDataGetErrorInfo, defaultResponseDataHandlerCatch, WebFetchImplementer, RequestApiConfig } from "@imengyu/imengyu-utils";
+import type { DataModel, KeyValue, NewDataModel } from "@imengyu/js-request-transform";
+import { Modal } from "ant-design-vue";
+
+/**
+ * 说明:业务相关的请求模块
+ * * 请求数据处理函数。
+ * * 自定义请求模块。
+ * * 自定义错误报告处理函数。
+ */
+
+function matchNotReportMessage(code: number, str: string) {
+  if (ApiCofig.notReportErrorCode.includes(code))
+    return true;
+  for (let i = 0; i < ApiCofig.notReportMessages.length; i++) {
+    if (ApiCofig.notReportMessages[i]?.test(str))
+      return true;
+  }
+  return false;
+}
+//错误报告处理
+export function reportError<T extends DataModel>(instance: RequestCoreInstance<T>, response: RequestApiError | Error) {
+  if (isDev) {
+    //开发模式下直接弹窗显示
+    if (response instanceof RequestApiError) {
+      console.error({
+        message: `请求错误 ${response.apiName} : ${response.errorMessage}`,
+        detail: response.toString() +
+          '\r\n请求接口:' + response.apiName +
+          '\r\n请求地址:' + response.apiUrl +
+          '\r\n请求参数:' + JSON.stringify(response.apiRawReq) +
+          '\r\n返回参数:' + JSON.stringify(response.rawData) +
+          '\r\n状态码:' + response.code +
+          '\r\n信息:' + response.errorCodeMessage,
+        type: 'error',
+      });
+    } else {
+      console.error({
+        message: '错误报告 代码错误',
+        detail: response?.stack || ('' + response),
+        type: 'error',
+      });
+    }
+  } else {    
+    let errMsg = '';
+    if (response instanceof RequestApiError)
+      errMsg = response.errorMessage + '。';
+      
+    errMsg += '服务出现了异常,请稍后重试或联系客服。';
+    errMsg += '版本:' + AppCofig.version;
+
+    Modal.error({
+      title: '抱歉',
+      content: errMsg,
+    });
+  }
+}
+
+export class BaseAppServerRequestModule<T extends DataModel> extends RequestCoreInstance<T> {
+  constructor(baseUrl: string) {
+    super(WebFetchImplementer);
+    this.config.baseUrl = baseUrl;
+    this.config.errCodes = [];
+    //请求拦截器
+    this.config.requestInterceptor = (url, req) => {
+      //获取store中的token,追加到头;
+      const autoStore = useAuthStore();
+      if (StringUtils.isNullOrEmpty((req.headers as KeyValue).token as string)) {
+        if (!StringUtils.isNullOrEmpty(autoStore.token)) {
+          req.headers['token'] = autoStore.token;
+          req.headers['__token__'] = autoStore.token;
+        }
+      }
+      if (req.method == 'GET') {
+        //追加GET参数
+        url = appendGetUrlParams(url, 'main_body_id', ApiCofig.mainBodyId);
+        //url = appendGetUrlParams(url, 'platform', ApiCofig.platformId);
+      } else {
+        if (!(req.data instanceof FormData)) {
+          req.data = appendPostParams(req.data,'main_body_id', ApiCofig.mainBodyId);
+          //req.data = appendPostParams(req.data,'platform', ApiCofig.platformId);
+        } else {
+          req.data.append('main_body_id', ApiCofig.mainBodyId.toString());
+          //req.data = appendPostParams(req.data,'platform', ApiCofig.platformId);
+        }
+      } 
+  return { newUrl: url, newReq: req };
+    };
+    //响应数据处理函数
+    this.config.responseDataHandler = async function responseDataHandler<T extends DataModel>(response: RequestResponse, req: RequestOptions, resultModelClass: NewDataModel | undefined, instance: RequestCoreInstance<T>, apiInfo: RequestApiInfoStruct): Promise<RequestApiResult<T>> {
+      const method = req.method || 'GET';
+      try {
+        const json = await response.json();
+        if (response.ok) {
+          if (!json) {
+            throw new RequestApiError(
+              'businessError',
+              '后端未返回数据',
+              '',
+              response.status,
+              null,
+              null,
+              response.headers,
+              apiInfo
+            );
+          }
+
+          //code == 0 错误
+          if (json.code === 0) {
+            throw createError(json);
+          }
+
+          //处理后端的数据
+          let message = '未知错误';
+          let data = {} as any;
+
+          //后端返回格式不统一,所以在这里处理格式
+          if (typeof json.data === 'object') {
+            data = json.data;
+            message = json.data?.msg || response.statusText;
+          }
+          else {
+            //否则返回上层对象
+            data = json;
+            message = json.msg || response.statusText;
+          }
+
+          return new RequestApiResult(
+            resultModelClass ?? instance.config.modelClassCreator,
+            json?.code || response.status,
+            message,
+            data,
+            json,
+            response.headers,
+            apiInfo
+          );
+        }
+        else {
+          throw createError(json);
+        }
+
+        function createError(json: any): RequestApiError {
+          let errType : RequestApiErrorType = 'unknow';
+          let errString = '';
+          let errCodeStr = '';
+
+          if (typeof json.message === 'string') 
+            errString = json.message;
+          if (typeof json.msg === 'string') 
+            errString += json.msg;
+
+          if (StringUtils.isStringAllEnglish(errString))
+            errString = '服务器返回:' + errString;
+
+          //错误处理
+          if (errString) {
+            //如果后端有返回错误信息,则收集错误信息并返回
+            errType = 'businessError';
+            if (typeof json.data === 'object' && json.data?.errmsg) {
+              errString += '\n' + json.data.errmsg;
+            }
+            if (typeof json.errors === 'object') {
+              for (const key in json.errors) {
+                if (Object.prototype.hasOwnProperty.call(json.errors, key)) {
+                  errString += '\n' + json.errors[key];
+                }
+              }
+            }
+          } else {
+            const res = defaultResponseDataGetErrorInfo(response, json);
+            errType = res.errType;
+            errString = res.errString;
+            errCodeStr = res.errCodeStr;
+          }
+
+          const res = new RequestApiError(
+            errType,
+            errString,
+            errCodeStr,
+            response.status,
+            null,
+            null,
+            response.headers,
+            apiInfo
+          );
+          console.log(res);
+          return res;
+        }
+      } catch (err) {
+        if (err instanceof RequestApiError) {
+          throw err;
+        }
+        //错误统一处理
+        return new Promise<RequestApiResult<T>>((resolve, reject) => {
+          defaultResponseDataHandlerCatch(method, req, response, null, err as any, apiInfo, response.url, reject, instance);
+        });
+      }
+    };    /**
+    * 此函数用于请求失败时,如何处理错误信息
+    * 
+    * NOTE: 这里配置Api请求时要如何处理错误
+    * @param err 错误信息实例 
+    * @param response 返回数据
+    * @returns 返回一个 Promise,可以定义最终调用该接口的代码会接收到什么样的错误对象。
+    */
+   this.config.responseErrorHandler = (err, instance, apiInfo) => {
+     function createErr(errorType: RequestApiErrorType, message: string) {
+       return new RequestApiError(errorType, message, undefined, undefined, undefined, err, undefined, apiInfo);
+     }
+     if (err instanceof Error) {
+       if (err.message == 'Network Error')
+         return createErr('networkError', '请求失败,请检查您的网络连接?');
+       if (err.message.includes('Timeout') 
+         || err.message.includes('timeout') 
+         || err.message.includes('singal'))
+         return createErr('networkError', '请求超时');
+       if (err.message.includes('cancelled') 
+         || err.message.includes('aborted'))
+         return createErr('networkError', '请求已取消');
+       if (err.message.includes('AbortError'))
+         return createErr('networkError', '请求失败,请检查您的网络连接?');
+       return createErr('scriptError', err.message);
+     } else if (err instanceof RequestApiError) {
+       return err;
+     }
+     return createErr('unknow', '请求失败,未知错误:' + err);
+    };
+    //错误报告处理
+    this.config.responseErrorReportInterceptor = (instance, response) => {
+      return (
+        (response.errorType !== 'businessError' && response.errorType !== 'networkError') ||
+        matchNotReportMessage(response.code, response.errorMessage) === true
+      );
+    };
+    //错误报告处理函数
+    this.config.reportError = reportError;
+  }
+}
+
+RequestApiConfig.setConfig({
+  ...RequestApiConfig.getConfig(),
+  EnableApiDataLog: false,
+  EnableApiRequestLog: import.meta.env.DEV,
+})

+ 17 - 11
api/CommonContent.ts

@@ -1,4 +1,4 @@
-import { DataModel, transformArrayDataModel, type NewDataModel } from '@imengyu/js-request-transform';
+import { DataModel, transformArrayDataModel, type KeyValue, type NewDataModel } from '@imengyu/js-request-transform';
 import { AppServerRequestModule } from './RequestModules';
 import type { QueryParams } from "@imengyu/imengyu-utils";
 import { transformSomeToArray } from './Utils';
@@ -232,7 +232,7 @@ export class GetContentDetailItem extends DataModel<GetContentDetailItem> {
       if (this.image === 'https://mncdn.wenlvti.net')
         this.image = '';
       if (!this.image && this.images && this.images && this.images.length > 0  ) {
-        this.image = this.images[0]
+        this.image = this.images[0] || ''
       }
       if ((!this.images || this.images.length == 0) && this.image && this.image != '') {
         this.images = [ this.image ]
@@ -328,12 +328,12 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
     type?: number,
     withself?: boolean,
   ) {
-    return (this.get('/content/category/getCategoryList', '获取分类列表', {
+    return (this.get<KeyValue[]>('/content/category/getCategoryList', '获取分类列表', {
       type,
       is_tree: false,
       withself,
     }))
-      .then(res => transformArrayDataModel<CategoryListItem>(CategoryListItem, res.data2, `获取分类列表`, true))
+      .then(res => transformArrayDataModel<CategoryListItem>(CategoryListItem, res.data || [], `获取分类列表`, true))
       .catch(e => { throw e });
   }
   /**
@@ -347,7 +347,7 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
     }))
       .then(res => transformArrayDataModel<CategoryListItem>(
         CategoryListItem, 
-        transformSomeToArray(res.data2), 
+        transformSomeToArray(res.data), 
         `获取分类列表`, 
         true
       ))
@@ -367,14 +367,17 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
    * @returns 
    */
   getColumList<T extends DataModel = GetColumContentList>(params: GetColumListParams, modelClassCreator: NewDataModel = GetColumContentList, querys?: QueryParams) {
-    return this.get('/content/content/getMainBodyColumnContentList', `${this.debugName} 主体栏目列表`, {
+    return this.get<{
+      list: KeyValue[],
+      total: number,
+    }>('/content/content/getMainBodyColumnContentList', `${this.debugName} 主体栏目列表`, {
       model_id: this.modelId,
       ...params.toServerSide(),
       ...querys,
     })
       .then(res => ({
-        list: transformArrayDataModel<T>(modelClassCreator, res.data2.list, `${this.debugName} 主体栏目列表`, true),
-        total: res.data2.total as number,
+        list: transformArrayDataModel<T>(modelClassCreator, res.data?.list || [], `${this.debugName} 主体栏目列表`, true),
+        total: res.data?.total as number,
       }))
       .catch(e => { throw e });
   }
@@ -387,7 +390,10 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
    * @returns 
    */
   getContentList<T extends DataModel = GetContentListItem>(params: GetContentListParams, page: number, pageSize: number = 10, modelClassCreator: NewDataModel = GetContentListItem, querys?: QueryParams) {
-    return this.get('/content/content/getContentList', `${this.debugName} 模型内容列表`, {
+    return this.get<{
+      list: KeyValue[],
+      total: number,
+    }>('/content/content/getContentList', `${this.debugName} 模型内容列表`, {
       ...params.toServerSide(),
       ...querys,
       model_id: params.modelId || this.modelId,
@@ -396,8 +402,8 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
       pageSize,
     })
       .then(res => ({ 
-        list: transformArrayDataModel<T>(modelClassCreator, res.data2.list, `${this.debugName} 模型内容列表`, true),
-        total: res.data2.total as number,
+        list: transformArrayDataModel<T>(modelClassCreator, res.data?.list || [], `${this.debugName} 模型内容列表`, true),
+        total: res.data?.total as number,
       }))
       .catch(e => { throw e });
   }

+ 6 - 214
api/RequestModules.ts

@@ -1,221 +1,13 @@
-
-/**
- * 这里写的是业务相关的:
- * * 请求数据处理函数。
- * * 自定义请求模块。
- * * 自定义错误报告处理函数。
- */
-
-import AppCofig, { isDev } from "@/common/config/AppCofig";
+import type { DataModel } from "@imengyu/js-request-transform";
+import { BaseAppServerRequestModule } from "./BaseAppServerRequestModule";
+import { isDev } from "@/common/config/AppCofig";
 import ApiCofig from "@/common/config/ApiCofig";
-import { 
-  RequestApiError, RequestApiResult, type RequestApiErrorType, 
-  RequestCoreInstance, RequestOptions, 
-  defaultResponseDataGetErrorInfo, defaultResponseDataHandlerCatch, 
-  RequestResponse,
-  WebFetchImplementer,
-  StringUtils,
-  appendGetUrlParams, 
-  appendPostParams,
-} from "@imengyu/imengyu-utils";
-import type { DataModel, KeyValue, NewDataModel } from "@imengyu/js-request-transform";
-//import { useAuthStore } from "@/stores/auth";
-import { Modal } from "ant-design-vue";
-
-/**
- * 不报告错误的 code
- */
-const notReportErrorCode = [401] as number[];
-const notReportMessages = [
-  /请授权绑定手机号/g,
-] as RegExp[];
-function matchNotReportMessage(str: string) {
-  for (let i = 0; i < notReportMessages.length; i++) {
-    if (notReportMessages[i].test(str))
-      return true;
-  }
-  return false;
-}
-
-//请求拦截器
-function requestInceptor(url: string, req: RequestOptions) {
-  //获取store中的token,追加到头;
-  /*const autoStore = useAuthStore();
-  if (StringUtils.isNullOrEmpty((req.header as KeyValue).token as string)) {
-    req.header['token'] = autoStore.token;
-    req.header['__token__'] = autoStore.token;
-  }*/
-  if (req.method == 'GET') {
-    //追加GET参数
-    url = appendGetUrlParams(url, 'main_body_id', ApiCofig.mainBodyId);
-  } else {
-    req.data = appendPostParams(req.data,'main_body_id', ApiCofig.mainBodyId);
-  }
-  return { newUrl: url, newReq: req };
-}
-//响应数据处理函数
-function responseDataHandler<T extends DataModel>(response: RequestResponse, req: RequestOptions, resultModelClass: NewDataModel|undefined, instance: RequestCoreInstance<T>, apiName: string | undefined): Promise<RequestApiResult<T>> {
-  return new Promise<RequestApiResult<T>>((resolve, reject) => {
-    const method = req.method || 'GET';
-    response.json().then((json) => {
-      if (response.ok) {
-        if (!json) {
-          reject(new RequestApiError(
-            'businessError',
-            '后端未返回数据',
-            '',
-            response.status,
-            null,
-            null,
-            req,
-            apiName,
-            response.url
-          ));
-          return;
-        }
-
-        //code == 0 错误
-        if (json.code === 0) {
-          handleError();
-          return;
-        }
-
-        //处理后端的数据
-        let message = '未知错误';
-        let data = {} as any;
-
-        //后端返回格式不统一,所以在这里处理格式
-        if (typeof json.data === 'object') {
-          data = json.data;
-          message = json.data?.msg || response.statusText;
-        }
-        else {
-          //否则返回上层对象
-          data = json;
-          message = json.msg || response.statusText;
-        }
-
-        resolve(new RequestApiResult(
-          resultModelClass ?? instance.config.modelClassCreator,
-          json?.code || response.status,
-          message,
-          data,
-          json
-        ));
-      }
-      else {
-        handleError();
-      }
-
-      function handleError() {
-        let errType : RequestApiErrorType = 'unknow';
-        let errString = '';
-        let errCodeStr = '';
-
-        if (typeof json.message === 'string') 
-          errString = json.message;
-        if (typeof json.msg === 'string') 
-          errString += json.msg;
-
-        if (StringUtils.isStringAllEnglish(errString))
-          errString = '服务器返回:' + errString;
-
-        //错误处理
-        if (errString) {
-          //如果后端有返回错误信息,则收集错误信息并返回
-          errType = 'businessError';
-          if (typeof json.data === 'object' && json.data?.errmsg) {
-            errString += '\n' + json.data.errmsg;
-          }
-          if (typeof json.errors === 'object') {
-            for (const key in json.errors) {
-              if (Object.prototype.hasOwnProperty.call(json.errors, key)) {
-                errString += '\n' + json.errors[key];
-              }
-            }
-          }
-        } else {
-          const res = defaultResponseDataGetErrorInfo(response, json);
-          errType = res.errType;
-          errString = res.errString;
-          errCodeStr = res.errCodeStr;
-        }
-
-        reject(new RequestApiError(
-          errType,
-          errString,
-          errCodeStr,
-          response.status,
-          null,
-          null,
-          req,
-          apiName,
-          response.url
-        ));
-      }
-    }).catch((err) => {
-      //错误统一处理
-      defaultResponseDataHandlerCatch(method, req, response, null, err, apiName, response.url, reject, instance);
-    });
-  });
-}
-//错误报告处理
-function responseErrReoprtInceptor<T extends DataModel>(instance: RequestCoreInstance<T>, response: RequestApiError) {
-  return (
-    (response.errorType !== 'businessError' && response.errorType !== 'networkError') ||
-    notReportErrorCode.indexOf(response.code) >= 0 ||
-    matchNotReportMessage(response.errorMessage) === true
-  );
-}
-
-//错误报告处理
-export function reportError<T extends DataModel>(instance: RequestCoreInstance<T>, response: RequestApiError | Error) {
-  if (isDev) {
-    if (response instanceof RequestApiError) {
-      (globalThis as any).$error({
-        message: `请求错误 ${response.apiName} : ${response.errorMessage}`,
-        detail: response.toString() +
-          '\r\n请求接口:' + response.apiName +
-          '\r\n请求地址:' + response.apiUrl +
-          '\r\n请求参数:' + JSON.stringify(response.rawRequest) +
-          '\r\n返回参数:' + JSON.stringify(response.rawData) +
-          '\r\n状态码:' + response.code +
-          '\r\n信息:' + response.errorCodeMessage,
-        type: 'error',
-      });
-    } else {
-      (globalThis as any).$error({
-        message: '错误报告 代码错误',
-        detail: response?.stack || ('' + response),
-        type: 'error',
-      });
-    }
-  } else {    
-    let errMsg = '';
-    if (response instanceof RequestApiError)
-      errMsg = response.errorMessage + '。';
-      
-    errMsg += '服务出现了异常,请稍后重试或联系客服。';
-    errMsg += '版本:' + AppCofig.version;
-
-    Modal.error({
-      title: '抱歉',
-      content: errMsg,
-    });
-}
-}
 
 /**
- * App服务请求模块
+ * 主应用服务请求模块
  */
-export class AppServerRequestModule<T extends DataModel> extends RequestCoreInstance<T> {
+export class AppServerRequestModule<T extends DataModel> extends BaseAppServerRequestModule<T> {
   constructor() {
-    super(WebFetchImplementer);
-    this.config.baseUrl = ApiCofig.serverProd;
-    this.config.errCodes = []; //
-    this.config.requestInceptor = requestInceptor;
-    this.config.responseDataHandler = responseDataHandler;
-    this.config.responseErrReoprtInceptor = responseErrReoprtInceptor;
-    this.config.reportError = reportError;
+    super(isDev ? ApiCofig.serverDev : ApiCofig.serverProd);
   }
 }

+ 3 - 3
api/village/VillageApi.ts

@@ -23,7 +23,7 @@ export class VillageListItem extends DataModel<VillageListItem> {
     };
     this._afterSolveServer = () => {
       if (this.images && this.images && this.images.length > 0  ) {
-        this.image = this.images[0]
+        this.image = this.images[0] || ''
       }
     }
   }
@@ -81,7 +81,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
     return (this.get('/village/village/getList', '村落列表', {
       history_level: level,
     })) 
-      .then(res => transformArrayDataModel<VillageListItem>(VillageListItem, res.data2, `村落`, true))
+      .then(res => transformArrayDataModel<VillageListItem>(VillageListItem, res.data || [], `村落`, true))
       .catch(e => { throw e });
   }
   async getVillageMenuList(id: number) {
@@ -89,7 +89,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
       platform: 1,
       village_id: id,
     })) 
-      .then(res => transformArrayDataModel<VillageMenuListItem>(VillageMenuListItem, res.data2, `村落菜单`, true))
+      .then(res => transformArrayDataModel<VillageMenuListItem>(VillageMenuListItem, res.data || [], `村落菜单`, true))
       .catch(e => { throw e });
   }
 

+ 12 - 2
common/config/ApiCofig.ts

@@ -3,7 +3,17 @@
  * 说明:后端接口配置
  */
 export default {
-  serverDev: 'https://mn.wenlvti.net/api',
-  serverProd: 'https://mn.wenlvti.net/api',
+  serverDev: 'https://mnwh.wenlvti.net/api',
+  serverProd: 'https://mnwh.wenlvti.net/api',
   mainBodyId: 1,
+  /**
+   * 不报告错误的 code
+   */
+  notReportErrorCode: [401],
+  /**
+   * 不报告错误的 message
+   */
+  notReportMessages: [
+    /请授权绑定手机号/g,
+  ] as RegExp[],
 }

+ 0 - 29
components/dynamicf/ActionRender.ts

@@ -1,29 +0,0 @@
-export interface ActionRenderProps {
-  /**
-   * 操作条目
-   */
-  actions: Array<ActionRenderItem>;
-}
-
-export interface ActionRenderItem {
-  /**
-   * 按钮文字
-   */
-  text: string,
-  /**
-   * 按钮键值
-   */
-  key?: string,
-  /**
-   * 这个按钮是否换行,默认否
-   */
-  wrap?: boolean,
-  /**
-   * 按钮类型
-   */
-  type?: 'primary'|'danger'|'success'|'warning'|'secondary',
-  /**
-   * 按钮点击回调
-   */
-  onClick?: (key: string|undefined, record: Record<string, unknown>) => void;
-}

+ 0 - 35
components/dynamicf/ActionRender.vue

@@ -1,35 +0,0 @@
-<template>
-  <span>
-    <a-button
-      v-for="(act, k) in actions" 
-      :key="k" 
-      :class="`mr-3 text-${act.type}` + (act.wrap ? ' display-block' : '')"
-      @click="actionClick(act)"
-    >{{act.text}}</a-button>
-  </span>
-</template>
-
-<script lang="ts">
-import type { DataModel } from '@imengyu/js-request-transform';
-import type { ActionRenderItem } from './ActionRender';
-import { defineComponent, type PropType } from "vue";
-
-export default defineComponent({
-  props: {
-    rawModel: {
-      type: Object as PropType<Record<string, unknown>>,
-    },
-    actions: {
-      type: Object as PropType<Array<ActionRenderItem>>,
-    },
-  },
-  methods: {
-    actionClick(action: ActionRenderItem) {
-      if (typeof action.onClick === 'function')
-        action.onClick(action.key, this.rawModel as DataModel);
-      else
-        console.warn('action ' + action.key + ' onClick is not a function!');
-    },
-  },
-});
-</script>

+ 0 - 50
components/dynamicf/CascaderFormItem.ts

@@ -1,50 +0,0 @@
-import type { CascaderProps } from "ant-design-vue";
-
-
-export type CascaderFormItemOptionType = CascaderProps['options'];
-
-export type LoadDataFun = (parentValue: string|number|null, level: number, parentObject: unknown) => Promise<CascaderFormItemOptionType>;
-export type OnChooseFun = (values: (string|number|null)[], objects: unknown[]) => void;
-
-/**
- * CascaderFormItem 的公共接口
- */
-export interface CascaderFormItemInterface {
-  /**
-   * 加载树形数据至当前选中层级
-   */
-  doLoadDataToCurrentValue: () => void;
-}
-/**
- * CascaderFormItem 的公共接口
- */
-export interface CascaderFormItemProps {
-  /**
-   * 初始化时加载数据
-   */
-  loadAtStart:  boolean,
-  /**
-   * 初始化时是否递归加载数据到当前选中的数据
-   */
-  loadCascaderToCurrentValueAtStart:  boolean,
-  /**
-   * 加载数据
-   */
-  loadData: LoadDataFun;
-  /**
-   * placeholder
-   */
-  placeholder?: string;
-  /**
-   * 选择后回调查找出的对象键,默认是id
-   */
-  onSelectFindIdKey?: string;
-  /**
-   * 选择后回调
-   */
-  onSelect?: OnChooseFun;
-  /**
-   * a-cascader 其他自定义参数
-   */
-  customProps?: CascaderProps;
-}

+ 0 - 165
components/dynamicf/CascaderFormItem.vue

@@ -1,165 +0,0 @@
-<template>
-  <a-cascader
-    :value="value"
-    :options="options"
-    :load-data="doLoadData"
-    :placeholder="placeholder"
-    @update:value="onUpdateValue"
-    change-on-select
-    v-bind="(customProps as any)"
-  />
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, type PropType, ref, toRefs, watch } from "vue";
-import type { CascaderProps } from "ant-design-vue";
-import type { LoadDataFun, OnChooseFun } from "./CascaderFormItem";
-
-export default defineComponent({
-  props: {
-    loadData: {
-      type: Function as PropType<LoadDataFun>,
-      default: null,
-    },
-    placeholder: {
-      type: String,
-      default: '请选择地址',
-    },
-    loadAtStart:  {
-      type: Boolean,
-      default: true,
-    },
-    loadCascaderToCurrentValueAtStart: {
-      type: Boolean,
-      default: true,
-    },
-    value: {},
-    /**
-     * 选择后回调
-     */
-    onSelect: {
-      type: Function as PropType<OnChooseFun>,
-      default: null,
-    },
-    onSelectFindIdKey: {
-      type: String,
-      default: 'id',
-    },
-    /**
-     * a-cascader 其他自定义参数
-     */
-    customProps: {
-      //type: Object as PropType<CascaderProps>,
-      default: null,
-    },
-  },
-  emits: [
-    'update:value'
-  ],
-  setup(props, context) {
-
-    const { loadAtStart, loadCascaderToCurrentValueAtStart, value, loadData, onSelect } = toRefs(props);
-    const options = ref<CascaderProps['options']>([]);
-
-    const doLoadData = ((selectedOptions) => {
-      const parent = selectedOptions && selectedOptions.length > 0 ? selectedOptions[selectedOptions.length - 1] : null;
-      loadData.value(parent?.value as number, selectedOptions ? selectedOptions.length : 0, parent)
-        .then((d) => {
-          if (!d)
-            throw new Error("loadData return invalid data!");
-          if (parent) {
-            //添加数据至指定层级下方
-            if (!parent.children)
-              parent.children = d;
-            else
-              parent.children = parent.children.concat(d);
-          } else {
-            //添加数据
-            options.value = d;
-            //这个时候加载一下默认选择项目
-            if (loadCascaderToCurrentValueAtStart.value)
-              doLoadDataToCurrentValue();
-          }
-        }).catch((e) => {
-          console.error(e);
-        });
-    }) as CascaderProps['loadData'];
-
-    function onUpdateValue(v: number[]) {
-      context.emit('update:value', v);
-
-      //选中后回调
-      if (typeof onSelect.value === 'function') {
-        const objArr = [] as unknown[];
-        const valueArrNow = v.concat();
-        //通过ID查找指定的对象
-        let optionsCurrent = options.value as CascaderProps['options'];
-        for (let i = 0; i < v.length; i++) {
-          if (!optionsCurrent)
-            break;
-          const item = optionsCurrent.find(k => k.value === valueArrNow[i]);
-          if (item) {
-            objArr.push(item);
-            optionsCurrent = item.children;//下一级
-          } else {
-            break;
-          }
-        }
-        //回调
-        onSelect.value(v, objArr);
-      }
-    }
-
-    //加载树形数据至当前选中层级
-    function doLoadDataToCurrentValue() {
-      const valueArrNow = (value.value as number[]).concat();
-
-      function findChildren(index: number, optionsCurrent: CascaderProps['options']) {
-        if (!optionsCurrent)
-          return;
-        //当前级数据,查找是否存在,
-        const option = optionsCurrent.find(k => k.value === valueArrNow[index]);
-        if (option && (!option.children || option.children.length === 0)) {
-          //存在,尝试加载下一级数据
-          loadData.value(valueArrNow[index], index, option)
-            .then((d) => {
-              if (!d)
-                throw new Error("loadData return invalid data!");
-              if (parent) {
-                if (!option.children)
-                  option.children = d;
-                else
-                  option.children = option.children.concat(d);
-                
-                //如果还没有达到输入选择的层级,则进行下一次加载
-                if (index < valueArrNow.length - 1) {
-                  findChildren(index + 1, option.children);
-                }
-              }
-            }).catch((e) => {
-              console.error(e);
-            });
-        }
-      }
-
-      findChildren(0, options.value);
-    }
-
-    onMounted(() => {
-      if (loadAtStart.value && typeof doLoadData === 'function')
-        doLoadData([]);
-    });
-
-    watch(value, () => {
-      doLoadDataToCurrentValue();
-    });
-
-    return {
-      doLoadData,
-      doLoadDataToCurrentValue,
-      onUpdateValue,
-      options,
-    };
-  },
-});
-</script>

+ 0 - 56
components/dynamicf/CheckBoxToInt.vue

@@ -1,56 +0,0 @@
-<template>
-  <a-checkbox 
-    :checked="checked"
-    @update:checked="(v: boolean) => $emit('update:value', v)"
-    :disabled="disabled"
-  >
-    <slot>{{text}}</slot>
-  </a-checkbox>
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-
-export default defineComponent({
-  name: "CheckBoxToInt",
-  data() {
-    return {
-      checked: false
-    }
-  },
-  emits: [ 'update:value' ],
-  props: {
-    checkedValue: {
-      type: Number,
-      default: 1
-    },
-    uncheckedValue: {
-      type: Number,
-      default: 0
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    text: {
-      type: String,
-      default: '启用',
-    },
-    value: {
-      default: null,
-    }
-  },
-  mounted: function() {
-    this.loadChecked();
-  },
-  watch: {
-    value() { this.loadChecked(); },
-    checked() { this.$emit('update:value', this.checked ? this.checkedValue : this.uncheckedValue) }
-  },
-  methods: {
-    loadChecked() {
-      this.checked = this.value == this.checkedValue;
-    }
-  }
-});
-</script>

+ 0 - 8
components/dynamicf/CheckBoxValue.ts

@@ -1,8 +0,0 @@
-import type { CheckboxProps } from "ant-design-vue";
-
-export interface CheckBoxValueProps {
-  checkboxProps?: CheckboxProps,
-  text: string,
-  checkedValue?: unknown,
-  uncheckedValue?: unknown,
-}

+ 0 - 51
components/dynamicf/CheckBoxValue.vue

@@ -1,51 +0,0 @@
-<template>
-  <a-checkbox 
-    v-model:checked="checked" 
-    v-bind="checkboxProps"
-  >{{text}}</a-checkbox>
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from "vue";
-import type { CheckboxProps } from "ant-design-vue";
-
-export default defineComponent({
-  props: {
-    checkboxProps: {
-      type: Object as PropType<CheckboxProps>,
-      default: null,
-    },
-    text: {
-      type: String,
-      default: '',
-    },
-    checkedValue: {
-      default: true,
-    },
-    uncheckedValue: {
-      default: false,
-    },
-    value: {
-    },
-  },
-  emits: [ 'update:value' ],
-  watch: {
-    checked(v) {
-      this.$emit('update:value', v ? this.checkedValue : this.uncheckedValue);
-    },
-    value(v) {
-      const checked = v === this.checkedValue;
-      if (this.checked != checked)
-        this.checked = checked;
-    },
-  },
-  data() {
-    return {
-      checked: false,
-    }
-  },
-  mounted() {
-    this.checked = this.value === this.checkedValue;
-  },
-});
-</script>

+ 0 - 30
components/dynamicf/Display/ShowDateOrNull.vue

@@ -1,30 +0,0 @@
-<template>
-  <span :class="'vc-show-date '+size">
-    <span v-if="value !== undefined && value !== null">
-      {{ typeof value.format === 'function' ? value.format() : '不是日期类型' }}
-    </span>
-    <span v-else class="text-secondary"><i>{{ nullText }}</i></span>
-  </span>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  name: "ShowDateOrNull",
-  props: {
-    nullText: {
-      default: '暂无',
-      type: String
-    },
-    size: {
-      default: '',
-      type: String
-    },
-    value: {
-      type: Object as import('vue').PropType<Date>,
-      default: null,
-    }
-  },
-});
-</script>

+ 0 - 86
components/dynamicf/Display/ShowImageList.vue

@@ -1,86 +0,0 @@
-<template>
-  <span v-if="!images||images.length==0">无图片</span>
-  <div v-else-if="images" class="image-list">
-    <a-image 
-      v-for="(image, k) in (showAll ? images : images.filter((_: unknown, i: number) => i < maxCount))"
-      :key="k"
-      :width="imgSize"
-      :height="imgSize"
-      :src="image"
-      :fallback="failImage"
-    />
-    <div v-if="images.length > maxCount" class="overflow-count" :style="{ 
-      width: `${imgSize}px`, 
-      height: `${imgSize}px`,
-      lineHeight: `${imgSize}px`,
-    }" @click="showAll=!showAll">
-      {{showAll ? '折叠' : `+${images.length - maxCount}` }}
-    </div>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from "vue";
-
-export default defineComponent({
-  name: "ShowImageList",
-  props: {
-    images: {
-      type: Object as PropType<Array<string>>,
-      default: null,
-    },
-    size: {
-      type: [Number,String],
-      default: 30,
-    },
-    maxCount: {
-      type: Number,
-      default: 5,
-    },
-    failImage: {
-      default: () => require('@/assets/images/failed.svg'),
-      type: String
-    },
-  },
-  computed: {
-    imgSize() : number {
-      if (typeof this.size === 'string')
-        switch(this.size) {
-          case 'default': return 45;
-          case 'middle': return 30;
-          case 'small': return 20;
-        }
-      return this.size as number;
-    },  
-  },
-  data() {
-    return {
-      showAll: false,
-    };
-  },
-});
-</script>
-
-<style lang="scss">
-.image-list {
-  position: relative;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  flex-wrap: wrap;
-
-  .overflow-count {
-    color: #fff;
-    background-color: rgba(0,0,0,0.5);
-    text-align: center;
-    cursor: pointer;
-  }
-  .ant-image {
-    background-color: #ececec;
-    overflow: hidden;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-  }
-}
-</style>

+ 0 - 76
components/dynamicf/Display/ShowImageOrNull.vue

@@ -1,76 +0,0 @@
-<template>
-  <div :style="{
-    display: 'inline-block',
-    overflow: 'hidden',
-    width: `${imgSize}px`,
-    height: `${imgSize}px`,
-  }">
-    <a-image
-      :src="imgUrl"
-      :fallback="failImage"
-      :width="imgSize"
-      :height="imgSize"
-    />
-  </div>
-</template>
-
-<script lang="ts">
-import { StringUtils } from '@imengyu/imengyu-utils';
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  name: "ShowImageOrNull",
-  props: {
-    nullImage: {
-      type: String,
-      default: () => require('@/assets/images/none.png'),
-    },
-    failImage: {
-      type: String,
-      default: () => require('@/assets/images/failed.svg'),
-    },
-    size: {
-      type: [Number,String],
-      default: 30,
-    },
-    src: {
-      type: String,
-      default: null,
-    }
-  },
-  data() {
-    return {
-      imgUrl: '',
-    }
-  },
-  computed: {
-    imgSize() : number {
-      if (typeof this.size === 'string')
-        switch(this.size) {
-          case 'default': return 55;
-          case 'middle': return 45;
-          case 'small': return 25;
-        }
-      return this.size as number;
-    },  
-  },
-  mounted() {
-    setTimeout(() => { this.loadImage(); },100);
-  },
-  watch: {
-    src() { this.loadImage(); }
-  },
-  methods: {
-    loadImage() {
-      if(StringUtils.isNullOrEmpty(this.src))
-        this.imgUrl = this.nullImage as string;
-      else
-        this.imgUrl = this.src as string;
-    },
-    onError() {
-      if(this.imgUrl != this.failImage)
-        this.imgUrl = this.failImage as string;
-    }
-  }
-});
-</script>

+ 0 - 69
components/dynamicf/Display/ShowInList.vue

@@ -1,69 +0,0 @@
-<template>
-  <div>{{ result }}</div>
-</template>
-
-<script lang="ts">
-import type { KeyValue } from "@imengyu/js-request-transform";
-import { defineComponent, type PropType } from "vue";
-
-export default defineComponent({
-  name: "ShowInList",
-  data() {
-    return {
-      result: ''
-    }
-  },
-  props: {
-    noMatchText: {
-      type: String,
-      default: '暂无',
-    },
-    useProp: {
-      type: Boolean,
-      default: true,
-    },
-    usePropName: {
-      type: String,
-      default: 'id',
-    },
-    usePropValue: {
-      type: String,
-      default: 'name',
-    },
-    list: {
-      type: Object as PropType<Array<KeyValue>>,
-      default: null,
-    },
-    value: {
-      default: null,
-    }
-  },
-  mounted: function() {
-    this.loadText();
-  },
-  watch: {
-    list() { this.loadText(); },
-    value() { this.loadText(); }
-  },
-  methods: {
-    loadText() {
-      const list = this.list as Array<KeyValue>;
-      if(list && this.value && list.length > 0){
-        for(let i = 0, c = list.length; i < c; i++){
-          if(this.useProp)
-            if(list[i][this.usePropName as string] == this.value) {
-              this.result = list[i][this.usePropValue as string] as string;
-              return;
-            }
-          else
-            if(list[i] == this.value) {
-              this.result = list[i] as unknown as string;
-              return;
-            }
-        }
-        this.result = this.noMatchText as string;
-      }else this.result = this.noMatchText as string;
-    }
-  }
-});
-</script>

+ 0 - 35
components/dynamicf/Display/ShowMomentOrNull.vue

@@ -1,35 +0,0 @@
-<template>
-  <span :class="'vc-show-date '+size">
-    <span v-if="value && value!=null">
-      {{ typeof value.format === 'function' ? value.format(dateFormat) : '不是日期类型' }}
-    </span>
-    <span v-else class="text-secondary"><i>{{ nullText }}</i></span>
-  </span>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import dayjs from 'dayjs';
-
-export default defineComponent({
-  name: "ShowDateOrNull",
-  props: {
-    nullText: {
-      default: '暂无',
-      type: String
-    },
-    dateFormat: {
-      default: 'YYYY-MM-DD HH:mm:ss',
-      type: String
-    },
-    size: {
-      default: '',
-      type: String
-    },
-    value: {
-      type: Object as import('vue').PropType<dayjs.Dayjs>,
-      default: null,
-    }
-  },
-});
-</script>

+ 0 - 48
components/dynamicf/Display/ShowTagList.vue

@@ -1,48 +0,0 @@
-<template>
-  <span v-if="!tags||tags.length==0">暂无</span>
-  <span v-else-if="small">
-    {{ tags[0] }}等{{tags.length}}个
-  </span>
-  <div v-else-if="tagsC" class="d-flex flex-row flex-wrap">
-    <a-tag v-for="(n, k) in tagsC" :key="k">{{n}}</a-tag>
-    <small class="text-primary" v-if="!expand && tags.length > maxCount" @click="expand=true">等{{tags.length}}个</small>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType } from "vue";
-
-export default defineComponent({
-  name: "ShowImageList",
-  data() {
-    return {
-      expand: false,
-    };
-  },
-  computed: {
-    tagsC() {
-      if (!this.expand && this.maxCount < this.tags.length)
-        return this.tags.slice(0, this.maxCount)
-      else
-        return this.tags;
-    },
-    small() {
-      return this.size === 'small';
-    },
-  },
-  props: {
-    size: {
-      type: String,
-      default: '',
-    },
-    maxCount: {
-      type: Number,
-      default: 10,
-    },
-    tags: {
-      type: Object as PropType<Array<string>>,
-      default: null,
-    },
-  },
-});
-</script>

+ 0 - 72
components/dynamicf/Display/ShowValueOrNull.vue

@@ -1,72 +0,0 @@
-<template>
-  <span
-    :class="'vc-show-value'+(block? ' d-block' : '')+(clickable?' link':'')"
-    @click="onClick"
-  >
-    {{ prefix }}
-    <template v-if="(value && value!='') || value === 0">
-      <span v-if="typeof value === 'number'">{{ numericalPrecision > 0 ? (value as number).toFixed(numericalPrecision) : value }}</span>
-      <span v-else-if="typeof value === 'boolean'">{{ value ? '是' : '否' }}</span>
-      <span v-else-if="typeof value === 'object'">{{ JSON.stringify(value) }}</span>
-      <span v-else>{{ value }}</span>
-    </template>
-    <span v-else class="text-secondary"><i>{{ nullText }}</i></span>
-  </span>
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-
-export default defineComponent({
-  name: "ShowValueOrNull",
-  data() {
-    return {
-      result: ''
-    }
-  },
-  emits: [ 'click' ],
-  props: {
-    block: {
-      default: false,
-      type: Boolean
-    },
-    clickable: {
-      default: false,
-      type: Boolean
-    },
-    prefix: {
-      default: '',
-      type: String
-    },
-    numericalPrecision: {
-      default: 0,
-      type: Number
-    },
-    nullText: {
-      default: '暂无',
-      type: String
-    },
-    record: {
-      default: null,
-    },
-    value: {
-      default: null,
-    },
-  },
-  methods: {
-    onClick() {
-      if (this.clickable)
-        this.$emit('click', this.record);
-    },
-  },
-});
-</script>
-
-<style lang="scss">
-.vc-show-value {
-  &.link {
-    color: #008cff;
-    cursor: pointer;
-  }
-}
-</style>

+ 0 - 37
components/dynamicf/Display/StateRenderer.vue

@@ -1,37 +0,0 @@
-<template>
-  <span>
-    <a-badge 
-      v-if="currentState"
-      :status="currentState.badgeState" 
-      :color="currentState.badgeColor" 
-      :text="currentState.text"
-    />
-    <span v-else>未知状态:{{value}}</span>
-  </span>
-</template>
-
-<script lang="ts">
-import type { IDynamicFormItemSelectOption } from "@imengyu/vue-dynamic-form";
-import { defineComponent, type PropType } from "vue";
-
-export interface IStateOption extends IDynamicFormItemSelectOption {
-  badgeState?: 'success' | 'processing' | 'error' | 'default' | 'warning';
-  badgeColor?: string;
-}
-
-export default defineComponent({
-  props: {
-    value: {
-    },
-    stateValues: {
-      type: Object as PropType<Array<IStateOption>>,
-    },
-  },
-  computed: {
-    currentState() {
-      return (this.stateValues as IStateOption[])
-        .find(k => k.value === this.value || k.text === this.value);
-    },
-  },
-});
-</script>

+ 0 - 88
components/dynamicf/Dropdown/IdAsValueDropdown.ts

@@ -1,88 +0,0 @@
-import type { DataModel } from "@imengyu/js-request-transform";
-import type { SelectProps } from "ant-design-vue";
-import type { VNode } from "vue";
-
-/**
- * 通用下拉框返回结构定义
- */
-export interface DropdownValues<T> {
-  label: string,
-  value: number,
-  raw: T;
-}
-
-export type LoadDataFun<T extends DataModel> = (val: string | null) => Promise<DropdownValues<T>[]>;
-
-/**
- * IdAsValueDropdown 的公共接口
- */
-export interface IdAsValueDropdownInterface {
-  /**
-   * 获取某个ID的Lablel
-   * @param value 要获取的ID
-   */
-  getLableByValue(value: number): string;
-  /**
-   * 重新加载数据
-   * @param clearValue 是否需要清除选中数据,默认否
-   */
-  reload(clearValue?: boolean): void;
-}
-/**
- * IdAsValueDropdown 的公共接口
- */
-export interface IdAsValueDropdownProps<T extends DataModel> {
-  /**
-   * 允许清除
-   */
-  allowClear?: boolean,
-  /**
-   * 显示空?
-   */
-  showNull?: boolean,
-  /**
-   * 禁用
-   */
-  disabled?: boolean,
-  /**
-   * 多选?
-   */
-  multiple?: boolean,
-  /**
-   * 允许搜索
-   */
-  showSearch?: boolean,
-  placeholder?: string,
-  /**
-   * 未找到数据时的文案
-   */
-  notFoundContent?: string,
-  /**
-   * 初始化时加载数据
-   */
-  loadAtStart?: boolean,
-  /**
-   * 不使用后端筛选数据而是前端直接筛选
-   */
-  filterDirectly?: boolean,
-  /**
-   * 初始化时的搜索数据
-   */
-  intitialSearchValue?: Record<string, unknown>,
-  /**
-   * 加载数据回调
-   */
-  loadData: LoadDataFun<T>,
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps?: SelectProps,
-  /**
-   * 是否自定义渲染option插槽
-   */
-  renderOption?: RenderOption;
-}
-export type RenderOption = (data: {
-  value: unknown,
-  label: string,
-}) => VNode;

+ 0 - 253
components/dynamicf/Dropdown/IdAsValueDropdown.vue

@@ -1,253 +0,0 @@
-<template>
-  <a-select
-    :value="valueV"
-    :mode="multiple ? 'multiple' : 'combobox'"
-    :allowClear="allowClear"
-    :showSearch="showSearch"
-    :disabled="disabled"
-    :placeholder="placeholder"
-    :default-active-first-option="false"
-    :notFoundContent="notFoundContent"
-    :options="data"
-    :filterOption="showSearch && filterDirectly ? filterOption : false"
-    @update:value="handleChange"
-    @search="handleSearch"
-    v-bind="customProps"
-    style="min-width: 150px"
-  >
-    <template v-if="renderOption" #option="data">
-      <VNodeRenderer :render="renderOption" :data="data" />
-    </template>
-    <a-select-option v-if="showNull" :value="null">(空)</a-select-option>
-  </a-select>
-</template>
-
-<script lang="ts">
-import VNodeRenderer from "@/components/VNodeRenderer.vue";
-import { type SelectProps } from "ant-design-vue";
-import { defineComponent, markRaw, type PropType, type VNode } from "vue";
-import { debounce } from 'lodash-es';
-import type { DropdownValues, LoadDataFun } from "./IdAsValueDropdown";
-import type { DataModel } from "@imengyu/js-request-transform";
-import { StringUtils } from "@imengyu/imengyu-utils";
-
-/**
- * IdAsValueDropdown 的公共接口
- */
-export interface IdAsValueDropdownInterface {
-  /**
-   * 获取某个ID的Lablel
-   * @param value 要获取的ID
-   */
-  getLableByValue(value: number): string;
-  /**
-   * 重新加载数据
-   * @param clearValue 是否需要清除选中数据,默认否
-   */
-  reload(clearValue?: boolean): void;
-}
-/**
- * IdAsValueDropdown 的公共接口
- */
-export interface IdAsValueDropdownProps<T extends DataModel> {
-  /**
-   * 允许清除
-   */
-  allowClear?: boolean,
-  /**
-   * 显示空?
-   */
-  showNull?: boolean,
-  /**
-   * 禁用
-   */
-  disabled?: boolean,
-  /**
-   * 多选?
-   */
-  multiple?: boolean,
-  /**
-   * 允许搜索
-   */
-  showSearch?: boolean,
-  placeholder?: string,
-  /**
-   * 未找到数据时的文案
-   */
-  notFoundContent?: string,
-  /**
-   * 初始化时加载数据
-   */
-  loadAtStart?: boolean,
-  /**
-   * 不使用后端筛选数据而是前端直接筛选
-   */
-  filterDirectly?: boolean,
-  /**
-   * 初始化时的搜索数据
-   */
-  intitialSearchValue?: Record<string, unknown>,
-  /**
-   * 加载数据回调
-   */
-  loadData: LoadDataFun<T>,
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps?: SelectProps,
-  /**
-   * 是否自定义渲染option插槽
-   */
-  renderOption?: RenderOption<T>;
-}
-type RenderOption<T> = (data: {
-  value: unknown,
-  label: string,
-  raw: T
-}) => VNode;
-
-/**
- * 使用数据的ID作为value的下拉框包装
- */
-export default defineComponent({
-  name: "IdAsValueDropdown",
-  data() {
-    return {
-      valueV: null,
-      data: [] as DropdownValues<DataModel>[],
-      lastLoadValue: null,
-      handleSearch: markRaw(debounce((val: string) => {
-        if (!this.filterDirectly)
-          this.doLoadData(val);
-      }, 500)),
-    };
-  },
-  emits: [
-    "update:value",
-    "change",
-    "loaded",
-  ],
-  props: {
-    showNull: {
-      default: false,
-      type: Boolean
-    },
-    renderOption: {
-      default: null,
-      type: Function as PropType<RenderOption<DataModel>>
-    },
-    allowClear: {
-      default: false,
-      type: Boolean
-    },
-    multiple: {
-      default: false,
-      type: Boolean
-    },
-    disabled: {
-      default: false,
-      type: Boolean
-    },
-    showSearch: {
-      default: true,
-      type: Boolean
-    },
-    placeholder: {
-      default: "输入可进行搜索",
-      type: String
-    },
-    notFoundContent: {
-      default: "未找到数据,请换个搜索词再试",
-      type: String
-    },
-    loadAtStart: {
-      default: true,
-      type: Boolean
-    },
-    filterDirectly: {
-      default: true,
-      type: Boolean
-    },
-    value: {
-      default: null,
-    },
-    intitialSearchValue: {
-      default: null,
-      type: String
-    },
-    loadData: {
-      type: Function as PropType<LoadDataFun<DataModel>>,
-      default: null,
-    },
-    /**
-     * a-select 其他自定义参数
-     */
-    customProps: {
-      type: Object as PropType<SelectProps>,
-      default: null,
-    },
-  },
-  methods: {
-    handleChange(value: unknown) {
-      this.$emit("change", value);
-      this.$emit("update:value", value);
-    },
-    doLoadData(val: string | null) {
-      if (typeof this.loadData === "function") {
-        const oldValue = this.valueV;
-        this.valueV = null;
-        (this.loadData as LoadDataFun<DataModel>)(val).then((d) => {
-          this.data = d;
-          setTimeout(() => {
-            this.valueV = oldValue;
-            this.$emit("loaded");
-          }, 30);
-        });
-      }
-    },
-    filterOption(input: string, option: {
-      label: string;
-    }) {
-      return !this.filterDirectly || option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
-    },
-    getLableByValue(value: number) {
-      for (let i = 0; i < this.data.length; i++) {
-        if (this.data[i].value == value) {
-          return this.data[i].label;
-        }
-      }
-      return "";
-    },
-    reload(clearValue = false) {
-      if (clearValue) {
-        this.valueV = null;
-        this.handleChange(null);
-      }
-      this.data = [];
-      this.doLoadData(this.intitialSearchValue as string);
-    },
-  },
-  watch: {
-    loadData() {
-      this.doLoadData(this.intitialSearchValue);
-    },
-    intitialSearchValue(v) {
-      if (!this.filterDirectly && !StringUtils.isNullOrEmpty(v)) {
-        this.doLoadData(v);
-      }
-    },
-    value(v) {
-      this.valueV = v;
-    },
-  },
-  mounted() {
-    this.valueV = this.value;
-    setTimeout(() => {
-      if (this.loadAtStart) {
-        this.doLoadData(this.intitialSearchValue as string);
-      }
-    }, 300);
-  },
-  components: { VNodeRenderer }
-});
-</script>

+ 0 - 336
components/dynamicf/Dropdown/IdAsValueTreeDropdown.vue

@@ -1,336 +0,0 @@
-<template>
-  <div v-if="showDisplayValue" class="display-value" @click="handleDisplayValueClick">
-    <span>{{displayValue}}</span>
-  </div>
-  <a-tree-select
-    v-else
-    ref="selectRef"
-    style="min-width: 150px"
-    :defaultOpen="true"
-    :value="valueV"
-    :dropdown-style="dropdownStyle"
-    :notFoundContent="notFoundContent"
-    :tree-data="treeData"
-    :load-data="handleLoadData"
-    :treeDataSimpleMode="true"
-    :placeholder="placeholder"
-    :allow-clear="allowClear"
-    :multiple="multiple"
-    v-bind="customProps"
-    @blur="handleSelectBlur"
-    @update:value="handleChange"
-  />
-</template>
-
-<script lang="ts">
-import type { SelectProps } from "ant-design-vue";
-import { defineComponent, type PropType } from "vue";
-import type { TreeDataItem } from "../IdAsValueTree";
-
-export type LoadDataFun = (pid: string|number, level: number) => Promise<TreeDataItem[]>;
-export type CheckClickableFun = (item: TreeDataItem) => Promise<boolean>;
-
-export type GetDiaplayValue = (ref: IdAsValueTreeDropdownInterface) => string;
-export type GetRef = (ref: IdAsValueTreeDropdownInterface) => void;
-
-/**
- * IdAsValueTreeDropdown 的公共接口
- */
-export interface IdAsValueTreeDropdownInterface {
-  /**
-   * 获取某个ID的树(正排列)
-   * @param value 要获取的ID
-   */
-  getTree(value: number) : Array<TreeDataItem>;
-  /**
-   * 获取某个ID的Lablel
-   * @param value 要获取的ID
-   */
-  getLableByValue(value: number) : string;
-  /**
-   * 重新加载数据
-   */
-  reload(): void;
-}
-/**
- * IdAsValueTreeDropdown 的公共接口
- */
-export interface IdAsValueTreeDropdownProps {
-  /**
-   * 允许清除
-   */
-  allowClear?: boolean,
-  /**
-   * 多选?
-   */
-  multiple?: boolean,
-  dropdownStyle?: Record<string, unknown>,
-  disabled?: boolean,
-  placeholder?: string,
-  /**
-   * 未找到数据时的文案
-   */
-  notFoundContent?: string,
-  /**
-   * 初始化时加载数据
-   */
-  loadAtStart?: boolean,
-  /**
-   * 加载数据
-   */
-  loadData?: LoadDataFun,
-  /**
-   * 自定义检查条目是否可点击回调
-   */
-  checkClickable?: CheckClickableFun,
-  /**
-   * 获取显示数据回调
-   */
-  getDisplayValue?: GetDiaplayValue,
-  /**
-   * 是否在非激活时显示临时字符串(防止树形数据没有加载,而无法显示当前值)
-   */
-  showDisplayValueBeforeEdit?: boolean,
-  /**
-   * 子数据最大层级
-   */
-  maxLevel?: number,
-  /**
-   * 是否只有最后一级可以点击
-   */
-  onlyLastLevelClickable?: boolean,
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps?: SelectProps,
-}
-
-/**
- * 使用数据的ID作为value的下拉框包装
- */
-export default defineComponent({
-  name: "IdAsValueTreeDropdown",
-  emits: [
-    'update:value',
-    'change',
-    'blur',
-  ],
-  props: {
-    allowClear: {
-      default: true,
-      type: Boolean
-    },
-    multiple: {
-      default: false,
-      type: Boolean
-    },
-    dropdownStyle: {
-      type: Object,
-      default: () => { return { maxHeight: '400px', overflow: 'auto' } }
-    },
-    disabled: {
-      default: false,
-      type: Boolean
-    },
-    placeholder: {
-      default: '请选择,输入可进行搜索',
-      type: String
-    },
-    notFoundContent: {
-      default: '未找到数据,请换个搜索词再试',
-      type: String
-    },
-    loadAtStart: {
-      default: true,
-      type: Boolean
-    },
-    value: {
-      default: null,
-    },
-    loadData: {
-      type: Function as PropType<LoadDataFun>,
-      default: null,
-    },
-    checkClickable: {
-      type: Function as PropType<CheckClickableFun>,
-      default: null,
-    },
-    getDisplayValue: {
-      type: Function as PropType<GetDiaplayValue>,
-      default: null,
-    },
-    defaultDisplayValue: {
-      type: String,
-      default: '',
-    },
-    showDisplayValueBeforeEdit: {
-      default: false,
-      type: Boolean
-    },
-    maxLevel: {
-      default: 0,
-      type: Number,
-    },
-    onlyLastLevelClickable: {
-      default: false,
-      type: Boolean
-    },
-    /**
-     * a-select 其他自定义参数
-     */
-    customProps: {
-      type: Object as PropType<SelectProps>,
-      default: null,
-    },
-  },
-  computed: {
-    displayValue() : string {
-      if (this.valueV != null && this.valueV != 0 && this.defaultDisplayValue != '')
-        return this.defaultDisplayValue;
-      if (this.getDisplayValue)
-        return (this.getDisplayValue as GetDiaplayValue)(this as IdAsValueTreeDropdownInterface); 
-      return '';
-    },
-  },
-  methods: {
-    handleChange(value: unknown) {
-      this.$nextTick(() => {
-        if(value != this.value) {
-          this.$emit('update:value', value); 
-          this.$emit('change', value); 
-        }
-      })
-    },
-    handleLoadData(treeNode: { dataRef: TreeDataItem }) {
-      return new Promise((resolve: (value?: unknown) => void) => {
-        const { id, level } = treeNode.dataRef;
-        this.doLoadData(id, level as number).then(() => resolve()).catch(() => resolve());
-      });
-    },
-    handleDisplayValueClick() {
-      this.showDisplayValue = false;
-      setTimeout(() => {
-        (this.$refs.selectRef as {
-          focus: () => void
-        }).focus();
-      }, 200);
-    },
-    handleSelectBlur() {
-      if(this.showDisplayValueBeforeEdit) {
-        if(this.valueV != null && this.valueV != 0 && this.defaultDisplayValue != '')
-          this.showDisplayValue = true;
-        else if (this.getLableByValue(this.valueV as number) === '') //只有没有在列表中搜索到数据时,才显示临时数据
-          this.showDisplayValue = true;
-      }
-    },
-    doLoadData(pid: string|number|null, level: number) {
-      const loadData = this.loadData;
-      if(typeof loadData === 'function') {
-        return (loadData as LoadDataFun)(pid as string, level).then((d) => {
-          for(let i = this.treeData.length - 1; i >= 0; i--)
-            if(this.treeData[i].pId == pid)
-              this.treeData.splice(i, 1);
-          d.forEach(h => {
-            h.level = level + 1;
-            if(this.maxLevel > 0 && h.level >= this.maxLevel)
-              h.isLeaf = true;
-            if(typeof this.checkClickable === 'function')
-              this.checkClickable(h).then((v: boolean) => h.selectable = v);
-            else if(this.maxLevel > 0) { 
-              if(h.level >= this.maxLevel)
-                h.selectable = false;
-              if(this.onlyLastLevelClickable) 
-                h.selectable = (h.level == this.maxLevel);
-            }
-            this.treeData.push(h)
-          });
-        });
-      } else 
-        return Promise.resolve();
-    },
-
-    /**
-     * 获取某个ID的树(正排列)
-     */
-    getTree(value: number) {
-      const result = new Array<TreeDataItem>();
-      let child : TreeDataItem|null = this.treeData.find((v) => v.id == value) as TreeDataItem;
-      while(child) {
-        result.unshift(child);
-        if(child.pId == 0) child = null;
-        else child = this.treeData.find((v) => v.id == (child as TreeDataItem).pId) as TreeDataItem;
-      }
-      return result;
-    },
-    /**
-     * 获取某个ID的Lablel
-     */
-    getLableByValue(value: number) {
-      const data = this.treeData;
-      for (let i = 0; i < data.length; i++) {
-        if(data[i].value == value) {
-          return data[i].title;
-        }
-      }
-      return '';
-    },
-    /**
-     * 重新加载数据
-     */
-    reload() {
-      this.treeData = [];
-      this.doLoadData(0, 0) 
-    },
-  },
-  watch: {
-    value(v) {
-      this.valueV = v;
-    },
-    showDisplayValueBeforeEdit(v, old) {
-      if(!old && v) {
-        this.showDisplayValue = true;
-      }
-    },
-  },
-  data() {
-    return {
-      showDisplayValue: false,
-      valueV: null as null|number|string,
-      treeData: [] as TreeDataItem[],
-    }
-  },
-  mounted() { 
-    this.valueV = this.value;
-    if(this.showDisplayValueBeforeEdit)
-      this.showDisplayValue = true;
-    setTimeout(() => { 
-      if(this.loadAtStart) {
-        this.treeData = [];
-        this.doLoadData(0, 0) ;
-      }
-    } , 300);
-  }
-});
-</script>
-
-<style lang="scss" scoped>
-.display-value {
-  min-width: 150px;
-  padding: 4px 11px;
-  color: rgba(0, 0, 0, 0.85);
-  font-size: 14px;
-  line-height: 1.5715;
-  background-color: #fff;
-  background-image: none;
-  border: 1px solid #d9d9d9;
-  border-radius: 2px;
-}
-//暗黑主题
-body[data-theme="dark"] {
-  .display-value {
-    color: #dedede;
-    background-color: #1f1f1f;
-    border: 1px solid #434343;
-  }
-}
-</style>

+ 0 - 127
components/dynamicf/IdAsValueTree.ts

@@ -1,127 +0,0 @@
-import type { SelectProps } from "ant-design-vue";
-
-export type LoadDataFun = (pid: string|number, level: number) => Promise<TreeNode[]>;
-export type CheckClickableFun = (item: TreeNode) => Promise<boolean>;
-
-export type GetDiaplayValue = (ref: IdAsValueTreeInterface) => string;
-export type GetRef = (ref: IdAsValueTreeInterface) => void;
-export interface TreeDataItem {
-  id: string | number;
-  pId?: number;
-  value: string | number;
-  title?: string;
-  isLeaf?: boolean;
-  selectable?: boolean;
-  checkable?: boolean;
-  disableCheckbox?: boolean;
-  disabled?: boolean;
-  level?: number;
-}
-
-export interface TreeNode {
-  id?: number;
-  pid?: number;
-  level?: number;
-
-  /**
-   * 当树为 checkable 时,设置独立节点是否展示 Checkbox
-   */
-  checkable?: boolean;
-  /**
-   * 节点的 class
-   */
-  class?: string;
-  /**
-   * 	禁掉 checkbox
-   */
-  disableCheckbox?: boolean;
-  /**
-   * 禁掉响应
-   */
-  disabled?: boolean;
-  /**
-   * 自定义图标。可接收组件,props 为当前节点 props
-   */
-  icon?: unknown;
-  /**
-   * 设置为叶子节点(设置了loadData时有效)
-   */
-  isLeaf?: boolean;
-  /**
-   * 被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内的所有节点的 key 值不能重复!
-   */
-  key: string | number;
-  /**
-   * 设置节点是否可被选中
-   */
-  selectable?: boolean;
-  /**
-   * 节点的 style	
-   */
-  // eslint-disable-next-line @typescript-eslint/ban-types
-  style?: string|object;
-  /**
-   * 标题
-   */
-  title: string;
-
-  children?: TreeNode[],
-}
-
-/**
- * IdAsValueTree 的公共接口
- */
-export interface IdAsValueTreeInterface {
-  /**
-   * 获取某个ID的树(正排列)
-   * @param value 要获取的ID
-   */
-  getTree(value: number) : Array<TreeNode>;
-  /**
-   * 获取某个ID的Lablel
-   * @param value 要获取的ID
-   */
-  getLableByValue(value: number) : string;
-  /**
-   * 重新加载数据
-   */
-  reload(): void;
-}
-/**
- * IdAsValueTree 的公共接口
- */
-export interface IdAsValueTreeProps {
-  /**
-   * 允许清除
-   */
-  allowClear?: boolean,
-  /**
-   * 多选?
-   */
-  multiple?: boolean,
-  disabled?: boolean,
-  /**
-   * 初始化时加载数据
-   */
-  loadAtStart?: boolean,
-  /**
-   * 加载数据
-   */
-  loadData?: LoadDataFun,
-  /**
-   * 自定义检查条目是否可点击回调
-   */
-  checkClickable?: CheckClickableFun,
-  /**
-   * 子数据最大层级
-   */
-  maxLevel?: number,
-  /**
-   * 是否只有最后一级可以点击
-   */
-  onlyLastLevelClickable?: boolean,
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps?: SelectProps,
-}

+ 0 - 179
components/dynamicf/IdAsValueTree.vue

@@ -1,179 +0,0 @@
-<template>
-  <div class="IdAsValueTree">
-    <a-tree
-      ref="selectRef"
-      style="min-width: 150px"
-      :defaultOpen="true"
-      v-model:expandedKeys="expandedKeys"
-      v-model:checkedKeys="checkedKeys"
-      checkable
-      :tree-data="treeData"
-      :load-data="handleLoadData"
-      :allow-clear="allowClear"
-      v-bind="customProps"
-    />
-  </div>
-</template>
-
-<script lang="ts">
-import type { TreeProps } from "ant-design-vue";
-import { defineComponent, type PropType } from "vue";
-import type { CheckClickableFun, LoadDataFun, TreeNode } from "./IdAsValueTree";
-
-/**
- * 使用数据的ID作为value的tree包装
- */
-export default defineComponent({
-  name: "IdAsValueTree",
-  emits: [
-    'update:value',
-    'change',
-    'blur',
-  ],
-  props: {
-    allowClear: {
-      default: true,
-      type: Boolean
-    },
-    disabled: {
-      default: false,
-      type: Boolean
-    },
-    loadAtStart: {
-      default: true,
-      type: Boolean
-    },
-    value: {
-      default: null,
-    },
-    loadData: {
-      type: Function as PropType<LoadDataFun>,
-      default: null,
-    },
-    checkClickable: {
-      type: Function as PropType<CheckClickableFun>,
-      default: null,
-    },
-    maxLevel: {
-      default: 0,
-      type: Number,
-    },
-    onlyLastLevelClickable: {
-      default: false,
-      type: Boolean
-    },
-    /**
-     * a-select 其他自定义参数
-     */
-    customProps: {
-      type: Object as PropType<TreeProps>,
-      default: null,
-    },
-  },
-  methods: {
-    handleChange() {
-      this.$nextTick(() => {
-        this.$emit('update:value', this.checkedKeys); 
-        this.$emit('change', this.checkedKeys); 
-      })
-    },
-    handleLoadData(treeNode: { dataRef: TreeNode }|null) {
-      return new Promise((resolve: (value?: unknown) => void) => {
-        this.doLoadData(treeNode?.dataRef || null).then(() => resolve()).catch(() => resolve());
-      });
-    },
-    doLoadData(dataRef: TreeNode|null) {
-      const { id, level } = dataRef || { id: 0, level: 0 };
-      const pid = id as number;
-      const loadData = this.loadData;
-      if(typeof loadData === 'function') {
-        return (loadData as LoadDataFun)(pid, level as number).then((d) => {
-          if (dataRef && !dataRef.children)
-            dataRef.children = [];
-          d.forEach(h => {
-            h.level = level as number + 1;
-            if(this.maxLevel > 0 && h.level >= this.maxLevel)
-              h.isLeaf = true;
-            if(typeof this.checkClickable === 'function')
-              this.checkClickable(h).then((v: boolean) => h.selectable = v);
-            else if(this.maxLevel > 0) { 
-              if(h.level >= this.maxLevel)
-                h.selectable = false;
-              if(this.onlyLastLevelClickable) 
-                h.selectable = (h.level == this.maxLevel);
-            }
-            if (dataRef)
-              dataRef.children?.push(h);
-            else
-              this.treeData.push(h);
-          });
-        });
-      } else 
-        return Promise.resolve();
-    },
-
-    /**
-     * 获取某个ID的树(正排列)
-     */
-    getTree(value: number) {
-      const result = new Array<TreeNode>();
-      let child : undefined|TreeNode = (this.treeData as TreeNode[]).find((v) => v.id == value);
-      while(child) {
-        result.unshift(child);
-        if(child.pid == 0) child = undefined;
-        else child = (this.treeData as TreeNode[]).find((v) => v.id == (child as TreeNode).pid);
-      }
-      return result;
-    },
-    /**
-     * 获取某个ID的Lablel
-     */
-    getLableByValue(value: number) {
-      const data = this.treeData;
-      for (let i = 0; i < data.length; i++) {
-        if(data[i].id == value) {
-          return data[i].title;
-        }
-      }
-      return '';
-    },
-    /**
-     * 重新加载数据
-     */
-    reload() {
-      this.treeData = [];
-    },
-  },
-  watch: {
-    value(v) {
-      this.checkedKeys = (v as string[]);
-    },
-    checkedKeys() {
-      this.handleChange();
-    },
-  },
-  data() {
-    return {
-      expandedKeys: [] as string[],
-      checkedKeys: [] as string[],
-      treeData: [] as TreeNode[],
-    }
-  },
-  mounted() { 
-    this.checkedKeys = (this.value as unknown as string[]) || [];
-    setTimeout(() => { 
-      if(this.loadAtStart) {
-        this.treeData = [];
-        this.handleLoadData(null);
-      }
-    } , 300);
-  }
-});
-</script>
-
-<style>
-.IdAsValueTree {
-  border: 1px solid #efefef;
-  padding: 10px;
-}
-</style>

+ 0 - 69
components/dynamicf/NumberRange.vue

@@ -1,69 +0,0 @@
-<template>
-  <div class="vc-number-range d-flex flex-row align-items-center">
-    <a-input-number placeholder="最小值" :disabled="disabled" :value="realValue[0]" @update:value="(v: number) => onUpdateValue(v, 0)" v-bind="customProps" />
-    <span class="p-2">-</span>
-    <a-input-number placeholder="最大值" :disabled="disabled" :value="realValue[1]" @update:value="(v: number) => onUpdateValue(v, 1)" v-bind="customProps" />
-  </div>
-</template>
-
-<script lang="ts" setup>
-/**
- * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
- */
-import { Form, type InputNumberProps } from 'ant-design-vue';
-import { type PropType, ref, watch, onMounted } from 'vue';
-
-const props = defineProps({
-  /**
-   * 是否禁用
-   */
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * 选择值
-   */
-  value: {
-  },
-  /**
-   * a-number-input 其他自定义参数
-   */
-  customProps: {
-    type: Object as PropType<InputNumberProps>,
-    default: null,
-  },
-});
-
-const emits = defineEmits([
-  'update:value',
-]);
-
-const realValue = ref<number[]>([]);
-const { onFieldChange } = Form.useInjectFormItemContext();
-
-watch(() => props.value, (v) => {
-  if ((v as number[])?.length == 2)
-    realValue.value = v as number[];
-  else {
-    if (realValue.value.length === 1 && realValue.value[0] === undefined) {
-      realValue.value = [];
-      emits('update:value', []);
-    } else {
-      realValue.value = [];
-    }
-  }
-});
-onMounted(() => {
-  realValue.value = (props.value as number[])?.length == 2 ? props.value as number[] : [];
-});
-
-function onUpdateValue(v : number, index: number) {
-  realValue.value[index] = v;
-  if (realValue.value.length < 2)
-    realValue.value.push(0);
-  emits('update:value', realValue.value);
-  onFieldChange();
-}
-
-</script>

+ 0 - 116
components/dynamicf/PasswordStrengthMeter.vue

@@ -1,116 +0,0 @@
-<template>
-  <div class="password-meter">
-    <div class="bar">
-      <div :class="'level'+level"></div>
-    </div>
-    <span :class="'level'+level">密码强度 {{levelString}}</span>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue'
-import { checkPassWordSecrityLevel } from '@imengyu/imengyu-utils'
-
-/**
- * 密码强度显示组件
- */
-export default defineComponent({
-  props: {
-    password: {
-      type: String,
-      default: '',
-    }
-  },
-  data() {
-    return {
-      level: 0,
-      levelString: '',
-    }
-  },
-  watch: {
-    password(v: string) {
-      this.level = Math.floor((checkPassWordSecrityLevel(v) / 100) * 5);
-      switch(this.level) {
-        case 0: this.levelString = '非常弱'; break;
-        case 1: this.levelString =  '弱'; break;
-        case 2: this.levelString =  '中等'; break;
-        case 3: this.levelString =  '强'; break;
-        case 4: this.levelString =  '非常强'; break;
-      }
-    },
-  }
-})
-</script>
-
-<style lang="scss">
-.password-meter {
-  position: relative;
-  height: 20px;
-  margin: 10px 0;
-
-  .bar {
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    right: 130px;
-    background-color: #e7e7e7;
-    border: 1px solid #a1a1a1;
-
-    div {
-      position: absolute;
-      left: 0;
-      top: 0;
-      bottom: 0;
-
-      &.level0 {
-        width: 0;
-        background-color: #000;
-      }
-      &.level1 {
-        width: 25%;
-        background-color: #ca410a;
-      }
-      &.level2 {
-        width: 50%;
-        background-color: #d8c40c;
-      }
-      &.level3 {
-        width: 75%;
-        background-color: #9ab814;
-      }
-      &.level4 {
-        width: 100%;
-        background-color: #2fbe0b;
-      } 
-    }
-  }
-  > span {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    width: 100px;
-    right: 0;
-    font-size: 12px;
-
-    &.level0 {
-      color: #646464;
-    }
-    &.level1 {
-      color: #a8380c;
-    }
-    &.level2 {
-      color: #af9f0e;
-    }
-    &.level3 {
-      color: #91ac18;
-    }
-    &.level4 {
-      color: #26920b;
-    }
-  }
-
-
-
-}
-</style>

+ 0 - 37
components/dynamicf/PasswordWithStrengthInput.vue

@@ -1,37 +0,0 @@
-<template>
-  <div>
-    <a-input 
-      :value="value"
-      @update:value="(v: string) => $emit('update:value', v)"
-      :disabled="disabled"
-      type="password"
-      v-bind="(item?.additionalProps as {})"
-    />
-    <PasswordStrengthMeter :password="(value as string)" />
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from "vue";
-import PasswordStrengthMeter from "./PasswordStrengthMeter.vue";
-import type { IDynamicFormItem } from "@imengyu/vue-dynamic-form";
-
-export default defineComponent({
-  props: {
-    item: {
-      type: Object as PropType<IDynamicFormItem>,
-    },
-    disabled: {
-      type: Boolean
-    },
-    value: {},
-    additionalProps: {
-      type: Object as PropType<Record<string, unknown>>,
-    },
-  },
-  emits: [
-    "update:value"
-  ],
-  components: { PasswordStrengthMeter }
-});
-</script>

+ 0 - 29
components/dynamicf/RadioValue.ts

@@ -1,29 +0,0 @@
-import type { RadioGroupProps, RadioProps } from "ant-design-vue";
-
-export interface IDynamicFormItemRadioValueOption {
-  text: string,
-  value: unknown,
-}
-
-export interface SimpleRadioValueFormItemProps {
-  /**
-   * 是否禁用
-   */
-  disabled: boolean;
-  /**
-   * 选项数据
-   */
-  options: IDynamicFormItemRadioValueOption[];
-  /**
-   * 选择值
-   */
-  value: unknown;
-  /**
-   * a-radio 其他自定义参数
-   */
-  customProps: RadioProps;
-  /**
-   * a-radio 其他自定义参数
-   */
-  customGroupProps: RadioGroupProps;
-}

+ 0 - 77
components/dynamicf/RadioValue.vue

@@ -1,77 +0,0 @@
-<template>
-  <a-radio-group 
-    :value="selectValue"
-    @update:value="onUpdateValue"
-    :disabled="disabled"
-    v-bind="customGroupProps"
-  >
-    <a-radio 
-      v-for="it in options"
-      :key="it.text"
-      :value="it.text"
-      v-bind="customProps"
-    >
-      {{it.text}}
-    </a-radio>
-  </a-radio-group>
-</template>
-
-<script lang="ts" setup>
-/**
- * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
- */
-import type { RadioGroupProps, RadioProps } from 'ant-design-vue';
-import { type PropType, ref, watch, onMounted } from 'vue';
-import type { IDynamicFormItemRadioValueOption } from './RadioValue';
-
-const props = defineProps({
-  /**
-   * 是否禁用
-   */
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * 选项数据
-   */
-  options: {
-    type: Object as PropType<IDynamicFormItemRadioValueOption[]>,
-    default: null,
-  },
-  value: {
-  },
-  customProps: {
-    type: Object as PropType<RadioProps>,
-    default: null,
-  },
-  customGroupProps: {
-    type: Object as PropType<RadioGroupProps>,
-    default: null,
-  },
-});
-
-const emits = defineEmits([
-  'update:value',
-]);
-
-const selectValue = ref<string|null>('');
-
-function setRadioValue() {
-  selectValue.value = props.options.find(k => (k.value === props.value))?.text || null;
-  if (selectValue.value === null)
-    selectValue.value = props.options.find(k => (typeof k.value === typeof props.value))?.text || null;
-}
-
-watch(() => props.value, () => {
-  setRadioValue();
-});
-onMounted(() => {
-  setRadioValue();
-});
-
-function onUpdateValue(v : unknown) {
-  emits('update:value', props.options.find(k => k.text === v)?.value);
-}
-
-</script>

+ 0 - 25
components/dynamicf/SelectValue.ts

@@ -1,25 +0,0 @@
-import type { SelectProps } from "ant-design-vue";
-
-export interface IDynamicFormItemSelectValueOption {
-  text: string,
-  value: unknown,
-}
-
-export interface SimpleSelectValueFormItemProps {
-  /**
-   * 是否禁用
-   */
-  disabled: boolean;
-  /**
-   * 选项数据
-   */
-  options: IDynamicFormItemSelectValueOption[];
-  /**
-   * 选择值
-   */
-  value: unknown;
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps: SelectProps;
-}

+ 0 - 74
components/dynamicf/SelectValue.vue

@@ -1,74 +0,0 @@
-<template>
-  <a-select
-    :value="selectValue"
-    @update:value="onUpdateValue"
-    :disabled="disabled"
-    v-bind="customProps"
-  >
-    <a-select-option v-for="it in options" :key="it.text" :value="it.text">
-      {{it.text}}
-    </a-select-option>
-  </a-select>
-</template>
-
-<script lang="ts" setup>
-/**
- * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
- */
-import type { SelectProps } from 'ant-design-vue/lib/vc-select';
-import { type PropType, ref, watch, onMounted } from 'vue';
-import type { IDynamicFormItemSelectValueOption } from './SelectValue';
-
-const props = defineProps({
-  /**
-   * 是否禁用
-   */
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * 选项数据
-   */
-  options: {
-    type: Object as PropType<IDynamicFormItemSelectValueOption[]>,
-    default: null,
-  },
-  /**
-   * 选择值
-   */
-  value: {
-  },
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps: {
-    type: Object as PropType<SelectProps>,
-    default: null,
-  },
-});
-
-const emits = defineEmits([
-  'update:value',
-]);
-
-const selectValue = ref<string|null>('');
-
-function setSelectValue() {
-  selectValue.value = props.options.find(k => (k.value === props.value))?.text || null;
-  if (selectValue.value === null)
-    selectValue.value = props.options.find(k => (typeof k.value === typeof props.value))?.text || null;
-}
-
-watch(() => props.value, () => {
-  setSelectValue();
-});
-onMounted(() => {
-  setSelectValue();
-});
-
-function onUpdateValue(v : unknown) {
-  emits('update:value', props.options.find(k => k.text === v)?.value);
-}
-
-</script>

+ 0 - 99
components/dynamicf/SimpleEditDynamicStringList.vue

@@ -1,99 +0,0 @@
-<template>
-  <div>
-    <div>
-      <a-button @click="() => value?.push('')">
-        添加 <template #icon><PlusOutlined /></template>
-      </a-button>
-      <a-popconfirm
-        v-if="value && value.length > 0"
-        title="真的要清空所有条目吗?"
-        @confirm="value?.splice(0, value.length)"
-      >
-        <a-button class="ml-3" danger >
-          <template #icon>
-            <CloseSquareOutlined />
-          </template>
-            清空
-        </a-button>
-      </a-popconfirm>
-    </div>
-    <div class="mt-2 position-relative" v-for="(v, k) in value" :key="k">
-      <input 
-        class="display-inline-block ant-input" 
-        style="width:calc(100% - 100px)" 
-        v-model="value[k]" 
-        :maxlength="maxLength" 
-        :disabled="disabled" 
-        :placeholder="placeholder" />
-      <a-popconfirm
-        title="确定删除此条目吗?"
-        @confirm="value?.slice(value.indexOf(v), 1)"
-      >
-        <a-button class="ml-3">
-          <template #icon><DeleteOutlined /></template>
-        </a-button>
-      </a-popconfirm>
-    </div>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from 'vue'
-import { PlusOutlined, DeleteOutlined, CloseSquareOutlined } from '@ant-design/icons-vue'
-
-/**
- * 简单的字符串列表动态表单
- */
-export default defineComponent({
-  name: 'SimpleEditDynamicStringList',
-  components: {
-    PlusOutlined,
-    DeleteOutlined,
-    CloseSquareOutlined,
-  },  
-  emits: [
-    'update:value',
-    'blur',
-  ],
-  props: {
-    /**
-     * ID
-     */
-    id: {
-      required: false
-    },
-    /**
-     * 参数数组
-     */
-    value: {
-      type: Object as PropType<Array<string>>
-    },
-    /**
-     * 输入框 placeholder
-     */
-    placeholder: {
-      type: String,
-      default: '请输入参数值',
-    },
-    /**
-     * 输入框最大输入长度
-     */
-    maxLength: {
-      type: Number,
-      default: 50,
-    },
-    /**
-     * sf
-     */
-    disabled: {
-      type: Boolean,
-      default: false,
-    },
-  },
-  data() {
-    return {
-      deleteConfirm: false,
-    }
-  },
-})
-</script>

+ 0 - 128
components/dynamicf/SimpleListDynamicForm.vue

@@ -1,128 +0,0 @@
-<template>
-  <a-row :gutter="gutter">
-    <a-col :span="span" v-for="(item, key) in items" :key="key">
-      <div class="d-flex flex-row">
-        <div class="flex-grow-1">
-          <a-form-item v-if="item.type=='text'" :label="item.label" :labelCol="labelCol" :wrapperCol="wrapperCol" :name="item.name||item.key">
-            <a-input 
-              :required="item.required"
-              v-model:value="item.value"
-              :placeholder="item.placeholder||''"
-              :addon-before="item.prefix"
-              :addon-after="item.suffix"
-              :max-length="item.appenderParams?(item.appenderParams.maxLength):undefined"
-            />
-          </a-form-item>
-          <a-form-item v-else-if="item.type=='number'" :label="item.label" :labelCol="labelCol" :wrapperCol="wrapperCol" :name="item.name||item.key">
-            <a-input-number 
-              :required="item.required"
-              v-model:value="item.value"
-              :style="item.suffix&&item.suffix!=''?'':'width: 100%;'"
-              :min="item.appenderParams?(item.appenderParams.min):undefined"
-              :max="item.appenderParams?(item.appenderParams.max):undefined"
-              :step="item.appenderParams?(item.appenderParams.step):undefined"
-            />
-            <span v-if="item.suffix&&item.suffix!=''" class="ml-2">{{item.suffix}}</span>
-          </a-form-item>
-          <a-form-item v-if="item.type=='checkbox'" :label="item.label" :labelCol="labelCol" :wrapperCol="wrapperCol" :name="item.name||item.key">
-            <a-checkbox v-model:checked="item.value">{{item.label}}</a-checkbox>
-          </a-form-item>
-          <a-form-item v-else-if="item.type=='text-array'" :label="item.label" :labelCol="labelCol" :wrapperCol="wrapperCol" :name="item.name||item.key">
-            <SimpleEditDynamicStringList
-              :array="(item.value as string[])"
-              placeholder="请输入参数值"
-            />
-          </a-form-item>
-          <a-form-item v-else-if="item.type=='text-select'" :label="item.label" :labelCol="labelCol" :wrapperCol="wrapperCol" :name="item.name||item.key">
-            <a-select
-              :required="item.required"
-              v-model:value="item.value"
-              style="width: 100%"
-              :options="item.appenderParams?.options"
-            >
-            </a-select>
-          </a-form-item>
-        </div>
-        <a-popconfirm
-          v-if="canDelete"
-          title="确定删除此参数吗?"
-          @confirm="$emit('delete', item, key)"
-        >
-          <a-button class="ml-3">
-            <template #icon><DeleteOutlined /></template>
-          </a-button>
-        </a-popconfirm>
-      </div>
-    </a-col>
-  </a-row>
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from 'vue'
-import {  DeleteOutlined } from '@ant-design/icons-vue'
-import SimpleEditDynamicStringList from './SimpleEditDynamicStringList.vue';
-
-export interface SimpleListDynamicFormItem {
-  label: string;
-  value: unknown;
-  name: string;
-  key: string;
-  required?: boolean,
-  type: 'text'|'number'|'checkbox'|'radio'|'switch'|'text-array'|'text-select';
-  placeholder ?: string;
-  prefix ?: string;
-  suffix ?: string;
-  appenderParams ?: {
-    [index: string]: unknown
-  };
-}
-
-/**
- * 简单的列表动态表单
- */
-export default defineComponent({
-  name: 'SimpleListDynamicForm',
-  components: {
-    SimpleEditDynamicStringList,
-    DeleteOutlined,
-  },
-  props: {
-    /**
-     * 表单条目
-     */
-    items: {
-      required: true,
-      type: Object as PropType<SimpleListDynamicFormItem[]>
-    },
-    /**
-     * 是否可以删除表单条目
-     */
-    canDelete: {
-      default: true,
-      type: Boolean
-    },
-    span: {
-      default: 7,
-      type: Number
-    },
-    gutter: {
-      default: 30,
-    },
-    labelCol: {
-      default: () => { return { span: 10 } },
-      type: Object
-    },
-    wrapperCol: {
-      default: () => { return { span: 14 } },
-      type: Object
-    },
-    /**
-     * 是否显示删除按钮
-     */
-    showDelete: {
-      default: false,
-      type: Boolean
-    },
-  },
-})
-</script>

+ 0 - 42
components/dynamicf/SimpleSelectFormItem.ts

@@ -1,42 +0,0 @@
-import type { SelectProps } from "ant-design-vue";
-import type { VNode } from 'vue';
-
-export interface IDynamicFormItemSelectOption {
-  text: string,
-  value: string|number,
-  badgeState?: 'success'|'error'|'default'|'processing'|'warning',
-  badgeColor?: string,
-  raw?: unknown;
-}
-
-export interface IDynamicFormItemAdditionalOptions {
-  options: IDynamicFormItemSelectOption[],
-}
-
-export interface SimpleSelectFormItemProps {
-  /**
-   * 是否禁用
-   */
-  disabled: boolean;
-  /**
-   * 选项数据
-   */
-  options: IDynamicFormItemSelectOption[];
-  /**
-   * 选择值
-   */
-  value: unknown;
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps: SelectProps;
-  /**
-   * 是否自定义渲染option插槽
-   */
-  renderOption?: RenderOption;
-}
-
-export type RenderOption = (data: {
-  value: unknown,
-  label: string,
-}) => VNode;

+ 0 - 69
components/dynamicf/SimpleSelectFormItem.vue

@@ -1,69 +0,0 @@
-<template>
-  <a-select
-    :value="value"
-    @update:value="onUpdateValue"
-    :disabled="disabled"
-    v-bind="customProps"
-  >
-    <template v-if="renderOption" #option="data">
-      <VNodeRenderer :render="renderOption" :data="data" />
-    </template>
-    <a-select-option v-for="it in options" :key="it.value" :value="it.value">
-      <a-badge v-if="it.badgeColor" :color="it.badgeColor" :text="it.text" />
-      <a-badge v-else-if="it.badgeState" :status="it.badgeState" :text="it.text" />
-      <span v-else>{{it.text}}</span>
-    </a-select-option>
-  </a-select>
-</template>
-
-<script lang="ts" setup>
-/**
- * 简单下拉框表单控件
- */
-import VNodeRenderer from '@/components/VNodeRenderer.vue';
-import type { SelectProps } from 'ant-design-vue/lib/vc-select';
-import { type PropType } from 'vue';
-import type { IDynamicFormItemSelectOption, RenderOption } from './SimpleSelectFormItem';
-
-defineProps({
-  /**
-   * 是否禁用
-   */
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * 选项数据
-   */
-  options: {
-    type: Object as PropType<IDynamicFormItemSelectOption[]>,
-    default: null,
-  },
-  /**
-   * 选择值
-   */
-  value: {
-  },
-  /**
-   * a-select 其他自定义参数
-   */
-  customProps: {
-    type: Object as PropType<SelectProps>,
-    default: null,
-  },
-  renderOption: {
-    default: null,
-    type: Function as PropType<RenderOption>
-  },
-});
-
-const emits = defineEmits([
-  'update:value',
-]);
-
-function onUpdateValue(v : unknown) {
-  emits('update:value', v);
-}
-
-</script>

+ 0 - 140
components/dynamicf/UploadImageFormItem.ts

@@ -1,140 +0,0 @@
-import { StringUtils } from "@imengyu/imengyu-utils";
-import { message, type UploadProps } from "ant-design-vue";
-
-export interface UploadImageFormItemProps {
-  /**
-   * 是否禁用
-   */
-  disabled?: boolean;
-  /**
-   * 上传工厂类
-   */
-  uploadCo: UploadCoInterface;
-  /**
-   * 上传之前的自定义检查回调
-   * 如果返回false,将停止上传
-   */
-  beforeUpload?: (file: FileItem) => boolean;
-  /**
-   * 是否限制单图上传
-   */
-  single?: boolean;
-  /**
-   * single 为false时,限制最多上传图片的数量
-   */
-  maxCount?: number
-  /**
-   * 类样式
-   */
-   uploadClass?: unknown;
-  /**
-   * single 模式下图片显示大小
-   */
-  singleImageSize?: { width: number, height: number },
-  /**
-   * 参数,可以是单张 string,多张 string[]
-   */
-  value?: unknown;
-  /**
-   * a-upload 其他自定义参数
-   */
-  customProps?: UploadProps;
-}
-
-/**
- * 上传工厂接口
- */
-export interface UploadCoInterface {
-  /**
-   * 请求上传Token
-   */
-  requestUploadToken : (key: string,  bucketNameDa: string, expire ?:number) => Promise<string>,
-  /**
-    * 上传主函数。由 ant-upload 调用。
-    */
-  uploadRequest: (requestOption: AntUploadRequestOption) => void,
-  /**
-    * 获取上传返回后的URL函数。
-    */
-  getUrlByUploadResponse: (response: unknown) => string,
-}
-
-export interface FileItem {
-  uid: string;
-  name?: string;
-  status?: string;
-  response?: string;
-  url: string;
-  type: string;
-  size: number;
-  originFileObj?: unknown;
-}
-
-export interface FileInfo {
-  file: FileItem;
-  fileList: FileItem[];
-}
-
-export interface AntUploadRequestOption {
-  action: string|Promise<string>;
-  filename: string;
-  data : unknown;
-  file: File;
-  headers: { [index: string]: string; };
-  withCredentials: boolean;
-  method: string;
-  onProgress: (e: number) => void;
-  onSuccess: (ret : { url: string, key: string }, xhr : XMLHttpRequest|null) => void;
-  onError: (err : Error|null|undefined, ret : unknown) => void;
-}
-
-/**
- * 上传图片大小检查组合代码。
- * @param limitSizeMB 限制大小MB.
- * @returns 
- */
-export function useBeforeUploadImageChecker(limitSizeMB = 8) : (file: FileItem) => boolean {
-  return (file) => {
-    const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
-    if (!isJpgOrPng) 
-      message.error('请选择图片文件!');
-    const isLt2M = file.size / 1024 / 1024 < limitSizeMB;
-    if (!isLt2M) 
-      message.error(`图片大小不能大于${limitSizeMB}MB!`);
-    return isJpgOrPng && isLt2M;
-  };
-}
-
-/**
- * 上传视频大小检查组合代码。
- * @param limitSizeMB 限制大小MB.
- * @returns 
- */
-export function useBeforeUploadVideoChecker(limitSizeMB = 256) : (file: FileItem) => boolean {
-  return (file) => {
-    const isVideo = file.type.startsWith('video/');
-    if (!isVideo) 
-      message.error('请选择视频文件!');
-    const isLt2M = file.size / 1024 / 1024 < limitSizeMB;
-    if (!isLt2M) 
-      message.error(`视频大小不能大于${limitSizeMB}MB!`);
-    return isVideo && isLt2M;
-  };
-}
-
-/**
- * 把字符串URL数组转为a-upload已上传的条目
- * @param arr URL数组
- */
-export function stringUrlsToUploadedItems(arr: string[]) : FileItem[] {
-  return arr.map((i, k) => {
-    return {
-      uid: k.toString(),
-      name: StringUtils.path.getFileName(i),
-      status: 'done',
-      url: i,
-      size: 0,
-      type: '',
-    }
-  });
-}

+ 0 - 169
components/dynamicf/UploadImageFormItem.vue

@@ -1,169 +0,0 @@
-<template>
-  <a-upload 
-    v-bind="customProps"
-    :disabled="disabled"
-    v-model:file-list="uploadSubImgList"
-    list-type="picture-card"
-    :class="uploadClass"
-    :max-count="maxCount"
-    :show-upload-list="!single"
-    :customRequest="handleUpload"
-    :before-upload="beforeUpload"
-    @change="handleUploadSubImgChange"
-  >
-    <template v-if="single">
-      <a-image v-if="value != ''" 
-        :src="(value as string)"
-        alt="avatar"
-        :width="singleImageSize.width"
-        :height="singleImageSize.height"
-        :preview="false"
-        :fallback="failImage" 
-      />
-      <div v-else :style="{ width: singleImageSize.width, height: singleImageSize.height }">
-        <loading-outlined v-if="uploadingSubImg"></loading-outlined>
-        <plus-outlined v-else></plus-outlined>
-        <div class="ant-upload-text">上传</div>
-      </div>
-    </template>
-    <template v-else>
-      <loading-outlined v-if="uploadingSubImg"></loading-outlined>
-      <plus-outlined v-else></plus-outlined>
-      <div class="ant-upload-text">上传</div>
-    </template>
-  </a-upload>
-</template>
-
-<script lang="ts" setup>
-/**
- * 上传图片表单控件
- */
-import { 
-  stringUrlsToUploadedItems, type UploadCoInterface, 
-  type AntUploadRequestOption, type FileInfo, type FileItem 
-} from './UploadImageFormItem';
-import { message, type UploadProps } from 'ant-design-vue';
-import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
-import { type PropType, ref, onMounted, watch } from 'vue';
-import FailImage from '@/assets/images/imageFailed.png';
-
-const props = defineProps({
-  /**
-   * 是否禁用
-   */
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * 预览图加载失败时显示图片
-   */
-  failImage: {
-    type: String,
-    default: () => FailImage,
-  },
-  /**
-   * 上传工厂类
-   */
-  uploadCo: {
-    type: Object as PropType<UploadCoInterface>,
-    default: null,
-  },
-  /**
-   * 上传之前的自定义检查回调
-   * (file: FileItem) => boolean
-   * 如果返回false,将停止上传
-   */
-  beforeUpload: {
-    type: Function,
-    default: null,
-  },
-  /**
-   * 类样式
-   */
-  uploadClass: {},
-  /**
-   * 是否限制单图上传
-   */
-  single: {
-    type: Boolean,
-    default: false
-  },
-  /**
-   * single 为false时,限制最多上传图片的数量
-   */
-  maxCount: {
-    type: Number,
-    default: 0,
-  },
-  /**
-   * single 模式下图片显示大小
-   */
-  singleImageSize: {
-    type: Object as PropType<{ width: number, height: number }>,
-    default: () => ({ width: 100, height: 100 })
-  },
-  /**
-   * 参数,可以是单张 string,多张 string[]
-   */
-  value: {},
-  /**
-   * a-upload 其他自定义参数
-   */
-  customProps: {
-    type: Object as PropType<UploadProps>,
-    default: null,
-  },
-});
-
-const emits = defineEmits([
-  'update:value',
-]);
-
-const uploadSubImgList = ref<FileItem[]>([]);
-const uploadingSubImg = ref(false);
-
-onMounted(() => {
-  //将之前上传的图片包括URL设置到已上传列表中
-  if (!props.single) {
-    setTimeout(() => {
-      uploadSubImgList.value = stringUrlsToUploadedItems(props.value instanceof Array ? (props.value as string[] || []) : [])
-    }, 400);
-  }
-});
-
-watch(() => props.value, () => {
-  if (!props.single) {
-    uploadSubImgList.value = stringUrlsToUploadedItems(props.value instanceof Array ? (props.value as string[] || []) : [])
-  }
-});
-
-function handleUpload(requestOption: AntUploadRequestOption) {
-  props.uploadCo?.uploadRequest(requestOption);
-}
-function handleUploadSubImgChange(info: FileInfo) {
-  if (info.file.status === 'uploading') {
-    uploadingSubImg.value = true;
-    return;
-  }
-  if (info.file.status === 'removed') {
-    if (props.single)
-      emits('update:value', '');
-    else
-      emits('update:value', (props.value as string[] || []).filter(url => url != info.file.url));
-    return;
-  }
-  if (info.file.status === 'done') {
-    const url = props.uploadCo?.getUrlByUploadResponse(info.file.response) || '';
-    if (props.single)
-      emits('update:value', url);
-    else
-      emits('update:value', (props.value as string[] || []).concat([ url ]));
-    uploadingSubImg.value = false;
-  }
-  if (info.file.status === 'error') {
-    uploadingSubImg.value = false;
-    message.error('上传失败!' + info.file.response);
-  }
-}
-</script>

+ 0 - 6
components/dynamicf/WhiteSpace.ts

@@ -1,6 +0,0 @@
-/**
- * WhiteSpace 的公共接口
- */
-export interface WhiteSpaceProps {
-  height: number,
-}

+ 0 - 16
components/dynamicf/WhiteSpace.vue

@@ -1,16 +0,0 @@
-<template>
-  <div :style="{ height: height }"></div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-
-export default defineComponent({
-  props: {
-    height: {
-      type: [Number,String],
-      default: 0,
-    },
-  }
-});
-</script>

+ 0 - 28
components/dynamicf/WrapperRangePicker.vue

@@ -1,28 +0,0 @@
-<template>
-  <a-range-picker
-    :value="value"
-    @update:value="(v: unknown) => $emit('update:value', v)"
-    :showTime="showTime"
-    v-bind="additionalProps"
-  />
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from "vue";
-
-export default defineComponent({
-  props: {
-    additionalProps: {
-      type: Object as PropType<Record<string, unknown>>,
-    },
-    showTime: {
-      type: Boolean,
-    },
-    value: {
-    },
-  },
-  emist: [
-    'update:value'
-  ],
-});
-</script>

+ 0 - 24
components/dynamicf/WrapperTimeRangePicker.vue

@@ -1,24 +0,0 @@
-<template>
-  <a-time-range-picker
-    :value="value"
-    @update:value="(v: unknown) => $emit('update:value', v)"
-    v-bind="additionalProps"
-  />
-</template>
-
-<script lang="ts">
-import { defineComponent, type PropType } from "vue";
-
-export default defineComponent({
-  props: {
-    additionalProps: {
-      type: Object as PropType<Record<string, unknown>>,
-    },
-    value: {
-    },
-  },
-  emist: [
-    'update:value'
-  ],
-});
-</script>

+ 0 - 91
components/dynamicf/index.ts

@@ -1,91 +0,0 @@
-import { markRaw } from "vue";
-import { 
-  Alert, Checkbox, DatePicker, Form, 
-  FormItem, Image, Input, InputNumber, 
-  Rate, Switch, Textarea, TimePicker
-} from "ant-design-vue";
-import { DynamicFormItemRegistry, type IDynamicFormOptions, configDefaultDynamicFormOptions } from "@imengyu/vue-dynamic-form";
-import PasswordWithStrengthInput from "./PasswordWithStrengthInput.vue";
-import CheckBoxValue from "./CheckBoxValue.vue";
-import SimpleSelectFormItem from "./SimpleSelectFormItem.vue";
-import IdAsValueDropdown from "./Dropdown/IdAsValueDropdown.vue";
-import IdAsValueTreeDropdown from "./Dropdown/IdAsValueTreeDropdown.vue";
-import SelectValue from "./SelectValue.vue";
-import IdAsValueTree from "./IdAsValueTree.vue";
-import UploadImageFormItem from "./UploadImageFormItem.vue";
-import WrapperTimeRangePicker from "./WrapperTimeRangePicker.vue";
-import WrapperRangePicker from "./WrapperRangePicker.vue";
-import ActionRender from "./ActionRender.vue";
-import CheckBoxToInt from "./CheckBoxToInt.vue";
-import RadioValueVue from "./RadioValue.vue";
-import StateRendererVue from "./Display/StateRenderer.vue";
-import ShowDateOrNullVue from "./Display/ShowDateOrNull.vue";
-import ShowImageListVue from "./Display/ShowImageList.vue";
-import ShowValueOrNullVue from "./Display/ShowValueOrNull.vue";
-import CascaderFormItemVue from "./CascaderFormItem.vue";
-import SimpleEditDynamicStringListVue from "./SimpleEditDynamicStringList.vue";
-import WhiteSpaceVue from "./WhiteSpace.vue";
-import NumberRange from "./NumberRange.vue";
-
-export const defaultConfig = {
-  internalWidgets: {
-    Form: {
-      component: markRaw(Form),
-      propsMap: {
-        rules: 'rules',
-        wrapperCol: 'wrapperCol',
-        labelCol: 'labelCol',
-      },
-    },
-    FormItem: {
-      component: markRaw(FormItem),
-      propsMap: {
-        name: 'name',
-        wrapperCol: 'wrapperCol',
-        labelCol: 'labelCol',
-      },
-    },
-  },
-} as IDynamicFormOptions
-
-export function registerAllFormComponents() {
-  configDefaultDynamicFormOptions(defaultConfig);
-
-  DynamicFormItemRegistry
-
-    .register('text', markRaw(Input))
-    .register('password', markRaw(Input), { type: "password" })
-    .register('number', markRaw(InputNumber))
-    .register('text-area', markRaw(Textarea))
-    .register('password-with-strength', markRaw(PasswordWithStrengthInput))
-    .register('switch', markRaw(Switch), {}, 'checked')
-    .register('cascader', markRaw(CascaderFormItemVue))
-    .register('check-box', markRaw(Checkbox), {}, 'checked')
-    .register('check-box-int', markRaw(CheckBoxToInt))
-    .register('check-box-value', markRaw(CheckBoxValue))
-    .register('radio-value', markRaw(RadioValueVue))
-    .register('number-range', markRaw(NumberRange))
-    .register('rate', markRaw(Rate))
-    .register('select', markRaw(SimpleSelectFormItem))
-    .register('select-value', markRaw(SelectValue))
-    .register('select-id', markRaw(IdAsValueDropdown))
-    .register('select-tree-id', markRaw(IdAsValueTreeDropdown))
-    .register('tree-id', markRaw(IdAsValueTree))
-    .register('date', markRaw(DatePicker))
-    .register('time', markRaw(TimePicker))
-    .register('date-time', markRaw(DatePicker), { showTime: true })
-    .register('date-range', markRaw(WrapperRangePicker))
-    .register('time-range', markRaw(WrapperTimeRangePicker))
-    .register('date-time-range', markRaw(WrapperRangePicker), { showTime: true })
-    .register('single-image', markRaw(UploadImageFormItem), { single: true })
-    .register('mulit-image', markRaw(UploadImageFormItem))
-    .register('actions', markRaw(ActionRender))
-    .register('alert', markRaw(Alert))
-    .register('string-list', markRaw(SimpleEditDynamicStringListVue))
-    .register('static-image', markRaw(Image), {}, "src")
-    .register('static-state', markRaw(StateRendererVue))
-    .register('static-value', markRaw(ShowValueOrNullVue))
-    .register('static-date', markRaw(ShowDateOrNullVue))
-    .register('static-image-list', markRaw(ShowImageListVue), {}, "images")
-    .register('space', markRaw(WhiteSpaceVue))
-}

+ 0 - 98
components/error/ErrorReporter.vue

@@ -1,98 +0,0 @@
-<template>
-  <div class="nana-error-report-container">
-    <Transition name="nana-scale-transform">
-      <div v-if="messageItems.length > 0" class="nana-error-report" @click="showError">
-        <img :src="ErrorIcon" />
-        <span>{{ errorCount }}</span>
-        <img :src="WarningIcon" /> 
-        <span>{{ warningCount }}</span>
-      </div>
-    </Transition>
-  </div>
-  <a-modal v-model:open="showModal" title="错误报告" width="60%" height="80%">
-    <a-alert
-      v-for="(item, index) in messageItems"
-      :key="index"
-      :message="item.message"
-      :type="item.type"
-      :description="item.detail"
-    />
-    <template #footer>
-      <a-button @click="messageItems = [];showModal = false">清空</a-button>
-      <a-button @click="showModal = false">关闭</a-button>
-    </template>
-  </a-modal>
-</template>
-
-<script setup lang="ts">
-import { onMounted, provide, ref } from 'vue';
-import WarningIcon from '../AlertIcons/warning.svg';
-import ErrorIcon from '../AlertIcons/error.svg';
-
-export interface ErrorReportItem {
-  time?: number,
-  message: string,
-  detail?: string,
-  type: 'error' | 'warning' | 'info',
-}
-export interface ErrorReportRef {
-  logError(info: ErrorReportItem): void;
-}
-
-const messageItems = ref<ErrorReportItem[]>([]);
-const errorCount = ref(0);
-const warningCount = ref(0);
-const showModal = ref(false);
-
-function logError(info: ErrorReportItem) {
-  messageItems.value.push(info);
-  if (info.type === 'error') 
-    errorCount.value++;
-  if (info.type === 'warning') 
-    warningCount.value++;
-}
-function showError() {
-  showModal.value = true;
-}
-
-provide<ErrorReportRef>("ErrorReporter", {
-  logError,
-});
-
-onMounted(() => {
-  (window as any).$error = logError;
-})
-</script>
-
-<style lang="scss">
-.nana-error-report-container {
-  position: fixed;
-  top: 2rem;
-  left: 0;
-  right: 0;
-  display: flex;
-  flex-direction: row;
-  justify-content: center;
-  pointer-events: none;
-  z-index: 100;
-
-  img {
-    width: 1rem;
-    height: 1rem;
-  }
-  span {
-    color: #333;
-  }
-}
-.nana-error-report {
-  display: flex;
-  flex-direction: row;
-  align-items: center; 
-  width: auto;
-  gap: 0.5rem;
-  background-color: #fff;
-  padding: 0.6rem;
-  border-radius: 0.5rem;
-  pointer-events: all;
-}
-</style>

+ 0 - 28
components/error/ErrorReporterIs.ts

@@ -1,28 +0,0 @@
-import { inject } from "vue";
-import type { ErrorReportItem, ErrorReportRef } from "./ErrorReporter.vue";
-import { RequestApiError } from "@imengyu/imengyu-utils";
-
-export function useErrorReporter() {
-  const r = inject<ErrorReportRef>("ErrorReporter");
-  if (!r)
-    throw new Error("ErrorReporter is not provided"); 
-  return r;
-}
-
-export function formatError(err: any) {
-  if (err instanceof RequestApiError) 
-    return err.toStringDetail();
-  if (err instanceof Error) 
-    return err.message + '\n' + err.stack;
-  return '' + err;
-}
-export function logError(info: ErrorReportItem) {
-  (window as any).$error?.(info);
-}
-export function logErrorSimple(err: any, message: string) { 
-  logError({
-    type: 'error',
-    detail: formatError(err),
-    message,
-  });
-}

+ 1 - 1
composeable/SimplePagerDataLoader.ts

@@ -1,6 +1,6 @@
 import { watch, ref, computed, type Ref } from "vue"
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
-import { formatError } from "~/components/error/ErrorReporterIs";
+import { formatError } from "@imengyu/imengyu-utils";
 
 export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
   list: Ref<T[]>;

+ 5 - 1
nuxt.config.ts

@@ -1,5 +1,6 @@
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
+  compatibilityDate: '2026-03-06',
   app: {
     head: {
       title: '闽南文化生态保护区(厦门市)',
@@ -16,7 +17,10 @@ export default defineNuxtConfig({
     }
   },
   devtools: { enabled: true },
-  modules: ['@pinia/nuxt', '@ant-design-vue/nuxt'],
+  modules: [
+    '@pinia/nuxt',
+    '@ant-design-vue/nuxt',
+  ],
   components: [
     {
       path: '~/components',

File diff suppressed because it is too large
+ 7656 - 7338
package-lock.json


+ 15 - 15
package.json

@@ -15,20 +15,24 @@
     "build:ich": "webpack --config webpack.ich.config.js"
   },
   "dependencies": {
+    "ant-design-vue": "^4.2.6",
     "@ant-design-vue/nuxt": "^1.4.6",
-    "@imengyu/imengyu-utils": "^0.0.19",
-    "@imengyu/js-request-transform": "^0.3.5",
+    "@imengyu/imengyu-utils": "^0.0.27",
+    "@imengyu/js-request-transform": "^0.4.0",
     "@imengyu/vue-dynamic-form": "^0.1.1",
-    "@imengyu/vue-scroll-rect": "^0.1.3",
+    "@imengyu/vue-scroll-rect": "^0.1.8",
+    "@nuxt/icon": "^2.1.1",
     "@pinia/nuxt": "^0.11.1",
+    "@popperjs/core": "^2.11.8",
     "@vuemap/vue-amap": "^2.1.12",
-    "ant-design-vue": "^4.2.6",
-    "axios": "^1.9.0",
-    "bootstrap": "^5.3.0",
+    "axios": "^1.13.2",
+    "bootstrap": "^5.3.8",
+    "json5": "^2.2.3",
     "lodash-es": "^4.17.21",
     "md5": "^2.3.0",
     "mitt": "^3.0.1",
-    "nuxt": "^3.17.6",
+    "mysql2": "^3.16.0",
+    "nuxt": "^4.2.2",
     "pinia": "^3.0.1",
     "tslib": "^2.8.1",
     "vue": "^3.5.13",
@@ -36,10 +40,10 @@
     "vue3-carousel": "^0.15.0"
   },
   "devDependencies": {
-    "@inquirer/prompts": "^7.5.3",
+    "@iconify-json/material-symbols": "^1.2.50",
+    "@inquirer/prompts": "^7.10.1",
     "@tsconfig/node22": "^22.0.1",
     "@types/node": "^22.14.0",
-    "@types/webpack": "^5.28.5",
     "@vitejs/plugin-vue": "^5.2.3",
     "@vitejs/plugin-vue-jsx": "^4.1.2",
     "@vue/tsconfig": "^0.7.0",
@@ -47,17 +51,13 @@
     "archiver": "^7.0.1",
     "cli-progress": "^3.12.0",
     "cli-table3": "^0.6.5",
-    "commander": "^14.0.0",
+    "commander": "^14.0.2",
     "npm-run-all2": "^7.0.2",
     "sass": "^1.87.0",
     "terser": "^5.44.1",
-    "ts-loader": "^9.5.4",
-    "ts-node": "^10.9.2",
     "typescript": "~5.8.0",
     "vite": "^6.2.4",
     "vite-plugin-vue-devtools": "^7.7.2",
-    "vue-tsc": "^2.2.8",
-    "webpack": "^5.104.1",
-    "webpack-cli": "^6.0.1"
+    "vue-tsc": "^2.2.8"
   }
 }