mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 04:54:58 +08:00
refactor: components use setup (#3299)
* refactor: /@/ => @/ * refactor: table demo use script setup * refactor: change /@/ to @/
This commit is contained in:
52
.vscode/settings.json
vendored
52
.vscode/settings.json
vendored
@@ -104,38 +104,36 @@
|
|||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"vben",
|
"antd",
|
||||||
"browserslist",
|
|
||||||
"tailwindcss",
|
|
||||||
"esnext",
|
|
||||||
"antv",
|
"antv",
|
||||||
"tinymce",
|
"brotli",
|
||||||
|
"browserslist",
|
||||||
|
"codemirror",
|
||||||
|
"commitlint",
|
||||||
|
"cropperjs",
|
||||||
|
"echarts",
|
||||||
|
"esnext",
|
||||||
|
"esno",
|
||||||
|
"iconify",
|
||||||
|
"INTLIFY",
|
||||||
|
"lintstagedrc",
|
||||||
|
"logicflow",
|
||||||
|
"mockjs",
|
||||||
|
"nprogress",
|
||||||
|
"pinia",
|
||||||
|
"pnpm",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
"sider",
|
"sider",
|
||||||
"pinia",
|
|
||||||
"sider",
|
|
||||||
"nprogress",
|
|
||||||
"INTLIFY",
|
|
||||||
"stylelint",
|
|
||||||
"esno",
|
|
||||||
"vitejs",
|
|
||||||
"sortablejs",
|
"sortablejs",
|
||||||
"mockjs",
|
"stylelint",
|
||||||
"codemirror",
|
|
||||||
"iconify",
|
|
||||||
"commitlint",
|
|
||||||
"vditor",
|
|
||||||
"echarts",
|
|
||||||
"cropperjs",
|
|
||||||
"logicflow",
|
|
||||||
"vueuse",
|
|
||||||
"zxcvbn",
|
|
||||||
"lintstagedrc",
|
|
||||||
"brotli",
|
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"sider",
|
"tinymce",
|
||||||
"pnpm",
|
"unref",
|
||||||
"antd"
|
"vben",
|
||||||
|
"vditor",
|
||||||
|
"vitejs",
|
||||||
|
"vueuse",
|
||||||
|
"zxcvbn"
|
||||||
],
|
],
|
||||||
"vetur.format.scriptInitialIndent": true,
|
"vetur.format.scriptInitialIndent": true,
|
||||||
"vetur.format.styleInitialIndent": true,
|
"vetur.format.styleInitialIndent": true,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
|
|
||||||
import appLogo from './src/AppLogo.vue';
|
import appLogo from './src/AppLogo.vue';
|
||||||
import appProvider from './src/AppProvider.vue';
|
import appProvider from './src/AppProvider.vue';
|
||||||
|
@@ -7,12 +7,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, unref } from 'vue';
|
import { computed, unref } from 'vue';
|
||||||
import { SvgIcon } from '/@/components/Icon';
|
import { SvgIcon } from '@/components/Icon';
|
||||||
import { ThemeEnum } from '/@/enums/appEnum';
|
import { ThemeEnum } from '@/enums/appEnum';
|
||||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { updateDarkTheme } from '/@/logics/theme/dark';
|
import { updateDarkTheme } from '@/logics/theme/dark';
|
||||||
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
|
import { updateHeaderBgColor, updateSidebarBgColor } from '@/logics/theme/updateBackground';
|
||||||
|
|
||||||
const { prefixCls } = useDesign('dark-switch');
|
const { prefixCls } = useDesign('dark-switch');
|
||||||
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
|
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
|
||||||
@@ -61,7 +61,9 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
transition: transform 0.5s, background-color 0.5s;
|
transition:
|
||||||
|
transform 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
@@ -19,12 +19,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LocaleType } from '/#/config';
|
import type { LocaleType } from '/#/config';
|
||||||
import type { DropMenu } from '/@/components/Dropdown';
|
import type { DropMenu } from '@/components/Dropdown';
|
||||||
import { ref, watchEffect, unref, computed } from 'vue';
|
import { ref, watchEffect, unref, computed } from 'vue';
|
||||||
import { Dropdown } from '/@/components/Dropdown';
|
import { Dropdown } from '@/components/Dropdown';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
import { useLocale } from '/@/locales/useLocale';
|
import { useLocale } from '@/locales/useLocale';
|
||||||
import { localeList } from '/@/settings/localeSetting';
|
import { localeList } from '@/settings/localeSetting';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
|
@@ -12,12 +12,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, unref } from 'vue';
|
import { computed, unref } from 'vue';
|
||||||
import { useGlobSetting } from '/@/hooks/setting';
|
import { useGlobSetting } from '@/hooks/setting';
|
||||||
import { useGo } from '/@/hooks/web/usePage';
|
import { useGo } from '@/hooks/web/usePage';
|
||||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
import { useMenuSetting } from '@/hooks/setting/useMenuSetting';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { PageEnum } from '/@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import { useUserStore } from '/@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, ref, unref } from 'vue';
|
import { defineComponent, toRefs, ref, unref } from 'vue';
|
||||||
import { createAppProviderContext } from './useAppContext';
|
import { createAppProviderContext } from './useAppContext';
|
||||||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
|
import { createBreakpointListen } from '@/hooks/event/useBreakpoint';
|
||||||
import { prefixCls } from '/@/settings/designSetting';
|
import { prefixCls } from '@/settings/designSetting';
|
||||||
import { useAppStore } from '/@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
import { Tooltip } from 'ant-design-vue';
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||||
import AppSearchModal from './AppSearchModal.vue';
|
import AppSearchModal from './AppSearchModal.vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AppSearch',
|
name: 'AppSearch',
|
||||||
|
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import AppSearchKeyItem from './AppSearchKeyItem.vue';
|
import AppSearchKeyItem from './AppSearchKeyItem.vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
const { prefixCls } = useDesign('app-search-footer');
|
const { prefixCls } = useDesign('app-search-footer');
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -44,7 +44,9 @@
|
|||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
|
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
|
||||||
box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
|
box-shadow:
|
||||||
|
inset 0 -2px 0 0 #cdcde6,
|
||||||
|
inset 0 0 1px 1px #fff,
|
||||||
0 1px 2px 1px rgb(30 35 90 / 40%);
|
0 1px 2px 1px rgb(30 35 90 / 40%);
|
||||||
|
|
||||||
&:nth-child(2),
|
&:nth-child(2),
|
||||||
|
@@ -63,12 +63,12 @@
|
|||||||
import AppSearchFooter from './AppSearchFooter.vue';
|
import AppSearchFooter from './AppSearchFooter.vue';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import vClickOutside from '/@/directives/clickOutside';
|
import vClickOutside from '@/directives/clickOutside';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useRefs } from '@vben/hooks';
|
import { useRefs } from '@vben/hooks';
|
||||||
import { useMenuSearch } from './useMenuSearch';
|
import { useMenuSearch } from './useMenuSearch';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
import { useAppInject } from '@/hooks/web/useAppInject';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: { type: Boolean },
|
visible: { type: Boolean },
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { type Menu } from '/@/router/types';
|
import { type Menu } from '@/router/types';
|
||||||
import { type AnyFunction } from '@vben/types';
|
import { type AnyFunction } from '@vben/types';
|
||||||
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
|
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
|
||||||
import { getMenus } from '/@/router/menus';
|
import { getMenus } from '@/router/menus';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { filter, forEach } from '/@/utils/helper/treeHelper';
|
import { filter, forEach } from '@/utils/helper/treeHelper';
|
||||||
import { useGo } from '/@/hooks/web/usePage';
|
import { useGo } from '@/hooks/web/usePage';
|
||||||
import { useScrollTo } from '@vben/hooks';
|
import { useScrollTo } from '@vben/hooks';
|
||||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { InjectionKey, Ref } from 'vue';
|
import { InjectionKey, Ref } from 'vue';
|
||||||
import { createContext, useContext } from '/@/hooks/core/useContext';
|
import { createContext, useContext } from '@/hooks/core/useContext';
|
||||||
|
|
||||||
export interface AppProviderContextProps {
|
export interface AppProviderContextProps {
|
||||||
prefixCls: Ref<string>;
|
prefixCls: Ref<string>;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import authority from './src/Authority.vue';
|
import authority from './src/Authority.vue';
|
||||||
|
|
||||||
export const Authority = withInstall(authority);
|
export const Authority = withInstall(authority);
|
||||||
|
@@ -4,9 +4,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { RoleEnum } from '/@/enums/roleEnum';
|
import { RoleEnum } from '@/enums/roleEnum';
|
||||||
import { usePermission } from '/@/hooks/web/usePermission';
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Authority',
|
name: 'Authority',
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import basicArrow from './src/BasicArrow.vue';
|
import basicArrow from './src/BasicArrow.vue';
|
||||||
import basicTitle from './src/BasicTitle.vue';
|
import basicTitle from './src/BasicTitle.vue';
|
||||||
import basicHelp from './src/BasicHelp.vue';
|
import basicHelp from './src/BasicHelp.vue';
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
import { defineComponent, computed, unref } from 'vue';
|
import { defineComponent, computed, unref } from 'vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||||
import { getPopupContainer } from '/@/utils';
|
import { getPopupContainer } from '@/utils';
|
||||||
import { isString, isArray } from '/@/utils/is';
|
import { isString, isArray } from '@/utils/is';
|
||||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { useSlots, computed } from 'vue';
|
import { useSlots, computed } from 'vue';
|
||||||
import BasicHelp from './BasicHelp.vue';
|
import BasicHelp from './BasicHelp.vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import type { ExtractPropTypes } from 'vue';
|
import type { ExtractPropTypes } from 'vue';
|
||||||
import button from './src/BasicButton.vue';
|
import button from './src/BasicButton.vue';
|
||||||
import popConfirmButton from './src/PopConfirmButton.vue';
|
import popConfirmButton from './src/PopConfirmButton.vue';
|
||||||
|
@@ -2,10 +2,10 @@
|
|||||||
import { computed, defineComponent, h, unref } from 'vue';
|
import { computed, defineComponent, h, unref } from 'vue';
|
||||||
import BasicButton from './BasicButton.vue';
|
import BasicButton from './BasicButton.vue';
|
||||||
import { Popconfirm } from 'ant-design-vue';
|
import { Popconfirm } from 'ant-design-vue';
|
||||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
import { extendSlots } from '@/utils/helper/tsxHelper';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import cardList from './src/CardList.vue';
|
import cardList from './src/CardList.vue';
|
||||||
|
|
||||||
export const CardList = withInstall(cardList);
|
export const CardList = withInstall(cardList);
|
||||||
|
@@ -10,22 +10,23 @@
|
|||||||
:pagination="paginationProp"
|
:pagination="paginationProp"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex justify-end space-x-2"
|
<div class="flex justify-end space-x-2">
|
||||||
><slot name="header"></slot>
|
<slot name="header"> </slot>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="w-50">每行显示数量</div
|
<div class="w-50">每行显示数量</div>
|
||||||
><Slider
|
<Slider
|
||||||
id="slider"
|
id="slider"
|
||||||
v-bind="sliderProp"
|
v-bind="sliderProp"
|
||||||
v-model:value="grid"
|
v-model:value="grid"
|
||||||
@change="sliderChange"
|
@change="sliderChange"
|
||||||
/></template>
|
/>
|
||||||
<Button><TableOutlined /></Button>
|
</template>
|
||||||
|
<a-button><TableOutlined /></a-button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip @click="fetch">
|
<Tooltip @click="fetch">
|
||||||
<template #title>刷新</template>
|
<template #title>刷新</template>
|
||||||
<Button><RedoOutlined /></Button>
|
<a-button><RedoOutlined /></a-button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -83,11 +84,10 @@
|
|||||||
TableOutlined,
|
TableOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
|
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
|
||||||
import { Dropdown } from '/@/components/Dropdown';
|
import { Dropdown } from '@/components/Dropdown';
|
||||||
import { BasicForm, useForm } from '/@/components/Form';
|
import { BasicForm, useForm } from '@/components/Form';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { Button } from '/@/components/Button';
|
import { isFunction } from '@/utils/is';
|
||||||
import { isFunction } from '/@/utils/is';
|
|
||||||
import { useSlider, grid } from './data';
|
import { useSlider, grid } from './data';
|
||||||
|
|
||||||
const ListItem = List.Item;
|
const ListItem = List.Item;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import clickOutSide from './src/ClickOutSide.vue';
|
import clickOutSide from './src/ClickOutSide.vue';
|
||||||
|
|
||||||
export const ClickOutSide = withInstall(clickOutSide);
|
export const ClickOutSide = withInstall(clickOutSide);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import codeEditor from './src/CodeEditor.vue';
|
import codeEditor from './src/CodeEditor.vue';
|
||||||
import jsonPreview from './src/json-preview/JsonPreview.vue';
|
import jsonPreview from './src/json-preview/JsonPreview.vue';
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
|
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
|
||||||
import { isString } from '/@/utils/is';
|
import { isString } from '@/utils/is';
|
||||||
import { MODE } from './typing';
|
import { MODE } from './typing';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
import type { Nullable } from '@vben/types';
|
import type { Nullable } from '@vben/types';
|
||||||
import { useWindowSizeFn } from '@vben/hooks';
|
import { useWindowSizeFn } from '@vben/hooks';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
import { useAppStore } from '/@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import CodeMirror from 'codemirror';
|
import CodeMirror from 'codemirror';
|
||||||
import { MODE } from './../typing';
|
import { MODE } from './../typing';
|
||||||
// css
|
// css
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import collapseContainer from './src/collapse/CollapseContainer.vue';
|
import collapseContainer from './src/collapse/CollapseContainer.vue';
|
||||||
import scrollContainer from './src/ScrollContainer.vue';
|
import scrollContainer from './src/ScrollContainer.vue';
|
||||||
|
|
||||||
|
@@ -9,80 +9,69 @@
|
|||||||
</Scrollbar>
|
</Scrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, unref, nextTick } from 'vue';
|
import { ref, unref, nextTick } from 'vue';
|
||||||
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
|
import { Scrollbar, ScrollbarType } from '@/components/Scrollbar';
|
||||||
import { useScrollTo } from '@vben/hooks';
|
import { useScrollTo } from '@vben/hooks';
|
||||||
import { type Nullable } from '@vben/types';
|
import { type Nullable } from '@vben/types';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ScrollContainer' });
|
||||||
name: 'ScrollContainer',
|
|
||||||
components: { Scrollbar },
|
defineProps({
|
||||||
props: {
|
scrollHeight: {
|
||||||
scrollHeight: { type: Number },
|
type: Number,
|
||||||
},
|
},
|
||||||
setup() {
|
});
|
||||||
const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
|
|
||||||
|
|
||||||
/**
|
const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
|
||||||
* Scroll to the specified position
|
|
||||||
*/
|
function getScrollWrap() {
|
||||||
function scrollTo(to: number, duration = 500) {
|
const scrollbar = unref(scrollbarRef);
|
||||||
const scrollbar = unref(scrollbarRef);
|
if (!scrollbar) {
|
||||||
if (!scrollbar) {
|
return null;
|
||||||
return;
|
}
|
||||||
}
|
return scrollbar.wrap;
|
||||||
nextTick(() => {
|
}
|
||||||
const wrap = unref(scrollbar.wrap);
|
|
||||||
if (!wrap) {
|
/**
|
||||||
return;
|
* Scroll to the specified position
|
||||||
}
|
*/
|
||||||
const { start } = useScrollTo({
|
function scrollTo(to: number, duration = 500) {
|
||||||
el: wrap,
|
const wrap = unref(getScrollWrap());
|
||||||
to,
|
nextTick(() => {
|
||||||
duration,
|
if (!wrap) {
|
||||||
});
|
return;
|
||||||
start();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const { start } = useScrollTo({
|
||||||
|
el: wrap,
|
||||||
|
to,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getScrollWrap() {
|
/**
|
||||||
const scrollbar = unref(scrollbarRef);
|
* Scroll to the bottom
|
||||||
if (!scrollbar) {
|
*/
|
||||||
return null;
|
function scrollBottom() {
|
||||||
}
|
const wrap = unref(getScrollWrap());
|
||||||
return scrollbar.wrap;
|
nextTick(() => {
|
||||||
|
if (!wrap) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const scrollHeight = wrap.scrollHeight as number;
|
||||||
|
const { start } = useScrollTo({
|
||||||
|
el: wrap,
|
||||||
|
to: scrollHeight,
|
||||||
|
});
|
||||||
|
start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
defineExpose({
|
||||||
* Scroll to the bottom
|
scrollTo,
|
||||||
*/
|
scrollBottom,
|
||||||
function scrollBottom() {
|
|
||||||
const scrollbar = unref(scrollbarRef);
|
|
||||||
if (!scrollbar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nextTick(() => {
|
|
||||||
const wrap = unref(scrollbar.wrap) as any;
|
|
||||||
if (!wrap) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const scrollHeight = wrap.scrollHeight as number;
|
|
||||||
const { start } = useScrollTo({
|
|
||||||
el: wrap,
|
|
||||||
to: scrollHeight,
|
|
||||||
});
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
scrollbarRef,
|
|
||||||
scrollTo,
|
|
||||||
scrollBottom,
|
|
||||||
getScrollWrap,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { Skeleton } from 'ant-design-vue';
|
import { Skeleton } from 'ant-design-vue';
|
||||||
import { useTimeoutFn } from '@vben/hooks';
|
import { useTimeoutFn } from '@vben/hooks';
|
||||||
import { CollapseTransition } from '/@/components/Transition';
|
import { CollapseTransition } from '@/components/Transition';
|
||||||
import CollapseHeader from './CollapseHeader.vue';
|
import CollapseHeader from './CollapseHeader.vue';
|
||||||
import { triggerWindowResize } from '/@/utils/event';
|
import { triggerWindowResize } from '@/utils/event';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
const collapseContainerProps = {
|
const collapseContainerProps = {
|
||||||
title: { type: String, default: '' },
|
title: { type: String, default: '' },
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { defineComponent, computed, unref, type ExtractPropTypes, PropType } from 'vue';
|
import { defineComponent, computed, unref, type ExtractPropTypes, PropType } from 'vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { BasicArrow, BasicTitle } from '/@/components/Basic';
|
import { BasicArrow, BasicTitle } from '@/components/Basic';
|
||||||
|
|
||||||
const collapseHeaderProps = {
|
const collapseHeaderProps = {
|
||||||
prefixCls: String,
|
prefixCls: String,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import contextMenuVue from './ContextMenu.vue';
|
import contextMenuVue from './ContextMenu.vue';
|
||||||
import { isClient } from '/@/utils/is';
|
import { isClient } from '@/utils/is';
|
||||||
import { CreateContextOptions, ContextMenuProps } from './typing';
|
import { CreateContextOptions, ContextMenuProps } from './typing';
|
||||||
import { createVNode, render } from 'vue';
|
import { createVNode, render } from 'vue';
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import countButton from './src/CountButton.vue';
|
import countButton from './src/CountButton.vue';
|
||||||
import countdownInput from './src/CountdownInput.vue';
|
import countdownInput from './src/CountdownInput.vue';
|
||||||
|
|
||||||
|
@@ -3,60 +3,53 @@
|
|||||||
{{ getButtonText }}
|
{{ getButtonText }}
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watchEffect, computed, unref } from 'vue';
|
import { ref, watchEffect, computed, unref } from 'vue';
|
||||||
import { Button } from 'ant-design-vue';
|
import { Button } from 'ant-design-vue';
|
||||||
import { useCountdown } from './useCountdown';
|
import { useCountdown } from './useCountdown';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CountButton' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
value: { type: [Object, Number, String, Array] },
|
value: { type: [Object, Number, String, Array] },
|
||||||
count: { type: Number, default: 60 },
|
count: { type: Number, default: 60 },
|
||||||
beforeStartFunc: {
|
beforeStartFunc: {
|
||||||
type: Function as PropType<() => Promise<boolean>>,
|
type: Function as PropType<() => Promise<boolean>>,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CountButton',
|
|
||||||
components: { Button },
|
|
||||||
props,
|
|
||||||
setup(props) {
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const { currentCount, isStart, start, reset } = useCountdown(props.count);
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const getButtonText = computed(() => {
|
|
||||||
return !unref(isStart)
|
|
||||||
? t('component.countdown.normalText')
|
|
||||||
: t('component.countdown.sendText', [unref(currentCount)]);
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.value === undefined && reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: Judge whether there is an external function before execution, and decide whether to start after execution
|
|
||||||
*/
|
|
||||||
async function handleStart() {
|
|
||||||
const { beforeStartFunc } = props;
|
|
||||||
if (beforeStartFunc && isFunction(beforeStartFunc)) {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const canStart = await beforeStartFunc();
|
|
||||||
canStart && start();
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { handleStart, currentCount, loading, getButtonText, isStart };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const loading = ref(false);
|
||||||
|
const { currentCount, isStart, start, reset } = useCountdown(props.count);
|
||||||
|
|
||||||
|
const getButtonText = computed(() => {
|
||||||
|
return !unref(isStart)
|
||||||
|
? t('component.countdown.normalText')
|
||||||
|
: t('component.countdown.sendText', [unref(currentCount)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.value === undefined && reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Judge whether there is an external function before execution, and decide whether to start after execution
|
||||||
|
*/
|
||||||
|
async function handleStart() {
|
||||||
|
const { beforeStartFunc } = props;
|
||||||
|
if (beforeStartFunc && isFunction(beforeStartFunc)) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const canStart = await beforeStartFunc();
|
||||||
|
canStart && start();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -8,34 +8,26 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { PropType } from 'vue';
|
||||||
import CountButton from './CountButton.vue';
|
import CountButton from './CountButton.vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CountDownInput', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
value: { type: String },
|
value: { type: String },
|
||||||
size: { type: String, validator: (v) => ['default', 'large', 'small'].includes(v) },
|
size: { type: String, validator: (v: string) => ['default', 'large', 'small'].includes(v) },
|
||||||
count: { type: Number, default: 60 },
|
count: { type: Number, default: 60 },
|
||||||
sendCodeApi: {
|
sendCodeApi: {
|
||||||
type: Function as PropType<() => Promise<boolean>>,
|
type: Function as PropType<() => Promise<boolean>>,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CountDownInput',
|
|
||||||
components: { CountButton },
|
|
||||||
inheritAttrs: false,
|
|
||||||
props,
|
|
||||||
setup(props) {
|
|
||||||
const { prefixCls } = useDesign('countdown-input');
|
|
||||||
const [state] = useRuleFormItem(props);
|
|
||||||
|
|
||||||
return { prefixCls, state };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('countdown-input');
|
||||||
|
const [state] = useRuleFormItem(props);
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@prefix-cls: ~'@{namespace}-countdown-input';
|
@prefix-cls: ~'@{namespace}-countdown-input';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import countTo from './src/CountTo.vue';
|
import countTo from './src/CountTo.vue';
|
||||||
|
|
||||||
export const CountTo = withInstall(countTo);
|
export const CountTo = withInstall(countTo);
|
||||||
|
@@ -3,12 +3,14 @@
|
|||||||
{{ value }}
|
{{ value }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
import { ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
||||||
import { useTransition, TransitionPresets } from '@vueuse/core';
|
import { useTransition, TransitionPresets } from '@vueuse/core';
|
||||||
import { isNumber } from '/@/utils/is';
|
import { isNumber } from '@/utils/is';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CountTo' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
startVal: { type: Number, default: 0 },
|
startVal: { type: Number, default: 0 },
|
||||||
endVal: { type: Number, default: 2021 },
|
endVal: { type: Number, default: 2021 },
|
||||||
duration: { type: Number, default: 1500 },
|
duration: { type: Number, default: 1500 },
|
||||||
@@ -36,75 +38,70 @@
|
|||||||
* Digital animation
|
* Digital animation
|
||||||
*/
|
*/
|
||||||
transition: { type: String, default: 'linear' },
|
transition: { type: String, default: 'linear' },
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CountTo',
|
|
||||||
props,
|
|
||||||
emits: ['onStarted', 'onFinished'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const source = ref(props.startVal);
|
|
||||||
const disabled = ref(false);
|
|
||||||
let outputValue = useTransition(source);
|
|
||||||
|
|
||||||
const value = computed(() => formatNumber(unref(outputValue)));
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
source.value = props.startVal;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([() => props.startVal, () => props.endVal], () => {
|
|
||||||
if (props.autoplay) {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.autoplay && start();
|
|
||||||
});
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
run();
|
|
||||||
source.value = props.endVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
source.value = props.startVal;
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
outputValue = useTransition(source, {
|
|
||||||
disabled,
|
|
||||||
duration: props.duration,
|
|
||||||
onFinished: () => emit('onFinished'),
|
|
||||||
onStarted: () => emit('onStarted'),
|
|
||||||
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNumber(num: number | string) {
|
|
||||||
if (!num && num !== 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const { decimals, decimal, separator, suffix, prefix } = props;
|
|
||||||
num = Number(num).toFixed(decimals);
|
|
||||||
num += '';
|
|
||||||
|
|
||||||
const x = num.split('.');
|
|
||||||
let x1 = x[0];
|
|
||||||
const x2 = x.length > 1 ? decimal + x[1] : '';
|
|
||||||
|
|
||||||
const rgx = /(\d+)(\d{3})/;
|
|
||||||
if (separator && !isNumber(separator)) {
|
|
||||||
while (rgx.test(x1)) {
|
|
||||||
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefix + x1 + x2 + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { value, start, reset };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['onStarted', 'onFinished']);
|
||||||
|
|
||||||
|
const source = ref(props.startVal);
|
||||||
|
const disabled = ref(false);
|
||||||
|
let outputValue = useTransition(source);
|
||||||
|
|
||||||
|
const value = computed(() => formatNumber(unref(outputValue)));
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
source.value = props.startVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([() => props.startVal, () => props.endVal], () => {
|
||||||
|
if (props.autoplay) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
props.autoplay && start();
|
||||||
|
});
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
run();
|
||||||
|
source.value = props.endVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
source.value = props.startVal;
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
outputValue = useTransition(source, {
|
||||||
|
disabled,
|
||||||
|
duration: props.duration,
|
||||||
|
onFinished: () => emit('onFinished'),
|
||||||
|
onStarted: () => emit('onStarted'),
|
||||||
|
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(num: number | string) {
|
||||||
|
if (!num && num !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||||
|
num = Number(num).toFixed(decimals);
|
||||||
|
num += '';
|
||||||
|
|
||||||
|
const x = num.split('.');
|
||||||
|
let x1 = x[0];
|
||||||
|
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||||
|
|
||||||
|
const rgx = /(\d+)(\d{3})/;
|
||||||
|
if (separator && !isNumber(separator)) {
|
||||||
|
while (rgx.test(x1)) {
|
||||||
|
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefix + x1 + x2 + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ reset });
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import cropperImage from './src/Cropper.vue';
|
import cropperImage from './src/Cropper.vue';
|
||||||
import avatarCropper from './src/CropperAvatar.vue';
|
import avatarCropper from './src/CropperAvatar.vue';
|
||||||
|
|
||||||
|
@@ -10,13 +10,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from 'vue';
|
import { onMounted, ref, unref, computed, onUnmounted } from 'vue';
|
||||||
import Cropper from 'cropperjs';
|
import Cropper from 'cropperjs';
|
||||||
import 'cropperjs/dist/cropper.css';
|
import 'cropperjs/dist/cropper.css';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useDebounceFn } from '@vueuse/shared';
|
import { useDebounceFn } from '@vueuse/shared';
|
||||||
|
import { useAttrs } from '@vben/hooks';
|
||||||
|
|
||||||
type Options = Cropper.Options;
|
type Options = Cropper.Options;
|
||||||
|
|
||||||
@@ -43,7 +44,9 @@
|
|||||||
rotatable: true,
|
rotatable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CropperImage' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
src: { type: String, required: true },
|
src: { type: String, required: true },
|
||||||
alt: { type: String },
|
alt: { type: String },
|
||||||
circled: { type: Boolean, default: false },
|
circled: { type: Boolean, default: false },
|
||||||
@@ -55,124 +58,119 @@
|
|||||||
},
|
},
|
||||||
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
|
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
|
||||||
options: { type: Object as PropType<Options>, default: () => ({}) },
|
options: { type: Object as PropType<Options>, default: () => ({}) },
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CropperImage',
|
|
||||||
props,
|
|
||||||
emits: ['cropend', 'ready', 'cropendError'],
|
|
||||||
setup(props, { attrs, emit }) {
|
|
||||||
const imgElRef = ref<ElRef<HTMLImageElement>>();
|
|
||||||
const cropper = ref<Nullable<Cropper>>();
|
|
||||||
const isReady = ref(false);
|
|
||||||
|
|
||||||
const { prefixCls } = useDesign('cropper-image');
|
|
||||||
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
|
|
||||||
|
|
||||||
const getImageStyle = computed((): CSSProperties => {
|
|
||||||
return {
|
|
||||||
height: props.height,
|
|
||||||
maxWidth: '100%',
|
|
||||||
...props.imageStyle,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getClass = computed(() => {
|
|
||||||
return [
|
|
||||||
prefixCls,
|
|
||||||
attrs.class,
|
|
||||||
{
|
|
||||||
[`${prefixCls}--circled`]: props.circled,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const getWrapperStyle = computed((): CSSProperties => {
|
|
||||||
return { height: `${props.height}`.replace(/px/, '') + 'px' };
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(init);
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
cropper.value?.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
const imgEl = unref(imgElRef);
|
|
||||||
if (!imgEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cropper.value = new Cropper(imgEl, {
|
|
||||||
...defaultOptions,
|
|
||||||
ready: () => {
|
|
||||||
isReady.value = true;
|
|
||||||
realTimeCroppered();
|
|
||||||
emit('ready', cropper.value);
|
|
||||||
},
|
|
||||||
crop() {
|
|
||||||
debounceRealTimeCroppered();
|
|
||||||
},
|
|
||||||
zoom() {
|
|
||||||
debounceRealTimeCroppered();
|
|
||||||
},
|
|
||||||
cropmove() {
|
|
||||||
debounceRealTimeCroppered();
|
|
||||||
},
|
|
||||||
...props.options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Real-time display preview
|
|
||||||
function realTimeCroppered() {
|
|
||||||
props.realTimePreview && croppered();
|
|
||||||
}
|
|
||||||
|
|
||||||
// event: return base64 and width and height information after cropping
|
|
||||||
function croppered() {
|
|
||||||
if (!cropper.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let imgInfo = cropper.value.getData();
|
|
||||||
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (!blob) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let fileReader: FileReader = new FileReader();
|
|
||||||
fileReader.readAsDataURL(blob);
|
|
||||||
fileReader.onloadend = (e) => {
|
|
||||||
emit('cropend', {
|
|
||||||
imgBase64: e.target?.result ?? '',
|
|
||||||
imgInfo,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
fileReader.onerror = () => {
|
|
||||||
emit('cropendError');
|
|
||||||
};
|
|
||||||
}, 'image/png');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a circular picture canvas
|
|
||||||
function getRoundedCanvas() {
|
|
||||||
const sourceCanvas = cropper.value!.getCroppedCanvas();
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const context = canvas.getContext('2d')!;
|
|
||||||
const width = sourceCanvas.width;
|
|
||||||
const height = sourceCanvas.height;
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
context.imageSmoothingEnabled = true;
|
|
||||||
context.drawImage(sourceCanvas, 0, 0, width, height);
|
|
||||||
context.globalCompositeOperation = 'destination-in';
|
|
||||||
context.beginPath();
|
|
||||||
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
|
|
||||||
context.fill();
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['cropend', 'ready', 'cropendError']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const imgElRef = ref<ElRef<HTMLImageElement>>();
|
||||||
|
const cropper = ref<Nullable<Cropper>>();
|
||||||
|
const isReady = ref(false);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('cropper-image');
|
||||||
|
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
|
||||||
|
|
||||||
|
const getImageStyle = computed((): CSSProperties => {
|
||||||
|
return {
|
||||||
|
height: props.height,
|
||||||
|
maxWidth: '100%',
|
||||||
|
...props.imageStyle,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getClass = computed(() => {
|
||||||
|
return [
|
||||||
|
prefixCls,
|
||||||
|
attrs.class,
|
||||||
|
{
|
||||||
|
[`${prefixCls}--circled`]: props.circled,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperStyle = computed((): CSSProperties => {
|
||||||
|
return { height: `${props.height}`.replace(/px/, '') + 'px' };
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(init);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cropper.value?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const imgEl = unref(imgElRef);
|
||||||
|
if (!imgEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cropper.value = new Cropper(imgEl, {
|
||||||
|
...defaultOptions,
|
||||||
|
ready: () => {
|
||||||
|
isReady.value = true;
|
||||||
|
realTimeCroppered();
|
||||||
|
emit('ready', cropper.value);
|
||||||
|
},
|
||||||
|
crop() {
|
||||||
|
debounceRealTimeCroppered();
|
||||||
|
},
|
||||||
|
zoom() {
|
||||||
|
debounceRealTimeCroppered();
|
||||||
|
},
|
||||||
|
cropmove() {
|
||||||
|
debounceRealTimeCroppered();
|
||||||
|
},
|
||||||
|
...props.options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real-time display preview
|
||||||
|
function realTimeCroppered() {
|
||||||
|
props.realTimePreview && croppered();
|
||||||
|
}
|
||||||
|
|
||||||
|
// event: return base64 and width and height information after cropping
|
||||||
|
function croppered() {
|
||||||
|
if (!cropper.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let imgInfo = cropper.value.getData();
|
||||||
|
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (!blob) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let fileReader: FileReader = new FileReader();
|
||||||
|
fileReader.readAsDataURL(blob);
|
||||||
|
fileReader.onloadend = (e) => {
|
||||||
|
emit('cropend', {
|
||||||
|
imgBase64: e.target?.result ?? '',
|
||||||
|
imgInfo,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fileReader.onerror = () => {
|
||||||
|
emit('cropendError');
|
||||||
|
};
|
||||||
|
}, 'image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a circular picture canvas
|
||||||
|
function getRoundedCanvas() {
|
||||||
|
const sourceCanvas = cropper.value!.getCroppedCanvas();
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d')!;
|
||||||
|
const width = sourceCanvas.width;
|
||||||
|
const height = sourceCanvas.height;
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
context.imageSmoothingEnabled = true;
|
||||||
|
context.drawImage(sourceCanvas, 0, 0, width, height);
|
||||||
|
context.globalCompositeOperation = 'destination-in';
|
||||||
|
context.beginPath();
|
||||||
|
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
|
||||||
|
context.fill();
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@prefix-cls: ~'@{namespace}-cropper-image';
|
@prefix-cls: ~'@{namespace}-cropper-image';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="getClass" :style="getStyle">
|
<div :class="getClass" :style="getStyle">
|
||||||
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
|
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal()">
|
||||||
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
|
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ant-design:cloud-upload-outlined"
|
icon="ant-design:cloud-upload-outlined"
|
||||||
@@ -29,26 +29,19 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, CSSProperties, unref, ref, watchEffect, watch, PropType } from 'vue';
|
||||||
defineComponent,
|
|
||||||
computed,
|
|
||||||
CSSProperties,
|
|
||||||
unref,
|
|
||||||
ref,
|
|
||||||
watchEffect,
|
|
||||||
watch,
|
|
||||||
PropType,
|
|
||||||
} from 'vue';
|
|
||||||
import CropperModal from './CropperModal.vue';
|
import CropperModal from './CropperModal.vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useModal } from '/@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import type { ButtonProps } from '/@/components/Button';
|
import type { ButtonProps } from '@/components/Button';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CropperAvatar' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
width: { type: [String, Number], default: '200px' },
|
width: { type: [String, Number], default: '200px' },
|
||||||
value: { type: String },
|
value: { type: String },
|
||||||
showBtn: { type: Boolean, default: true },
|
showBtn: { type: Boolean, default: true },
|
||||||
@@ -59,65 +52,46 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
size: { type: Number, default: 5 },
|
size: { type: Number, default: 5 },
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CropperAvatar',
|
|
||||||
components: { CropperModal, Icon },
|
|
||||||
props,
|
|
||||||
emits: ['update:value', 'change'],
|
|
||||||
setup(props, { emit, expose }) {
|
|
||||||
const sourceValue = ref(props.value || '');
|
|
||||||
const { prefixCls } = useDesign('cropper-avatar');
|
|
||||||
const [register, { openModal, closeModal }] = useModal();
|
|
||||||
const { createMessage } = useMessage();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const getClass = computed(() => [prefixCls]);
|
|
||||||
|
|
||||||
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
|
|
||||||
|
|
||||||
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
|
|
||||||
|
|
||||||
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
|
|
||||||
|
|
||||||
const getImageWrapperStyle = computed(
|
|
||||||
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
|
|
||||||
);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
sourceValue.value = props.value || '';
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => sourceValue.value,
|
|
||||||
(v: string) => {
|
|
||||||
emit('update:value', v);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleUploadSuccess({ source, data }) {
|
|
||||||
sourceValue.value = source;
|
|
||||||
emit('change', { source, data });
|
|
||||||
createMessage.success(t('component.cropper.uploadSuccess'));
|
|
||||||
}
|
|
||||||
|
|
||||||
expose({ openModal: openModal.bind(null, true), closeModal });
|
|
||||||
|
|
||||||
return {
|
|
||||||
t,
|
|
||||||
prefixCls,
|
|
||||||
register,
|
|
||||||
openModal: openModal as any,
|
|
||||||
getIconWidth,
|
|
||||||
sourceValue,
|
|
||||||
getClass,
|
|
||||||
getImageWrapperStyle,
|
|
||||||
getStyle,
|
|
||||||
handleUploadSuccess,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change']);
|
||||||
|
|
||||||
|
const sourceValue = ref(props.value || '');
|
||||||
|
const { prefixCls } = useDesign('cropper-avatar');
|
||||||
|
const [register, { openModal, closeModal }] = useModal();
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const getClass = computed(() => [prefixCls]);
|
||||||
|
|
||||||
|
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
|
||||||
|
|
||||||
|
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
|
||||||
|
|
||||||
|
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
|
||||||
|
|
||||||
|
const getImageWrapperStyle = computed(
|
||||||
|
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
|
||||||
|
);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
sourceValue.value = props.value || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => sourceValue.value,
|
||||||
|
(v: string) => {
|
||||||
|
emit('update:value', v);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleUploadSuccess({ source, data }) {
|
||||||
|
sourceValue.value = source;
|
||||||
|
emit('change', { source, data });
|
||||||
|
createMessage.success(t('component.cropper.uploadSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openModal: openModal.bind(null, true), closeModal });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@@ -1,289 +1,274 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasicModal
|
<BasicModal
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@register="register"
|
@register="register"
|
||||||
:title="t('component.cropper.modalTitle')"
|
:title="t('component.cropper.modalTitle')"
|
||||||
width="800px"
|
width="800px"
|
||||||
:canFullscreen="false"
|
:canFullscreen="false"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
:okText="t('component.cropper.okText')"
|
:okText="t('component.cropper.okText')"
|
||||||
>
|
>
|
||||||
<div :class="prefixCls">
|
<div :class="prefixCls">
|
||||||
<div :class="`${prefixCls}-left`">
|
<div :class="`${prefixCls}-left`">
|
||||||
<div :class="`${prefixCls}-cropper`">
|
<div :class="`${prefixCls}-cropper`">
|
||||||
<CropperImage
|
<CropperImage
|
||||||
v-if="src"
|
v-if="src"
|
||||||
:src="src"
|
:src="src"
|
||||||
height="300px"
|
height="300px"
|
||||||
:circled="circled"
|
:circled="circled"
|
||||||
@cropend="handleCropend"
|
@cropend="handleCropend"
|
||||||
@ready="handleReady"
|
@ready="handleReady"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="`${prefixCls}-toolbar`">
|
<div :class="`${prefixCls}-toolbar`">
|
||||||
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
|
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
|
||||||
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
|
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
|
||||||
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
|
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Upload>
|
</Upload>
|
||||||
<Space>
|
<Space>
|
||||||
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="ant-design:reload-outlined"
|
preIcon="ant-design:reload-outlined"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('reset')"
|
@click="handlerToolbar('reset')"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="ant-design:rotate-left-outlined"
|
preIcon="ant-design:rotate-left-outlined"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('rotate', -45)"
|
@click="handlerToolbar('rotate', -45)"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="ant-design:rotate-right-outlined"
|
preIcon="ant-design:rotate-right-outlined"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('rotate', 45)"
|
@click="handlerToolbar('rotate', 45)"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="vaadin:arrows-long-h"
|
preIcon="vaadin:arrows-long-h"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('scaleX')"
|
@click="handlerToolbar('scaleX')"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="vaadin:arrows-long-v"
|
preIcon="vaadin:arrows-long-v"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('scaleY')"
|
@click="handlerToolbar('scaleY')"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="ant-design:zoom-in-outlined"
|
preIcon="ant-design:zoom-in-outlined"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('zoom', 0.1)"
|
@click="handlerToolbar('zoom', 0.1)"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
|
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
preIcon="ant-design:zoom-out-outlined"
|
preIcon="ant-design:zoom-out-outlined"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!src"
|
:disabled="!src"
|
||||||
@click="handlerToolbar('zoom', -0.1)"
|
@click="handlerToolbar('zoom', -0.1)"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="`${prefixCls}-right`">
|
<div :class="`${prefixCls}-right`">
|
||||||
<div :class="`${prefixCls}-preview`">
|
<div :class="`${prefixCls}-preview`">
|
||||||
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
|
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="previewSource">
|
<template v-if="previewSource">
|
||||||
<div :class="`${prefixCls}-group`">
|
<div :class="`${prefixCls}-group`">
|
||||||
<Avatar :src="previewSource" size="large" />
|
<Avatar :src="previewSource" size="large" />
|
||||||
<Avatar :src="previewSource" :size="48" />
|
<Avatar :src="previewSource" :size="48" />
|
||||||
<Avatar :src="previewSource" :size="64" />
|
<Avatar :src="previewSource" :size="64" />
|
||||||
<Avatar :src="previewSource" :size="80" />
|
<Avatar :src="previewSource" :size="80" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { CropendResult, Cropper } from './typing';
|
import type { CropendResult, Cropper } from './typing';
|
||||||
|
|
||||||
import { defineComponent, ref, PropType } from 'vue';
|
import { ref, PropType } from 'vue';
|
||||||
import CropperImage from './Cropper.vue';
|
import CropperImage from './Cropper.vue';
|
||||||
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
|
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||||
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
|
import { dataURLtoBlob } from '@/utils/file/base64Conver';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
type apiFunParams = { file: Blob; name: string; filename: string };
|
type apiFunParams = { file: Blob; name: string; filename: string };
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CropperModal' });
|
||||||
circled: { type: Boolean, default: true },
|
|
||||||
uploadApi: {
|
const props = defineProps({
|
||||||
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
|
circled: { type: Boolean, default: true },
|
||||||
},
|
uploadApi: {
|
||||||
src: { type: String },
|
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
|
||||||
size: { type: Number },
|
},
|
||||||
};
|
src: { type: String },
|
||||||
|
size: { type: Number },
|
||||||
export default defineComponent({
|
});
|
||||||
name: 'CropperModal',
|
|
||||||
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
|
const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']);
|
||||||
props,
|
|
||||||
emits: ['uploadSuccess', 'uploadError', 'register'],
|
let filename = '';
|
||||||
setup(props, { emit }) {
|
const src = ref(props.src || '');
|
||||||
let filename = '';
|
const previewSource = ref('');
|
||||||
const src = ref(props.src || '');
|
const cropper = ref<Cropper>();
|
||||||
const previewSource = ref('');
|
let scaleX = 1;
|
||||||
const cropper = ref<Cropper>();
|
let scaleY = 1;
|
||||||
let scaleX = 1;
|
|
||||||
let scaleY = 1;
|
const { prefixCls } = useDesign('cropper-am');
|
||||||
|
const [register, { closeModal, setModalProps }] = useModalInner();
|
||||||
const { prefixCls } = useDesign('cropper-am');
|
const { t } = useI18n();
|
||||||
const [register, { closeModal, setModalProps }] = useModalInner();
|
|
||||||
const { t } = useI18n();
|
// Block upload
|
||||||
|
function handleBeforeUpload(file: File) {
|
||||||
// Block upload
|
if (props.size && file.size > 1024 * 1024 * props.size) {
|
||||||
function handleBeforeUpload(file: File) {
|
emit('uploadError', { msg: t('component.cropper.imageTooBig') });
|
||||||
if (props.size && file.size > 1024 * 1024 * props.size) {
|
return;
|
||||||
emit('uploadError', { msg: t('component.cropper.imageTooBig') });
|
}
|
||||||
return;
|
const reader = new FileReader();
|
||||||
}
|
reader.readAsDataURL(file);
|
||||||
const reader = new FileReader();
|
src.value = '';
|
||||||
reader.readAsDataURL(file);
|
previewSource.value = '';
|
||||||
src.value = '';
|
reader.onload = function (e) {
|
||||||
previewSource.value = '';
|
src.value = (e.target?.result as string) ?? '';
|
||||||
reader.onload = function (e) {
|
filename = file.name;
|
||||||
src.value = (e.target?.result as string) ?? '';
|
};
|
||||||
filename = file.name;
|
return false;
|
||||||
};
|
}
|
||||||
return false;
|
|
||||||
}
|
function handleCropend({ imgBase64 }: CropendResult) {
|
||||||
|
previewSource.value = imgBase64;
|
||||||
function handleCropend({ imgBase64 }: CropendResult) {
|
}
|
||||||
previewSource.value = imgBase64;
|
|
||||||
}
|
function handleReady(cropperInstance: Cropper) {
|
||||||
|
cropper.value = cropperInstance;
|
||||||
function handleReady(cropperInstance: Cropper) {
|
}
|
||||||
cropper.value = cropperInstance;
|
|
||||||
}
|
function handlerToolbar(event: string, arg?: number) {
|
||||||
|
if (event === 'scaleX') {
|
||||||
function handlerToolbar(event: string, arg?: number) {
|
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||||
if (event === 'scaleX') {
|
}
|
||||||
scaleX = arg = scaleX === -1 ? 1 : -1;
|
if (event === 'scaleY') {
|
||||||
}
|
scaleY = arg = scaleY === -1 ? 1 : -1;
|
||||||
if (event === 'scaleY') {
|
}
|
||||||
scaleY = arg = scaleY === -1 ? 1 : -1;
|
cropper?.value?.[event]?.(arg);
|
||||||
}
|
}
|
||||||
cropper?.value?.[event]?.(arg);
|
|
||||||
}
|
async function handleOk() {
|
||||||
|
const uploadApi = props.uploadApi;
|
||||||
async function handleOk() {
|
if (uploadApi && isFunction(uploadApi)) {
|
||||||
const uploadApi = props.uploadApi;
|
const blob = dataURLtoBlob(previewSource.value);
|
||||||
if (uploadApi && isFunction(uploadApi)) {
|
try {
|
||||||
const blob = dataURLtoBlob(previewSource.value);
|
setModalProps({ confirmLoading: true });
|
||||||
try {
|
const result = await uploadApi({ name: 'file', file: blob, filename });
|
||||||
setModalProps({ confirmLoading: true });
|
emit('uploadSuccess', { source: previewSource.value, data: result.url });
|
||||||
const result = await uploadApi({ name: 'file', file: blob, filename });
|
closeModal();
|
||||||
emit('uploadSuccess', { source: previewSource.value, data: result.url });
|
} finally {
|
||||||
closeModal();
|
setModalProps({ confirmLoading: false });
|
||||||
} finally {
|
}
|
||||||
setModalProps({ confirmLoading: false });
|
}
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
}
|
|
||||||
|
<style lang="less">
|
||||||
return {
|
@prefix-cls: ~'@{namespace}-cropper-am';
|
||||||
t,
|
|
||||||
prefixCls,
|
.@{prefix-cls} {
|
||||||
src,
|
display: flex;
|
||||||
register,
|
|
||||||
previewSource,
|
&-left,
|
||||||
handleBeforeUpload,
|
&-right {
|
||||||
handleCropend,
|
height: 340px;
|
||||||
handleReady,
|
}
|
||||||
handlerToolbar,
|
|
||||||
handleOk,
|
&-left {
|
||||||
};
|
width: 55%;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
</script>
|
&-right {
|
||||||
|
width: 45%;
|
||||||
<style lang="less">
|
}
|
||||||
@prefix-cls: ~'@{namespace}-cropper-am';
|
|
||||||
|
&-cropper {
|
||||||
.@{prefix-cls} {
|
height: 300px;
|
||||||
display: flex;
|
background: #eee;
|
||||||
|
background-image: linear-gradient(
|
||||||
&-left,
|
45deg,
|
||||||
&-right {
|
rgb(0 0 0 / 25%) 25%,
|
||||||
height: 340px;
|
transparent 0,
|
||||||
}
|
transparent 75%,
|
||||||
|
rgb(0 0 0 / 25%) 0
|
||||||
&-left {
|
),
|
||||||
width: 55%;
|
linear-gradient(
|
||||||
}
|
45deg,
|
||||||
|
rgb(0 0 0 / 25%) 25%,
|
||||||
&-right {
|
transparent 0,
|
||||||
width: 45%;
|
transparent 75%,
|
||||||
}
|
rgb(0 0 0 / 25%) 0
|
||||||
|
);
|
||||||
&-cropper {
|
background-position:
|
||||||
height: 300px;
|
0 0,
|
||||||
background: #eee;
|
12px 12px;
|
||||||
background-image: linear-gradient(
|
background-size: 24px 24px;
|
||||||
45deg,
|
}
|
||||||
rgb(0 0 0 / 25%) 25%,
|
|
||||||
transparent 0,
|
&-toolbar {
|
||||||
transparent 75%,
|
display: flex;
|
||||||
rgb(0 0 0 / 25%) 0
|
align-items: center;
|
||||||
),
|
justify-content: space-between;
|
||||||
linear-gradient(
|
margin-top: 10px;
|
||||||
45deg,
|
}
|
||||||
rgb(0 0 0 / 25%) 25%,
|
|
||||||
transparent 0,
|
&-preview {
|
||||||
transparent 75%,
|
width: 220px;
|
||||||
rgb(0 0 0 / 25%) 0
|
height: 220px;
|
||||||
);
|
margin: 0 auto;
|
||||||
background-position: 0 0, 12px 12px;
|
overflow: hidden;
|
||||||
background-size: 24px 24px;
|
border: 1px solid @border-color-base;
|
||||||
}
|
border-radius: 50%;
|
||||||
|
|
||||||
&-toolbar {
|
img {
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
height: 100%;
|
||||||
justify-content: space-between;
|
}
|
||||||
margin-top: 10px;
|
}
|
||||||
}
|
|
||||||
|
&-group {
|
||||||
&-preview {
|
display: flex;
|
||||||
width: 220px;
|
align-items: center;
|
||||||
height: 220px;
|
justify-content: space-around;
|
||||||
margin: 0 auto;
|
margin-top: 8px;
|
||||||
overflow: hidden;
|
padding-top: 8px;
|
||||||
border: 1px solid @border-color-base;
|
border-top: 1px solid @border-color-base;
|
||||||
border-radius: 50%;
|
}
|
||||||
|
}
|
||||||
img {
|
</style>
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-top: 8px;
|
|
||||||
border-top: 1px solid @border-color-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import description from './src/Description.vue';
|
import description from './src/Description.vue';
|
||||||
|
|
||||||
export * from './src/typing';
|
export * from './src/typing';
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import type { DescriptionProps, DescInstance, DescItem } from './typing';
|
import type { DescriptionProps, DescInstance, DescItem } from './typing';
|
||||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
|
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
import type { CollapseContainerOptions } from '@/components/Container';
|
||||||
import {
|
import {
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
type PropType,
|
type PropType,
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
import { Descriptions } from 'ant-design-vue';
|
import { Descriptions } from 'ant-design-vue';
|
||||||
import { CollapseContainer } from '/@/components/Container/index';
|
import { CollapseContainer } from '@/components/Container';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
title: { type: String, default: '' },
|
title: { type: String, default: '' },
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
validator: (v) => ['small', 'default', 'middle', undefined].includes(v),
|
validator: (v: string) => ['small', 'default', 'middle', undefined].includes(v),
|
||||||
default: 'small',
|
default: 'small',
|
||||||
},
|
},
|
||||||
bordered: { type: Boolean, default: true },
|
bordered: { type: Boolean, default: true },
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { VNode, CSSProperties } from 'vue';
|
import type { VNode, CSSProperties } from 'vue';
|
||||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
import type { CollapseContainerOptions } from '@/components/Container';
|
||||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
|
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||||
|
|
||||||
export interface DescItem {
|
export interface DescItem {
|
||||||
labelMinWidth?: number;
|
labelMinWidth?: number;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
|
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
|
||||||
import { ref, getCurrentInstance, unref } from 'vue';
|
import { ref, getCurrentInstance, unref } from 'vue';
|
||||||
import { isProdMode } from '/@/utils/env';
|
import { isProdMode } from '@/utils/env';
|
||||||
|
|
||||||
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
|
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
|
||||||
if (!getCurrentInstance()) {
|
if (!getCurrentInstance()) {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import basicDrawer from './src/BasicDrawer.vue';
|
import basicDrawer from './src/BasicDrawer.vue';
|
||||||
|
|
||||||
export const BasicDrawer = withInstall(basicDrawer);
|
export const BasicDrawer = withInstall(basicDrawer);
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<Drawer v-bind="getBindValues" :class="prefixCls" @close="onClose">
|
<Drawer v-bind="getBindValues" :class="prefixCls" @close="onClose">
|
||||||
<template #title v-if="!$slots.title">
|
<template #title v-if="!$slots.title">
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
:title="getMergeProps.title"
|
:title="mergeProps.title"
|
||||||
:isDetail="isDetail"
|
:isDetail="isDetail"
|
||||||
:showDetailBack="showDetailBack"
|
:showDetailBack="showDetailBack"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@@ -30,166 +30,142 @@
|
|||||||
</DrawerFooter>
|
</DrawerFooter>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { DrawerInstance, DrawerProps } from './typing';
|
import type { DrawerInstance, DrawerProps } from './typing';
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import {
|
import { ref, computed, watch, unref, nextTick, toRaw, getCurrentInstance } from 'vue';
|
||||||
defineComponent,
|
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
watch,
|
|
||||||
unref,
|
|
||||||
nextTick,
|
|
||||||
toRaw,
|
|
||||||
getCurrentInstance,
|
|
||||||
} from 'vue';
|
|
||||||
import { Drawer } from 'ant-design-vue';
|
import { Drawer } from 'ant-design-vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { isFunction, isNumber } from '/@/utils/is';
|
import { isFunction, isNumber } from '@/utils/is';
|
||||||
import { deepMerge } from '/@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
import DrawerFooter from './components/DrawerFooter.vue';
|
import DrawerFooter from './components/DrawerFooter.vue';
|
||||||
import DrawerHeader from './components/DrawerHeader.vue';
|
import DrawerHeader from './components/DrawerHeader.vue';
|
||||||
import { ScrollContainer } from '/@/components/Container';
|
import { ScrollContainer } from '@/components/Container';
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ inheritAttrs: false });
|
||||||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: basicProps,
|
|
||||||
emits: ['open-change', 'ok', 'close', 'register'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const openRef = ref(false);
|
|
||||||
const attrs = useAttrs();
|
|
||||||
const propsRef = ref<Partial<DrawerProps | null>>(null);
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const props = defineProps(basicProps);
|
||||||
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
|
||||||
|
|
||||||
const drawerInstance: DrawerInstance = {
|
const emit = defineEmits(['open-change', 'ok', 'close', 'register']);
|
||||||
setDrawerProps: setDrawerProps as any,
|
|
||||||
emitOpen: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const openRef = ref(false);
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const propsRef = ref<Partial<DrawerProps | null>>(null);
|
||||||
|
|
||||||
instance && emit('register', drawerInstance, instance.uid);
|
const { t } = useI18n();
|
||||||
|
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
||||||
|
|
||||||
const getMergeProps = computed((): DrawerProps => {
|
const drawerInstance: DrawerInstance = {
|
||||||
return deepMerge(toRaw(props), unref(propsRef)) as any;
|
setDrawerProps: setDrawerProps as any,
|
||||||
});
|
emitOpen: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const getProps = computed((): DrawerProps => {
|
const instance = getCurrentInstance();
|
||||||
const opt = {
|
|
||||||
placement: 'right',
|
|
||||||
...unref(attrs),
|
|
||||||
...unref(getMergeProps),
|
|
||||||
open: unref(openRef),
|
|
||||||
};
|
|
||||||
opt.title = undefined;
|
|
||||||
const { isDetail, width, wrapClassName, getContainer } = opt;
|
|
||||||
if (isDetail) {
|
|
||||||
if (!width) {
|
|
||||||
opt.width = '100%';
|
|
||||||
}
|
|
||||||
const detailCls = `${prefixCls}__detail`;
|
|
||||||
opt.rootClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
|
||||||
|
|
||||||
if (!getContainer) {
|
instance && emit('register', drawerInstance, instance.uid);
|
||||||
opt.getContainer = `.${prefixVar}-layout-content`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return opt as DrawerProps;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getBindValues = computed((): DrawerProps => {
|
const getMergeProps = computed((): DrawerProps => {
|
||||||
return {
|
return deepMerge(toRaw(props), unref(propsRef)) as any;
|
||||||
...attrs,
|
|
||||||
...unref(getProps),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Custom implementation of the bottom button,
|
|
||||||
const getFooterHeight = computed(() => {
|
|
||||||
const { footerHeight, showFooter } = unref(getProps);
|
|
||||||
if (showFooter && footerHeight) {
|
|
||||||
return isNumber(footerHeight)
|
|
||||||
? `${footerHeight}px`
|
|
||||||
: `${footerHeight.replace('px', '')}px`;
|
|
||||||
}
|
|
||||||
return `0px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getScrollContentStyle = computed((): CSSProperties => {
|
|
||||||
const footerHeight = unref(getFooterHeight);
|
|
||||||
return {
|
|
||||||
position: 'relative',
|
|
||||||
height: `calc(100% - ${footerHeight})`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getLoading = computed(() => {
|
|
||||||
return !!unref(getProps)?.loading;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.open,
|
|
||||||
(newVal, oldVal) => {
|
|
||||||
if (newVal !== oldVal) openRef.value = newVal;
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => openRef.value,
|
|
||||||
(open) => {
|
|
||||||
nextTick(() => {
|
|
||||||
emit('open-change', open);
|
|
||||||
instance && drawerInstance.emitOpen?.(open, instance.uid);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cancel event
|
|
||||||
async function onClose(e) {
|
|
||||||
const { closeFunc } = unref(getProps);
|
|
||||||
emit('close', e);
|
|
||||||
if (closeFunc && isFunction(closeFunc)) {
|
|
||||||
const res = await closeFunc();
|
|
||||||
openRef.value = !res;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openRef.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
|
||||||
// Keep the last setDrawerProps
|
|
||||||
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
|
||||||
|
|
||||||
if (Reflect.has(props, 'open')) {
|
|
||||||
openRef.value = !!props.open;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOk() {
|
|
||||||
emit('ok');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
onClose,
|
|
||||||
t,
|
|
||||||
prefixCls,
|
|
||||||
getMergeProps: getMergeProps as any,
|
|
||||||
getScrollContentStyle,
|
|
||||||
getProps: getProps as any,
|
|
||||||
getLoading,
|
|
||||||
getBindValues,
|
|
||||||
getFooterHeight,
|
|
||||||
handleOk,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getProps = computed((): DrawerProps => {
|
||||||
|
const opt = {
|
||||||
|
placement: 'right',
|
||||||
|
...unref(attrs),
|
||||||
|
...unref(getMergeProps),
|
||||||
|
open: unref(openRef),
|
||||||
|
};
|
||||||
|
opt.title = undefined;
|
||||||
|
const { isDetail, width, wrapClassName, getContainer } = opt;
|
||||||
|
if (isDetail) {
|
||||||
|
if (!width) {
|
||||||
|
opt.width = '100%';
|
||||||
|
}
|
||||||
|
const detailCls = `${prefixCls}__detail`;
|
||||||
|
opt.rootClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||||
|
|
||||||
|
if (!getContainer) {
|
||||||
|
opt.getContainer = `.${prefixVar}-layout-content`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt as DrawerProps;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getBindValues = computed((): DrawerProps => {
|
||||||
|
return {
|
||||||
|
...attrs,
|
||||||
|
...unref(getProps),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom implementation of the bottom button,
|
||||||
|
const getFooterHeight = computed(() => {
|
||||||
|
const { footerHeight, showFooter } = unref(getProps);
|
||||||
|
if (showFooter && footerHeight) {
|
||||||
|
return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
|
||||||
|
}
|
||||||
|
return `0px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getScrollContentStyle = computed((): CSSProperties => {
|
||||||
|
const footerHeight = unref(getFooterHeight);
|
||||||
|
return {
|
||||||
|
position: 'relative',
|
||||||
|
height: `calc(100% - ${footerHeight})`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLoading = computed(() => {
|
||||||
|
return !!unref(getProps)?.loading;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (newVal !== oldVal) openRef.value = newVal;
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => openRef.value,
|
||||||
|
(open) => {
|
||||||
|
nextTick(() => {
|
||||||
|
emit('open-change', open);
|
||||||
|
instance && drawerInstance.emitOpen?.(open, instance.uid);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cancel event
|
||||||
|
async function onClose(e) {
|
||||||
|
const { closeFunc } = unref(getProps);
|
||||||
|
emit('close', e);
|
||||||
|
if (closeFunc && isFunction(closeFunc)) {
|
||||||
|
const res = await closeFunc();
|
||||||
|
openRef.value = !res;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openRef.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||||
|
// Keep the last setDrawerProps
|
||||||
|
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
||||||
|
|
||||||
|
if (Reflect.has(props, 'open')) {
|
||||||
|
openRef.value = !!props.open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOk() {
|
||||||
|
emit('ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeProps = getMergeProps as any;
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@header-height: 60px;
|
@header-height: 60px;
|
||||||
|
@@ -24,44 +24,41 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import { defineComponent, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
import { footerProps } from '../props';
|
import { footerProps } from '../props';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicDrawerFooter' });
|
||||||
name: 'BasicDrawerFooter',
|
|
||||||
props: {
|
|
||||||
...footerProps,
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '60px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['ok', 'close'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { prefixCls } = useDesign('basic-drawer-footer');
|
|
||||||
|
|
||||||
const getStyle = computed((): CSSProperties => {
|
const props = defineProps({
|
||||||
const heightStr = `${props.height}`;
|
...footerProps,
|
||||||
return {
|
height: {
|
||||||
height: heightStr,
|
type: String,
|
||||||
lineHeight: `calc(${heightStr} - 1px)`,
|
default: '60px',
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleOk() {
|
|
||||||
emit('ok');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
emit('close');
|
|
||||||
}
|
|
||||||
return { handleOk, prefixCls, handleClose, getStyle };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['ok', 'close']);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('basic-drawer-footer');
|
||||||
|
|
||||||
|
const getStyle = computed((): CSSProperties => {
|
||||||
|
const heightStr = `${props.height}`;
|
||||||
|
return {
|
||||||
|
height: heightStr,
|
||||||
|
lineHeight: `calc(${heightStr} - 1px)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleOk() {
|
||||||
|
emit('ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@@ -17,34 +17,27 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { BasicTitle } from '@/components/Basic';
|
||||||
import { BasicTitle } from '/@/components/Basic';
|
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
defineOptions({ name: 'BasicDrawerHeader' });
|
||||||
|
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
defineProps({
|
||||||
|
isDetail: propTypes.bool,
|
||||||
export default defineComponent({
|
showDetailBack: propTypes.bool,
|
||||||
name: 'BasicDrawerHeader',
|
title: propTypes.string,
|
||||||
components: { BasicTitle, ArrowLeftOutlined },
|
|
||||||
props: {
|
|
||||||
isDetail: propTypes.bool,
|
|
||||||
showDetailBack: propTypes.bool,
|
|
||||||
title: propTypes.string,
|
|
||||||
},
|
|
||||||
emits: ['close'],
|
|
||||||
setup(_, { emit }) {
|
|
||||||
const { prefixCls } = useDesign('basic-drawer-header');
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
emit('close');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { prefixCls, handleClose };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('basic-drawer-header');
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
||||||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
|
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
|
||||||
import type { ScrollContainerOptions } from '/@/components/Container/index';
|
import type { ScrollContainerOptions } from '@/components/Container';
|
||||||
|
|
||||||
export interface DrawerInstance {
|
export interface DrawerInstance {
|
||||||
setDrawerProps: (props: Partial<DrawerProps>) => void;
|
setDrawerProps: (props: Partial<DrawerProps>) => void;
|
||||||
|
@@ -15,11 +15,11 @@ import {
|
|||||||
toRaw,
|
toRaw,
|
||||||
computed,
|
computed,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { isProdMode } from '/@/utils/env';
|
import { isProdMode } from '@/utils/env';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { tryOnUnmounted } from '@vueuse/core';
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { error } from '/@/utils/log';
|
import { error } from '@/utils/log';
|
||||||
|
|
||||||
const dataTransferRef = reactive<any>({});
|
const dataTransferRef = reactive<any>({});
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import dropdown from './src/Dropdown.vue';
|
import dropdown from './src/Dropdown.vue';
|
||||||
|
|
||||||
export * from './src/typing';
|
export * from './src/typing';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import impExcel from './src/ImportExcel.vue';
|
import impExcel from './src/ImportExcel.vue';
|
||||||
import expExcelModal from './src/ExportExcelModal.vue';
|
import expExcelModal from './src/ExportExcelModal.vue';
|
||||||
|
|
||||||
|
@@ -13,13 +13,12 @@
|
|||||||
/>
|
/>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { ExportModalResult } from './typing';
|
import type { ExportModalResult } from './typing';
|
||||||
import { defineComponent } from 'vue';
|
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
import { BasicForm, FormSchema, useForm } from '@/components/Form';
|
||||||
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
|
|
||||||
|
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -62,30 +61,19 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export default defineComponent({
|
|
||||||
components: { BasicModal, BasicForm },
|
|
||||||
emits: ['success', 'register'],
|
|
||||||
setup(_, { emit }) {
|
|
||||||
const [registerForm, { validate }] = useForm();
|
|
||||||
const [registerModal, { closeModal }] = useModalInner();
|
|
||||||
|
|
||||||
const handleOk = async () => {
|
const emit = defineEmits(['success', 'register']);
|
||||||
const res = await validate<ExportModalResult>();
|
|
||||||
const { filename, bookType } = res;
|
|
||||||
emit('success', {
|
|
||||||
filename: `${filename.split('.').shift()}.${bookType}`,
|
|
||||||
bookType,
|
|
||||||
});
|
|
||||||
closeModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
const [registerForm, { validate }] = useForm();
|
||||||
schemas,
|
const [registerModal, { closeModal }] = useModalInner();
|
||||||
handleOk,
|
|
||||||
registerForm,
|
const handleOk = async () => {
|
||||||
registerModal,
|
const res = await validate<ExportModalResult>();
|
||||||
t,
|
const { filename, bookType } = res;
|
||||||
};
|
emit('success', {
|
||||||
},
|
filename: `${filename.split('.').shift()}.${bookType}`,
|
||||||
});
|
bookType,
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -12,214 +12,210 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, unref } from 'vue';
|
import { ref, unref } from 'vue';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '@/utils/dateUtil';
|
||||||
|
|
||||||
import type { ExcelData } from './typing';
|
import type { ExcelData } from './typing';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ImportExcel' });
|
||||||
name: 'ImportExcel',
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
// 日期时间格式。如果不提供或者提供空值,将返回原始Date对象
|
// 日期时间格式。如果不提供或者提供空值,将返回原始Date对象
|
||||||
dateFormat: {
|
dateFormat: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
|
||||||
// 时区调整。实验性功能,仅为了解决读取日期时间值有偏差的问题。目前仅提供了+08:00时区的偏差修正值
|
|
||||||
// https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554
|
|
||||||
timeZone: {
|
|
||||||
type: Number,
|
|
||||||
default: 8,
|
|
||||||
},
|
|
||||||
// 是否直接返回选中文件
|
|
||||||
isReturnFile: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['success', 'error', 'cancel'],
|
// 时区调整。实验性功能,仅为了解决读取日期时间值有偏差的问题。目前仅提供了+08:00时区的偏差修正值
|
||||||
setup(props, { emit }) {
|
// https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554
|
||||||
const inputRef = ref<HTMLInputElement | null>(null);
|
timeZone: {
|
||||||
const loadingRef = ref<Boolean>(false);
|
type: Number,
|
||||||
const cancelRef = ref<Boolean>(true);
|
default: 8,
|
||||||
|
},
|
||||||
function shapeWorkSheel(sheet: XLSX.WorkSheet, range: XLSX.Range) {
|
// 是否直接返回选中文件
|
||||||
let str = ' ',
|
isReturnFile: {
|
||||||
char = 65,
|
type: Boolean,
|
||||||
customWorkSheet = {
|
default: false,
|
||||||
t: 's',
|
|
||||||
v: str,
|
|
||||||
r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>',
|
|
||||||
h: str,
|
|
||||||
w: str,
|
|
||||||
};
|
|
||||||
if (!sheet || !sheet['!ref']) return [];
|
|
||||||
let c = 0,
|
|
||||||
r = 1;
|
|
||||||
while (c < range.e.c + 1) {
|
|
||||||
while (r < range.e.r + 1) {
|
|
||||||
if (!sheet[String.fromCharCode(char) + r]) {
|
|
||||||
sheet[String.fromCharCode(char) + r] = customWorkSheet;
|
|
||||||
}
|
|
||||||
r++;
|
|
||||||
}
|
|
||||||
r = 1;
|
|
||||||
str += ' ';
|
|
||||||
customWorkSheet = {
|
|
||||||
t: 's',
|
|
||||||
v: str,
|
|
||||||
r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>',
|
|
||||||
h: str,
|
|
||||||
w: str,
|
|
||||||
};
|
|
||||||
c++;
|
|
||||||
char++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 第一行作为头部
|
|
||||||
*/
|
|
||||||
function getHeaderRow(sheet: XLSX.WorkSheet) {
|
|
||||||
if (!sheet || !sheet['!ref']) return [];
|
|
||||||
const headers: string[] = [];
|
|
||||||
// A3:B7=>{s:{c:0, r:2}, e:{c:1, r:6}}
|
|
||||||
const range: XLSX.Range = XLSX.utils.decode_range(sheet['!ref']);
|
|
||||||
shapeWorkSheel(sheet, range);
|
|
||||||
const R = range.s.r;
|
|
||||||
/* start in the first row */
|
|
||||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
|
||||||
/* walk every column in the range */
|
|
||||||
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
|
|
||||||
/* find the cell in the first row */
|
|
||||||
let hdr = 'UNKNOWN ' + C; // <-- replace with your desired default
|
|
||||||
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
|
|
||||||
headers.push(hdr);
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 获得excel数据
|
|
||||||
*/
|
|
||||||
function getExcelData(workbook: XLSX.WorkBook) {
|
|
||||||
const excelData: ExcelData[] = [];
|
|
||||||
const { dateFormat, timeZone } = props;
|
|
||||||
for (const sheetName of workbook.SheetNames) {
|
|
||||||
const worksheet = workbook.Sheets[sheetName];
|
|
||||||
const header: string[] = getHeaderRow(worksheet);
|
|
||||||
let results = XLSX.utils.sheet_to_json(worksheet, {
|
|
||||||
raw: true,
|
|
||||||
dateNF: dateFormat, //Not worked
|
|
||||||
}) as object[];
|
|
||||||
results = results.map((row: object) => {
|
|
||||||
for (let field in row) {
|
|
||||||
if (row[field] instanceof Date) {
|
|
||||||
if (timeZone === 8) {
|
|
||||||
row[field].setSeconds(row[field].getSeconds() + 43);
|
|
||||||
}
|
|
||||||
if (dateFormat) {
|
|
||||||
row[field] = dateUtil(row[field]).format(dateFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
excelData.push({
|
|
||||||
header,
|
|
||||||
results,
|
|
||||||
meta: {
|
|
||||||
sheetName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return excelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 读取excel数据
|
|
||||||
*/
|
|
||||||
function readerData(rawFile: File) {
|
|
||||||
loadingRef.value = true;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = async (e) => {
|
|
||||||
try {
|
|
||||||
const data = e.target && e.target.result;
|
|
||||||
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
|
|
||||||
// console.log(workbook);
|
|
||||||
/* DO SOMETHING WITH workbook HERE */
|
|
||||||
const excelData = getExcelData(workbook);
|
|
||||||
emit('success', excelData);
|
|
||||||
resolve('');
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
emit('error');
|
|
||||||
} finally {
|
|
||||||
loadingRef.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(rawFile);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function upload(rawFile: File) {
|
|
||||||
const inputRefDom = unref(inputRef);
|
|
||||||
if (inputRefDom) {
|
|
||||||
// fix can't select the same excel
|
|
||||||
inputRefDom.value = '';
|
|
||||||
}
|
|
||||||
await readerData(rawFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 触发选择文件管理器
|
|
||||||
*/
|
|
||||||
function handleInputClick(e: Event) {
|
|
||||||
const target = e && (e.target as HTMLInputElement);
|
|
||||||
const files = target?.files;
|
|
||||||
const rawFile = files && files[0]; // only setting files[0]
|
|
||||||
|
|
||||||
target.value = '';
|
|
||||||
|
|
||||||
if (!rawFile) return;
|
|
||||||
|
|
||||||
cancelRef.value = false;
|
|
||||||
if (props.isReturnFile) {
|
|
||||||
emit('success', rawFile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
upload(rawFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 文件选择器关闭后,判断取消状态
|
|
||||||
*/
|
|
||||||
function handleFocusChange() {
|
|
||||||
const timeId = setInterval(() => {
|
|
||||||
if (cancelRef.value === true) {
|
|
||||||
emit('cancel');
|
|
||||||
}
|
|
||||||
clearInterval(timeId);
|
|
||||||
window.removeEventListener('focus', handleFocusChange);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 点击上传按钮
|
|
||||||
*/
|
|
||||||
function handleUpload() {
|
|
||||||
const inputRefDom = unref(inputRef);
|
|
||||||
if (inputRefDom) {
|
|
||||||
cancelRef.value = true;
|
|
||||||
inputRefDom.click();
|
|
||||||
window.addEventListener('focus', handleFocusChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { handleUpload, handleInputClick, inputRef };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'error', 'cancel']);
|
||||||
|
|
||||||
|
const inputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
const loadingRef = ref<Boolean>(false);
|
||||||
|
const cancelRef = ref<Boolean>(true);
|
||||||
|
|
||||||
|
function shapeWorkSheel(sheet: XLSX.WorkSheet, range: XLSX.Range) {
|
||||||
|
let str = ' ',
|
||||||
|
char = 65,
|
||||||
|
customWorkSheet = {
|
||||||
|
t: 's',
|
||||||
|
v: str,
|
||||||
|
r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>',
|
||||||
|
h: str,
|
||||||
|
w: str,
|
||||||
|
};
|
||||||
|
if (!sheet || !sheet['!ref']) return [];
|
||||||
|
let c = 0,
|
||||||
|
r = 1;
|
||||||
|
while (c < range.e.c + 1) {
|
||||||
|
while (r < range.e.r + 1) {
|
||||||
|
if (!sheet[String.fromCharCode(char) + r]) {
|
||||||
|
sheet[String.fromCharCode(char) + r] = customWorkSheet;
|
||||||
|
}
|
||||||
|
r++;
|
||||||
|
}
|
||||||
|
r = 1;
|
||||||
|
str += ' ';
|
||||||
|
customWorkSheet = {
|
||||||
|
t: 's',
|
||||||
|
v: str,
|
||||||
|
r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>',
|
||||||
|
h: str,
|
||||||
|
w: str,
|
||||||
|
};
|
||||||
|
c++;
|
||||||
|
char++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 第一行作为头部
|
||||||
|
*/
|
||||||
|
function getHeaderRow(sheet: XLSX.WorkSheet) {
|
||||||
|
if (!sheet || !sheet['!ref']) return [];
|
||||||
|
const headers: string[] = [];
|
||||||
|
// A3:B7=>{s:{c:0, r:2}, e:{c:1, r:6}}
|
||||||
|
const range: XLSX.Range = XLSX.utils.decode_range(sheet['!ref']);
|
||||||
|
shapeWorkSheel(sheet, range);
|
||||||
|
const R = range.s.r;
|
||||||
|
/* start in the first row */
|
||||||
|
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||||
|
/* walk every column in the range */
|
||||||
|
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
|
||||||
|
/* find the cell in the first row */
|
||||||
|
let hdr = 'UNKNOWN ' + C; // <-- replace with your desired default
|
||||||
|
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
|
||||||
|
headers.push(hdr);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获得excel数据
|
||||||
|
*/
|
||||||
|
function getExcelData(workbook: XLSX.WorkBook) {
|
||||||
|
const excelData: ExcelData[] = [];
|
||||||
|
const { dateFormat, timeZone } = props;
|
||||||
|
for (const sheetName of workbook.SheetNames) {
|
||||||
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
|
const header: string[] = getHeaderRow(worksheet);
|
||||||
|
let results = XLSX.utils.sheet_to_json(worksheet, {
|
||||||
|
raw: true,
|
||||||
|
dateNF: dateFormat, //Not worked
|
||||||
|
}) as object[];
|
||||||
|
results = results.map((row: object) => {
|
||||||
|
for (let field in row) {
|
||||||
|
if (row[field] instanceof Date) {
|
||||||
|
if (timeZone === 8) {
|
||||||
|
row[field].setSeconds(row[field].getSeconds() + 43);
|
||||||
|
}
|
||||||
|
if (dateFormat) {
|
||||||
|
row[field] = dateUtil(row[field]).format(dateFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
excelData.push({
|
||||||
|
header,
|
||||||
|
results,
|
||||||
|
meta: {
|
||||||
|
sheetName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return excelData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 读取excel数据
|
||||||
|
*/
|
||||||
|
function readerData(rawFile: File) {
|
||||||
|
loadingRef.value = true;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
try {
|
||||||
|
const data = e.target && e.target.result;
|
||||||
|
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
|
||||||
|
// console.log(workbook);
|
||||||
|
/* DO SOMETHING WITH workbook HERE */
|
||||||
|
const excelData = getExcelData(workbook);
|
||||||
|
emit('success', excelData);
|
||||||
|
resolve('');
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
emit('error');
|
||||||
|
} finally {
|
||||||
|
loadingRef.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(rawFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload(rawFile: File) {
|
||||||
|
const inputRefDom = unref(inputRef);
|
||||||
|
if (inputRefDom) {
|
||||||
|
// fix can't select the same excel
|
||||||
|
inputRefDom.value = '';
|
||||||
|
}
|
||||||
|
await readerData(rawFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 触发选择文件管理器
|
||||||
|
*/
|
||||||
|
function handleInputClick(e: Event) {
|
||||||
|
const target = e && (e.target as HTMLInputElement);
|
||||||
|
const files = target?.files;
|
||||||
|
const rawFile = files && files[0]; // only setting files[0]
|
||||||
|
|
||||||
|
target.value = '';
|
||||||
|
|
||||||
|
if (!rawFile) return;
|
||||||
|
|
||||||
|
cancelRef.value = false;
|
||||||
|
if (props.isReturnFile) {
|
||||||
|
emit('success', rawFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
upload(rawFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 文件选择器关闭后,判断取消状态
|
||||||
|
*/
|
||||||
|
function handleFocusChange() {
|
||||||
|
const timeId = setInterval(() => {
|
||||||
|
if (cancelRef.value === true) {
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
clearInterval(timeId);
|
||||||
|
window.removeEventListener('focus', handleFocusChange);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 点击上传按钮
|
||||||
|
*/
|
||||||
|
function handleUpload() {
|
||||||
|
const inputRefDom = unref(inputRef);
|
||||||
|
if (inputRefDom) {
|
||||||
|
cancelRef.value = true;
|
||||||
|
inputRefDom.click();
|
||||||
|
window.addEventListener('focus', handleFocusChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import flowChart from './src/FlowChart.vue';
|
import flowChart from './src/FlowChart.vue';
|
||||||
|
|
||||||
export const FlowChart = withInstall(flowChart);
|
export const FlowChart = withInstall(flowChart);
|
||||||
|
@@ -7,152 +7,141 @@
|
|||||||
</BasicModal>
|
</BasicModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { Definition } from '@logicflow/core';
|
import type { Definition } from '@logicflow/core';
|
||||||
import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue';
|
import { ref, onMounted, unref, nextTick, computed, watch } from 'vue';
|
||||||
import FlowChartToolbar from './FlowChartToolbar.vue';
|
import FlowChartToolbar from './FlowChartToolbar.vue';
|
||||||
import LogicFlow from '@logicflow/core';
|
import LogicFlow from '@logicflow/core';
|
||||||
import { Snapshot, BpmnElement, Menu, DndPanel, SelectionSelect } from '@logicflow/extension';
|
import { Snapshot, BpmnElement, Menu, DndPanel, SelectionSelect } from '@logicflow/extension';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { useAppStore } from '/@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { createFlowChartContext } from './useFlowContext';
|
import { createFlowChartContext } from './useFlowContext';
|
||||||
import { toLogicFlowData } from './adpterForTurbo';
|
import { toLogicFlowData } from './adpterForTurbo';
|
||||||
import { useModal, BasicModal } from '/@/components/Modal';
|
import { useModal, BasicModal } from '@/components/Modal';
|
||||||
import { JsonPreview } from '/@/components/CodeEditor';
|
import { JsonPreview } from '@/components/CodeEditor';
|
||||||
import { configDefaultDndPanel } from './config';
|
import { configDefaultDndPanel } from './config';
|
||||||
import '@logicflow/core/dist/style/index.css';
|
import '@logicflow/core/dist/style/index.css';
|
||||||
import '@logicflow/extension/lib/style/index.css';
|
import '@logicflow/extension/lib/style/index.css';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'FlowChart' });
|
||||||
name: 'FlowChart',
|
|
||||||
components: { BasicModal, FlowChartToolbar, JsonPreview },
|
|
||||||
props: {
|
|
||||||
flowOptions: {
|
|
||||||
type: Object as PropType<Definition>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
|
|
||||||
data: {
|
const props = defineProps({
|
||||||
type: Object as PropType<any>,
|
flowOptions: {
|
||||||
default: () => ({}),
|
type: Object as PropType<Definition>,
|
||||||
},
|
default: () => ({}),
|
||||||
|
|
||||||
toolbar: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
patternItems: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup(props) {
|
|
||||||
const lfElRef = ref(null);
|
|
||||||
const graphData = ref({});
|
|
||||||
|
|
||||||
const lfInstance = ref(null) as Ref<LogicFlow | null>;
|
data: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
|
||||||
const { prefixCls } = useDesign('flow-chart');
|
toolbar: {
|
||||||
const appStore = useAppStore();
|
type: Boolean,
|
||||||
const [register, { openModal }] = useModal();
|
default: true,
|
||||||
createFlowChartContext({
|
},
|
||||||
logicFlow: lfInstance as unknown as LogicFlow,
|
patternItems: {
|
||||||
});
|
type: Array,
|
||||||
|
|
||||||
const getFlowOptions = computed(() => {
|
|
||||||
const { flowOptions } = props;
|
|
||||||
|
|
||||||
const defaultOptions: Partial<Definition> = {
|
|
||||||
grid: true,
|
|
||||||
background: {
|
|
||||||
color: appStore.getDarkMode === 'light' ? '#f7f9ff' : '#151515',
|
|
||||||
},
|
|
||||||
keyboard: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
...flowOptions,
|
|
||||||
};
|
|
||||||
return defaultOptions as Definition;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.data,
|
|
||||||
() => {
|
|
||||||
onRender();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// watch(
|
|
||||||
// () => appStore.getDarkMode,
|
|
||||||
// () => {
|
|
||||||
// init();
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => unref(getFlowOptions),
|
|
||||||
(options) => {
|
|
||||||
unref(lfInstance)?.updateEditConfig(options);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// init logicFlow
|
|
||||||
async function init() {
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const lfEl = unref(lfElRef);
|
|
||||||
if (!lfEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LogicFlow.use(DndPanel);
|
|
||||||
|
|
||||||
// Canvas configuration
|
|
||||||
LogicFlow.use(Snapshot);
|
|
||||||
// Use the bpmn plug-in to introduce bpmn elements, which can be used after conversion in turbo
|
|
||||||
LogicFlow.use(BpmnElement);
|
|
||||||
// Start the right-click menu
|
|
||||||
LogicFlow.use(Menu);
|
|
||||||
LogicFlow.use(SelectionSelect);
|
|
||||||
|
|
||||||
lfInstance.value = new LogicFlow({
|
|
||||||
...unref(getFlowOptions),
|
|
||||||
container: lfEl,
|
|
||||||
});
|
|
||||||
const lf = unref(lfInstance)!;
|
|
||||||
lf?.setDefaultEdgeType('line');
|
|
||||||
onRender();
|
|
||||||
lf?.setPatternItems(props.patternItems || configDefaultDndPanel(lf));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onRender() {
|
|
||||||
await nextTick();
|
|
||||||
const lf = unref(lfInstance);
|
|
||||||
if (!lf) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const lFData = toLogicFlowData(props.data);
|
|
||||||
lf.render(lFData);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePreview() {
|
|
||||||
const lf = unref(lfInstance);
|
|
||||||
if (!lf) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
graphData.value = unref(lf).getGraphData();
|
|
||||||
openModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(init);
|
|
||||||
|
|
||||||
return {
|
|
||||||
register,
|
|
||||||
prefixCls,
|
|
||||||
lfElRef,
|
|
||||||
handlePreview,
|
|
||||||
graphData,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lfElRef = ref(null);
|
||||||
|
const graphData = ref({});
|
||||||
|
|
||||||
|
const lfInstance = ref(null) as Ref<LogicFlow | null>;
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('flow-chart');
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const [register, { openModal }] = useModal();
|
||||||
|
createFlowChartContext({
|
||||||
|
logicFlow: lfInstance as unknown as LogicFlow,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getFlowOptions = computed(() => {
|
||||||
|
const { flowOptions } = props;
|
||||||
|
|
||||||
|
const defaultOptions: Partial<Definition> = {
|
||||||
|
grid: true,
|
||||||
|
background: {
|
||||||
|
color: appStore.getDarkMode === 'light' ? '#f7f9ff' : '#151515',
|
||||||
|
},
|
||||||
|
keyboard: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
...flowOptions,
|
||||||
|
};
|
||||||
|
return defaultOptions as Definition;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
onRender();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// watch(
|
||||||
|
// () => appStore.getDarkMode,
|
||||||
|
// () => {
|
||||||
|
// init();
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unref(getFlowOptions),
|
||||||
|
(options) => {
|
||||||
|
unref(lfInstance)?.updateEditConfig(options);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// init logicFlow
|
||||||
|
async function init() {
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const lfEl = unref(lfElRef);
|
||||||
|
if (!lfEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogicFlow.use(DndPanel);
|
||||||
|
|
||||||
|
// Canvas configuration
|
||||||
|
LogicFlow.use(Snapshot);
|
||||||
|
// Use the bpmn plug-in to introduce bpmn elements, which can be used after conversion in turbo
|
||||||
|
LogicFlow.use(BpmnElement);
|
||||||
|
// Start the right-click menu
|
||||||
|
LogicFlow.use(Menu);
|
||||||
|
LogicFlow.use(SelectionSelect);
|
||||||
|
|
||||||
|
lfInstance.value = new LogicFlow({
|
||||||
|
...unref(getFlowOptions),
|
||||||
|
container: lfEl,
|
||||||
|
});
|
||||||
|
const lf = unref(lfInstance)!;
|
||||||
|
lf?.setDefaultEdgeType('line');
|
||||||
|
onRender();
|
||||||
|
lf?.setPatternItems(props.patternItems || configDefaultDndPanel(lf));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRender() {
|
||||||
|
await nextTick();
|
||||||
|
const lf = unref(lfInstance);
|
||||||
|
if (!lf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lFData = toLogicFlowData(props.data);
|
||||||
|
lf.render(lFData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePreview() {
|
||||||
|
const lf = unref(lfInstance);
|
||||||
|
if (!lf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphData.value = unref(lf).getGraphData();
|
||||||
|
openModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(init);
|
||||||
</script>
|
</script>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">
|
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">
|
||||||
<Icon
|
<Icon
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
:class="item.disabled ? 'cursor-not-allowed disabeld' : 'cursor-pointer'"
|
:class="item.disabled ? 'cursor-not-allowed disabled' : 'cursor-pointer'"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -14,122 +14,119 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { ToolbarConfig } from './types';
|
import type { ToolbarConfig } from './types';
|
||||||
|
|
||||||
import { defineComponent, ref, onUnmounted, unref, nextTick, watchEffect } from 'vue';
|
import { ref, onUnmounted, unref, nextTick, watchEffect } from 'vue';
|
||||||
import { Divider, Tooltip } from 'ant-design-vue';
|
import { Divider, Tooltip } from 'ant-design-vue';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
|
|
||||||
import { useFlowChartContext } from './useFlowContext';
|
import { useFlowChartContext } from './useFlowContext';
|
||||||
import { ToolbarTypeEnum } from './enum';
|
import { ToolbarTypeEnum } from './enum';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'FlowChartToolbar' });
|
||||||
name: 'FlowChartToolbar',
|
|
||||||
components: { Icon, Divider, Tooltip },
|
defineProps({
|
||||||
props: {
|
prefixCls: String,
|
||||||
prefixCls: String,
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['view-data']);
|
||||||
|
|
||||||
|
const toolbarItemList = ref<ToolbarConfig[]>([
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.ZOOM_IN,
|
||||||
|
icon: 'codicon:zoom-out',
|
||||||
|
tooltip: '缩小',
|
||||||
},
|
},
|
||||||
emits: ['view-data'],
|
{
|
||||||
setup(_, { emit }) {
|
type: ToolbarTypeEnum.ZOOM_OUT,
|
||||||
const toolbarItemList = ref<ToolbarConfig[]>([
|
icon: 'codicon:zoom-in',
|
||||||
{
|
tooltip: '放大',
|
||||||
type: ToolbarTypeEnum.ZOOM_IN,
|
|
||||||
icon: 'codicon:zoom-out',
|
|
||||||
tooltip: '缩小',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.ZOOM_OUT,
|
|
||||||
icon: 'codicon:zoom-in',
|
|
||||||
tooltip: '放大',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.RESET_ZOOM,
|
|
||||||
icon: 'codicon:screen-normal',
|
|
||||||
tooltip: '重置比例',
|
|
||||||
},
|
|
||||||
{ separate: true },
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.UNDO,
|
|
||||||
icon: 'ion:arrow-undo-outline',
|
|
||||||
tooltip: '后退',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.REDO,
|
|
||||||
icon: 'ion:arrow-redo-outline',
|
|
||||||
tooltip: '前进',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{ separate: true },
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.SNAPSHOT,
|
|
||||||
icon: 'ion:download-outline',
|
|
||||||
tooltip: '下载',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ToolbarTypeEnum.VIEW_DATA,
|
|
||||||
icon: 'carbon:document-view',
|
|
||||||
tooltip: '查看数据',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { logicFlow } = useFlowChartContext();
|
|
||||||
|
|
||||||
function onHistoryChange({ data: { undoAble, redoAble } }) {
|
|
||||||
const itemsList = unref(toolbarItemList);
|
|
||||||
const undoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.UNDO);
|
|
||||||
const redoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.REDO);
|
|
||||||
if (undoIndex !== -1) {
|
|
||||||
unref(toolbarItemList)[undoIndex].disabled = !undoAble;
|
|
||||||
}
|
|
||||||
if (redoIndex !== -1) {
|
|
||||||
unref(toolbarItemList)[redoIndex].disabled = !redoAble;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onControl = (item) => {
|
|
||||||
const lf = unref(logicFlow);
|
|
||||||
if (!lf) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (item.type) {
|
|
||||||
case ToolbarTypeEnum.ZOOM_IN:
|
|
||||||
lf.zoom();
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.ZOOM_OUT:
|
|
||||||
lf.zoom(true);
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.RESET_ZOOM:
|
|
||||||
lf.resetZoom();
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.UNDO:
|
|
||||||
lf.undo();
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.REDO:
|
|
||||||
lf.redo();
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.SNAPSHOT:
|
|
||||||
lf.getSnapshot();
|
|
||||||
break;
|
|
||||||
case ToolbarTypeEnum.VIEW_DATA:
|
|
||||||
emit('view-data');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watchEffect(async () => {
|
|
||||||
if (unref(logicFlow)) {
|
|
||||||
await nextTick();
|
|
||||||
unref(logicFlow)?.on('history:change', onHistoryChange);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
unref(logicFlow)?.off('history:change', onHistoryChange);
|
|
||||||
});
|
|
||||||
return { toolbarItemList, onControl };
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.RESET_ZOOM,
|
||||||
|
icon: 'codicon:screen-normal',
|
||||||
|
tooltip: '重置比例',
|
||||||
|
},
|
||||||
|
{ separate: true },
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.UNDO,
|
||||||
|
icon: 'ion:arrow-undo-outline',
|
||||||
|
tooltip: '后退',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.REDO,
|
||||||
|
icon: 'ion:arrow-redo-outline',
|
||||||
|
tooltip: '前进',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{ separate: true },
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.SNAPSHOT,
|
||||||
|
icon: 'ion:download-outline',
|
||||||
|
tooltip: '下载',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ToolbarTypeEnum.VIEW_DATA,
|
||||||
|
icon: 'carbon:document-view',
|
||||||
|
tooltip: '查看数据',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { logicFlow } = useFlowChartContext();
|
||||||
|
|
||||||
|
function onHistoryChange({ data: { undoAble, redoAble } }) {
|
||||||
|
const itemsList = unref(toolbarItemList);
|
||||||
|
const undoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.UNDO);
|
||||||
|
const redoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.REDO);
|
||||||
|
if (undoIndex !== -1) {
|
||||||
|
unref(toolbarItemList)[undoIndex].disabled = !undoAble;
|
||||||
|
}
|
||||||
|
if (redoIndex !== -1) {
|
||||||
|
unref(toolbarItemList)[redoIndex].disabled = !redoAble;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onControl = (item) => {
|
||||||
|
const lf = unref(logicFlow);
|
||||||
|
if (!lf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (item.type) {
|
||||||
|
case ToolbarTypeEnum.ZOOM_IN:
|
||||||
|
lf.zoom();
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.ZOOM_OUT:
|
||||||
|
lf.zoom(true);
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.RESET_ZOOM:
|
||||||
|
lf.resetZoom();
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.UNDO:
|
||||||
|
lf.undo();
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.REDO:
|
||||||
|
lf.redo();
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.SNAPSHOT:
|
||||||
|
lf.getSnapshot();
|
||||||
|
break;
|
||||||
|
case ToolbarTypeEnum.VIEW_DATA:
|
||||||
|
emit('view-data');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
|
if (unref(logicFlow)) {
|
||||||
|
await nextTick();
|
||||||
|
unref(logicFlow)?.on('history:change', onHistoryChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unref(logicFlow)?.off('history:change', onHistoryChange);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@@ -37,286 +37,265 @@
|
|||||||
</Row>
|
</Row>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from './types/form';
|
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from './types/form';
|
||||||
import type { AdvanceState } from './types/hooks';
|
import type { AdvanceState } from './types/hooks';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
|
import { reactive, ref, computed, unref, onMounted, watch, nextTick, useAttrs } from 'vue';
|
||||||
import { Form, Row, type FormProps as AntFormProps } from 'ant-design-vue';
|
import { Form, Row, type FormProps as AntFormProps } from 'ant-design-vue';
|
||||||
import FormItem from './components/FormItem.vue';
|
import FormItem from './components/FormItem.vue';
|
||||||
import FormAction from './components/FormAction.vue';
|
import FormAction from './components/FormAction.vue';
|
||||||
|
|
||||||
import { dateItemType } from './helper';
|
import { dateItemType } from './helper';
|
||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '@/utils/dateUtil';
|
||||||
|
|
||||||
import { deepMerge } from '/@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
|
|
||||||
import { useFormValues } from './hooks/useFormValues';
|
import { useFormValues } from './hooks/useFormValues';
|
||||||
import useAdvanced from './hooks/useAdvanced';
|
import useAdvanced from './hooks/useAdvanced';
|
||||||
import { useFormEvents } from './hooks/useFormEvents';
|
import { useFormEvents } from './hooks/useFormEvents';
|
||||||
import { createFormContext } from './hooks/useFormContext';
|
import { createFormContext } from './hooks/useFormContext';
|
||||||
import { useAutoFocus } from './hooks/useAutoFocus';
|
import { useAutoFocus } from './hooks/useAutoFocus';
|
||||||
import { useModalContext } from '/@/components/Modal';
|
import { useModalContext } from '@/components/Modal';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicForm' });
|
||||||
name: 'BasicForm',
|
|
||||||
components: { FormItem, Form, Row, FormAction },
|
|
||||||
props: basicProps,
|
|
||||||
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
|
|
||||||
setup(props, { emit, attrs }) {
|
|
||||||
const formModel = reactive({});
|
|
||||||
const modalFn = useModalContext();
|
|
||||||
|
|
||||||
const advanceState = reactive<AdvanceState>({
|
const props = defineProps(basicProps);
|
||||||
isAdvanced: true,
|
|
||||||
hideAdvanceBtn: false,
|
|
||||||
isLoad: false,
|
|
||||||
actionSpan: 6,
|
|
||||||
});
|
|
||||||
|
|
||||||
const defaultValueRef = ref({});
|
const emit = defineEmits([
|
||||||
const isInitedDefaultRef = ref(false);
|
'advanced-change',
|
||||||
const propsRef = ref<Partial<FormProps>>();
|
'reset',
|
||||||
const schemaRef = ref<FormSchema[] | null>(null);
|
'submit',
|
||||||
const formElRef = ref<FormActionType | null>(null);
|
'register',
|
||||||
|
'field-value-change',
|
||||||
|
]);
|
||||||
|
|
||||||
const { prefixCls } = useDesign('basic-form');
|
const attrs = useAttrs();
|
||||||
|
|
||||||
// Get the basic configuration of the form
|
const formModel = reactive({});
|
||||||
const getProps = computed(() => {
|
const modalFn = useModalContext();
|
||||||
return { ...props, ...unref(propsRef) } as FormProps;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getFormClass = computed(() => {
|
const advanceState = reactive<AdvanceState>({
|
||||||
return [
|
isAdvanced: true,
|
||||||
prefixCls,
|
hideAdvanceBtn: false,
|
||||||
{
|
isLoad: false,
|
||||||
[`${prefixCls}--compact`]: unref(getProps).compact,
|
actionSpan: 6,
|
||||||
},
|
});
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get uniform row style and Row configuration for the entire form
|
const defaultValueRef = ref({});
|
||||||
const getRow = computed(() => {
|
const isInitedDefaultRef = ref(false);
|
||||||
const { baseRowStyle = {}, rowProps } = unref(getProps);
|
const propsRef = ref<Partial<FormProps>>();
|
||||||
return {
|
const schemaRef = ref<FormSchema[] | null>(null);
|
||||||
style: baseRowStyle,
|
const formElRef = ref<FormActionType | null>(null);
|
||||||
...rowProps,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getBindValue = computed(
|
const { prefixCls } = useDesign('basic-form');
|
||||||
() => ({ ...attrs, ...props, ...unref(getProps) }) as AntFormProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getSchema = computed((): FormSchema[] => {
|
// Get the basic configuration of the form
|
||||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
const getProps = computed(() => {
|
||||||
for (const schema of schemas) {
|
return { ...props, ...unref(propsRef) } as FormProps;
|
||||||
const {
|
});
|
||||||
defaultValue,
|
|
||||||
component,
|
const getFormClass = computed(() => {
|
||||||
componentProps,
|
return [
|
||||||
isHandleDateDefaultValue = true,
|
prefixCls,
|
||||||
} = schema;
|
{
|
||||||
// handle date type
|
[`${prefixCls}--compact`]: unref(getProps).compact,
|
||||||
if (
|
},
|
||||||
isHandleDateDefaultValue &&
|
];
|
||||||
defaultValue &&
|
});
|
||||||
component &&
|
|
||||||
dateItemType.includes(component)
|
// Get uniform row style and Row configuration for the entire form
|
||||||
) {
|
const getRow = computed(() => {
|
||||||
const valueFormat = componentProps ? componentProps['valueFormat'] : null;
|
const { baseRowStyle = {}, rowProps } = unref(getProps);
|
||||||
if (!Array.isArray(defaultValue)) {
|
return {
|
||||||
schema.defaultValue = valueFormat
|
style: baseRowStyle,
|
||||||
? dateUtil(defaultValue).format(valueFormat)
|
...rowProps,
|
||||||
: dateUtil(defaultValue);
|
};
|
||||||
} else {
|
});
|
||||||
const def: any[] = [];
|
|
||||||
defaultValue.forEach((item) => {
|
const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) }) as AntFormProps);
|
||||||
def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item));
|
|
||||||
});
|
const getSchema = computed((): FormSchema[] => {
|
||||||
schema.defaultValue = def;
|
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||||
}
|
for (const schema of schemas) {
|
||||||
}
|
const { defaultValue, component, componentProps, isHandleDateDefaultValue = true } = schema;
|
||||||
}
|
// handle date type
|
||||||
if (unref(getProps).showAdvancedButton) {
|
if (
|
||||||
return cloneDeep(
|
isHandleDateDefaultValue &&
|
||||||
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
|
defaultValue &&
|
||||||
);
|
component &&
|
||||||
|
dateItemType.includes(component)
|
||||||
|
) {
|
||||||
|
const valueFormat = componentProps ? componentProps['valueFormat'] : null;
|
||||||
|
if (!Array.isArray(defaultValue)) {
|
||||||
|
schema.defaultValue = valueFormat
|
||||||
|
? dateUtil(defaultValue).format(valueFormat)
|
||||||
|
: dateUtil(defaultValue);
|
||||||
} else {
|
} else {
|
||||||
return cloneDeep(schemas as FormSchema[]);
|
const def: any[] = [];
|
||||||
}
|
defaultValue.forEach((item) => {
|
||||||
});
|
def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item));
|
||||||
|
|
||||||
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
|
|
||||||
advanceState,
|
|
||||||
emit,
|
|
||||||
getProps,
|
|
||||||
getSchema,
|
|
||||||
formModel,
|
|
||||||
defaultValueRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { handleFormValues, initDefault } = useFormValues({
|
|
||||||
getProps,
|
|
||||||
defaultValueRef,
|
|
||||||
getSchema,
|
|
||||||
formModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
useAutoFocus({
|
|
||||||
getSchema,
|
|
||||||
getProps,
|
|
||||||
isInitedDefault: isInitedDefaultRef,
|
|
||||||
formElRef: formElRef as Ref<FormActionType>,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
setFieldsValue,
|
|
||||||
clearValidate,
|
|
||||||
validate,
|
|
||||||
validateFields,
|
|
||||||
getFieldsValue,
|
|
||||||
updateSchema,
|
|
||||||
resetSchema,
|
|
||||||
appendSchemaByField,
|
|
||||||
removeSchemaByField,
|
|
||||||
resetFields,
|
|
||||||
scrollToField,
|
|
||||||
} = useFormEvents({
|
|
||||||
emit,
|
|
||||||
getProps,
|
|
||||||
formModel,
|
|
||||||
getSchema,
|
|
||||||
defaultValueRef,
|
|
||||||
formElRef: formElRef as Ref<FormActionType>,
|
|
||||||
schemaRef: schemaRef as Ref<FormSchema[]>,
|
|
||||||
handleFormValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
createFormContext({
|
|
||||||
resetAction: resetFields,
|
|
||||||
submitAction: handleSubmit,
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => unref(getProps).model,
|
|
||||||
() => {
|
|
||||||
const { model } = unref(getProps);
|
|
||||||
if (!model) return;
|
|
||||||
setFieldsValue(model);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => unref(getProps).schemas,
|
|
||||||
(schemas) => {
|
|
||||||
resetSchema(schemas ?? []);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => getSchema.value,
|
|
||||||
(schema) => {
|
|
||||||
nextTick(() => {
|
|
||||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
|
||||||
modalFn?.redoModalHeight?.();
|
|
||||||
});
|
});
|
||||||
if (unref(isInitedDefaultRef)) {
|
schema.defaultValue = def;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (schema?.length) {
|
|
||||||
initDefault();
|
|
||||||
isInitedDefaultRef.value = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => formModel,
|
|
||||||
useDebounceFn(() => {
|
|
||||||
unref(getProps).submitOnChange && handleSubmit();
|
|
||||||
}, 300),
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
|
||||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFormModel(key: string, value: any, schema: FormSchema) {
|
|
||||||
formModel[key] = value;
|
|
||||||
emit('field-value-change', key, value);
|
|
||||||
// TODO 优化验证,这里如果是autoLink=false手动关联的情况下才会再次触发此函数
|
|
||||||
if (schema && schema.itemProps && !schema.itemProps.autoLink) {
|
|
||||||
validateFields([key]).catch((_) => {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (unref(getProps).showAdvancedButton) {
|
||||||
|
return cloneDeep(schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[]);
|
||||||
|
} else {
|
||||||
|
return cloneDeep(schemas as FormSchema[]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function handleEnterPress(e: KeyboardEvent) {
|
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
|
||||||
const { autoSubmitOnEnter } = unref(getProps);
|
advanceState,
|
||||||
if (!autoSubmitOnEnter) return;
|
emit,
|
||||||
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
|
getProps,
|
||||||
const target: HTMLElement = e.target as HTMLElement;
|
getSchema,
|
||||||
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
|
formModel,
|
||||||
handleSubmit();
|
defaultValueRef,
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formActionType: Partial<FormActionType> = {
|
const { handleFormValues, initDefault } = useFormValues({
|
||||||
getFieldsValue,
|
getProps,
|
||||||
setFieldsValue,
|
defaultValueRef,
|
||||||
resetFields,
|
getSchema,
|
||||||
updateSchema,
|
formModel,
|
||||||
resetSchema,
|
});
|
||||||
setProps,
|
|
||||||
removeSchemaByField,
|
|
||||||
appendSchemaByField,
|
|
||||||
clearValidate,
|
|
||||||
validateFields,
|
|
||||||
validate,
|
|
||||||
submit: handleSubmit,
|
|
||||||
scrollToField: scrollToField,
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
useAutoFocus({
|
||||||
initDefault();
|
getSchema,
|
||||||
emit('register', formActionType);
|
getProps,
|
||||||
});
|
isInitedDefault: isInitedDefaultRef,
|
||||||
|
formElRef: formElRef as Ref<FormActionType>,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
const {
|
||||||
getBindValue,
|
handleSubmit,
|
||||||
handleToggleAdvanced,
|
setFieldsValue,
|
||||||
handleEnterPress,
|
clearValidate,
|
||||||
formModel,
|
validate,
|
||||||
defaultValueRef,
|
validateFields,
|
||||||
advanceState,
|
getFieldsValue,
|
||||||
getRow,
|
updateSchema,
|
||||||
getProps,
|
resetSchema,
|
||||||
formElRef,
|
appendSchemaByField,
|
||||||
getSchema,
|
removeSchemaByField,
|
||||||
formActionType: formActionType as any,
|
resetFields,
|
||||||
setFormModel,
|
scrollToField,
|
||||||
getFormClass,
|
} = useFormEvents({
|
||||||
getFormActionBindProps: computed(
|
emit,
|
||||||
() =>
|
getProps,
|
||||||
({ ...getProps.value, ...advanceState }) as InstanceType<typeof FormAction>['$props'],
|
formModel,
|
||||||
),
|
getSchema,
|
||||||
fieldsIsAdvancedMap,
|
defaultValueRef,
|
||||||
...formActionType,
|
formElRef: formElRef as Ref<FormActionType>,
|
||||||
};
|
schemaRef: schemaRef as Ref<FormSchema[]>,
|
||||||
|
handleFormValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
createFormContext({
|
||||||
|
resetAction: resetFields,
|
||||||
|
submitAction: handleSubmit,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unref(getProps).model,
|
||||||
|
() => {
|
||||||
|
const { model } = unref(getProps);
|
||||||
|
if (!model) return;
|
||||||
|
setFieldsValue(model);
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unref(getProps).schemas,
|
||||||
|
(schemas) => {
|
||||||
|
resetSchema(schemas ?? []);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => getSchema.value,
|
||||||
|
(schema) => {
|
||||||
|
nextTick(() => {
|
||||||
|
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||||
|
modalFn?.redoModalHeight?.();
|
||||||
|
});
|
||||||
|
if (unref(isInitedDefaultRef)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (schema?.length) {
|
||||||
|
initDefault();
|
||||||
|
isInitedDefaultRef.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formModel,
|
||||||
|
useDebounceFn(() => {
|
||||||
|
unref(getProps).submitOnChange && handleSubmit();
|
||||||
|
}, 300),
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||||
|
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFormModel(key: string, value: any, schema: FormSchema) {
|
||||||
|
formModel[key] = value;
|
||||||
|
emit('field-value-change', key, value);
|
||||||
|
// TODO 优化验证,这里如果是autoLink=false手动关联的情况下才会再次触发此函数
|
||||||
|
if (schema && schema.itemProps && !schema.itemProps.autoLink) {
|
||||||
|
validateFields([key]).catch((_) => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnterPress(e: KeyboardEvent) {
|
||||||
|
const { autoSubmitOnEnter } = unref(getProps);
|
||||||
|
if (!autoSubmitOnEnter) return;
|
||||||
|
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
|
||||||
|
const target: HTMLElement = e.target as HTMLElement;
|
||||||
|
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formActionType: Partial<FormActionType> = {
|
||||||
|
getFieldsValue,
|
||||||
|
setFieldsValue,
|
||||||
|
resetFields,
|
||||||
|
updateSchema,
|
||||||
|
resetSchema,
|
||||||
|
setProps,
|
||||||
|
removeSchemaByField,
|
||||||
|
appendSchemaByField,
|
||||||
|
clearValidate,
|
||||||
|
validateFields,
|
||||||
|
validate,
|
||||||
|
submit: handleSubmit,
|
||||||
|
scrollToField: scrollToField,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormActionBindProps = computed(
|
||||||
|
() => ({ ...getProps.value, ...advanceState }) as InstanceType<typeof FormAction>['$props'],
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initDefault();
|
||||||
|
emit('register', formActionType);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import type { ComponentType } from './types/index';
|
import type { ComponentType } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component list, register here to setting it in the form
|
* Component list, register here to setting it in the form
|
||||||
@@ -27,10 +27,10 @@ import ApiTree from './components/ApiTree.vue';
|
|||||||
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
||||||
import ApiCascader from './components/ApiCascader.vue';
|
import ApiCascader from './components/ApiCascader.vue';
|
||||||
import ApiTransfer from './components/ApiTransfer.vue';
|
import ApiTransfer from './components/ApiTransfer.vue';
|
||||||
import { BasicUpload, ImageUpload } from '/@/components/Upload';
|
import { BasicUpload, ImageUpload } from '@/components/Upload';
|
||||||
import { StrengthMeter } from '/@/components/StrengthMeter';
|
import { StrengthMeter } from '@/components/StrengthMeter';
|
||||||
import { IconPicker } from '/@/components/Icon';
|
import { IconPicker } from '@/components/Icon';
|
||||||
import { CountdownInput } from '/@/components/CountDown';
|
import { CountdownInput } from '@/components/CountDown';
|
||||||
|
|
||||||
const componentMap = new Map<ComponentType, Component>();
|
const componentMap = new Map<ComponentType, Component>();
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-cascader
|
<Cascader
|
||||||
v-model:value="state"
|
v-model:value="state"
|
||||||
:options="options"
|
:options="options"
|
||||||
:load-data="loadData"
|
:load-data="loadData"
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
{{ t('component.form.apiSelectNotFound') }}
|
{{ t('component.form.apiSelectNotFound') }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</a-cascader>
|
</Cascader>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { type Recordable } from '@vben/types';
|
import { type Recordable } from '@vben/types';
|
||||||
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue';
|
import { PropType, ref, unref, watch, watchEffect } from 'vue';
|
||||||
import { Cascader } from 'ant-design-vue';
|
import { Cascader } from 'ant-design-vue';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { get, omit } from 'lodash-es';
|
import { get, omit } from 'lodash-es';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -36,165 +36,151 @@
|
|||||||
isLeaf?: boolean;
|
isLeaf?: boolean;
|
||||||
children?: Option[];
|
children?: Option[];
|
||||||
}
|
}
|
||||||
export default defineComponent({
|
|
||||||
name: 'ApiCascader',
|
defineOptions({ name: 'ApiCascader' });
|
||||||
components: {
|
|
||||||
LoadingOutlined,
|
const props = defineProps({
|
||||||
[Cascader.name]: Cascader,
|
value: {
|
||||||
|
type: Array,
|
||||||
},
|
},
|
||||||
props: {
|
api: {
|
||||||
value: {
|
type: Function as PropType<(arg?: Recordable<any>) => Promise<Option[]>>,
|
||||||
type: Array,
|
default: null,
|
||||||
},
|
|
||||||
api: {
|
|
||||||
type: Function as PropType<(arg?: Recordable<any>) => Promise<Option[]>>,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
numberToString: propTypes.bool,
|
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
labelField: propTypes.string.def('label'),
|
|
||||||
valueField: propTypes.string.def('value'),
|
|
||||||
childrenField: propTypes.string.def('children'),
|
|
||||||
apiParamKey: propTypes.string.def('parentCode'),
|
|
||||||
immediate: propTypes.bool.def(true),
|
|
||||||
// init fetch params
|
|
||||||
initFetchParams: {
|
|
||||||
type: Object as PropType<Recordable<any>>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
// 是否有下级,默认是
|
|
||||||
isLeaf: {
|
|
||||||
type: Function as PropType<(arg: Recordable<any>) => boolean>,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
displayRenderArray: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['change', 'defaultChange'],
|
numberToString: propTypes.bool,
|
||||||
setup(props, { emit }) {
|
resultField: propTypes.string.def(''),
|
||||||
const apiData = ref<any[]>([]);
|
labelField: propTypes.string.def('label'),
|
||||||
const options = ref<Option[]>([]);
|
valueField: propTypes.string.def('value'),
|
||||||
const loading = ref<boolean>(false);
|
childrenField: propTypes.string.def('children'),
|
||||||
const emitData = ref<any[]>([]);
|
apiParamKey: propTypes.string.def('parentCode'),
|
||||||
const isFirstLoad = ref(true);
|
immediate: propTypes.bool.def(true),
|
||||||
const { t } = useI18n();
|
// init fetch params
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
initFetchParams: {
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
type: Object as PropType<Recordable<any>>,
|
||||||
|
default: () => ({}),
|
||||||
watch(
|
},
|
||||||
apiData,
|
// 是否有下级,默认是
|
||||||
(data) => {
|
isLeaf: {
|
||||||
const opts = generatorOptions(data);
|
type: Function as PropType<(arg: Recordable<any>) => boolean>,
|
||||||
options.value = opts;
|
default: null,
|
||||||
},
|
},
|
||||||
{ deep: true },
|
displayRenderArray: {
|
||||||
);
|
type: Array,
|
||||||
|
|
||||||
function generatorOptions(options: any[]): Option[] {
|
|
||||||
const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
|
|
||||||
return options.reduce((prev, next: Recordable<any>) => {
|
|
||||||
if (next) {
|
|
||||||
const value = next[valueField];
|
|
||||||
const item = {
|
|
||||||
...omit(next, [labelField, valueField]),
|
|
||||||
label: next[labelField],
|
|
||||||
value: numberToString ? `${value}` : value,
|
|
||||||
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
|
|
||||||
};
|
|
||||||
const children = Reflect.get(next, childrenField);
|
|
||||||
if (children) {
|
|
||||||
Reflect.set(item, childrenField, generatorOptions(children));
|
|
||||||
}
|
|
||||||
prev.push(item);
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, [] as Option[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialFetch() {
|
|
||||||
const api = props.api;
|
|
||||||
if (!api || !isFunction(api)) return;
|
|
||||||
apiData.value = [];
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await api(props.initFetchParams);
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
apiData.value = res;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.resultField) {
|
|
||||||
apiData.value = get(res, props.resultField) || [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadData(selectedOptions: Option[]) {
|
|
||||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
|
||||||
targetOption.loading = true;
|
|
||||||
|
|
||||||
const api = props.api;
|
|
||||||
if (!api || !isFunction(api)) return;
|
|
||||||
try {
|
|
||||||
const res = await api({
|
|
||||||
[props.apiParamKey]: Reflect.get(targetOption, 'value'),
|
|
||||||
});
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
const children = generatorOptions(res);
|
|
||||||
targetOption.children = children;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.resultField) {
|
|
||||||
const children = generatorOptions(get(res, props.resultField) || []);
|
|
||||||
targetOption.children = children;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
targetOption.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.immediate && initialFetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.initFetchParams,
|
|
||||||
() => {
|
|
||||||
!unref(isFirstLoad) && initialFetch();
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleChange(keys, args) {
|
|
||||||
emitData.value = args;
|
|
||||||
emit('defaultChange', keys, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRenderDisplay({ labels, selectedOptions }) {
|
|
||||||
if (unref(emitData).length === selectedOptions.length) {
|
|
||||||
return labels.join(' / ');
|
|
||||||
}
|
|
||||||
if (props.displayRenderArray) {
|
|
||||||
return props.displayRenderArray.join(' / ');
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
state,
|
|
||||||
options,
|
|
||||||
loading,
|
|
||||||
t,
|
|
||||||
handleChange,
|
|
||||||
loadData,
|
|
||||||
handleRenderDisplay,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'defaultChange']);
|
||||||
|
|
||||||
|
const apiData = ref<any[]>([]);
|
||||||
|
const options = ref<Option[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const emitData = ref<any[]>([]);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const { t } = useI18n();
|
||||||
|
// Embedded in the form, just use the hook binding to perform form verification
|
||||||
|
const [state]: any = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
apiData,
|
||||||
|
(data) => {
|
||||||
|
const opts = generatorOptions(data);
|
||||||
|
options.value = opts;
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function generatorOptions(options: any[]): Option[] {
|
||||||
|
const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
|
||||||
|
return options.reduce((prev, next: Recordable<any>) => {
|
||||||
|
if (next) {
|
||||||
|
const value = next[valueField];
|
||||||
|
const item = {
|
||||||
|
...omit(next, [labelField, valueField]),
|
||||||
|
label: next[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
|
||||||
|
};
|
||||||
|
const children = Reflect.get(next, childrenField);
|
||||||
|
if (children) {
|
||||||
|
Reflect.set(item, childrenField, generatorOptions(children));
|
||||||
|
}
|
||||||
|
prev.push(item);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, [] as Option[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initialFetch() {
|
||||||
|
const api = props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
apiData.value = [];
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await api(props.initFetchParams);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
apiData.value = res;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.resultField) {
|
||||||
|
apiData.value = get(res, props.resultField) || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(selectedOptions: Option[]) {
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
targetOption.loading = true;
|
||||||
|
|
||||||
|
const api = props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
const res = await api({
|
||||||
|
[props.apiParamKey]: Reflect.get(targetOption, 'value'),
|
||||||
|
});
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
const children = generatorOptions(res);
|
||||||
|
targetOption.children = children;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.resultField) {
|
||||||
|
const children = generatorOptions(get(res, props.resultField) || []);
|
||||||
|
targetOption.children = children;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
targetOption.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && initialFetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.initFetchParams,
|
||||||
|
() => {
|
||||||
|
!unref(isFirstLoad) && initialFetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleChange(keys, args) {
|
||||||
|
emitData.value = args;
|
||||||
|
emit('defaultChange', keys, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRenderDisplay({ labels, selectedOptions }) {
|
||||||
|
if (unref(emitData).length === selectedOptions.length) {
|
||||||
|
return labels.join(' / ');
|
||||||
|
}
|
||||||
|
if (props.displayRenderArray) {
|
||||||
|
return props.displayRenderArray.join(' / ');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -2,135 +2,125 @@
|
|||||||
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
|
<Radio.Group v-bind="attrs" v-model:value="state" button-style="solid">
|
||||||
<template v-for="item in getOptions" :key="`${item.value}`">
|
<template v-for="item in getOptions" :key="`${item.value}`">
|
||||||
<RadioButton
|
<Radio.Button
|
||||||
v-if="props.isBtn"
|
v-if="props.isBtn"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
@click="handleClick(item)"
|
@click="handleClick(item)"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</RadioButton>
|
</Radio.Button>
|
||||||
<Radio v-else :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
|
<Radio v-else :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</Radio>
|
</Radio>
|
||||||
</template>
|
</template>
|
||||||
</RadioGroup>
|
</Radio.Group>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, type PropType, ref, watchEffect, computed, unref, watch } from 'vue';
|
import { type PropType, ref, watchEffect, computed, unref, watch } from 'vue';
|
||||||
import { Radio } from 'ant-design-vue';
|
import { Radio } from 'ant-design-vue';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { get, omit } from 'lodash-es';
|
import { get, omit } from 'lodash-es';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
|
||||||
|
|
||||||
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ApiRadioGroup' });
|
||||||
name: 'ApiRadioGroup',
|
|
||||||
components: {
|
const props = defineProps({
|
||||||
RadioGroup: Radio.Group,
|
api: {
|
||||||
RadioButton: Radio.Button,
|
type: Function as PropType<(arg?: any | string) => Promise<OptionsItem[]>>,
|
||||||
Radio,
|
default: null,
|
||||||
},
|
},
|
||||||
props: {
|
params: {
|
||||||
api: {
|
type: [Object, String] as PropType<any | string>,
|
||||||
type: Function as PropType<(arg?: any | string) => Promise<OptionsItem[]>>,
|
default: () => ({}),
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
type: [Object, String] as PropType<any | string>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
|
||||||
},
|
|
||||||
isBtn: {
|
|
||||||
type: [Boolean] as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
numberToString: propTypes.bool,
|
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
labelField: propTypes.string.def('label'),
|
|
||||||
valueField: propTypes.string.def('value'),
|
|
||||||
immediate: propTypes.bool.def(true),
|
|
||||||
},
|
},
|
||||||
emits: ['options-change', 'change'],
|
value: {
|
||||||
setup(props, { emit }) {
|
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||||
const options = ref<OptionsItem[]>([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const isFirstLoad = ref(true);
|
|
||||||
const emitData = ref<any[]>([]);
|
|
||||||
const attrs = useAttrs();
|
|
||||||
const { t } = useI18n();
|
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
|
||||||
|
|
||||||
// Processing options value
|
|
||||||
const getOptions = computed(() => {
|
|
||||||
const { labelField, valueField, numberToString } = props;
|
|
||||||
|
|
||||||
return unref(options).reduce((prev, next: any) => {
|
|
||||||
if (next) {
|
|
||||||
const value = next[valueField];
|
|
||||||
prev.push({
|
|
||||||
label: next[labelField],
|
|
||||||
value: numberToString ? `${value}` : value,
|
|
||||||
...omit(next, [labelField, valueField]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, [] as OptionsItem[]);
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.immediate && fetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.params,
|
|
||||||
() => {
|
|
||||||
!unref(isFirstLoad) && fetch();
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
async function fetch() {
|
|
||||||
const api = props.api;
|
|
||||||
if (!api || !isFunction(api)) return;
|
|
||||||
options.value = [];
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const res = await api(props.params);
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
options.value = res;
|
|
||||||
emitChange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.resultField) {
|
|
||||||
options.value = get(res, props.resultField) || [];
|
|
||||||
}
|
|
||||||
emitChange();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitChange() {
|
|
||||||
emit('options-change', unref(getOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClick(...args) {
|
|
||||||
emitData.value = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { state, getOptions, attrs, loading, t, handleClick, props };
|
|
||||||
},
|
},
|
||||||
|
isBtn: {
|
||||||
|
type: [Boolean] as PropType<boolean>,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
numberToString: propTypes.bool,
|
||||||
|
resultField: propTypes.string.def(''),
|
||||||
|
labelField: propTypes.string.def('label'),
|
||||||
|
valueField: propTypes.string.def('value'),
|
||||||
|
immediate: propTypes.bool.def(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['options-change', 'change']);
|
||||||
|
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const emitData = ref<any[]>([]);
|
||||||
|
const attrs = useAttrs();
|
||||||
|
// Embedded in the form, just use the hook binding to perform form verification
|
||||||
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
|
|
||||||
|
// Processing options value
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
|
||||||
|
return unref(options).reduce((prev, next: any) => {
|
||||||
|
if (next) {
|
||||||
|
const value = next[valueField];
|
||||||
|
prev.push({
|
||||||
|
label: next[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
...omit(next, [labelField, valueField]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, [] as OptionsItem[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!unref(isFirstLoad) && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const api = props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
options.value = [];
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await api(props.params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emitChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.resultField) {
|
||||||
|
options.value = get(res, props.resultField) || [];
|
||||||
|
}
|
||||||
|
emitChange();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChange() {
|
||||||
|
emit('options-change', unref(getOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(...args) {
|
||||||
|
emitData.value = args;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -20,137 +20,128 @@
|
|||||||
</template>
|
</template>
|
||||||
</Select>
|
</Select>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, ref, computed, unref, watch } from 'vue';
|
import { PropType, ref, computed, unref, watch } from 'vue';
|
||||||
import { Select } from 'ant-design-vue';
|
import { Select } from 'ant-design-vue';
|
||||||
import type { SelectValue } from 'ant-design-vue/es/select';
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
import { useAttrs } from '@vben/hooks';
|
|
||||||
import { get, omit } from 'lodash-es';
|
import { get, omit } from 'lodash-es';
|
||||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
|
||||||
type OptionsItem = { label?: string; value?: string; disabled?: boolean; [name: string]: any };
|
type OptionsItem = { label?: string; value?: string; disabled?: boolean; [name: string]: any };
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
|
||||||
name: 'ApiSelect',
|
|
||||||
components: {
|
const props = defineProps({
|
||||||
Select,
|
value: { type: [Array, Object, String, Number] as PropType<SelectValue> },
|
||||||
LoadingOutlined,
|
numberToString: propTypes.bool,
|
||||||
|
api: {
|
||||||
|
type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
// api params
|
||||||
props: {
|
params: propTypes.any.def({}),
|
||||||
value: { type: [Array, Object, String, Number] as PropType<SelectValue> },
|
// support xxx.xxx.xx
|
||||||
numberToString: propTypes.bool,
|
resultField: propTypes.string.def(''),
|
||||||
api: {
|
labelField: propTypes.string.def('label'),
|
||||||
type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>,
|
valueField: propTypes.string.def('value'),
|
||||||
default: null,
|
immediate: propTypes.bool.def(true),
|
||||||
},
|
alwaysLoad: propTypes.bool.def(false),
|
||||||
// api params
|
options: {
|
||||||
params: propTypes.any.def({}),
|
type: Array<OptionsItem>,
|
||||||
// support xxx.xxx.xx
|
default: [],
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
labelField: propTypes.string.def('label'),
|
|
||||||
valueField: propTypes.string.def('value'),
|
|
||||||
immediate: propTypes.bool.def(true),
|
|
||||||
alwaysLoad: propTypes.bool.def(false),
|
|
||||||
options: {
|
|
||||||
type: Array<OptionsItem>,
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['options-change', 'change', 'update:value'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const options = ref<OptionsItem[]>([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
// 首次是否加载过了
|
|
||||||
const isFirstLoaded = ref(false);
|
|
||||||
const emitData = ref<OptionsItem[]>([]);
|
|
||||||
const attrs = useAttrs();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
|
||||||
|
|
||||||
const getOptions = computed(() => {
|
|
||||||
const { labelField, valueField, numberToString } = props;
|
|
||||||
|
|
||||||
let data = unref(options).reduce((prev, next: any) => {
|
|
||||||
if (next) {
|
|
||||||
const value = get(next, valueField);
|
|
||||||
prev.push({
|
|
||||||
...omit(next, [labelField, valueField]),
|
|
||||||
label: get(next, labelField),
|
|
||||||
value: numberToString ? `${value}` : value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, [] as OptionsItem[]);
|
|
||||||
return data.length > 0 ? data : props.options;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.value,
|
|
||||||
(v) => {
|
|
||||||
emit('update:value', v);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.params,
|
|
||||||
() => {
|
|
||||||
!unref(isFirstLoaded) && fetch();
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: props.immediate },
|
|
||||||
);
|
|
||||||
|
|
||||||
async function fetch() {
|
|
||||||
const api = props.api;
|
|
||||||
if (!api || !isFunction(api) || loading.value) return;
|
|
||||||
options.value = [];
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const res = await api(props.params);
|
|
||||||
isFirstLoaded.value = true;
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
options.value = res;
|
|
||||||
emitChange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.resultField) {
|
|
||||||
options.value = get(res, props.resultField) || [];
|
|
||||||
}
|
|
||||||
emitChange();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
// reset status
|
|
||||||
isFirstLoaded.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFetch(visible: boolean) {
|
|
||||||
if (visible) {
|
|
||||||
if (props.alwaysLoad) {
|
|
||||||
await fetch();
|
|
||||||
} else if (!props.immediate && !unref(isFirstLoaded)) {
|
|
||||||
await fetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitChange() {
|
|
||||||
emit('options-change', unref(getOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChange(_, ...args) {
|
|
||||||
emitData.value = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { state, attrs, getOptions, loading, t, handleFetch, handleChange };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['options-change', 'change', 'update:value']);
|
||||||
|
|
||||||
|
const optionsRef = ref<OptionsItem[]>([]);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
// 首次是否加载过了
|
||||||
|
const isFirstLoaded = ref(false);
|
||||||
|
const emitData = ref<OptionsItem[]>([]);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// Embedded in the form, just use the hook binding to perform form verification
|
||||||
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
|
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
|
||||||
|
let data = unref(optionsRef).reduce((prev, next: any) => {
|
||||||
|
if (next) {
|
||||||
|
const value = get(next, valueField);
|
||||||
|
prev.push({
|
||||||
|
...omit(next, [labelField, valueField]),
|
||||||
|
label: get(next, labelField),
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, [] as OptionsItem[]);
|
||||||
|
return data.length > 0 ? data : props.options;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.value,
|
||||||
|
(v) => {
|
||||||
|
emit('update:value', v);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!unref(isFirstLoaded) && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: props.immediate },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const api = props.api;
|
||||||
|
if (!api || !isFunction(api) || loading.value) return;
|
||||||
|
optionsRef.value = [];
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await api(props.params);
|
||||||
|
isFirstLoaded.value = true;
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
optionsRef.value = res;
|
||||||
|
emitChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.resultField) {
|
||||||
|
optionsRef.value = get(res, props.resultField) || [];
|
||||||
|
}
|
||||||
|
emitChange();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
// reset status
|
||||||
|
isFirstLoaded.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFetch(visible: boolean) {
|
||||||
|
if (visible) {
|
||||||
|
if (props.alwaysLoad) {
|
||||||
|
await fetch();
|
||||||
|
} else if (!props.immediate && !unref(isFirstLoaded)) {
|
||||||
|
await fetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChange() {
|
||||||
|
emit('options-change', unref(getOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange(_, ...args) {
|
||||||
|
emitData.value = args;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -12,127 +12,116 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, watch, ref, unref, watchEffect, PropType } from 'vue';
|
import { computed, watch, ref, unref, watchEffect, PropType } from 'vue';
|
||||||
import { Transfer } from 'ant-design-vue';
|
import { Transfer } from 'ant-design-vue';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { get, omit } from 'lodash-es';
|
import { get, omit } from 'lodash-es';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
|
||||||
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
|
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ApiTransfer' });
|
||||||
name: 'ApiTransfer',
|
|
||||||
components: { Transfer },
|
const props = defineProps({
|
||||||
props: {
|
value: { type: Array as PropType<Array<string>> },
|
||||||
value: { type: Array as PropType<Array<string>> },
|
api: {
|
||||||
api: {
|
type: Function as PropType<(arg) => Promise<TransferItem[]>>,
|
||||||
type: Function as PropType<(arg) => Promise<TransferItem[]>>,
|
default: null,
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
params: { type: Object },
|
|
||||||
dataSource: { type: Array as PropType<Array<TransferItem>> },
|
|
||||||
immediate: propTypes.bool.def(true),
|
|
||||||
alwaysLoad: propTypes.bool.def(false),
|
|
||||||
afterFetch: { type: Function },
|
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
labelField: propTypes.string.def('title'),
|
|
||||||
valueField: propTypes.string.def('key'),
|
|
||||||
showSearch: { type: Boolean, default: false },
|
|
||||||
disabled: { type: Boolean, default: false },
|
|
||||||
filterOption: {
|
|
||||||
type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>,
|
|
||||||
},
|
|
||||||
selectedKeys: { type: Array as PropType<Array<string>> },
|
|
||||||
showSelectAll: { type: Boolean, default: false },
|
|
||||||
targetKeys: { type: Array as PropType<Array<string>> },
|
|
||||||
},
|
},
|
||||||
emits: ['options-change', 'change'],
|
params: { type: Object },
|
||||||
setup(props, { attrs, emit }) {
|
dataSource: { type: Array as PropType<Array<TransferItem>> },
|
||||||
const _dataSource = ref<TransferItem[]>([]);
|
immediate: propTypes.bool.def(true),
|
||||||
const _targetKeys = ref<string[]>([]);
|
alwaysLoad: propTypes.bool.def(false),
|
||||||
const { t } = useI18n();
|
afterFetch: { type: Function },
|
||||||
|
resultField: propTypes.string.def(''),
|
||||||
|
labelField: propTypes.string.def('title'),
|
||||||
|
valueField: propTypes.string.def('key'),
|
||||||
|
showSearch: { type: Boolean, default: false },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
filterOption: {
|
||||||
|
type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>,
|
||||||
|
},
|
||||||
|
selectedKeys: { type: Array as PropType<Array<string>> },
|
||||||
|
showSelectAll: { type: Boolean, default: false },
|
||||||
|
targetKeys: { type: Array as PropType<Array<string>> },
|
||||||
|
});
|
||||||
|
|
||||||
const getAttrs = computed(() => {
|
const emit = defineEmits(['options-change', 'change']);
|
||||||
return {
|
|
||||||
...(!props.api ? { dataSource: unref(_dataSource) } : {}),
|
|
||||||
...attrs,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const getdataSource = computed(() => {
|
|
||||||
const { labelField, valueField } = props;
|
|
||||||
|
|
||||||
return unref(_dataSource).reduce((prev, next) => {
|
const _dataSource = ref<TransferItem[]>([]);
|
||||||
if (next) {
|
const _targetKeys = ref<string[]>([]);
|
||||||
prev.push({
|
|
||||||
...omit(next, [labelField, valueField]),
|
const getdataSource = computed(() => {
|
||||||
title: next[labelField],
|
const { labelField, valueField } = props;
|
||||||
key: next[valueField],
|
|
||||||
});
|
return unref(_dataSource).reduce((prev, next) => {
|
||||||
}
|
if (next) {
|
||||||
return prev;
|
prev.push({
|
||||||
}, [] as TransferItem[]);
|
...omit(next, [labelField, valueField]),
|
||||||
});
|
title: next[labelField],
|
||||||
const getTargetKeys = computed<string[]>(() => {
|
key: next[valueField],
|
||||||
/* if (unref(_targetKeys).length > 0) {
|
});
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, [] as TransferItem[]);
|
||||||
|
});
|
||||||
|
const getTargetKeys = computed<string[]>(() => {
|
||||||
|
/* if (unref(_targetKeys).length > 0) {
|
||||||
return unref(_targetKeys);
|
return unref(_targetKeys);
|
||||||
} */
|
} */
|
||||||
if (Array.isArray(props.value)) {
|
if (Array.isArray(props.value)) {
|
||||||
return props.value;
|
return props.value;
|
||||||
}
|
}
|
||||||
if (Array.isArray(props.targetKeys)) {
|
if (Array.isArray(props.targetKeys)) {
|
||||||
return props.targetKeys;
|
return props.targetKeys;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
|
||||||
|
|
||||||
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
|
|
||||||
_targetKeys.value = keys;
|
|
||||||
console.log(direction);
|
|
||||||
console.log(moveKeys);
|
|
||||||
emit('change', keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.immediate && !props.alwaysLoad && fetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.params,
|
|
||||||
() => {
|
|
||||||
fetch();
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
async function fetch() {
|
|
||||||
const api = props.api;
|
|
||||||
if (!api || !isFunction(api)) {
|
|
||||||
if (Array.isArray(props.dataSource)) {
|
|
||||||
_dataSource.value = props.dataSource;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_dataSource.value = [];
|
|
||||||
try {
|
|
||||||
const res = await api(props.params);
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
_dataSource.value = res;
|
|
||||||
emitChange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.resultField) {
|
|
||||||
_dataSource.value = get(res, props.resultField) || [];
|
|
||||||
}
|
|
||||||
emitChange();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function emitChange() {
|
|
||||||
emit('options-change', unref(getdataSource));
|
|
||||||
}
|
|
||||||
return { getTargetKeys, getdataSource, t, getAttrs, handleChange };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
|
||||||
|
_targetKeys.value = keys;
|
||||||
|
console.log(direction);
|
||||||
|
console.log(moveKeys);
|
||||||
|
emit('change', keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && !props.alwaysLoad && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const api = props.api;
|
||||||
|
if (!api || !isFunction(api)) {
|
||||||
|
if (Array.isArray(props.dataSource)) {
|
||||||
|
_dataSource.value = props.dataSource;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_dataSource.value = [];
|
||||||
|
try {
|
||||||
|
const res = await api(props.params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
_dataSource.value = res;
|
||||||
|
emitChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.resultField) {
|
||||||
|
_dataSource.value = get(res, props.resultField) || [];
|
||||||
|
}
|
||||||
|
emitChange();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function emitChange() {
|
||||||
|
emit('options-change', unref(getdataSource));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,99 +1,97 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-tree v-bind="getAttrs" v-model:selectedKeys="state">
|
<Tree v-bind="getAttrs" v-model:selectedKeys="state">
|
||||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||||
<slot :name="item" v-bind="data || {}"></slot>
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
</template>
|
</template>
|
||||||
</a-tree>
|
</Tree>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { type Recordable, type AnyFunction } from '@vben/types';
|
import { type Recordable, type AnyFunction } from '@vben/types';
|
||||||
import { type PropType, computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
|
import { type PropType, computed, watch, ref, onMounted, unref, useAttrs } from 'vue';
|
||||||
import { Tree, TreeProps } from 'ant-design-vue';
|
import { Tree, TreeProps } from 'ant-design-vue';
|
||||||
import { isArray, isFunction } from '/@/utils/is';
|
import { isArray, isFunction } from '@/utils/is';
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
|
||||||
import { DataNode } from 'ant-design-vue/es/tree';
|
import { DataNode } from 'ant-design-vue/es/tree';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ApiTree' });
|
||||||
name: 'ApiTree',
|
|
||||||
components: { ATree: Tree },
|
|
||||||
props: {
|
|
||||||
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
|
||||||
params: { type: Object },
|
|
||||||
immediate: { type: Boolean, default: true },
|
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
afterFetch: { type: Function as PropType<AnyFunction> },
|
|
||||||
value: {
|
|
||||||
type: Array as PropType<TreeProps['selectedKeys']>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['options-change', 'change', 'update:value'],
|
|
||||||
setup(props, { attrs, emit }) {
|
|
||||||
const treeData = ref<DataNode[]>([]);
|
|
||||||
const isFirstLoaded = ref<Boolean>(false);
|
|
||||||
const loading = ref(false);
|
|
||||||
const emitData = ref<any[]>([]);
|
|
||||||
|
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
const props = defineProps({
|
||||||
const getAttrs = computed(() => {
|
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
||||||
return {
|
params: { type: Object },
|
||||||
...(props.api ? { treeData: unref(treeData) } : {}),
|
immediate: { type: Boolean, default: true },
|
||||||
...attrs,
|
resultField: { type: String, default: '' },
|
||||||
};
|
afterFetch: { type: Function as PropType<AnyFunction> },
|
||||||
});
|
value: {
|
||||||
|
type: Array as PropType<TreeProps['selectedKeys']>,
|
||||||
watch(
|
|
||||||
() => state.value,
|
|
||||||
(v) => {
|
|
||||||
emit('update:value', v);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.params,
|
|
||||||
() => {
|
|
||||||
!unref(isFirstLoaded) && fetch();
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.immediate,
|
|
||||||
(v) => {
|
|
||||||
v && !isFirstLoaded.value && fetch();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.immediate && fetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function fetch() {
|
|
||||||
const { api, afterFetch } = props;
|
|
||||||
if (!api || !isFunction(api)) return;
|
|
||||||
loading.value = true;
|
|
||||||
treeData.value = [];
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = await api(props.params);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
if (afterFetch && isFunction(afterFetch)) {
|
|
||||||
result = afterFetch(result);
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
if (!result) return;
|
|
||||||
if (!isArray(result)) {
|
|
||||||
result = get(result, props.resultField);
|
|
||||||
}
|
|
||||||
treeData.value = (result as (Recordable & { key: string | number })[]) || [];
|
|
||||||
isFirstLoaded.value = true;
|
|
||||||
emit('options-change', treeData.value);
|
|
||||||
}
|
|
||||||
return { getAttrs, loading, state };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['options-change', 'change', 'update:value']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const treeData = ref<DataNode[]>([]);
|
||||||
|
const isFirstLoaded = ref<Boolean>(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const emitData = ref<any[]>([]);
|
||||||
|
|
||||||
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
|
const getAttrs = computed(() => {
|
||||||
|
return {
|
||||||
|
...(props.api ? { treeData: unref(treeData) } : {}),
|
||||||
|
...attrs,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.value,
|
||||||
|
(v) => {
|
||||||
|
emit('update:value', v);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!unref(isFirstLoaded) && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.immediate,
|
||||||
|
(v) => {
|
||||||
|
v && !isFirstLoaded.value && fetch();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { api, afterFetch } = props;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
loading.value = true;
|
||||||
|
treeData.value = [];
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await api(props.params);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (afterFetch && isFunction(afterFetch)) {
|
||||||
|
result = afterFetch(result);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
if (!result) return;
|
||||||
|
if (!isArray(result)) {
|
||||||
|
result = get(result, props.resultField);
|
||||||
|
}
|
||||||
|
treeData.value = (result as (Recordable & { key: string | number })[]) || [];
|
||||||
|
isFirstLoaded.value = true;
|
||||||
|
emit('options-change', treeData.value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-tree-select
|
<TreeSelect
|
||||||
v-bind="getAttrs"
|
v-bind="getAttrs"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
:field-names="fieldNames"
|
:field-names="fieldNames"
|
||||||
@@ -11,102 +11,100 @@
|
|||||||
<template #suffixIcon v-if="loading">
|
<template #suffixIcon v-if="loading">
|
||||||
<LoadingOutlined spin />
|
<LoadingOutlined spin />
|
||||||
</template>
|
</template>
|
||||||
</a-tree-select>
|
</TreeSelect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { type Recordable } from '@vben/types';
|
import { type Recordable } from '@vben/types';
|
||||||
import { type PropType, computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
|
import { type PropType, computed, watch, ref, onMounted, unref, useAttrs } from 'vue';
|
||||||
import { TreeSelect } from 'ant-design-vue';
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
import { isArray, isFunction } from '/@/utils/is';
|
import { isArray, isFunction } from '@/utils/is';
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ApiTreeSelect' });
|
||||||
name: 'ApiTreeSelect',
|
|
||||||
components: { ATreeSelect: TreeSelect, LoadingOutlined },
|
|
||||||
props: {
|
|
||||||
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
|
||||||
params: { type: Object },
|
|
||||||
immediate: { type: Boolean, default: true },
|
|
||||||
async: { type: Boolean, default: false },
|
|
||||||
resultField: propTypes.string.def(''),
|
|
||||||
labelField: propTypes.string.def('title'),
|
|
||||||
valueField: propTypes.string.def('value'),
|
|
||||||
childrenField: propTypes.string.def('children'),
|
|
||||||
},
|
|
||||||
emits: ['options-change', 'change', 'load-data'],
|
|
||||||
setup(props, { attrs, emit }) {
|
|
||||||
const treeData = ref<Recordable<any>[]>([]);
|
|
||||||
const isFirstLoaded = ref<Boolean>(false);
|
|
||||||
const loading = ref(false);
|
|
||||||
const getAttrs = computed(() => {
|
|
||||||
return {
|
|
||||||
...(props.api ? { treeData: unref(treeData) } : {}),
|
|
||||||
...attrs,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const fieldNames = {
|
|
||||||
children: props.childrenField,
|
|
||||||
value: props.valueField,
|
|
||||||
label: props.labelField,
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleChange(...args) {
|
const props = defineProps({
|
||||||
emit('change', ...args);
|
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
||||||
}
|
params: { type: Object },
|
||||||
|
immediate: { type: Boolean, default: true },
|
||||||
watch(
|
async: { type: Boolean, default: false },
|
||||||
() => props.params,
|
resultField: propTypes.string.def(''),
|
||||||
() => {
|
labelField: propTypes.string.def('title'),
|
||||||
!unref(isFirstLoaded) && fetch();
|
valueField: propTypes.string.def('value'),
|
||||||
},
|
childrenField: propTypes.string.def('children'),
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.immediate,
|
|
||||||
(v) => {
|
|
||||||
v && !isFirstLoaded.value && fetch();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.immediate && fetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
function onLoadData(treeNode) {
|
|
||||||
return new Promise((resolve: (value?: unknown) => void) => {
|
|
||||||
if (isArray(treeNode.children) && treeNode.children.length > 0) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit('load-data', { treeData, treeNode, resolve });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetch() {
|
|
||||||
const { api } = props;
|
|
||||||
if (!api || !isFunction(api) || loading.value) return;
|
|
||||||
loading.value = true;
|
|
||||||
treeData.value = [];
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = await api(props.params);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
if (!result) return;
|
|
||||||
if (!isArray(result)) {
|
|
||||||
result = get(result, props.resultField);
|
|
||||||
}
|
|
||||||
treeData.value = (result as Recordable<any>[]) || [];
|
|
||||||
isFirstLoaded.value = true;
|
|
||||||
emit('options-change', treeData.value);
|
|
||||||
}
|
|
||||||
return { getAttrs, loading, handleChange, fieldNames, onLoadData };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['options-change', 'change', 'load-data']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const treeData = ref<Recordable<any>[]>([]);
|
||||||
|
const isFirstLoaded = ref<Boolean>(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const getAttrs = computed(() => {
|
||||||
|
return {
|
||||||
|
...(props.api ? { treeData: unref(treeData) } : {}),
|
||||||
|
...attrs,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const fieldNames = {
|
||||||
|
children: props.childrenField,
|
||||||
|
value: props.valueField,
|
||||||
|
label: props.labelField,
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleChange(...args) {
|
||||||
|
emit('change', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!unref(isFirstLoaded) && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.immediate,
|
||||||
|
(v) => {
|
||||||
|
v && !isFirstLoaded.value && fetch();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onLoadData(treeNode) {
|
||||||
|
return new Promise((resolve: (value?: unknown) => void) => {
|
||||||
|
if (isArray(treeNode.children) && treeNode.children.length > 0) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit('load-data', { treeData, treeNode, resolve });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { api } = props;
|
||||||
|
if (!api || !isFunction(api) || loading.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
treeData.value = [];
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await api(props.params);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
if (!result) return;
|
||||||
|
if (!isArray(result)) {
|
||||||
|
result = get(result, props.resultField);
|
||||||
|
}
|
||||||
|
treeData.value = (result as Recordable<any>[]) || [];
|
||||||
|
isFirstLoaded.value = true;
|
||||||
|
emit('options-change', treeData.value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
|
<Col v-bind="actionColOpt" v-if="showActionButtonGroup">
|
||||||
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
|
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
|
||||||
<FormItem>
|
<Form.Item>
|
||||||
<slot name="resetBefore"></slot>
|
<slot name="resetBefore"></slot>
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
@@ -35,100 +35,83 @@
|
|||||||
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
|
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
|
||||||
</Button>
|
</Button>
|
||||||
<slot name="advanceAfter"></slot>
|
<slot name="advanceAfter"></slot>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</Col>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { ColEx } from '../types/index';
|
import type { ColEx } from '../types';
|
||||||
import { defineComponent, computed, PropType } from 'vue';
|
import { computed, PropType } from 'vue';
|
||||||
import { Form, Col } from 'ant-design-vue';
|
import { Form, Col } from 'ant-design-vue';
|
||||||
import { Button, ButtonProps } from '/@/components/Button';
|
import { Button, ButtonProps } from '@/components/Button';
|
||||||
import { BasicArrow } from '/@/components/Basic';
|
import { BasicArrow } from '@/components/Basic';
|
||||||
import { useFormContext } from '../hooks/useFormContext';
|
import { useFormContext } from '../hooks/useFormContext';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
|
||||||
type ButtonOptions = Partial<ButtonProps> & { text: string };
|
type ButtonOptions = Partial<ButtonProps> & { text: string };
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicFormAction' });
|
||||||
name: 'BasicFormAction',
|
|
||||||
components: {
|
const props = defineProps({
|
||||||
FormItem: Form.Item,
|
showActionButtonGroup: propTypes.bool.def(true),
|
||||||
Button,
|
showResetButton: propTypes.bool.def(true),
|
||||||
BasicArrow,
|
showSubmitButton: propTypes.bool.def(true),
|
||||||
[Col.name]: Col,
|
showAdvancedButton: propTypes.bool.def(true),
|
||||||
|
resetButtonOptions: {
|
||||||
|
type: Object as PropType<ButtonOptions>,
|
||||||
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
props: {
|
submitButtonOptions: {
|
||||||
showActionButtonGroup: propTypes.bool.def(true),
|
type: Object as PropType<ButtonOptions>,
|
||||||
showResetButton: propTypes.bool.def(true),
|
default: () => ({}),
|
||||||
showSubmitButton: propTypes.bool.def(true),
|
|
||||||
showAdvancedButton: propTypes.bool.def(true),
|
|
||||||
resetButtonOptions: {
|
|
||||||
type: Object as PropType<ButtonOptions>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
submitButtonOptions: {
|
|
||||||
type: Object as PropType<ButtonOptions>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
actionColOptions: {
|
|
||||||
type: Object as PropType<Partial<ColEx>>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
actionSpan: propTypes.number.def(6),
|
|
||||||
isAdvanced: propTypes.bool,
|
|
||||||
hideAdvanceBtn: propTypes.bool,
|
|
||||||
},
|
},
|
||||||
emits: ['toggle-advanced'],
|
actionColOptions: {
|
||||||
setup(props, { emit }) {
|
type: Object as PropType<Partial<ColEx>>,
|
||||||
const { t } = useI18n();
|
default: () => ({}),
|
||||||
|
|
||||||
const actionColOpt = computed(() => {
|
|
||||||
const { showAdvancedButton, actionSpan: span, actionColOptions } = props;
|
|
||||||
const actionSpan = 24 - span;
|
|
||||||
const advancedSpanObj = showAdvancedButton
|
|
||||||
? { span: actionSpan < 6 ? 24 : actionSpan }
|
|
||||||
: {};
|
|
||||||
const actionColOpt: Partial<ColEx> = {
|
|
||||||
style: { textAlign: 'right' },
|
|
||||||
span: showAdvancedButton ? 6 : 4,
|
|
||||||
...advancedSpanObj,
|
|
||||||
...actionColOptions,
|
|
||||||
};
|
|
||||||
return actionColOpt;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getResetBtnOptions = computed((): ButtonOptions => {
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
text: t('common.resetText'),
|
|
||||||
},
|
|
||||||
props.resetButtonOptions,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const getSubmitBtnOptions = computed(() => {
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
text: t('common.queryText'),
|
|
||||||
},
|
|
||||||
props.submitButtonOptions,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggleAdvanced() {
|
|
||||||
emit('toggle-advanced');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
t,
|
|
||||||
actionColOpt,
|
|
||||||
getResetBtnOptions,
|
|
||||||
getSubmitBtnOptions,
|
|
||||||
toggleAdvanced,
|
|
||||||
...useFormContext(),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
actionSpan: propTypes.number.def(6),
|
||||||
|
isAdvanced: propTypes.bool,
|
||||||
|
hideAdvanceBtn: propTypes.bool,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['toggle-advanced']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { resetAction, submitAction } = useFormContext();
|
||||||
|
|
||||||
|
const actionColOpt = computed(() => {
|
||||||
|
const { showAdvancedButton, actionSpan: span, actionColOptions } = props;
|
||||||
|
const actionSpan = 24 - span;
|
||||||
|
const advancedSpanObj = showAdvancedButton ? { span: actionSpan < 6 ? 24 : actionSpan } : {};
|
||||||
|
const actionColOpt: Partial<ColEx> = {
|
||||||
|
style: { textAlign: 'right' },
|
||||||
|
span: showAdvancedButton ? 6 : 4,
|
||||||
|
...advancedSpanObj,
|
||||||
|
...actionColOptions,
|
||||||
|
};
|
||||||
|
return actionColOpt;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getResetBtnOptions = computed((): ButtonOptions => {
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
text: t('common.resetText'),
|
||||||
|
},
|
||||||
|
props.resetButtonOptions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSubmitBtnOptions = computed(() => {
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
text: t('common.queryText'),
|
||||||
|
},
|
||||||
|
props.submitButtonOptions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleAdvanced() {
|
||||||
|
emit('toggle-advanced');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -9,12 +9,12 @@
|
|||||||
type FormSchemaInner as FormSchema,
|
type FormSchemaInner as FormSchema,
|
||||||
} from '../types/form';
|
} from '../types/form';
|
||||||
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
|
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
|
||||||
import type { TableActionType } from '/@/components/Table';
|
import type { TableActionType } from '@/components/Table';
|
||||||
import { Col, Divider, Form } from 'ant-design-vue';
|
import { Col, Divider, Form } from 'ant-design-vue';
|
||||||
import { componentMap } from '../componentMap';
|
import { componentMap } from '../componentMap';
|
||||||
import { BasicHelp } from '/@/components/Basic';
|
import { BasicHelp } from '@/components/Basic';
|
||||||
import { isBoolean, isFunction, isNull } from '/@/utils/is';
|
import { isBoolean, isFunction, isNull } from '@/utils/is';
|
||||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||||
import {
|
import {
|
||||||
createPlaceholderMessage,
|
createPlaceholderMessage,
|
||||||
NO_AUTO_LINK_COMPONENTS,
|
NO_AUTO_LINK_COMPONENTS,
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
} from '../helper';
|
} from '../helper';
|
||||||
import { cloneDeep, upperFirst } from 'lodash-es';
|
import { cloneDeep, upperFirst } from 'lodash-es';
|
||||||
import { useItemLabelWidth } from '../hooks/useLabelWidth';
|
import { useItemLabelWidth } from '../hooks/useLabelWidth';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BasicFormItem',
|
name: 'BasicFormItem',
|
||||||
|
@@ -2,62 +2,55 @@
|
|||||||
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
|
<Radio.Group v-bind="attrs" v-model:value="state" button-style="solid">
|
||||||
<template v-for="item in getOptions" :key="`${item.value}`">
|
<template v-for="item in getOptions" :key="`${item.value}`">
|
||||||
<RadioButton :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
|
<Radio.Button :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</RadioButton>
|
</Radio.Button>
|
||||||
</template>
|
</template>
|
||||||
</RadioGroup>
|
</Radio.Group>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, computed, ref } from 'vue';
|
import { PropType, computed, ref } from 'vue';
|
||||||
import { Radio } from 'ant-design-vue';
|
import { Radio } from 'ant-design-vue';
|
||||||
import { isString } from '/@/utils/is';
|
import { isString } from '@/utils/is';
|
||||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
|
|
||||||
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
||||||
type RadioItem = string | OptionsItem;
|
type RadioItem = string | OptionsItem;
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'RadioButtonGroup' });
|
||||||
name: 'RadioButtonGroup',
|
|
||||||
components: {
|
const props = defineProps({
|
||||||
RadioGroup: Radio.Group,
|
value: {
|
||||||
RadioButton: Radio.Button,
|
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||||
},
|
},
|
||||||
props: {
|
options: {
|
||||||
value: {
|
type: Array as PropType<RadioItem[]>,
|
||||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
default: () => [],
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Array as PropType<RadioItem[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['change'],
|
|
||||||
setup(props) {
|
|
||||||
const attrs = useAttrs();
|
|
||||||
const emitData = ref<any[]>([]);
|
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
|
||||||
|
|
||||||
// Processing options value
|
|
||||||
const getOptions = computed((): OptionsItem[] => {
|
|
||||||
const { options } = props;
|
|
||||||
if (!options || options?.length === 0) return [];
|
|
||||||
|
|
||||||
const isStringArr = options.some((item) => isString(item));
|
|
||||||
if (!isStringArr) return options as OptionsItem[];
|
|
||||||
|
|
||||||
return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleClick(...args) {
|
|
||||||
emitData.value = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { state, getOptions, attrs, handleClick };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const emitData = ref<any[]>([]);
|
||||||
|
// Embedded in the form, just use the hook binding to perform form verification
|
||||||
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||||
|
|
||||||
|
// Processing options value
|
||||||
|
const getOptions = computed((): OptionsItem[] => {
|
||||||
|
const { options } = props;
|
||||||
|
if (!options || options?.length === 0) return [];
|
||||||
|
|
||||||
|
const isStringArr = options.some((item) => isString(item));
|
||||||
|
if (!isStringArr) return options as OptionsItem[];
|
||||||
|
|
||||||
|
return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClick(...args) {
|
||||||
|
emitData.value = args;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
|
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
|
||||||
import type { ComponentType } from './types/index';
|
import type { ComponentType } from './types';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '@/utils/dateUtil';
|
||||||
import { isNumber, isObject } from '/@/utils/is';
|
import { isNumber, isObject } from '@/utils/is';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@@ -2,8 +2,8 @@ import type { ColEx } from '../types';
|
|||||||
import type { AdvanceState } from '../types/hooks';
|
import type { AdvanceState } from '../types/hooks';
|
||||||
import { ComputedRef, getCurrentInstance, Ref, shallowReactive, computed, unref, watch } from 'vue';
|
import { ComputedRef, getCurrentInstance, Ref, shallowReactive, computed, unref, watch } from 'vue';
|
||||||
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
||||||
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
|
import { isBoolean, isFunction, isNumber, isObject } from '@/utils/is';
|
||||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
|
import { useBreakpoint } from '@/hooks/event/useBreakpoint';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
const BASIC_COL_LEN = 24;
|
const BASIC_COL_LEN = 24;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import type { ComponentType } from '../types/index';
|
import type { ComponentType } from '../types';
|
||||||
import { tryOnUnmounted } from '@vueuse/core';
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
import { add, del } from '../componentMap';
|
import { add, del } from '../componentMap';
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
|
@@ -7,9 +7,9 @@ import type {
|
|||||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||||
import type { DynamicProps } from '/#/utils';
|
import type { DynamicProps } from '/#/utils';
|
||||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
||||||
import { isProdMode } from '/@/utils/env';
|
import { isProdMode } from '@/utils/env';
|
||||||
import { error } from '/@/utils/log';
|
import { error } from '@/utils/log';
|
||||||
import { getDynamicProps } from '/@/utils';
|
import { getDynamicProps } from '@/utils';
|
||||||
|
|
||||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
|
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { InjectionKey } from 'vue';
|
import type { InjectionKey } from 'vue';
|
||||||
import { createContext, useContext } from '/@/hooks/core/useContext';
|
import { createContext, useContext } from '@/hooks/core/useContext';
|
||||||
|
|
||||||
export interface FormContextProps {
|
export interface FormContextProps {
|
||||||
resetAction: () => Promise<void>;
|
resetAction: () => Promise<void>;
|
||||||
|
@@ -2,12 +2,12 @@ import type { ComputedRef, Ref } from 'vue';
|
|||||||
import type { FormProps, FormSchemaInner as FormSchema, FormActionType } from '../types/form';
|
import type { FormProps, FormSchemaInner as FormSchema, FormActionType } from '../types/form';
|
||||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||||
import { unref, toRaw, nextTick } from 'vue';
|
import { unref, toRaw, nextTick } from 'vue';
|
||||||
import { isArray, isFunction, isObject, isString, isDef, isNil } from '/@/utils/is';
|
import { isArray, isFunction, isObject, isString, isDef, isNil } from '@/utils/is';
|
||||||
import { deepMerge } from '/@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
|
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
|
||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '@/utils/dateUtil';
|
||||||
import { cloneDeep, set, uniqBy, get } from 'lodash-es';
|
import { cloneDeep, set, uniqBy, get } from 'lodash-es';
|
||||||
import { error } from '/@/utils/log';
|
import { error } from '@/utils/log';
|
||||||
|
|
||||||
interface UseFormActionContext {
|
interface UseFormActionContext {
|
||||||
emit: EmitType;
|
emit: EmitType;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { isArray, isFunction, isEmpty, isObject, isString, isNil } from '/@/utils/is';
|
import { isArray, isFunction, isEmpty, isObject, isString, isNil } from '@/utils/is';
|
||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '@/utils/dateUtil';
|
||||||
import { unref } from 'vue';
|
import { unref } from 'vue';
|
||||||
import type { Ref, ComputedRef } from 'vue';
|
import type { Ref, ComputedRef } from 'vue';
|
||||||
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed, unref } from 'vue';
|
import { computed, unref } from 'vue';
|
||||||
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
import type { FormProps, FormSchemaInner as FormSchema } from '../types/form';
|
||||||
import { isNumber } from '/@/utils/is';
|
import { isNumber } from '@/utils/is';
|
||||||
|
|
||||||
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import type { FieldMapToTime, FormSchema } from './types/form';
|
import type { FieldMapToTime, FormSchema } from './types/form';
|
||||||
import type { CSSProperties, PropType } from 'vue';
|
import type { CSSProperties, PropType } from 'vue';
|
||||||
import type { ColEx } from './types';
|
import type { ColEx } from './types';
|
||||||
import type { TableActionType } from '/@/components/Table';
|
import type { TableActionType } from '@/components/Table';
|
||||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
model: {
|
model: {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
|
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
|
||||||
import type { VNode, CSSProperties } from 'vue';
|
import type { VNode, CSSProperties } from 'vue';
|
||||||
import type { ButtonProps as AntdButtonProps } from '/@/components/Button';
|
import type { ButtonProps as AntdButtonProps } from '@/components/Button';
|
||||||
import type { FormItem } from './formItem';
|
import type { FormItem } from './formItem';
|
||||||
import type { ColEx, ComponentType } from './index';
|
import type { ColEx, ComponentType } from './';
|
||||||
import type { TableActionType } from '/@/components/Table/src/types/table';
|
import type { TableActionType } from '@/components/Table/src/types/table';
|
||||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||||
|
|
||||||
export type FieldMapToTime = [string, [string, string], (string | [string, string])?][];
|
export type FieldMapToTime = [string, [string, string], (string | [string, string])?][];
|
||||||
|
@@ -13,91 +13,78 @@
|
|||||||
:style="getWrapStyle"
|
:style="getWrapStyle"
|
||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import {
|
import { ref, watch, onMounted, nextTick, unref, computed, CSSProperties } from 'vue';
|
||||||
defineComponent,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
unref,
|
|
||||||
computed,
|
|
||||||
CSSProperties,
|
|
||||||
} from 'vue';
|
|
||||||
import SvgIcon from './src/SvgIcon.vue';
|
import SvgIcon from './src/SvgIcon.vue';
|
||||||
import Iconify from '@purge-icons/generated';
|
import Iconify from '@purge-icons/generated';
|
||||||
import { isString } from '/@/utils/is';
|
import { isString } from '@/utils/is';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
|
||||||
const SVG_END_WITH_FLAG = '|svg';
|
const SVG_END_WITH_FLAG = '|svg';
|
||||||
export default defineComponent({
|
|
||||||
name: 'Icon',
|
defineOptions({ name: 'Icon' });
|
||||||
components: { SvgIcon },
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
// icon name
|
// icon name
|
||||||
icon: propTypes.string,
|
icon: propTypes.string,
|
||||||
// icon color
|
// icon color
|
||||||
color: propTypes.string,
|
color: propTypes.string,
|
||||||
// icon size
|
// icon size
|
||||||
size: {
|
size: {
|
||||||
type: [String, Number] as PropType<string | number>,
|
type: [String, Number] as PropType<string | number>,
|
||||||
default: 16,
|
default: 16,
|
||||||
},
|
|
||||||
spin: propTypes.bool.def(false),
|
|
||||||
prefix: propTypes.string.def(''),
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const elRef = ref(null);
|
|
||||||
|
|
||||||
const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG));
|
|
||||||
const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, ''));
|
|
||||||
const getIconRef = computed(() => `${props.prefix ? props.prefix + ':' : ''}${props.icon}`);
|
|
||||||
|
|
||||||
const update = async () => {
|
|
||||||
if (unref(isSvgIcon)) return;
|
|
||||||
|
|
||||||
const el: any = unref(elRef);
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
const icon = unref(getIconRef);
|
|
||||||
if (!icon) return;
|
|
||||||
|
|
||||||
const svg = Iconify.renderSVG(icon, {});
|
|
||||||
if (svg) {
|
|
||||||
el.textContent = '';
|
|
||||||
el.appendChild(svg);
|
|
||||||
} else {
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.className = 'iconify';
|
|
||||||
span.dataset.icon = icon;
|
|
||||||
el.textContent = '';
|
|
||||||
el.appendChild(span);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getWrapStyle = computed((): CSSProperties => {
|
|
||||||
const { size, color } = props;
|
|
||||||
let fs = size;
|
|
||||||
if (isString(size)) {
|
|
||||||
fs = parseInt(size, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fontSize: `${fs}px`,
|
|
||||||
color: color,
|
|
||||||
display: 'inline-flex',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => props.icon, update, { flush: 'post' });
|
|
||||||
|
|
||||||
onMounted(update);
|
|
||||||
|
|
||||||
return { elRef, getWrapStyle, isSvgIcon, getSvgIcon };
|
|
||||||
},
|
},
|
||||||
|
spin: propTypes.bool.def(false),
|
||||||
|
prefix: propTypes.string.def(''),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const elRef = ref(null);
|
||||||
|
|
||||||
|
const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG));
|
||||||
|
const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, ''));
|
||||||
|
const getIconRef = computed(() => `${props.prefix ? props.prefix + ':' : ''}${props.icon}`);
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
if (unref(isSvgIcon)) return;
|
||||||
|
|
||||||
|
const el: any = unref(elRef);
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
const icon = unref(getIconRef);
|
||||||
|
if (!icon) return;
|
||||||
|
|
||||||
|
const svg = Iconify.renderSVG(icon, {});
|
||||||
|
if (svg) {
|
||||||
|
el.textContent = '';
|
||||||
|
el.appendChild(svg);
|
||||||
|
} else {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'iconify';
|
||||||
|
span.dataset.icon = icon;
|
||||||
|
el.textContent = '';
|
||||||
|
el.appendChild(span);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWrapStyle = computed((): CSSProperties => {
|
||||||
|
const { size, color } = props;
|
||||||
|
let fs = size;
|
||||||
|
if (isString(size)) {
|
||||||
|
fs = parseInt(size, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fontSize: `${fs}px`,
|
||||||
|
color: color,
|
||||||
|
display: 'inline-flex',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.icon, update, { flush: 'post' });
|
||||||
|
|
||||||
|
onMounted(update);
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.app-iconify {
|
.app-iconify {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-input
|
<Input
|
||||||
readonly
|
readonly
|
||||||
:style="{ width }"
|
:style="{ width }"
|
||||||
:placeholder="t('component.icon.placeholder')"
|
:placeholder="t('component.icon.placeholder')"
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
@click="triggerPopover"
|
@click="triggerPopover"
|
||||||
>
|
>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-popover
|
<Popover
|
||||||
placement="bottomLeft"
|
placement="bottomLeft"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<a-input
|
<Input
|
||||||
:placeholder="t('component.icon.search')"
|
:placeholder="t('component.icon.search')"
|
||||||
@change="debounceHandleSearchChange"
|
@change="debounceHandleSearchChange"
|
||||||
allowClear
|
allowClear
|
||||||
@@ -36,14 +36,13 @@
|
|||||||
@click="handleClick(icon)"
|
@click="handleClick(icon)"
|
||||||
:title="icon"
|
:title="icon"
|
||||||
>
|
>
|
||||||
<!-- <Icon :icon="icon" :prefix="prefix" /> -->
|
|
||||||
<SvgIcon v-if="isSvgMode" :name="icon" />
|
<SvgIcon v-if="isSvgMode" :name="icon" />
|
||||||
<Icon :icon="icon" v-else />
|
<Icon :icon="icon" v-else />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
|
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
|
||||||
<a-pagination
|
<Pagination
|
||||||
showLessItems
|
showLessItems
|
||||||
size="small"
|
size="small"
|
||||||
:pageSize="pageSize"
|
:pageSize="pageSize"
|
||||||
@@ -52,8 +51,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-else
|
<template v-else>
|
||||||
><div class="p-5"><a-empty /></div>
|
<div class="p-5"><Empty /> </div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -70,30 +69,24 @@
|
|||||||
v-else
|
v-else
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</Input>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watchEffect, watch } from 'vue';
|
import { ref, watchEffect, watch } from 'vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { ScrollContainer } from '/@/components/Container';
|
import { ScrollContainer } from '@/components/Container';
|
||||||
import { Input, Popover, Pagination, Empty } from 'ant-design-vue';
|
import { Input, Popover, Pagination, Empty } from 'ant-design-vue';
|
||||||
import Icon from '../Icon.vue';
|
import Icon from '../Icon.vue';
|
||||||
import SvgIcon from './SvgIcon.vue';
|
import SvgIcon from './SvgIcon.vue';
|
||||||
|
|
||||||
import iconsData from '../data/icons.data';
|
import iconsData from '../data/icons.data';
|
||||||
import { usePagination } from '/@/hooks/web/usePagination';
|
import { usePagination } from '@/hooks/web/usePagination';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import svgIcons from 'virtual:svg-icons-names';
|
import svgIcons from 'virtual:svg-icons-names';
|
||||||
import { copyText } from '/@/utils/copyTextToClipboard';
|
import { copyText } from '@/utils/copyTextToClipboard';
|
||||||
|
|
||||||
// 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告
|
|
||||||
const AInput = Input;
|
|
||||||
const APopover = Popover;
|
|
||||||
const APagination = Pagination;
|
|
||||||
const AEmpty = Empty;
|
|
||||||
|
|
||||||
function getIcons() {
|
function getIcons() {
|
||||||
const prefix = iconsData.prefix;
|
const prefix = iconsData.prefix;
|
||||||
|
@@ -7,46 +7,43 @@
|
|||||||
<use :xlink:href="symbolId" />
|
<use :xlink:href="symbolId" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import { defineComponent, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'SvgIcon' });
|
||||||
name: 'SvgIcon',
|
|
||||||
props: {
|
|
||||||
prefix: {
|
|
||||||
type: String,
|
|
||||||
default: 'icon',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 16,
|
|
||||||
},
|
|
||||||
spin: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { prefixCls } = useDesign('svg-icon');
|
|
||||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
|
||||||
|
|
||||||
const getStyle = computed((): CSSProperties => {
|
const props = defineProps({
|
||||||
const { size } = props;
|
prefix: {
|
||||||
let s = `${size}`;
|
type: String,
|
||||||
s = `${s.replace('px', '')}px`;
|
default: 'icon',
|
||||||
return {
|
|
||||||
width: s,
|
|
||||||
height: s,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return { symbolId, prefixCls, getStyle };
|
|
||||||
},
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 16,
|
||||||
|
},
|
||||||
|
spin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('svg-icon');
|
||||||
|
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
||||||
|
|
||||||
|
const getStyle = computed((): CSSProperties => {
|
||||||
|
const { size } = props;
|
||||||
|
let s = `${size}`;
|
||||||
|
s = `${s.replace('px', '')}px`;
|
||||||
|
return {
|
||||||
|
width: s,
|
||||||
|
height: s,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@@ -8,41 +8,39 @@
|
|||||||
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
|
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { PropType, defineComponent } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { Spin } from 'ant-design-vue';
|
import { Spin } from 'ant-design-vue';
|
||||||
import { SizeEnum } from '/@/enums/sizeEnum';
|
import { SizeEnum } from '@/enums/sizeEnum';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'Loading' });
|
||||||
name: 'Loading',
|
|
||||||
components: { Spin },
|
defineProps({
|
||||||
props: {
|
tip: {
|
||||||
tip: {
|
type: String as PropType<string>,
|
||||||
type: String as PropType<string>,
|
default: '',
|
||||||
default: '',
|
},
|
||||||
},
|
size: {
|
||||||
size: {
|
type: String as PropType<SizeEnum>,
|
||||||
type: String as PropType<SizeEnum>,
|
default: SizeEnum.LARGE,
|
||||||
default: SizeEnum.LARGE,
|
validator: (v: SizeEnum): boolean => {
|
||||||
validator: (v: SizeEnum): boolean => {
|
return [SizeEnum.DEFAULT, SizeEnum.SMALL, SizeEnum.LARGE].includes(v);
|
||||||
return [SizeEnum.DEFAULT, SizeEnum.SMALL, SizeEnum.LARGE].includes(v);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
absolute: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
type: String as PropType<'dark' | 'light'>,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
absolute: {
|
||||||
|
type: Boolean as PropType<boolean>,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean as PropType<boolean>,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
type: String as PropType<'dark' | 'light'>,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { SizeEnum } from '/@/enums/sizeEnum';
|
import { SizeEnum } from '@/enums/sizeEnum';
|
||||||
|
|
||||||
export interface LoadingProps {
|
export interface LoadingProps {
|
||||||
tip: string;
|
tip: string;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import markDown from './src/Markdown.vue';
|
import markDown from './src/Markdown.vue';
|
||||||
import markDownViewer from './src/MarkdownViewer.vue';
|
import markDownViewer from './src/MarkdownViewer.vue';
|
||||||
|
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapRef"></div>
|
<div ref="wrapRef"></div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
|
||||||
ref,
|
ref,
|
||||||
unref,
|
unref,
|
||||||
nextTick,
|
nextTick,
|
||||||
@@ -12,149 +11,147 @@
|
|||||||
watch,
|
watch,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onDeactivated,
|
onDeactivated,
|
||||||
|
useAttrs,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import Vditor from 'vditor';
|
import Vditor from 'vditor';
|
||||||
import 'vditor/dist/index.css';
|
import 'vditor/dist/index.css';
|
||||||
import { useLocale } from '/@/locales/useLocale';
|
import { useLocale } from '@/locales/useLocale';
|
||||||
import { useModalContext } from '../../Modal';
|
import { useModalContext } from '../../Modal';
|
||||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
||||||
import { onMountedOrActivated } from '@vben/hooks';
|
import { onMountedOrActivated } from '@vben/hooks';
|
||||||
import { getTheme } from './getTheme';
|
import { getTheme } from './getTheme';
|
||||||
|
|
||||||
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
|
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ inheritAttrs: false });
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
height: { type: Number, default: 360 },
|
|
||||||
value: { type: String, default: '' },
|
|
||||||
},
|
|
||||||
emits: ['change', 'get', 'update:value'],
|
|
||||||
setup(props, { attrs, emit }) {
|
|
||||||
const wrapRef = ref(null);
|
|
||||||
const vditorRef = ref(null) as Ref<Vditor | null>;
|
|
||||||
const initedRef = ref(false);
|
|
||||||
|
|
||||||
const modalFn = useModalContext();
|
const props = defineProps({
|
||||||
|
height: { type: Number, default: 360 },
|
||||||
const { getLocale } = useLocale();
|
value: { type: String, default: '' },
|
||||||
const { getDarkMode } = useRootSetting();
|
|
||||||
const valueRef = ref(props.value || '');
|
|
||||||
|
|
||||||
watch(
|
|
||||||
[() => getDarkMode.value, () => initedRef.value],
|
|
||||||
([val, inited]) => {
|
|
||||||
if (!inited) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
instance
|
|
||||||
.getVditor()
|
|
||||||
?.setTheme(getTheme(val) as any, getTheme(val, 'content'), getTheme(val, 'code'));
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
flush: 'post',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
(v) => {
|
|
||||||
if (v !== valueRef.value) {
|
|
||||||
instance.getVditor()?.setValue(v);
|
|
||||||
}
|
|
||||||
valueRef.value = v;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
|
|
||||||
let lang: Lang;
|
|
||||||
switch (unref(getLocale)) {
|
|
||||||
case 'en':
|
|
||||||
lang = 'en_US';
|
|
||||||
break;
|
|
||||||
case 'ja':
|
|
||||||
lang = 'ja_JP';
|
|
||||||
break;
|
|
||||||
case 'ko':
|
|
||||||
lang = 'ko_KR';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
lang = 'zh_CN';
|
|
||||||
}
|
|
||||||
return lang;
|
|
||||||
});
|
|
||||||
function init() {
|
|
||||||
const wrapEl = unref(wrapRef);
|
|
||||||
if (!wrapEl) return;
|
|
||||||
const bindValue = { ...attrs, ...props };
|
|
||||||
const insEditor = new Vditor(wrapEl, {
|
|
||||||
// 设置外观主题
|
|
||||||
theme: getTheme(getDarkMode.value) as any,
|
|
||||||
lang: unref(getCurrentLang),
|
|
||||||
mode: 'sv',
|
|
||||||
fullscreen: {
|
|
||||||
index: 520,
|
|
||||||
},
|
|
||||||
preview: {
|
|
||||||
theme: {
|
|
||||||
// 设置内容主题
|
|
||||||
current: getTheme(getDarkMode.value, 'content'),
|
|
||||||
},
|
|
||||||
hljs: {
|
|
||||||
// 设置代码块主题
|
|
||||||
style: getTheme(getDarkMode.value, 'code'),
|
|
||||||
},
|
|
||||||
actions: [],
|
|
||||||
},
|
|
||||||
input: (v) => {
|
|
||||||
valueRef.value = v;
|
|
||||||
emit('update:value', v);
|
|
||||||
emit('change', v);
|
|
||||||
},
|
|
||||||
after: () => {
|
|
||||||
nextTick(() => {
|
|
||||||
modalFn?.redoModalHeight?.();
|
|
||||||
insEditor.setValue(valueRef.value);
|
|
||||||
vditorRef.value = insEditor;
|
|
||||||
initedRef.value = true;
|
|
||||||
emit('get', instance);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
blur: () => {
|
|
||||||
//unref(vditorRef)?.setValue(props.value);
|
|
||||||
},
|
|
||||||
...bindValue,
|
|
||||||
cache: {
|
|
||||||
enable: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = {
|
|
||||||
getVditor: (): Vditor => vditorRef.value!,
|
|
||||||
};
|
|
||||||
|
|
||||||
function destroy() {
|
|
||||||
const vditorInstance = unref(vditorRef);
|
|
||||||
if (!vditorInstance) return;
|
|
||||||
try {
|
|
||||||
vditorInstance?.destroy?.();
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
vditorRef.value = null;
|
|
||||||
initedRef.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMountedOrActivated(init);
|
|
||||||
|
|
||||||
onBeforeUnmount(destroy);
|
|
||||||
onDeactivated(destroy);
|
|
||||||
return {
|
|
||||||
wrapRef,
|
|
||||||
...instance,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'get', 'update:value']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const wrapRef = ref(null);
|
||||||
|
const vditorRef = ref(null) as Ref<Vditor | null>;
|
||||||
|
const initedRef = ref(false);
|
||||||
|
|
||||||
|
const modalFn = useModalContext();
|
||||||
|
|
||||||
|
const { getLocale } = useLocale();
|
||||||
|
const { getDarkMode } = useRootSetting();
|
||||||
|
const valueRef = ref(props.value || '');
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => getDarkMode.value, () => initedRef.value],
|
||||||
|
([val, inited]) => {
|
||||||
|
if (!inited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
instance
|
||||||
|
.getVditor()
|
||||||
|
?.setTheme(getTheme(val) as any, getTheme(val, 'content'), getTheme(val, 'code'));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
flush: 'post',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(v) => {
|
||||||
|
if (v !== valueRef.value) {
|
||||||
|
instance.getVditor()?.setValue(v);
|
||||||
|
}
|
||||||
|
valueRef.value = v;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
|
||||||
|
let lang: Lang;
|
||||||
|
switch (unref(getLocale)) {
|
||||||
|
case 'en':
|
||||||
|
lang = 'en_US';
|
||||||
|
break;
|
||||||
|
case 'ja':
|
||||||
|
lang = 'ja_JP';
|
||||||
|
break;
|
||||||
|
case 'ko':
|
||||||
|
lang = 'ko_KR';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lang = 'zh_CN';
|
||||||
|
}
|
||||||
|
return lang;
|
||||||
|
});
|
||||||
|
function init() {
|
||||||
|
const wrapEl = unref(wrapRef);
|
||||||
|
if (!wrapEl) return;
|
||||||
|
const bindValue = { ...attrs, ...props };
|
||||||
|
const insEditor = new Vditor(wrapEl, {
|
||||||
|
// 设置外观主题
|
||||||
|
theme: getTheme(getDarkMode.value) as any,
|
||||||
|
lang: unref(getCurrentLang),
|
||||||
|
mode: 'sv',
|
||||||
|
fullscreen: {
|
||||||
|
index: 520,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
theme: {
|
||||||
|
// 设置内容主题
|
||||||
|
current: getTheme(getDarkMode.value, 'content'),
|
||||||
|
},
|
||||||
|
hljs: {
|
||||||
|
// 设置代码块主题
|
||||||
|
style: getTheme(getDarkMode.value, 'code'),
|
||||||
|
},
|
||||||
|
actions: [],
|
||||||
|
},
|
||||||
|
input: (v) => {
|
||||||
|
valueRef.value = v;
|
||||||
|
emit('update:value', v);
|
||||||
|
emit('change', v);
|
||||||
|
},
|
||||||
|
after: () => {
|
||||||
|
nextTick(() => {
|
||||||
|
modalFn?.redoModalHeight?.();
|
||||||
|
insEditor.setValue(valueRef.value);
|
||||||
|
vditorRef.value = insEditor;
|
||||||
|
initedRef.value = true;
|
||||||
|
emit('get', instance);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
blur: () => {
|
||||||
|
//unref(vditorRef)?.setValue(props.value);
|
||||||
|
},
|
||||||
|
...bindValue,
|
||||||
|
cache: {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = {
|
||||||
|
getVditor: (): Vditor => vditorRef.value!,
|
||||||
|
};
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
const vditorInstance = unref(vditorRef);
|
||||||
|
if (!vditorInstance) return;
|
||||||
|
try {
|
||||||
|
vditorInstance?.destroy?.();
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
vditorRef.value = null;
|
||||||
|
initedRef.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMountedOrActivated(init);
|
||||||
|
|
||||||
|
onBeforeUnmount(destroy);
|
||||||
|
onDeactivated(destroy);
|
||||||
</script>
|
</script>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
import { onBeforeUnmount, onDeactivated, Ref, ref, unref, watch } from 'vue';
|
import { onBeforeUnmount, onDeactivated, Ref, ref, unref, watch } from 'vue';
|
||||||
import VditorPreview from 'vditor/dist/method.min';
|
import VditorPreview from 'vditor/dist/method.min';
|
||||||
import { onMountedOrActivated } from '@vben/hooks';
|
import { onMountedOrActivated } from '@vben/hooks';
|
||||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
||||||
import { getTheme } from './getTheme';
|
import { getTheme } from './getTheme';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Menu
|
<Menu
|
||||||
:selectedKeys="selectedKeys"
|
:selectedKeys="menuState.selectedKeys"
|
||||||
:defaultSelectedKeys="defaultSelectedKeys"
|
:defaultSelectedKeys="menuState.defaultSelectedKeys"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:openKeys="getOpenKeys"
|
:openKeys="getOpenKeys"
|
||||||
:inlineIndent="inlineIndent"
|
:inlineIndent="inlineIndent"
|
||||||
@@ -17,147 +17,132 @@
|
|||||||
</template>
|
</template>
|
||||||
</Menu>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { MenuState } from './types';
|
import type { MenuState } from './types';
|
||||||
import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue';
|
import { computed, unref, reactive, watch, toRefs, ref } from 'vue';
|
||||||
import { Menu, MenuProps } from 'ant-design-vue';
|
import { Menu, MenuProps } from 'ant-design-vue';
|
||||||
import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
|
import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
|
||||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum';
|
||||||
import { useOpenKeys } from './useOpenKeys';
|
import { useOpenKeys } from './useOpenKeys';
|
||||||
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
|
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
import { useMenuSetting } from '@/hooks/setting/useMenuSetting';
|
||||||
import { REDIRECT_NAME } from '/@/router/constant';
|
import { REDIRECT_NAME } from '@/router/constant';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { getCurrentParentPath } from '/@/router/menus';
|
import { getCurrentParentPath } from '@/router/menus';
|
||||||
import { listenerRouteChange } from '/@/logics/mitt/routeChange';
|
import { listenerRouteChange } from '@/logics/mitt/routeChange';
|
||||||
import { getAllParentPath } from '/@/router/helper/menuHelper';
|
import { getAllParentPath } from '@/router/helper/menuHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicMenu' });
|
||||||
name: 'BasicMenu',
|
|
||||||
components: {
|
|
||||||
Menu,
|
|
||||||
BasicSubMenuItem,
|
|
||||||
},
|
|
||||||
props: basicProps,
|
|
||||||
emits: ['menuClick'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const isClickGo = ref(false);
|
|
||||||
const currentActiveMenu = ref('');
|
|
||||||
|
|
||||||
const menuState = reactive<MenuState>({
|
const props = defineProps(basicProps);
|
||||||
defaultSelectedKeys: [],
|
|
||||||
openKeys: [],
|
|
||||||
selectedKeys: [],
|
|
||||||
collapsedOpenKeys: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { prefixCls } = useDesign('basic-menu');
|
const emit = defineEmits(['menuClick']);
|
||||||
const { items, mode, accordion } = toRefs(props);
|
|
||||||
|
|
||||||
const { getCollapsed, getTopMenuAlign, getSplit } = useMenuSetting();
|
const isClickGo = ref(false);
|
||||||
|
const currentActiveMenu = ref('');
|
||||||
|
|
||||||
const { currentRoute } = useRouter();
|
const menuState = reactive<MenuState>({
|
||||||
|
defaultSelectedKeys: [],
|
||||||
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
|
openKeys: [],
|
||||||
menuState,
|
selectedKeys: [],
|
||||||
items,
|
collapsedOpenKeys: [],
|
||||||
mode as any,
|
|
||||||
accordion,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getIsTopMenu = computed(() => {
|
|
||||||
const { type, mode } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
(type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
|
|
||||||
(props.isHorizontal && unref(getSplit))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const getMenuClass = computed(() => {
|
|
||||||
const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign);
|
|
||||||
return [
|
|
||||||
prefixCls,
|
|
||||||
`justify-${align}`,
|
|
||||||
{
|
|
||||||
[`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
|
|
||||||
[`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const getInlineCollapseOptions = computed(() => {
|
|
||||||
const isInline = props.mode === MenuModeEnum.INLINE;
|
|
||||||
|
|
||||||
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
|
|
||||||
if (isInline) {
|
|
||||||
inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed);
|
|
||||||
}
|
|
||||||
return inlineCollapseOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
listenerRouteChange((route) => {
|
|
||||||
if (route.name === REDIRECT_NAME) return;
|
|
||||||
handleMenuChange(route);
|
|
||||||
currentActiveMenu.value = route.meta?.currentActiveMenu as string;
|
|
||||||
|
|
||||||
if (unref(currentActiveMenu)) {
|
|
||||||
menuState.selectedKeys = [unref(currentActiveMenu)];
|
|
||||||
setOpenKeys(unref(currentActiveMenu));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
!props.mixSider &&
|
|
||||||
watch(
|
|
||||||
() => props.items,
|
|
||||||
() => {
|
|
||||||
handleMenuChange();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMenuClick: MenuProps['onClick'] = async ({ key }) => {
|
|
||||||
const { beforeClickFn } = props;
|
|
||||||
if (beforeClickFn && isFunction(beforeClickFn)) {
|
|
||||||
const flag = await beforeClickFn(key);
|
|
||||||
if (!flag) return;
|
|
||||||
}
|
|
||||||
emit('menuClick', key);
|
|
||||||
|
|
||||||
isClickGo.value = true;
|
|
||||||
menuState.selectedKeys = [key];
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
|
|
||||||
if (unref(isClickGo)) {
|
|
||||||
isClickGo.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const path =
|
|
||||||
(route || unref(currentRoute)).meta?.currentActiveMenu ||
|
|
||||||
(route || unref(currentRoute)).path;
|
|
||||||
setOpenKeys(path);
|
|
||||||
if (unref(currentActiveMenu)) return;
|
|
||||||
if (props.isHorizontal && unref(getSplit)) {
|
|
||||||
const parentPath = await getCurrentParentPath(path);
|
|
||||||
menuState.selectedKeys = [parentPath];
|
|
||||||
} else {
|
|
||||||
const parentPaths = await getAllParentPath(props.items, path);
|
|
||||||
menuState.selectedKeys = parentPaths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleMenuClick,
|
|
||||||
getInlineCollapseOptions,
|
|
||||||
getMenuClass,
|
|
||||||
handleOpenChange,
|
|
||||||
getOpenKeys,
|
|
||||||
...toRefs(menuState),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('basic-menu');
|
||||||
|
const { items, mode, accordion } = toRefs(props);
|
||||||
|
|
||||||
|
const { getCollapsed, getTopMenuAlign, getSplit } = useMenuSetting();
|
||||||
|
|
||||||
|
const { currentRoute } = useRouter();
|
||||||
|
|
||||||
|
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
|
||||||
|
menuState,
|
||||||
|
items,
|
||||||
|
mode as any,
|
||||||
|
accordion,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getIsTopMenu = computed(() => {
|
||||||
|
const { type, mode } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
|
||||||
|
(props.isHorizontal && unref(getSplit))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getMenuClass = computed(() => {
|
||||||
|
const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign);
|
||||||
|
return [
|
||||||
|
prefixCls,
|
||||||
|
`justify-${align}`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
|
||||||
|
[`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const getInlineCollapseOptions = computed(() => {
|
||||||
|
const isInline = props.mode === MenuModeEnum.INLINE;
|
||||||
|
|
||||||
|
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
|
||||||
|
if (isInline) {
|
||||||
|
inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed);
|
||||||
|
}
|
||||||
|
return inlineCollapseOptions;
|
||||||
|
});
|
||||||
|
|
||||||
|
listenerRouteChange((route) => {
|
||||||
|
if (route.name === REDIRECT_NAME) return;
|
||||||
|
handleMenuChange(route);
|
||||||
|
currentActiveMenu.value = route.meta?.currentActiveMenu as string;
|
||||||
|
|
||||||
|
if (unref(currentActiveMenu)) {
|
||||||
|
menuState.selectedKeys = [unref(currentActiveMenu)];
|
||||||
|
setOpenKeys(unref(currentActiveMenu));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
!props.mixSider &&
|
||||||
|
watch(
|
||||||
|
() => props.items,
|
||||||
|
() => {
|
||||||
|
handleMenuChange();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMenuClick: MenuProps['onClick'] = async ({ key }) => {
|
||||||
|
const { beforeClickFn } = props;
|
||||||
|
if (beforeClickFn && isFunction(beforeClickFn)) {
|
||||||
|
const flag = await beforeClickFn(key);
|
||||||
|
if (!flag) return;
|
||||||
|
}
|
||||||
|
emit('menuClick', key);
|
||||||
|
|
||||||
|
isClickGo.value = true;
|
||||||
|
menuState.selectedKeys = [key];
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
|
||||||
|
if (unref(isClickGo)) {
|
||||||
|
isClickGo.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path =
|
||||||
|
(route || unref(currentRoute)).meta?.currentActiveMenu || (route || unref(currentRoute)).path;
|
||||||
|
setOpenKeys(path);
|
||||||
|
if (unref(currentActiveMenu)) return;
|
||||||
|
if (props.isHorizontal && unref(getSplit)) {
|
||||||
|
const parentPath = await getCurrentParentPath(path);
|
||||||
|
menuState.selectedKeys = [parentPath];
|
||||||
|
} else {
|
||||||
|
const parentPaths = await getAllParentPath(props.items, path);
|
||||||
|
menuState.selectedKeys = parentPaths;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import url('./index.less');
|
@import url('./index.less');
|
||||||
|
@@ -1,21 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<MenuItem :key="item.path">
|
<Menu.Item :key="item.path">
|
||||||
<MenuItemContent v-bind="$props" :item="item" />
|
<MenuItemContent v-bind="$props" :item="item" />
|
||||||
</MenuItem>
|
</Menu.Item>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { Menu } from 'ant-design-vue';
|
import { Menu } from 'ant-design-vue';
|
||||||
import { itemProps } from '../props';
|
import { itemProps } from '../props';
|
||||||
|
|
||||||
import MenuItemContent from './MenuItemContent.vue';
|
import MenuItemContent from './MenuItemContent.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicMenuItem' });
|
||||||
name: 'BasicMenuItem',
|
|
||||||
components: { MenuItem: Menu.Item, MenuItemContent },
|
defineProps(itemProps);
|
||||||
props: itemProps,
|
|
||||||
setup() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasicMenuItem v-if="!menuHasChildren(item) && getShowMenu" v-bind="$props" />
|
<BasicMenuItem v-if="!menuHasChildren(item) && getShowMenu" v-bind="$props" />
|
||||||
<SubMenu
|
<Menu.SubMenu
|
||||||
v-if="menuHasChildren(item) && getShowMenu"
|
v-if="menuHasChildren(item) && getShowMenu"
|
||||||
:class="[theme]"
|
:class="[theme]"
|
||||||
:key="`submenu-${item.path}`"
|
:key="`submenu-${item.path}`"
|
||||||
@@ -13,43 +13,27 @@
|
|||||||
<template v-for="childrenItem in item.children || []" :key="childrenItem.path">
|
<template v-for="childrenItem in item.children || []" :key="childrenItem.path">
|
||||||
<BasicSubMenuItem v-bind="$props" :item="childrenItem" />
|
<BasicSubMenuItem v-bind="$props" :item="childrenItem" />
|
||||||
</template>
|
</template>
|
||||||
</SubMenu>
|
</Menu.SubMenu>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { Menu as MenuType } from '/@/router/types';
|
import type { Menu as MenuType } from '@/router/types';
|
||||||
import { defineComponent, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Menu } from 'ant-design-vue';
|
import { Menu } from 'ant-design-vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
|
||||||
import { itemProps } from '../props';
|
import { itemProps } from '../props';
|
||||||
import BasicMenuItem from './BasicMenuItem.vue';
|
import BasicMenuItem from './BasicMenuItem.vue';
|
||||||
import MenuItemContent from './MenuItemContent.vue';
|
import MenuItemContent from './MenuItemContent.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicSubMenuItem', isSubMenu: true });
|
||||||
name: 'BasicSubMenuItem',
|
|
||||||
isSubMenu: true,
|
|
||||||
components: {
|
|
||||||
BasicMenuItem,
|
|
||||||
SubMenu: Menu.SubMenu,
|
|
||||||
MenuItemContent,
|
|
||||||
},
|
|
||||||
props: itemProps,
|
|
||||||
setup(props) {
|
|
||||||
const { prefixCls } = useDesign('basic-menu-item');
|
|
||||||
|
|
||||||
const getShowMenu = computed(() => !props.item.meta?.hideMenu);
|
const props = defineProps(itemProps);
|
||||||
function menuHasChildren(menuTreeItem: MenuType): boolean {
|
|
||||||
return (
|
const getShowMenu = computed(() => !props.item.meta?.hideMenu);
|
||||||
!menuTreeItem.meta?.hideChildrenInMenu &&
|
function menuHasChildren(menuTreeItem: MenuType): boolean {
|
||||||
Reflect.has(menuTreeItem, 'children') &&
|
return (
|
||||||
!!menuTreeItem.children &&
|
!menuTreeItem.meta?.hideChildrenInMenu &&
|
||||||
menuTreeItem.children.length > 0
|
Reflect.has(menuTreeItem, 'children') &&
|
||||||
);
|
!!menuTreeItem.children &&
|
||||||
}
|
menuTreeItem.children.length > 0
|
||||||
return {
|
);
|
||||||
prefixCls,
|
}
|
||||||
menuHasChildren,
|
|
||||||
getShowMenu,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@@ -5,33 +5,21 @@
|
|||||||
{{ getI18nName }}
|
{{ getI18nName }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed } from 'vue';
|
||||||
import Icon from '@/components/Icon/Icon.vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
import { contentProps } from '../props';
|
import { contentProps } from '../props';
|
||||||
|
|
||||||
|
defineOptions({ name: 'MenuItemContent' });
|
||||||
|
|
||||||
|
const props = defineProps(contentProps);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { prefixCls } = useDesign('basic-menu-item-content');
|
||||||
|
|
||||||
export default defineComponent({
|
const getI18nName = computed(() => t(props.item?.name));
|
||||||
name: 'MenuItemContent',
|
const getIcon = computed(() => (props.item?.img ? undefined : props.item?.icon));
|
||||||
components: {
|
const getImg = computed(() => props.item?.img);
|
||||||
Icon,
|
|
||||||
},
|
|
||||||
props: contentProps,
|
|
||||||
setup(props) {
|
|
||||||
const { prefixCls } = useDesign('basic-menu-item-content');
|
|
||||||
const getI18nName = computed(() => t(props.item?.name));
|
|
||||||
const getIcon = computed(() => (props.item?.img ? undefined : props.item?.icon));
|
|
||||||
const getImg = computed(() => props.item?.img);
|
|
||||||
|
|
||||||
return {
|
|
||||||
prefixCls,
|
|
||||||
getI18nName,
|
|
||||||
getIcon,
|
|
||||||
getImg,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import type { Menu } from '/@/router/types';
|
import type { Menu } from '@/router/types';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum';
|
||||||
import { ThemeEnum } from '/@/enums/appEnum';
|
import { ThemeEnum } from '@/enums/appEnum';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import type { Key } from './types';
|
import type { Key } from './types';
|
||||||
import type { MenuTheme } from 'ant-design-vue';
|
import type { MenuTheme } from 'ant-design-vue';
|
||||||
import type { MenuMode } from 'ant-design-vue/lib/menu/src/interface';
|
import type { MenuMode } from 'ant-design-vue/lib/menu/src/interface';
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum } from '@/enums/menuEnum';
|
||||||
import type { Menu as MenuType } from '/@/router/types';
|
import type { Menu as MenuType } from '/@/router/types';
|
||||||
import type { MenuState, Key } from './types';
|
import type { MenuState, Key } from './types';
|
||||||
import { computed, Ref, toRaw, unref } from 'vue';
|
import { computed, Ref, toRaw, unref } from 'vue';
|
||||||
import { useTimeoutFn } from '@vben/hooks';
|
import { useTimeoutFn } from '@vben/hooks';
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from 'lodash-es';
|
||||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
import { useMenuSetting } from '@/hooks/setting/useMenuSetting';
|
||||||
import { getAllParentPath } from '/@/router/helper/menuHelper';
|
import { getAllParentPath } from '@/router/helper/menuHelper';
|
||||||
|
|
||||||
export function useOpenKeys(
|
export function useOpenKeys(
|
||||||
menuState: MenuState,
|
menuState: MenuState,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '@/utils';
|
||||||
import './src/index.less';
|
import './src/index.less';
|
||||||
import basicModal from './src/BasicModal.vue';
|
import basicModal from './src/BasicModal.vue';
|
||||||
|
|
||||||
|
@@ -48,11 +48,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { ModalProps, ModalMethods } from './typing';
|
import type { ModalProps, ModalMethods } from './typing';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
|
||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
@@ -61,183 +59,172 @@
|
|||||||
toRef,
|
toRef,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
useAttrs,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import Modal from './components/Modal';
|
import Modal from './components/Modal';
|
||||||
import ModalWrapper from './components/ModalWrapper.vue';
|
import ModalWrapper from './components/ModalWrapper.vue';
|
||||||
import ModalClose from './components/ModalClose.vue';
|
import ModalClose from './components/ModalClose.vue';
|
||||||
import ModalFooter from './components/ModalFooter.vue';
|
import ModalFooter from './components/ModalFooter.vue';
|
||||||
import ModalHeader from './components/ModalHeader.vue';
|
import ModalHeader from './components/ModalHeader.vue';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { deepMerge } from '/@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { useFullScreen } from './hooks/useModalFullScreen';
|
import { useFullScreen } from './hooks/useModalFullScreen';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicModal', inheritAttrs: false });
|
||||||
name: 'BasicModal',
|
|
||||||
components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: basicProps,
|
|
||||||
emits: ['open-change', 'height-change', 'cancel', 'ok', 'register', 'update:open'],
|
|
||||||
setup(props, { emit, attrs }) {
|
|
||||||
const openRef = ref(false);
|
|
||||||
const propsRef = ref<Partial<ModalProps> | null>(null);
|
|
||||||
const modalWrapperRef = ref<any>(null);
|
|
||||||
const { prefixCls } = useDesign('basic-modal');
|
|
||||||
|
|
||||||
// modal Bottom and top height
|
const props = defineProps(basicProps);
|
||||||
const extHeightRef = ref(0);
|
|
||||||
const modalMethods: ModalMethods = {
|
|
||||||
setModalProps,
|
|
||||||
emitOpen: undefined,
|
|
||||||
redoModalHeight: () => {
|
|
||||||
nextTick(() => {
|
|
||||||
if (unref(modalWrapperRef)) {
|
|
||||||
(unref(modalWrapperRef) as any).setModalHeight();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const emit = defineEmits([
|
||||||
if (instance) {
|
'open-change',
|
||||||
emit('register', modalMethods, instance.uid);
|
'height-change',
|
||||||
}
|
'cancel',
|
||||||
|
'ok',
|
||||||
|
'register',
|
||||||
|
'update:open',
|
||||||
|
]);
|
||||||
|
|
||||||
// Custom title component: get title
|
const attrs = useAttrs();
|
||||||
const getMergeProps = computed((): Recordable => {
|
const openRef = ref(false);
|
||||||
return {
|
const propsRef = ref<Partial<ModalProps> | null>(null);
|
||||||
...props,
|
const modalWrapperRef = ref<any>(null);
|
||||||
...(unref(propsRef) as any),
|
const { prefixCls } = useDesign('basic-modal');
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
|
// modal Bottom and top height
|
||||||
modalWrapperRef,
|
const extHeightRef = ref(0);
|
||||||
extHeightRef,
|
const modalMethods: ModalMethods = {
|
||||||
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
|
setModalProps,
|
||||||
});
|
emitOpen: undefined,
|
||||||
|
redoModalHeight: () => {
|
||||||
// modal component does not need title and origin buttons
|
nextTick(() => {
|
||||||
const getProps = computed((): Recordable => {
|
if (unref(modalWrapperRef)) {
|
||||||
const opt = {
|
(unref(modalWrapperRef) as any).setModalHeight();
|
||||||
...unref(getMergeProps),
|
|
||||||
open: unref(openRef),
|
|
||||||
okButtonProps: undefined,
|
|
||||||
cancelButtonProps: undefined,
|
|
||||||
title: undefined,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...opt,
|
|
||||||
wrapClassName: unref(getWrapClassName),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getBindValue = computed((): Recordable => {
|
|
||||||
const attr = {
|
|
||||||
...attrs,
|
|
||||||
...unref(getMergeProps),
|
|
||||||
open: unref(openRef),
|
|
||||||
};
|
|
||||||
attr['wrapClassName'] =
|
|
||||||
`${attr?.['wrapClassName'] || ''} ${unref(getWrapClassName)}` + 'vben-basic-modal-wrap';
|
|
||||||
if (unref(fullScreenRef)) {
|
|
||||||
return omit(attr, ['height', 'title']);
|
|
||||||
}
|
}
|
||||||
return omit(attr, 'title');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getWrapperHeight = computed(() => {
|
|
||||||
if (unref(fullScreenRef)) return undefined;
|
|
||||||
return unref(getProps).height;
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
openRef.value = !!props.open;
|
|
||||||
fullScreenRef.value = !!props.defaultFullscreen;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => unref(openRef),
|
|
||||||
(v) => {
|
|
||||||
emit('open-change', v);
|
|
||||||
emit('update:open', v);
|
|
||||||
instance && modalMethods.emitOpen?.(v, instance.uid);
|
|
||||||
nextTick(() => {
|
|
||||||
if (props.scrollTop && v && unref(modalWrapperRef)) {
|
|
||||||
(unref(modalWrapperRef) as any).scrollTop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 取消事件
|
|
||||||
async function handleCancel(e: Event) {
|
|
||||||
e?.stopPropagation();
|
|
||||||
// 过滤自定义关闭按钮的空白区域
|
|
||||||
if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
|
|
||||||
if (props.closeFunc && isFunction(props.closeFunc)) {
|
|
||||||
const isClose: boolean = await props.closeFunc();
|
|
||||||
openRef.value = !isClose;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openRef.value = false;
|
|
||||||
emit('cancel', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 设置modal参数
|
|
||||||
*/
|
|
||||||
function setModalProps(props: Partial<ModalProps>): void {
|
|
||||||
// Keep the last setModalProps
|
|
||||||
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
|
||||||
if (Reflect.has(props, 'open')) {
|
|
||||||
openRef.value = !!props.open;
|
|
||||||
}
|
|
||||||
if (Reflect.has(props, 'defaultFullscreen')) {
|
|
||||||
fullScreenRef.value = !!props.defaultFullscreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOk(e: Event) {
|
|
||||||
emit('ok', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleHeightChange(height: string) {
|
|
||||||
emit('height-change', height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleExtHeight(height: number) {
|
|
||||||
extHeightRef.value = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTitleDbClick(e) {
|
|
||||||
if (!props.canFullscreen) return;
|
|
||||||
e.stopPropagation();
|
|
||||||
handleFullScreen(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleCancel,
|
|
||||||
getBindValue,
|
|
||||||
getProps,
|
|
||||||
handleFullScreen,
|
|
||||||
fullScreenRef,
|
|
||||||
getMergeProps,
|
|
||||||
handleOk,
|
|
||||||
openRef,
|
|
||||||
omit,
|
|
||||||
modalWrapperRef,
|
|
||||||
handleExtHeight,
|
|
||||||
handleHeightChange,
|
|
||||||
handleTitleDbClick,
|
|
||||||
getWrapperHeight,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
if (instance) {
|
||||||
|
emit('register', modalMethods, instance.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom title component: get title
|
||||||
|
const getMergeProps = computed((): Recordable => {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
...(unref(propsRef) as any),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
|
||||||
|
modalWrapperRef,
|
||||||
|
extHeightRef,
|
||||||
|
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// modal component does not need title and origin buttons
|
||||||
|
const getProps = computed((): Recordable => {
|
||||||
|
const opt = {
|
||||||
|
...unref(getMergeProps),
|
||||||
|
open: unref(openRef),
|
||||||
|
okButtonProps: undefined,
|
||||||
|
cancelButtonProps: undefined,
|
||||||
|
title: undefined,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...opt,
|
||||||
|
wrapClassName: unref(getWrapClassName),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getBindValue = computed((): Recordable => {
|
||||||
|
const attr = {
|
||||||
|
...attrs,
|
||||||
|
...unref(getMergeProps),
|
||||||
|
open: unref(openRef),
|
||||||
|
};
|
||||||
|
attr['wrapClassName'] =
|
||||||
|
`${attr?.['wrapClassName'] || ''} ${unref(getWrapClassName)}` + 'vben-basic-modal-wrap';
|
||||||
|
if (unref(fullScreenRef)) {
|
||||||
|
return omit(attr, ['height', 'title']);
|
||||||
|
}
|
||||||
|
return omit(attr, 'title');
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperHeight = computed(() => {
|
||||||
|
if (unref(fullScreenRef)) return undefined;
|
||||||
|
return unref(getProps).height;
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
openRef.value = !!props.open;
|
||||||
|
fullScreenRef.value = !!props.defaultFullscreen;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unref(openRef),
|
||||||
|
(v) => {
|
||||||
|
emit('open-change', v);
|
||||||
|
emit('update:open', v);
|
||||||
|
instance && modalMethods.emitOpen?.(v, instance.uid);
|
||||||
|
nextTick(() => {
|
||||||
|
if (props.scrollTop && v && unref(modalWrapperRef)) {
|
||||||
|
(unref(modalWrapperRef) as any).scrollTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 取消事件
|
||||||
|
async function handleCancel(e: Event) {
|
||||||
|
e?.stopPropagation();
|
||||||
|
// 过滤自定义关闭按钮的空白区域
|
||||||
|
if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
|
||||||
|
if (props.closeFunc && isFunction(props.closeFunc)) {
|
||||||
|
const isClose: boolean = await props.closeFunc();
|
||||||
|
openRef.value = !isClose;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openRef.value = false;
|
||||||
|
emit('cancel', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 设置modal参数
|
||||||
|
*/
|
||||||
|
function setModalProps(props: Partial<ModalProps>): void {
|
||||||
|
// Keep the last setModalProps
|
||||||
|
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
||||||
|
if (Reflect.has(props, 'open')) {
|
||||||
|
openRef.value = !!props.open;
|
||||||
|
}
|
||||||
|
if (Reflect.has(props, 'defaultFullscreen')) {
|
||||||
|
fullScreenRef.value = !!props.defaultFullscreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOk(e: Event) {
|
||||||
|
emit('ok', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHeightChange(height: string) {
|
||||||
|
emit('height-change', height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleExtHeight(height: number) {
|
||||||
|
extHeightRef.value = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTitleDbClick(e) {
|
||||||
|
if (!props.canFullscreen) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
handleFullScreen(e);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -3,7 +3,7 @@ import { defineComponent, toRefs, unref } from 'vue';
|
|||||||
import { basicProps } from '../props';
|
import { basicProps } from '../props';
|
||||||
import { useModalDragMove } from '../hooks/useModalDrag';
|
import { useModalDragMove } from '../hooks/useModalDrag';
|
||||||
import { useAttrs } from '@vben/hooks';
|
import { useAttrs } from '@vben/hooks';
|
||||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
import { extendSlots } from '@/utils/helper/tsxHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
|
@@ -13,54 +13,44 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useDesign } from '@/hooks/web/useDesign';
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'ModalClose' });
|
||||||
name: 'ModalClose',
|
|
||||||
components: { Tooltip, FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
|
|
||||||
props: {
|
|
||||||
canFullscreen: { type: Boolean, default: true },
|
|
||||||
fullScreen: { type: Boolean },
|
|
||||||
},
|
|
||||||
emits: ['cancel', 'fullscreen'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { prefixCls } = useDesign('basic-modal-close');
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const getClass = computed(() => {
|
const props = defineProps({
|
||||||
return [
|
canFullscreen: { type: Boolean, default: true },
|
||||||
prefixCls,
|
fullScreen: { type: Boolean },
|
||||||
`${prefixCls}--custom`,
|
|
||||||
{
|
|
||||||
[`${prefixCls}--can-full`]: props.canFullscreen,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleCancel(e: Event) {
|
|
||||||
emit('cancel', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFullScreen(e: Event) {
|
|
||||||
e?.stopPropagation();
|
|
||||||
e?.preventDefault();
|
|
||||||
emit('fullscreen');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
t,
|
|
||||||
getClass,
|
|
||||||
prefixCls,
|
|
||||||
handleCancel,
|
|
||||||
handleFullScreen,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['cancel', 'fullscreen']);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('basic-modal-close');
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const getClass = computed(() => {
|
||||||
|
return [
|
||||||
|
prefixCls,
|
||||||
|
`${prefixCls}--custom`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}--can-full`]: props.canFullscreen,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleCancel(e: Event) {
|
||||||
|
emit('cancel', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFullScreen(e: Event) {
|
||||||
|
e?.stopPropagation();
|
||||||
|
e?.preventDefault();
|
||||||
|
emit('fullscreen');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@prefix-cls: ~'@{namespace}-basic-modal-close';
|
@prefix-cls: ~'@{namespace}-basic-modal-close';
|
||||||
|
@@ -17,25 +17,20 @@
|
|||||||
<slot name="appendFooter"></slot>
|
<slot name="appendFooter"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
import { basicProps } from '../props';
|
import { basicProps } from '../props';
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicModalFooter' });
|
||||||
name: 'BasicModalFooter',
|
|
||||||
props: basicProps,
|
|
||||||
emits: ['ok', 'cancel'],
|
|
||||||
setup(_, { emit }) {
|
|
||||||
function handleOk(e: Event) {
|
|
||||||
emit('ok', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel(e: Event) {
|
defineProps(basicProps);
|
||||||
emit('cancel', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { handleOk, handleCancel };
|
const emit = defineEmits(['ok', 'cancel']);
|
||||||
},
|
|
||||||
});
|
function handleOk(e: Event) {
|
||||||
|
emit('ok', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel(e: Event) {
|
||||||
|
emit('cancel', e);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -3,19 +3,16 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
</BasicTitle>
|
</BasicTitle>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { BasicTitle } from '@/components/Basic';
|
||||||
import { BasicTitle } from '/@/components/Basic';
|
|
||||||
|
|
||||||
export default defineComponent({
|
defineOptions({ name: 'BasicModalHeader' });
|
||||||
name: 'BasicModalHeader',
|
|
||||||
components: { BasicTitle },
|
defineProps({
|
||||||
props: {
|
helpMessage: {
|
||||||
helpMessage: {
|
type: [String, Array] as PropType<string | string[]>,
|
||||||
type: [String, Array] as PropType<string | string[]>,
|
|
||||||
},
|
|
||||||
title: { type: String },
|
|
||||||
},
|
},
|
||||||
|
title: { type: String },
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@@ -5,26 +5,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import {
|
import { computed, ref, watchEffect, unref, watch, onMounted, nextTick, onUnmounted } from 'vue';
|
||||||
defineComponent,
|
|
||||||
computed,
|
|
||||||
ref,
|
|
||||||
watchEffect,
|
|
||||||
unref,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
onUnmounted,
|
|
||||||
} from 'vue';
|
|
||||||
import { useWindowSizeFn } from '@vben/hooks';
|
import { useWindowSizeFn } from '@vben/hooks';
|
||||||
import { type AnyFunction } from '@vben/types';
|
import { type AnyFunction } from '@vben/types';
|
||||||
import { ScrollContainer } from '/@/components/Container';
|
import { ScrollContainer } from '@/components/Container';
|
||||||
import { createModalContext } from '../hooks/useModalContext';
|
import { createModalContext } from '../hooks/useModalContext';
|
||||||
import { useMutationObserver } from '@vueuse/core';
|
import { useMutationObserver } from '@vueuse/core';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'ModalWrapper', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
loading: { type: Boolean },
|
loading: { type: Boolean },
|
||||||
useWrapper: { type: Boolean, default: true },
|
useWrapper: { type: Boolean, default: true },
|
||||||
modalHeaderHeight: { type: Number, default: 57 },
|
modalHeaderHeight: { type: Number, default: 57 },
|
||||||
@@ -35,136 +27,128 @@
|
|||||||
open: { type: Boolean },
|
open: { type: Boolean },
|
||||||
fullScreen: { type: Boolean },
|
fullScreen: { type: Boolean },
|
||||||
loadingTip: { type: String },
|
loadingTip: { type: String },
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ModalWrapper',
|
|
||||||
components: { ScrollContainer },
|
|
||||||
inheritAttrs: false,
|
|
||||||
props,
|
|
||||||
emits: ['height-change', 'ext-height'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const wrapperRef = ref(null);
|
|
||||||
const spinRef = ref(null);
|
|
||||||
const realHeightRef = ref(0);
|
|
||||||
const minRealHeightRef = ref(0);
|
|
||||||
|
|
||||||
const realHeight = ref(0);
|
|
||||||
|
|
||||||
let stopElResizeFn: AnyFunction = () => {};
|
|
||||||
|
|
||||||
useWindowSizeFn(setModalHeight.bind(null));
|
|
||||||
|
|
||||||
useMutationObserver(
|
|
||||||
spinRef,
|
|
||||||
() => {
|
|
||||||
setModalHeight();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
attributes: true,
|
|
||||||
subtree: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
createModalContext({
|
|
||||||
redoModalHeight: setModalHeight,
|
|
||||||
});
|
|
||||||
|
|
||||||
const spinStyle = computed((): CSSProperties => {
|
|
||||||
return {
|
|
||||||
minHeight: `${props.minHeight}px`,
|
|
||||||
[props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.useWrapper && setModalHeight();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.fullScreen,
|
|
||||||
(v) => {
|
|
||||||
setModalHeight();
|
|
||||||
if (!v) {
|
|
||||||
realHeightRef.value = minRealHeightRef.value;
|
|
||||||
} else {
|
|
||||||
minRealHeightRef.value = realHeightRef.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const { modalHeaderHeight, modalFooterHeight } = props;
|
|
||||||
emit('ext-height', modalHeaderHeight + modalFooterHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
stopElResizeFn && stopElResizeFn();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function scrollTop() {
|
|
||||||
nextTick(() => {
|
|
||||||
const wrapperRefDom = unref(wrapperRef);
|
|
||||||
if (!wrapperRefDom) return;
|
|
||||||
(wrapperRefDom as any)?.scrollTo?.(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setModalHeight() {
|
|
||||||
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
|
|
||||||
// 加上这个,就必须在使用的时候传递父级的open
|
|
||||||
if (!props.open) return;
|
|
||||||
const wrapperRefDom = unref(wrapperRef);
|
|
||||||
if (!wrapperRefDom) return;
|
|
||||||
|
|
||||||
const bodyDom = (wrapperRefDom as any).$el.parentElement;
|
|
||||||
if (!bodyDom) return;
|
|
||||||
bodyDom.style.padding = '0';
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
|
|
||||||
if (!modalDom) return;
|
|
||||||
|
|
||||||
const modalRect = getComputedStyle(modalDom as Element).top;
|
|
||||||
const modalTop = Number.parseInt(modalRect);
|
|
||||||
let maxHeight =
|
|
||||||
window.innerHeight -
|
|
||||||
modalTop * 2 +
|
|
||||||
(props.footerOffset! || 0) -
|
|
||||||
props.modalFooterHeight -
|
|
||||||
props.modalHeaderHeight;
|
|
||||||
|
|
||||||
// 距离顶部过进会出现滚动条
|
|
||||||
if (modalTop < 40) {
|
|
||||||
maxHeight -= 26;
|
|
||||||
}
|
|
||||||
await nextTick();
|
|
||||||
const spinEl: any = unref(spinRef);
|
|
||||||
|
|
||||||
if (!spinEl) return;
|
|
||||||
await nextTick();
|
|
||||||
// if (!realHeight) {
|
|
||||||
realHeight.value = spinEl.scrollHeight;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (props.fullScreen) {
|
|
||||||
realHeightRef.value =
|
|
||||||
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28;
|
|
||||||
} else {
|
|
||||||
realHeightRef.value = props.height
|
|
||||||
? props.height
|
|
||||||
: realHeight.value > maxHeight
|
|
||||||
? maxHeight
|
|
||||||
: realHeight.value;
|
|
||||||
}
|
|
||||||
emit('height-change', unref(realHeightRef));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { wrapperRef, spinRef, spinStyle, scrollTop, setModalHeight, realHeight };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['height-change', 'ext-height']);
|
||||||
|
|
||||||
|
const wrapperRef = ref(null);
|
||||||
|
const spinRef = ref(null);
|
||||||
|
const realHeightRef = ref(0);
|
||||||
|
const minRealHeightRef = ref(0);
|
||||||
|
const realHeight = ref(0);
|
||||||
|
|
||||||
|
let stopElResizeFn: AnyFunction = () => {};
|
||||||
|
|
||||||
|
useWindowSizeFn(setModalHeight.bind(null));
|
||||||
|
|
||||||
|
useMutationObserver(
|
||||||
|
spinRef,
|
||||||
|
() => {
|
||||||
|
setModalHeight();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: true,
|
||||||
|
subtree: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
createModalContext({
|
||||||
|
redoModalHeight: setModalHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
const spinStyle = computed((): CSSProperties => {
|
||||||
|
return {
|
||||||
|
minHeight: `${props.minHeight}px`,
|
||||||
|
[props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.useWrapper && setModalHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.fullScreen,
|
||||||
|
(v) => {
|
||||||
|
setModalHeight();
|
||||||
|
if (!v) {
|
||||||
|
realHeightRef.value = minRealHeightRef.value;
|
||||||
|
} else {
|
||||||
|
minRealHeightRef.value = realHeightRef.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const { modalHeaderHeight, modalFooterHeight } = props;
|
||||||
|
emit('ext-height', modalHeaderHeight + modalFooterHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopElResizeFn && stopElResizeFn();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function scrollTop() {
|
||||||
|
nextTick(() => {
|
||||||
|
const wrapperRefDom = unref(wrapperRef);
|
||||||
|
if (!wrapperRefDom) return;
|
||||||
|
(wrapperRefDom as any)?.scrollTo?.(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setModalHeight() {
|
||||||
|
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
|
||||||
|
// 加上这个,就必须在使用的时候传递父级的open
|
||||||
|
if (!props.open) return;
|
||||||
|
const wrapperRefDom = unref(wrapperRef);
|
||||||
|
if (!wrapperRefDom) return;
|
||||||
|
|
||||||
|
const bodyDom = (wrapperRefDom as any).$el.parentElement;
|
||||||
|
if (!bodyDom) return;
|
||||||
|
bodyDom.style.padding = '0';
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
|
||||||
|
if (!modalDom) return;
|
||||||
|
|
||||||
|
const modalRect = getComputedStyle(modalDom as Element).top;
|
||||||
|
const modalTop = Number.parseInt(modalRect);
|
||||||
|
let maxHeight =
|
||||||
|
window.innerHeight -
|
||||||
|
modalTop * 2 +
|
||||||
|
(props.footerOffset! || 0) -
|
||||||
|
props.modalFooterHeight -
|
||||||
|
props.modalHeaderHeight;
|
||||||
|
|
||||||
|
// 距离顶部过进会出现滚动条
|
||||||
|
if (modalTop < 40) {
|
||||||
|
maxHeight -= 26;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
const spinEl: any = unref(spinRef);
|
||||||
|
|
||||||
|
if (!spinEl) return;
|
||||||
|
await nextTick();
|
||||||
|
// if (!realHeight) {
|
||||||
|
realHeight.value = spinEl.scrollHeight;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (props.fullScreen) {
|
||||||
|
realHeightRef.value =
|
||||||
|
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28;
|
||||||
|
} else {
|
||||||
|
realHeightRef.value = props.height
|
||||||
|
? props.height
|
||||||
|
: realHeight.value > maxHeight
|
||||||
|
? maxHeight
|
||||||
|
: realHeight.value;
|
||||||
|
}
|
||||||
|
emit('height-change', unref(realHeightRef));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ scrollTop });
|
||||||
</script>
|
</script>
|
||||||
|
@@ -16,11 +16,11 @@ import {
|
|||||||
toRaw,
|
toRaw,
|
||||||
computed,
|
computed,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { isProdMode } from '/@/utils/env';
|
import { isProdMode } from '@/utils/env';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { tryOnUnmounted } from '@vueuse/core';
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
import { error } from '/@/utils/log';
|
import { error } from '@/utils/log';
|
||||||
|
|
||||||
const dataTransfer = reactive<any>({});
|
const dataTransfer = reactive<any>({});
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user