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