Commit 23657547ab28fa65c2369ded8e73929dee76c750
1 parent
2b466eaf
perf: remove unless code
Showing
31 changed files
with
107 additions
and
169 deletions
package.json
... | ... | @@ -14,7 +14,8 @@ |
14 | 14 | "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", |
15 | 15 | "clean:lib": "npx rimraf node_modules", |
16 | 16 | "typecheck": "vuedx-typecheck .", |
17 | - "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", | |
17 | + "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" ", | |
18 | + "lint:eslint:fix": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", | |
18 | 19 | "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", |
19 | 20 | "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", |
20 | 21 | "lint:ls-lint": "ls-lint", | ... | ... |
src/components/Application/index.ts
1 | 1 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
2 | 2 | import AppLogo from './src/AppLogo.vue'; |
3 | 3 | import AppProvider from './src/AppProvider.vue'; |
4 | -import { withInstall } from '../util'; | |
5 | 4 | |
6 | 5 | export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue')); |
7 | 6 | export const AppSearch = createAsyncComponent(() => import('./src/search/AppSearch.vue'), { |
... | ... | @@ -10,5 +9,3 @@ export const AppSearch = createAsyncComponent(() => import('./src/search/AppSear |
10 | 9 | |
11 | 10 | export { useAppProviderContext } from './src/useAppContext'; |
12 | 11 | export { AppLogo, AppProvider }; |
13 | - | |
14 | -withInstall(AppLogo, AppProvider); | ... | ... |
src/components/Application/src/AppLocalePicker.vue
... | ... | @@ -17,19 +17,18 @@ |
17 | 17 | </Dropdown> |
18 | 18 | </template> |
19 | 19 | <script lang="ts"> |
20 | - import { defineComponent, ref, watchEffect, unref, computed } from 'vue'; | |
20 | + import type { LocaleType } from '/@/locales/types'; | |
21 | + import type { DropMenu } from '/@/components/Dropdown'; | |
21 | 22 | |
22 | - import { Dropdown, DropMenu } from '/@/components/Dropdown'; | |
23 | + import { defineComponent, ref, watchEffect, unref, computed } from 'vue'; | |
24 | + import { Dropdown } from '/@/components/Dropdown'; | |
25 | + import Icon from '/@/components/Icon'; | |
23 | 26 | |
24 | 27 | import { useLocale } from '/@/locales/useLocale'; |
25 | 28 | import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting'; |
26 | - | |
27 | - import { LocaleType } from '/@/locales/types'; | |
28 | - | |
29 | - import { propTypes } from '/@/utils/propTypes'; | |
30 | 29 | import { useDesign } from '/@/hooks/web/useDesign'; |
30 | + import { propTypes } from '/@/utils/propTypes'; | |
31 | 31 | |
32 | - import Icon from '/@/components/Icon'; | |
33 | 32 | export default defineComponent({ |
34 | 33 | name: 'AppLocalPicker', |
35 | 34 | components: { Dropdown, Icon }, | ... | ... |
src/components/Application/src/AppLogo.vue
... | ... | @@ -20,13 +20,11 @@ |
20 | 20 | import { useGlobSetting } from '/@/hooks/setting'; |
21 | 21 | import { useGo } from '/@/hooks/web/usePage'; |
22 | 22 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
23 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
23 | 24 | |
24 | 25 | import { PageEnum } from '/@/enums/pageEnum'; |
25 | - | |
26 | 26 | import { propTypes } from '/@/utils/propTypes'; |
27 | 27 | |
28 | - import { useDesign } from '/@/hooks/web/useDesign'; | |
29 | - | |
30 | 28 | export default defineComponent({ |
31 | 29 | name: 'AppLogo', |
32 | 30 | props: { | ... | ... |
src/components/Application/src/AppProvider.vue
1 | -<template> | |
2 | - <slot></slot> | |
3 | -</template> | |
4 | 1 | <script lang="ts"> |
5 | 2 | import type { PropType } from 'vue'; |
6 | 3 | import { defineComponent, toRefs, ref } from 'vue'; |
... | ... | @@ -19,7 +16,7 @@ |
19 | 16 | default: designSetting.prefixCls, |
20 | 17 | }, |
21 | 18 | }, |
22 | - setup(props) { | |
19 | + setup(props, { slots }) { | |
23 | 20 | const isMobileRef = ref(false); |
24 | 21 | |
25 | 22 | createBreakpointListen(({ screenMap, sizeEnum, width }) => { |
... | ... | @@ -31,7 +28,7 @@ |
31 | 28 | |
32 | 29 | const { prefixCls } = toRefs(props); |
33 | 30 | createAppProviderContext({ prefixCls, isMobile: isMobileRef }); |
34 | - return {}; | |
31 | + return () => slots.default?.(); | |
35 | 32 | }, |
36 | 33 | }); |
37 | 34 | </script> | ... | ... |
src/components/Application/src/search/AppSearch.vue
1 | -<template> | |
2 | - <div :class="prefixCls" v-if="getShowSearch" @click.stop="handleSearch"> | |
3 | - <Tooltip> | |
4 | - <template #title> | |
5 | - {{ t('common.searchText') }} | |
6 | - </template> | |
7 | - <SearchOutlined /> | |
8 | - </Tooltip> | |
9 | - | |
10 | - <AppSearchModal @close="handleClose" :visible="showModal" /> | |
11 | - </div> | |
12 | -</template> | |
13 | -<script lang="ts"> | |
14 | - import { defineComponent, ref } from 'vue'; | |
1 | +<script lang="tsx"> | |
2 | + import { defineComponent, ref, unref } from 'vue'; | |
15 | 3 | import { Tooltip } from 'ant-design-vue'; |
4 | + import { SearchOutlined } from '@ant-design/icons-vue'; | |
5 | + import AppSearchModal from './AppSearchModal.vue'; | |
16 | 6 | |
17 | 7 | import { useDesign } from '/@/hooks/web/useDesign'; |
18 | - import AppSearchModal from './AppSearchModal.vue'; | |
19 | 8 | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
20 | - import { SearchOutlined } from '@ant-design/icons-vue'; | |
21 | 9 | import { useI18n } from '/@/hooks/web/useI18n'; |
22 | 10 | |
23 | 11 | export default defineComponent({ |
... | ... | @@ -32,15 +20,26 @@ |
32 | 20 | function handleSearch() { |
33 | 21 | showModal.value = true; |
34 | 22 | } |
35 | - return { | |
36 | - t, | |
37 | - prefixCls, | |
38 | - showModal, | |
39 | - getShowSearch, | |
40 | - handleClose: () => { | |
41 | - showModal.value = false; | |
42 | - }, | |
43 | - handleSearch, | |
23 | + | |
24 | + function handleClose() { | |
25 | + showModal.value = false; | |
26 | + } | |
27 | + | |
28 | + return () => { | |
29 | + if (!getShowSearch.value) { | |
30 | + return null; | |
31 | + } | |
32 | + return ( | |
33 | + <div class={prefixCls} onClick={handleSearch}> | |
34 | + <Tooltip> | |
35 | + {{ | |
36 | + title: () => t('common.searchText'), | |
37 | + default: () => <SearchOutlined />, | |
38 | + }} | |
39 | + </Tooltip> | |
40 | + <AppSearchModal onClose={handleClose} visible={unref(showModal)} /> | |
41 | + </div> | |
42 | + ); | |
44 | 43 | }; |
45 | 44 | }, |
46 | 45 | }); | ... | ... |
src/components/Application/src/search/AppSearchFooter.vue
... | ... | @@ -18,12 +18,13 @@ |
18 | 18 | <span>{{ t('common.closeText') }}</span> |
19 | 19 | </div> |
20 | 20 | </template> |
21 | + | |
21 | 22 | <script lang="ts"> |
22 | 23 | import { defineComponent } from 'vue'; |
24 | + import Icon from '/@/components/Icon'; | |
23 | 25 | |
24 | 26 | import { useDesign } from '/@/hooks/web/useDesign'; |
25 | 27 | import { useI18n } from '/@/hooks/web/useI18n'; |
26 | - import Icon from '/@/components/Icon'; | |
27 | 28 | export default defineComponent({ |
28 | 29 | name: 'AppSearchFooter', |
29 | 30 | components: { Icon }, | ... | ... |
src/components/Application/src/search/AppSearchModal.vue
... | ... | @@ -56,17 +56,18 @@ |
56 | 56 | </template> |
57 | 57 | <script lang="ts"> |
58 | 58 | import { defineComponent, computed, unref, ref } from 'vue'; |
59 | + import { SearchOutlined } from '@ant-design/icons-vue'; | |
60 | + import { Input } from 'ant-design-vue'; | |
61 | + import AppSearchFooter from './AppSearchFooter.vue'; | |
62 | + import Icon from '/@/components/Icon'; | |
63 | + | |
64 | + import clickOutside from '/@/directives/clickOutside'; | |
59 | 65 | |
60 | 66 | import { useDesign } from '/@/hooks/web/useDesign'; |
61 | 67 | import { useRefs } from '/@/hooks/core/useRefs'; |
62 | 68 | import { useMenuSearch } from './useMenuSearch'; |
63 | - import { SearchOutlined } from '@ant-design/icons-vue'; | |
64 | - import AppSearchFooter from './AppSearchFooter.vue'; | |
65 | 69 | import { useI18n } from '/@/hooks/web/useI18n'; |
66 | 70 | import { useAppInject } from '/@/hooks/web/useAppInject'; |
67 | - import clickOutside from '/@/directives/clickOutside'; | |
68 | - import { Input } from 'ant-design-vue'; | |
69 | - import Icon from '/@/components/Icon'; | |
70 | 71 | |
71 | 72 | export default defineComponent({ |
72 | 73 | name: 'AppSearchModal', |
... | ... | @@ -108,6 +109,11 @@ |
108 | 109 | ]; |
109 | 110 | }); |
110 | 111 | |
112 | + function handleClose() { | |
113 | + searchResult.value = []; | |
114 | + emit('close'); | |
115 | + } | |
116 | + | |
111 | 117 | return { |
112 | 118 | t, |
113 | 119 | prefixCls, |
... | ... | @@ -120,10 +126,7 @@ |
120 | 126 | setRefs, |
121 | 127 | scrollWrap, |
122 | 128 | handleMouseenter, |
123 | - handleClose: () => { | |
124 | - searchResult.value = []; | |
125 | - emit('close'); | |
126 | - }, | |
129 | + handleClose, | |
127 | 130 | }; |
128 | 131 | }, |
129 | 132 | }); | ... | ... |
src/components/Application/src/search/useMenuSearch.ts
1 | -import { cloneDeep } from 'lodash-es'; | |
1 | +import type { Menu } from '/@/router/types'; | |
2 | + | |
2 | 3 | import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'; |
3 | -import { useI18n } from '/@/hooks/web/useI18n'; | |
4 | + | |
4 | 5 | import { getMenus } from '/@/router/menus'; |
5 | -import type { Menu } from '/@/router/types'; | |
6 | + | |
7 | +import { cloneDeep } from 'lodash-es'; | |
6 | 8 | import { filter, forEach } from '/@/utils/helper/treeHelper'; |
9 | + | |
7 | 10 | import { useDebounce } from '/@/hooks/core/useDebounce'; |
8 | 11 | import { useGo } from '/@/hooks/web/usePage'; |
9 | 12 | import { useScrollTo } from '/@/hooks/event/useScrollTo'; |
10 | 13 | import { useKeyPress } from '/@/hooks/event/useKeyPress'; |
14 | +import { useI18n } from '/@/hooks/web/useI18n'; | |
11 | 15 | |
12 | 16 | export interface SearchResult { |
13 | 17 | name: string; | ... | ... |
src/components/Authority/index.ts
src/components/Basic/src/BasicArrow.vue
... | ... | @@ -10,9 +10,11 @@ |
10 | 10 | <script lang="ts"> |
11 | 11 | import { defineComponent, computed } from 'vue'; |
12 | 12 | import { RightOutlined } from '@ant-design/icons-vue'; |
13 | - import { propTypes } from '/@/utils/propTypes'; | |
13 | + | |
14 | 14 | import { useDesign } from '/@/hooks/web/useDesign'; |
15 | 15 | |
16 | + import { propTypes } from '/@/utils/propTypes'; | |
17 | + | |
16 | 18 | export default defineComponent({ |
17 | 19 | name: 'BasicArrow', |
18 | 20 | components: { RightOutlined }, | ... | ... |
src/components/Basic/src/BasicHelp.vue
1 | -<script lang="ts"> | |
1 | +<script lang="tsx"> | |
2 | 2 | import type { CSSProperties, PropType } from 'vue'; |
3 | 3 | import { defineComponent, computed, unref, h } from 'vue'; |
4 | 4 | |
... | ... | @@ -9,7 +9,9 @@ |
9 | 9 | import { isString, isArray } from '/@/utils/is'; |
10 | 10 | import { getSlot } from '/@/utils/helper/tsxHelper'; |
11 | 11 | import { propTypes } from '/@/utils/propTypes'; |
12 | + | |
12 | 13 | import { useDesign } from '/@/hooks/web/useDesign'; |
14 | + | |
13 | 15 | export default defineComponent({ |
14 | 16 | name: 'BasicHelp', |
15 | 17 | components: { Tooltip }, |
... | ... | @@ -40,7 +42,7 @@ |
40 | 42 | setup(props, { slots }) { |
41 | 43 | const { prefixCls } = useDesign('basic-help'); |
42 | 44 | |
43 | - const getOverlayStyleRef = computed( | |
45 | + const getOverlayStyle = computed( | |
44 | 46 | (): CSSProperties => { |
45 | 47 | return { |
46 | 48 | maxWidth: props.maxWidth, |
... | ... | @@ -48,7 +50,7 @@ |
48 | 50 | } |
49 | 51 | ); |
50 | 52 | |
51 | - const getWrapStyleRef = computed( | |
53 | + const getWrapStyle = computed( | |
52 | 54 | (): CSSProperties => { |
53 | 55 | return { |
54 | 56 | color: props.color, |
... | ... | @@ -65,12 +67,19 @@ |
65 | 67 | const list = props.text; |
66 | 68 | |
67 | 69 | if (isString(list)) { |
68 | - return h('p', list); | |
70 | + return <p>{list}</p>; | |
69 | 71 | } |
70 | 72 | |
71 | 73 | if (isArray(list)) { |
72 | 74 | return list.map((item, index) => { |
73 | - return h('p', { key: item }, [props.showIndex ? `${index + 1}. ` : '', item]); | |
75 | + return ( | |
76 | + <p key={item}> | |
77 | + <> | |
78 | + {props.showIndex ? `${index + 1}. ` : ''} | |
79 | + {item} | |
80 | + </> | |
81 | + </p> | |
82 | + ); | |
74 | 83 | }); |
75 | 84 | } |
76 | 85 | |
... | ... | @@ -78,34 +87,19 @@ |
78 | 87 | }; |
79 | 88 | |
80 | 89 | return () => { |
81 | - return h( | |
82 | - // @ts-ignores | |
83 | - Tooltip, | |
84 | - { | |
85 | - title: h( | |
86 | - 'div', | |
87 | - { | |
88 | - style: unref(getWrapStyleRef), | |
89 | - }, | |
90 | - [renderTitle()] | |
91 | - ), | |
92 | - overlayClassName: `${prefixCls}__wrap`, | |
93 | - autoAdjustOverflow: true, | |
94 | - overlayStyle: unref(getOverlayStyleRef), | |
95 | - placement: props.placement, | |
96 | - getPopupContainer: () => getPopupContainer(), | |
97 | - }, | |
98 | - { | |
99 | - default: () => | |
100 | - h( | |
101 | - 'span', | |
102 | - { | |
103 | - class: prefixCls, | |
104 | - style: unref(getMainStyleRef), | |
105 | - }, | |
106 | - getSlot(slots) || h(InfoCircleOutlined) | |
107 | - ), | |
108 | - } | |
90 | + return ( | |
91 | + <Tooltip | |
92 | + title={<div style={unref(getWrapStyle)}>{renderTitle()}</div>} | |
93 | + overlayClassName={`${prefixCls}__wrap`} | |
94 | + autoAdjustOverflow={true} | |
95 | + overlayStyle={unref(getOverlayStyle)} | |
96 | + placement={props.placement as 'left'} | |
97 | + getPopupContainer={() => getPopupContainer()} | |
98 | + > | |
99 | + <span class={prefixCls} style={unref(getMainStyleRef)}> | |
100 | + {getSlot(slots) || <InfoCircleOutlined />} | |
101 | + </span> | |
102 | + </Tooltip> | |
109 | 103 | ); |
110 | 104 | }; |
111 | 105 | }, | ... | ... |
src/components/Basic/src/BasicTitle.vue
... | ... | @@ -8,11 +8,12 @@ |
8 | 8 | import type { PropType } from 'vue'; |
9 | 9 | |
10 | 10 | import { defineComponent } from 'vue'; |
11 | - | |
12 | 11 | import BasicHelp from './BasicHelp.vue'; |
13 | - import { propTypes } from '/@/utils/propTypes'; | |
12 | + | |
14 | 13 | import { useDesign } from '/@/hooks/web/useDesign'; |
15 | 14 | |
15 | + import { propTypes } from '/@/utils/propTypes'; | |
16 | + | |
16 | 17 | export default defineComponent({ |
17 | 18 | name: 'BasicTitle', |
18 | 19 | components: { BasicHelp }, | ... | ... |
src/components/Button/index.ts
src/components/Button/src/BasicButton.vue
src/components/Button/src/PopConfirmButton.vue
... | ... | @@ -2,12 +2,15 @@ |
2 | 2 | import { defineComponent, h, unref, computed } from 'vue'; |
3 | 3 | |
4 | 4 | import { Popconfirm } from 'ant-design-vue'; |
5 | + | |
5 | 6 | import BasicButton from './BasicButton.vue'; |
7 | + | |
6 | 8 | import { propTypes } from '/@/utils/propTypes'; |
7 | - import { useI18n } from '/@/hooks/web/useI18n'; | |
8 | 9 | import { extendSlots } from '/@/utils/helper/tsxHelper'; |
9 | 10 | import { omit } from 'lodash-es'; |
11 | + | |
10 | 12 | import { useAttrs } from '/@/hooks/core/useAttrs'; |
13 | + import { useI18n } from '/@/hooks/web/useI18n'; | |
11 | 14 | |
12 | 15 | export default defineComponent({ |
13 | 16 | name: 'PopButton', | ... | ... |
src/components/ClickOutSide/index.ts
src/components/Container/index.ts
1 | -import { withInstall } from '../util'; | |
2 | 1 | import CollapseContainer from './src/collapse/CollapseContainer.vue'; |
3 | 2 | import ScrollContainer from './src/ScrollContainer.vue'; |
4 | 3 | import LazyContainer from './src/LazyContainer.vue'; |
5 | 4 | |
6 | -withInstall(ScrollContainer, CollapseContainer, LazyContainer); | |
7 | 5 | export { CollapseContainer, ScrollContainer, LazyContainer }; |
8 | 6 | export * from './src/types'; | ... | ... |
src/components/Drawer/index.ts
src/components/Dropdown/index.ts
src/components/Form/index.ts
src/components/Icon/index.ts
src/components/Modal/index.ts
1 | 1 | import './src/index.less'; |
2 | -import { withInstall } from '../util'; | |
3 | 2 | import BasicModal from './src/BasicModal.vue'; |
4 | 3 | |
5 | -withInstall(BasicModal); | |
6 | - | |
7 | 4 | export { BasicModal }; |
8 | 5 | export { useModalContext } from './src/hooks/useModalContext'; |
9 | 6 | export { useModal, useModalInner } from './src/hooks/useModal'; | ... | ... |
src/components/Scrollbar/index.ts
src/components/util.tsx
1 | 1 | import type { VNodeChild } from 'vue'; |
2 | -import type { App } from 'vue'; | |
3 | - | |
4 | -export function withInstall(...components: any[]) { | |
5 | - components.forEach((comp) => { | |
6 | - comp.install = (app: App) => { | |
7 | - app.component(comp.displayName || comp.name, comp); | |
8 | - }; | |
9 | - }); | |
10 | -} | |
11 | 2 | |
12 | 3 | export function convertToUnit( |
13 | 4 | str: string | number | null | undefined, | ... | ... |
src/design/ant/btn.less
src/design/ant/index.less
... | ... | @@ -29,17 +29,6 @@ |
29 | 29 | } |
30 | 30 | |
31 | 31 | // ================================= |
32 | -// ==============descriptions======= | |
33 | -// ================================= | |
34 | -// .ant-descriptions-bordered .ant-descriptions-item-label { | |
35 | -// background-color: @background-color-light; | |
36 | -// } | |
37 | - | |
38 | -// .ant-descriptions .ant-descriptions-item-content { | |
39 | -// color: @text-color-call-out; | |
40 | -// } | |
41 | - | |
42 | -// ================================= | |
43 | 32 | // ==============modal message====== |
44 | 33 | // ================================= |
45 | 34 | .modal-icon-warning { |
... | ... | @@ -57,18 +46,3 @@ |
57 | 46 | .modal-icon-info { |
58 | 47 | color: @primary-color !important; |
59 | 48 | } |
60 | - | |
61 | -// ================================= | |
62 | -// ==============empty============== | |
63 | -// ================================= | |
64 | -.ant-empty-image { | |
65 | - max-height: 144px; | |
66 | - min-height: 60px; | |
67 | -} | |
68 | - | |
69 | -.ant-empty-description { | |
70 | - margin-top: 16px; | |
71 | - font-size: 14px; | |
72 | - line-height: 24px; | |
73 | - color: @text-color-call-out; | |
74 | -} | ... | ... |
src/directives/ripple/index.ts
src/settings/designSetting.ts
src/utils/env.ts
1 | 1 | import type { GlobEnvConfig } from '/@/types/config'; |
2 | 2 | |
3 | +/** | |
4 | + * Get the global configuration (the configuration will be extracted independently when packaging) | |
5 | + */ | |
3 | 6 | export function getGlobEnvConfig(): GlobEnvConfig { |
4 | 7 | const env = import.meta.env; |
5 | 8 | return (env as unknown) as GlobEnvConfig; |
6 | 9 | } |
7 | 10 | |
8 | 11 | /** |
9 | - * @description: 开发模式 | |
12 | + * @description: Development model | |
10 | 13 | */ |
11 | 14 | export const devMode = 'development'; |
12 | 15 | |
13 | 16 | /** |
14 | - * @description: 生产模式 | |
17 | + * @description: Production mode | |
15 | 18 | */ |
16 | 19 | export const prodMode = 'production'; |
17 | 20 | |
18 | 21 | /** |
19 | - * @description: 获取环境变量 | |
22 | + * @description: Get environment variables | |
20 | 23 | * @returns: |
21 | 24 | * @example: |
22 | 25 | */ |
... | ... | @@ -25,7 +28,7 @@ export function getEnv(): string { |
25 | 28 | } |
26 | 29 | |
27 | 30 | /** |
28 | - * @description: 是否是开发模式 | |
31 | + * @description: Is it a development mode | |
29 | 32 | * @returns: |
30 | 33 | * @example: |
31 | 34 | */ |
... | ... | @@ -34,7 +37,7 @@ export function isDevMode(): boolean { |
34 | 37 | } |
35 | 38 | |
36 | 39 | /** |
37 | - * @description: 是否是生产模式模式 | |
40 | + * @description: Is it a production mode | |
38 | 41 | * @returns: |
39 | 42 | * @example: |
40 | 43 | */ |
... | ... | @@ -43,7 +46,7 @@ export function isProdMode(): boolean { |
43 | 46 | } |
44 | 47 | |
45 | 48 | /** |
46 | - * @description: 是否开启mock | |
49 | + * @description: Whether to open mock | |
47 | 50 | * @returns: |
48 | 51 | * @example: |
49 | 52 | */ | ... | ... |