From 1ba88a00d3c7bb0f16a73feaa5ffcf7c1635e0ea Mon Sep 17 00:00:00 2001
From: Sanakey <714737083@qq.com>
Date: 星期五, 02 八月 2024 17:44:27 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/v2' into onbus-crm

---
 src/components/Form/src/components/ApiSelect.vue              |   80 ++++++++++++++++++-
 src/components/Preview/src/functional.ts                      |   15 ++-
 src/locales/lang/en/layout.json                               |    7 +
 src/components/Table/src/components/editable/EditableCell.vue |    2 
 src/views/demo/form/index.vue                                 |   35 ++++++++
 src/components/Loading/src/Loading.vue                        |    2 
 src/store/modules/multipleTab.ts                              |    2 
 src/layouts/default/header/index.vue                          |    3 
 src/hooks/web/useECharts.ts                                   |    8 +
 .husky/pre-commit                                             |    2 
 src/components/Scrollbar/src/Scrollbar.vue                    |    4 
 src/components/Upload/src/components/UploadModal.vue          |   16 ++--
 src/locales/lang/zh-CN/layout.json                            |    7 +
 src/design/public.less                                        |    6 
 .husky/commit-msg                                             |    2 
 src/layouts/default/header/components/UpgradePrompt.vue       |   33 ++++++++
 src/logics/initAppConfig.ts                                   |    2 
 17 files changed, 189 insertions(+), 37 deletions(-)

diff --git a/.husky/commit-msg b/.husky/commit-msg
index 274d2d8..260e10b 100755
--- a/.husky/commit-msg
+++ b/.husky/commit-msg
@@ -5,4 +5,4 @@
 
 PATH="/usr/local/bin:$PATH"
 
-npx --no-install commitlint --edit "$1"
+# npx --no-install commitlint --edit "$1"
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 53685f4..b158e0d 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -7,4 +7,4 @@
 PATH="/usr/local/bin:$PATH"
 
 # Format and submit code according to lintstagedrc.js configuration
-pnpm exec lint-staged
+# pnpm exec lint-staged
diff --git a/src/components/Form/src/components/ApiSelect.vue b/src/components/Form/src/components/ApiSelect.vue
index df59336..16648e4 100644
--- a/src/components/Form/src/components/ApiSelect.vue
+++ b/src/components/Form/src/components/ApiSelect.vue
@@ -3,6 +3,7 @@
     @dropdown-visible-change="handleFetch"
     v-bind="$attrs"
     @change="handleChange"
+    @search="debounceSearchFn"
     :options="getOptions"
     v-model:value="state"
   >
@@ -20,18 +21,33 @@
     </template>
   </Select>
 </template>
+
 <script lang="ts" setup>
-  import { PropType, ref, computed, unref, watch } from 'vue';
+  import { computed, PropType, ref, unref, watch } from 'vue';
   import { Select } from 'ant-design-vue';
   import type { SelectValue } from 'ant-design-vue/es/select';
-  import { isFunction } from '@/utils/is';
+  import { isEmpty, isFunction } from '@/utils/is';
   import { useRuleFormItem } from '@/hooks/component/useFormItem';
-  import { get, omit, isEqual } from 'lodash-es';
+  import { assignIn, get, isEqual, omit } from 'lodash-es';
   import { LoadingOutlined } from '@ant-design/icons-vue';
   import { useI18n } from '@/hooks/web/useI18n';
   import { propTypes } from '@/utils/propTypes';
+  import { useDebounceFn } from '@vueuse/core';
 
   type OptionsItem = { label?: string; value?: string; disabled?: boolean; [name: string]: any };
+
+  type ApiSearchOption = {
+    // 灞曠ず鎼滅储
+    show?: boolean;
+    // 寰呮悳绱㈠瓧娈靛悕
+    searchName?: string;
+    // 鏄惁鍏佽绌烘悳绱�
+    emptySearch?: boolean;
+    // 鎼滅储鍓嶇疆鏂规硶
+    beforeFetch?: (value?: string) => Promise<string>;
+    // 鎷︽埅鏂规硶
+    interceptFetch?: (value?: string) => Promise<boolean>;
+  };
 
   defineOptions({ name: 'ApiSelect', inheritAttrs: false });
 
@@ -39,7 +55,7 @@
     value: { type: [Array, Object, String, Number] as PropType<SelectValue> },
     numberToString: propTypes.bool,
     api: {
-      type: Function as PropType<(arg?: any) => Promise<OptionsItem[] | Recordable<any>>>,
+      type: Function as PropType<(arg?: any) => Promise<OptionsItem[] | Recordable>>,
       default: null,
     },
     // api params
@@ -53,6 +69,10 @@
     options: {
       type: Array<OptionsItem>,
       default: [],
+    },
+    apiSearch: {
+      type: Object as PropType<ApiSearchOption>,
+      default: () => null,
     },
     beforeFetch: {
       type: Function as PropType<Fn>,
@@ -72,6 +92,7 @@
   // 棣栨鏄惁鍔犺浇杩囦簡
   const isFirstLoaded = ref(false);
   const emitData = ref<OptionsItem[]>([]);
+  const searchParams = ref<any>({});
   const { t } = useI18n();
 
   // Embedded in the form, just use the hook binding to perform form verification
@@ -110,16 +131,29 @@
     { deep: true, immediate: props.immediate },
   );
 
+  watch(
+    () => searchParams.value,
+    (value, oldValue) => {
+      if (isEmpty(value) || isEqual(value, oldValue)) return;
+      (async () => {
+        await fetch();
+        searchParams.value = {};
+      })();
+    },
+    { deep: true, immediate: props.immediate },
+  );
+
   async function fetch() {
     let { api, beforeFetch, afterFetch, params, resultField } = props;
     if (!api || !isFunction(api) || loading.value) return;
     optionsRef.value = [];
     try {
       loading.value = true;
+      let apiParams = assignIn({}, params, searchParams.value);
       if (beforeFetch && isFunction(beforeFetch)) {
-        params = (await beforeFetch(params)) || params;
+        apiParams = (await beforeFetch(apiParams)) || apiParams;
       }
-      let res = await api(params);
+      let res = await api(apiParams);
       if (afterFetch && isFunction(afterFetch)) {
         res = (await afterFetch(res)) || res;
       }
@@ -147,11 +181,43 @@
       if (props.alwaysLoad) {
         await fetch();
       } else if (!props.immediate && !unref(isFirstLoaded)) {
-        await fetch();
+        // 鍔ㄦ�佹悳绱㈡煡璇㈡椂锛屽厑璁告帶鍒跺垵濮嬩笉鍔犺浇鏁版嵁
+        if (!(!!props.apiSearch && !!props.apiSearch.show && !props.apiSearch.emptySearch)) {
+          await fetch();
+        } else {
+          optionsRef.value = [];
+          emitChange();
+        }
       }
     }
   }
 
+  let debounceSearchFn = useDebounceFn(handleSearch, 500);
+
+  async function handleSearch(value: any) {
+    if (!props.apiSearch) {
+      return;
+    }
+    const { show, searchName, beforeFetch, interceptFetch } = props.apiSearch;
+    if (!show || !searchName) {
+      return;
+    }
+
+    value = value || undefined;
+    if (beforeFetch && isFunction(beforeFetch)) {
+      value = (await beforeFetch(value)) || value;
+    }
+
+    if (interceptFetch && isFunction(interceptFetch)) {
+      if (!(await interceptFetch(value))) {
+        return;
+      }
+    }
+    searchParams.value = {
+      [searchName]: value,
+    };
+  }
+
   function emitChange() {
     emit('options-change', unref(getOptions));
   }
diff --git a/src/components/Loading/src/Loading.vue b/src/components/Loading/src/Loading.vue
index 734974e..199b5a8 100644
--- a/src/components/Loading/src/Loading.vue
+++ b/src/components/Loading/src/Loading.vue
@@ -54,7 +54,7 @@
     justify-content: center;
     width: 100%;
     height: 100%;
-    background-color: rgb(240 242 245 / 40%);
+    background-color: #f0f2f566;
 
     &.absolute {
       position: absolute;
diff --git a/src/components/Preview/src/functional.ts b/src/components/Preview/src/functional.ts
index c3ee2bf..2d577f6 100644
--- a/src/components/Preview/src/functional.ts
+++ b/src/components/Preview/src/functional.ts
@@ -1,7 +1,7 @@
-import type { Options, Props } from './typing';
-import ImgPreview from './Functional.vue';
 import { isClient } from '@/utils/is';
 import { createVNode, render } from 'vue';
+import ImgPreview from './Functional.vue';
+import type { Options, Props } from './typing';
 
 let instance: ReturnType<typeof createVNode> | null = null;
 export function createImgPreview(options: Options) {
@@ -10,8 +10,13 @@
   const container = document.createElement('div');
   Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
 
-  instance = createVNode(ImgPreview, propsData);
-  render(instance, container);
-  document.body.appendChild(container);
+  if (instance?.component) {
+    // 瀛樺湪瀹炰緥鏃讹紝鏇存柊props
+    Object.assign(instance.component.props, propsData);
+  } else {
+    instance = createVNode(ImgPreview, propsData);
+    render(instance, container);
+    document.body.appendChild(container);
+  }
   return instance.component?.exposed;
 }
diff --git a/src/components/Scrollbar/src/Scrollbar.vue b/src/components/Scrollbar/src/Scrollbar.vue
index db8d7a1..e567aec 100644
--- a/src/components/Scrollbar/src/Scrollbar.vue
+++ b/src/components/Scrollbar/src/Scrollbar.vue
@@ -153,11 +153,11 @@
       height: 0;
       transition: 0.3s background-color;
       border-radius: inherit;
-      background-color: rgb(144 147 153 / 30%);
+      background-color: #9093994d;
       cursor: pointer;
 
       &:hover {
-        background-color: rgb(144 147 153 / 50%);
+        background-color: #90939980;
       }
     }
 
diff --git a/src/components/Table/src/components/editable/EditableCell.vue b/src/components/Table/src/components/editable/EditableCell.vue
index 6d998bb..a2f58f7 100644
--- a/src/components/Table/src/components/editable/EditableCell.vue
+++ b/src/components/Table/src/components/editable/EditableCell.vue
@@ -17,6 +17,7 @@
   import { treeToList } from '@/utils/helper/treeHelper';
   import { Spin } from 'ant-design-vue';
   import { parseRowKey } from '../../helper';
+  import { warn } from '@/utils/log';
 
   export default defineComponent({
     name: 'EditableCell',
@@ -282,6 +283,7 @@
               });
             } catch (e) {
               result = false;
+              warn(e);
             } finally {
               spinning.value = false;
             }
diff --git a/src/components/Upload/src/components/UploadModal.vue b/src/components/Upload/src/components/UploadModal.vue
index 698d930..e3db244 100644
--- a/src/components/Upload/src/components/UploadModal.vue
+++ b/src/components/Upload/src/components/UploadModal.vue
@@ -57,7 +57,7 @@
   import { useMessage } from '@/hooks/web/useMessage';
   //   types
   import { FileItem, UploadResultStatus } from '../types/typing';
-  import { basicProps } from '../props';
+  import { handleFnKey, basicProps } from '../props';
   import { createTableColumns, createActionColumn } from './data';
   // utils
   import { checkImgType, getBase64WithFile } from '../helper';
@@ -161,13 +161,13 @@
   }
 
   // 鍒犻櫎
-  function handleRemove(record: FileItem) {
-    const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
-    index !== -1 && fileListRef.value.splice(index, 1);
-    isUploadingRef.value = fileListRef.value.some(
-      (item) => item.status === UploadResultStatus.UPLOADING,
-    );
-    emit('delete', record);
+  function handleRemove(obj: Record<handleFnKey, any>) {
+    let { record = {}, uidKey = 'uid' } = obj;
+    const index = fileListRef.value.findIndex((item) => item[uidKey] === record[uidKey]);
+    if (index !== -1) {
+      const removed = fileListRef.value.splice(index, 1);
+      emit('delete', removed[0][uidKey]);
+    }
   }
 
   async function uploadApiByItem(item: FileItem) {
diff --git a/src/design/public.less b/src/design/public.less
index 0323b84..e927191 100644
--- a/src/design/public.less
+++ b/src/design/public.less
@@ -17,15 +17,15 @@
 // }
 
 ::-webkit-scrollbar-track {
-  background-color: rgb(0 0 0 / 5%);
+  background-color: #0000000d;
 }
 
 ::-webkit-scrollbar-thumb {
   // background-color: rgba(144, 147, 153, 0.3);
   border-radius: 2px;
   // background: rgba(0, 0, 0, 0.6);
-  background-color: rgb(144 147 153 / 30%);
-  box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
+  background-color: #9093994d;
+  box-shadow: inset 0 0 6px #00000033;
 }
 
 ::-webkit-scrollbar-thumb:hover {
diff --git a/src/hooks/web/useECharts.ts b/src/hooks/web/useECharts.ts
index c5478d7..6eac15f 100644
--- a/src/hooks/web/useECharts.ts
+++ b/src/hooks/web/useECharts.ts
@@ -1,8 +1,8 @@
 import type { EChartsOption } from 'echarts';
 import type { Ref } from 'vue';
+import { computed, nextTick, ref, unref, watch } from 'vue';
 import { useTimeoutFn } from '@vben/hooks';
 import { tryOnUnmounted, useDebounceFn } from '@vueuse/core';
-import { unref, nextTick, watch, computed, ref } from 'vue';
 import { useEventListener } from '@/hooks/event/useEventListener';
 import { useBreakpoint } from '@/hooks/event/useBreakpoint';
 import echarts from '@/utils/lib/echarts';
@@ -49,6 +49,10 @@
       listener: resizeFn,
     });
     removeResizeFn = removeEvent;
+
+    const resizeObserver = new ResizeObserver(resizeFn);
+    resizeObserver.observe(el);
+
     const { widthRef, screenEnum } = useBreakpoint();
     if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
       useTimeoutFn(() => {
@@ -64,7 +68,7 @@
         useTimeoutFn(() => {
           setOptions(unref(getOptions));
           resolve(null);
-        }, 30);
+        }, 50);
       }
       nextTick(() => {
         useTimeoutFn(() => {
diff --git a/src/layouts/default/header/components/UpgradePrompt.vue b/src/layouts/default/header/components/UpgradePrompt.vue
new file mode 100644
index 0000000..baeddd2
--- /dev/null
+++ b/src/layouts/default/header/components/UpgradePrompt.vue
@@ -0,0 +1,33 @@
+<script setup lang="ts">
+  import { h } from 'vue';
+  import { Modal } from 'ant-design-vue';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  const { t } = useI18n();
+
+  const localKey = 'vben-v5.0.0-upgrade-prompt';
+
+  if (!localStorage.getItem(localKey)) {
+    Modal.confirm({
+      title: t('layout.header.upgrade-prompt.title'),
+      content: h('div', {}, [h('p', t('layout.header.upgrade-prompt.content'))]),
+      onOk() {
+        handleClick();
+      },
+      okText: t('layout.header.upgrade-prompt.ok-text'),
+      cancelText: t('common.closeText'),
+    });
+  }
+  localStorage.setItem(localKey, String(Date.now()));
+
+  function handleClick() {
+    window.open('https://www.vben.pro', '_blank');
+  }
+</script>
+<template>
+  <div>
+    <a-button type="primary" @click="handleClick">{{
+      t('layout.header.upgrade-prompt.ok-text')
+    }}</a-button>
+  </div>
+</template>
diff --git a/src/layouts/default/header/index.vue b/src/layouts/default/header/index.vue
index 2ffd43c..a161883 100644
--- a/src/layouts/default/header/index.vue
+++ b/src/layouts/default/header/index.vue
@@ -33,6 +33,8 @@
 
     <!-- action  -->
     <div :class="`${prefixCls}-action`">
+      <UpgradePrompt class="mr-2" />
+
       <AppSearch v-if="getShowSearch" :class="`${prefixCls}-action__item `" />
 
       <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
@@ -70,6 +72,7 @@
   import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
   import { propTypes } from '@/utils/propTypes';
 
+  import UpgradePrompt from './components/UpgradePrompt.vue';
   import LayoutMenu from '../menu/index.vue';
   import LayoutTrigger from '../trigger/index.vue';
   import { ErrorAction, FullScreen, LayoutBreadcrumb, Notify, UserDropDown } from './components';
diff --git a/src/locales/lang/en/layout.json b/src/locales/lang/en/layout.json
index 74612d3..3152dfe 100644
--- a/src/locales/lang/en/layout.json
+++ b/src/locales/lang/en/layout.json
@@ -15,7 +15,12 @@
     "lockScreenPassword": "Lock screen password",
     "lockScreen": "Lock screen",
     "lockScreenBtn": "Locking",
-    "home": "Home"
+    "home": "Home",
+    "upgrade-prompt": {
+      "title": "New version released",
+      "content": "Vben Admin v5.0.0 preview version has been released",
+      "ok-text": "Go to new version"
+    }
   },
   "multipleTab": {
     "reload": "Refresh current",
diff --git a/src/locales/lang/zh-CN/layout.json b/src/locales/lang/zh-CN/layout.json
index d067381..4324e32 100644
--- a/src/locales/lang/zh-CN/layout.json
+++ b/src/locales/lang/zh-CN/layout.json
@@ -15,7 +15,12 @@
     "lockScreenPassword": "閿佸睆瀵嗙爜",
     "lockScreen": "閿佸畾灞忓箷",
     "lockScreenBtn": "閿佸畾",
-    "home": "棣栭〉"
+    "home": "棣栭〉",
+    "upgrade-prompt": {
+      "title": "鏂扮増鏈彂甯�",
+      "content": "Vben Admin v5.0.0 棰勮鐗堟湰宸插彂甯�",
+      "ok-text": "鍓嶅線浣撻獙鏂扮増"
+    }
   },
   "multipleTab": {
     "reload": "閲嶆柊鍔犺浇",
diff --git a/src/logics/initAppConfig.ts b/src/logics/initAppConfig.ts
index 364a4cb..9ab8432 100644
--- a/src/logics/initAppConfig.ts
+++ b/src/logics/initAppConfig.ts
@@ -24,7 +24,7 @@
 export function initAppConfigStore() {
   const localeStore = useLocaleStore();
   const appStore = useAppStore();
-  let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig;
+  let projCfg = Persistent.getLocal<ProjectConfig>(PROJ_CFG_KEY);
   projCfg = deepMerge(projectSetting, projCfg || {});
   const darkMode = appStore.getDarkMode;
   const {
diff --git a/src/store/modules/multipleTab.ts b/src/store/modules/multipleTab.ts
index 6a57acd..0ae9440 100644
--- a/src/store/modules/multipleTab.ts
+++ b/src/store/modules/multipleTab.ts
@@ -160,7 +160,7 @@
           const realPath = meta?.realPath ?? '';
           // 鑾峰彇鍒板凡缁忔墦寮�鐨勫姩鎬佽矾鐢辨暟, 鍒ゆ柇鏄惁澶т簬鏌愪竴涓��
           if (
-            this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel
+            this.tabList.filter((e) => (e.meta?.realPath ?? '') === realPath).length >= dynamicLevel
           ) {
             // 鍏抽棴绗竴涓�
             const index = this.tabList.findIndex((item) => item.meta.realPath === realPath);
diff --git a/src/views/demo/form/index.vue b/src/views/demo/form/index.vue
index 9300af0..ba5687a 100644
--- a/src/views/demo/form/index.vue
+++ b/src/views/demo/form/index.vue
@@ -57,8 +57,8 @@
 </template>
 <script lang="ts" setup>
   import { type Recordable } from '@vben/types';
-  import { computed, unref, ref } from 'vue';
-  import { BasicForm, ApiSelect, FormSchema } from '@/components/Form';
+  import { computed, ref, unref } from 'vue';
+  import { ApiSelect, BasicForm, FormSchema } from '@/components/Form';
   import { CollapseContainer } from '@/components/Container';
   import { useMessage } from '@/hooks/web/useMessage';
   import { PageWrapper } from '@/components/Page';
@@ -472,7 +472,7 @@
       },
     },
     {
-      field: 'field32',
+      field: 'field32-1',
       label: '涓嬫媺杩滅▼鎼滅储',
       helpMessage: ['ApiSelect缁勪欢', '灏嗗叧閿瘝鍙戦�佸埌鎺ュ彛杩涜杩滅▼鎼滅储'],
       required: true,
@@ -483,6 +483,35 @@
       defaultValue: '0',
     },
     {
+      field: 'field32-2',
+      label: '涓嬫媺杩滅▼鎼滅储',
+      component: 'ApiSelect',
+      helpMessage: ['ApiSelect缁勪欢', '灏嗗叧閿瘝鍙戦�佸埌鎺ュ彛杩涜杩滅▼鎼滅储'],
+      componentProps: {
+        api: optionsListApi,
+        showSearch: true,
+        apiSearch: {
+          show: true,
+          searchName: 'name',
+        },
+        resultField: 'list',
+        labelField: 'name',
+        valueField: 'id',
+        immediate: true,
+        onChange: (e, v) => {
+          console.log('ApiSelect====>:', e, v);
+        },
+        onOptionsChange: (options) => {
+          console.log('get options', options.length, options);
+        },
+      },
+      required: true,
+      colProps: {
+        span: 8,
+      },
+      defaultValue: '0',
+    },
+    {
       field: 'field33',
       component: 'ApiTreeSelect',
       label: '杩滅▼涓嬫媺鏍�',

--
Gitblit v1.8.0