diff --git a/devui/panel/index.ts b/devui/panel/index.ts index fee0e61cfaf6b95d4b915dd253473bc1dd9e74b8..a2e8ecc59f360e9e0c35690375a34f0a7d16715e 100644 --- a/devui/panel/index.ts +++ b/devui/panel/index.ts @@ -1,8 +1,14 @@ import type { App } from 'vue' import Panel from './src/panel' +import PanelHeader from './src/header/panel-header'; +import PanelBody from './src/body/panel-body'; +import PanelFooter from './src/foot/panel-footer'; Panel.install = function(app: App) { app.component(Panel.name, Panel) + app.component(PanelHeader.name, PanelHeader); + app.component(PanelBody.name, PanelBody); + app.component(PanelFooter.name, PanelFooter); } export { Panel } @@ -14,3 +20,4 @@ export default { app.use(Panel as any) } } + diff --git a/devui/panel/src/body/panel-body.tsx b/devui/panel/src/body/panel-body.tsx new file mode 100644 index 0000000000000000000000000000000000000000..84a64108e97c252dd8c011a9e6e187a7b8f4ac02 --- /dev/null +++ b/devui/panel/src/body/panel-body.tsx @@ -0,0 +1,53 @@ +import { defineComponent,ref,onMounted,Transition,inject } from 'vue'; +import { PanelProps } from '../panel.type'; +import Store from '../store/store'; + +export default defineComponent({ + name: 'DPanelBody', + props:PanelProps, + setup(props,ctx){ + const animationName = inject('showAnimation') ? 'devui-panel' : ''; + const hasLeftPadding = !inject('hasLeftPadding') ? 'no-left-padding' : ''; + + const keys = Object.keys(Store.state()); + const key = keys.pop(); + const isCollapsed = Store.state(); + const bodyEl = ref(); + onMounted(() => { + if(bodyEl.value) { + const dom = bodyEl.value; + if(isCollapsed[key]) + dom.style.height = `${dom.offsetHeight}px`; + } + }) + + const enter = (element: Element ) => { + const el = (element as HTMLElement); + el.style.height = ''; + const height = el.offsetHeight; + el.style.height = '0px'; + // 需要执行一次才会生效 + el.offsetHeight; + el.style.height = `${height}px`; + } + const leave = (element: Element) => { + const el = (element as HTMLElement); + el.style.height = '0px'; + } + + return () => { + return ( +
+ + {isCollapsed[key] === undefined || isCollapsed[key] ? +
+
+ {ctx.slots.default?.()} +
+
: null } +
+
+ ) + } + }, +}) \ No newline at end of file diff --git a/devui/panel/src/foot/panel-footer.tsx b/devui/panel/src/foot/panel-footer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..65d7a3ad6e53d042558a2f1d1402e31ec22ab4a3 --- /dev/null +++ b/devui/panel/src/foot/panel-footer.tsx @@ -0,0 +1,14 @@ +import {ref,defineComponent} from 'vue'; + +export default defineComponent({ + name: 'DPanelFooter', + setup(props,ctx){ + return () => { + const footerContent = (ctx.slots.default ? + : null); + return footerContent + } + } +}) \ No newline at end of file diff --git a/devui/panel/src/header/panel-header.tsx b/devui/panel/src/header/panel-header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..93f701621eff6f5dc97bd887900fdc2dfcccb0c4 --- /dev/null +++ b/devui/panel/src/header/panel-header.tsx @@ -0,0 +1,54 @@ +import { defineComponent,ref,inject } from 'vue'; +import {PanelProps} from '../panel.type'; +import Store from '../store/store'; + +export default defineComponent({ + name: 'DPanelHeader', + props: PanelProps, + setup(props,ctx){ + const beforeToggle = inject('beforeToggle'); + const keys = Object.keys(Store.state()); + const key = keys.pop(); + const isCollapsed = ref(Store.state()[key]); + + const canToggle = (): Promise => { + let changeResult = Promise.resolve(true); + if(beforeToggle) { + const result = beforeToggle(isCollapsed); + if(typeof result !== undefined) { + if(result instanceof Promise) { + changeResult = result; + } else { + changeResult = Promise.resolve(result); + } + } + } + return changeResult; + } + + const toggleBody = (): void => { + canToggle().then((val) => { + if (!val){ + return; + } + if (isCollapsed.value !== undefined) { + Store.setData(`${key}`, !isCollapsed.value); + isCollapsed.value = !isCollapsed.value; + props.toggle?.(isCollapsed.value); + } + }) + + }; + return () => { + let header = null; + if (ctx.slots.default){ + header = ( +
+ {ctx.slots.default?.()} +
+ ) + } + return header + } + }, +}) \ No newline at end of file diff --git a/devui/panel/src/panel.scss b/devui/panel/src/panel.scss index 38be0fd28e04eaf85077dea912ba04539ae3b52c..2cb1bb83b846d0ab25d96329a09c6ecc42c6d217 100644 --- a/devui/panel/src/panel.scss +++ b/devui/panel/src/panel.scss @@ -13,12 +13,24 @@ } } + .no-left-padding { + &.devui-panel-body-collapse { + &::before { + display: none; + } + + .devui-panel-content { + border-left: none !important; + } + } + } + .devui-panel-body { display: flex; position: relative; border-top: 1px solid $devui-dividing-line; - .d-panel-body { + .devui-panel-content { line-height: 1.5; padding: 15px; background: $devui-base-bg; @@ -32,7 +44,7 @@ height: 100%; } - .d-panel-body { + .devui-panel-content { border-left: 2px solid $devui-dividing-line; } } @@ -62,7 +74,7 @@ } .devui-panel-body-collapse { - .d-panel-body { + .devui-panel-content { border-color: $devui-primary-line; } } @@ -75,7 +87,7 @@ } .devui-panel-body-collapse { - .d-panel-body { + .devui-panel-content { border-color: $devui-info-line; } } @@ -88,7 +100,7 @@ } .devui-panel-body-collapse { - .d-panel-body { + .devui-panel-content { border-color: $devui-success-line; } } @@ -101,7 +113,7 @@ } .devui-panel-body-collapse { - .d-panel-body { + .devui-panel-content { border-color: $devui-warning-line; } } @@ -114,7 +126,7 @@ } .devui-panel-body-collapse { - .d-panel-body { + .devui-panel-content { border-color: $devui-danger-line; } } diff --git a/devui/panel/src/panel.tsx b/devui/panel/src/panel.tsx index 12ccc03d0560807ac6659381bfe70882f3cd7764..079a1de08567f717f11ddc4c298bf88778c3104c 100644 --- a/devui/panel/src/panel.tsx +++ b/devui/panel/src/panel.tsx @@ -1,65 +1,19 @@ -import { defineComponent, ref, Transition, onMounted } from 'vue'; -import './panel.scss' - -export type PanelType = 'default' | 'primary' | 'success' | 'danger' | 'warning' | 'info'; +import { defineComponent, ref, Transition, onMounted, provide } from 'vue'; +import './panel.scss'; +import { PanelProps } from './panel.type'; +import Store from './store/store'; export default defineComponent({ name: 'DPanel', - props: { - type: { - type: String as () => PanelType, - default: 'default' - }, - cssClass: { - type: String, - default: '' - }, - isCollapsed: { - type: Boolean, - default: undefined - }, - beforeToggle: { - type: Function as unknown as () => (value: boolean) => boolean | Promise, - default: null - }, - toggle: { - type: Function as unknown as ()=> ((value: boolean) => void), - default: null - } - }, + props: PanelProps, setup(props, ctx) { - + provide('beforeToggle', props.beforeToggle); + provide('showAnimation', props.showAnimation); + provide('hasLeftPadding', props.hasLeftPadding); const isCollapsed = ref(props.isCollapsed); const bodyEl = ref(); - const canToggle = (): Promise => { - let changeResult = Promise.resolve(true); - if(props.beforeToggle) { - const result = props.beforeToggle(props.isCollapsed); - if(typeof result !== undefined) { - if(result instanceof Promise) { - changeResult = result; - } else { - changeResult = Promise.resolve(result); - } - } - } - return changeResult; - } - - const toggleBody = (): void => { - canToggle().then((val) => { - if (!val){ - return; - } - if (isCollapsed.value !== undefined) { - isCollapsed.value = !isCollapsed.value; - props.toggle?.(isCollapsed.value); - } - }) - } - onMounted(() => { if(bodyEl.value) { const dom = bodyEl.value; @@ -67,47 +21,26 @@ export default defineComponent({ dom.style.height = `${dom.offsetHeight}px`; } }) + const timeStamp = new Date().getTime().toString(); + Store.setData(`isCollapsed[${timeStamp}]`, isCollapsed.value); - const enter = (element: Element ) => { - const el = (element as HTMLElement); - el.style.height = ''; - const height = el.offsetHeight; - el.style.height = '0px'; - // 需要执行一次才会生效 - el.offsetHeight; - el.style.height = `${height}px`; - } - const leave = (element: Element) => { - const el = (element as HTMLElement); - el.style.height = '0px'; + + return () => { + return ( +
+ {ctx.slots.default()} +
+ ) } - + }, + render(){ + const {props,$slots} = this; return () => { - - const headerContent = (ctx.slots.header ? -
- { ctx.slots.header?.() } -
: null); - - const footerContent = (ctx.slots.footer ? - : null); - return (
- {headerContent} - - {isCollapsed.value === undefined || isCollapsed.value ? -
-
- { ctx.slots.body?.() } -
-
: null } -
- {footerContent} + {$slots.default()}
) } } -}) +}) \ No newline at end of file diff --git a/devui/panel/src/panel.type.ts b/devui/panel/src/panel.type.ts new file mode 100644 index 0000000000000000000000000000000000000000..58a80760ef8040f5b3246f0b264bb6b304f9fd2a --- /dev/null +++ b/devui/panel/src/panel.type.ts @@ -0,0 +1,36 @@ +import {ExtractPropTypes} from 'vue'; + +export type PanelType = 'default' | 'primary' | 'success' | 'danger' | 'warning' | 'info'; + +export const PanelProps = { + type: { + type: String as () => PanelType, + default: 'default' + }, + cssClass: { + type: String, + default: '' + }, + isCollapsed: { + type: Boolean, + default: undefined + }, + beforeToggle: { + type: Function as unknown as () => (value: boolean) => boolean | Promise, + default: null + }, + toggle: { + type: Function as unknown as ()=> ((value: boolean) => void), + default: null + }, + showAnimation: { + type: Boolean, + default: true, + }, + hasLeftPadding:{ + type: Boolean, + default: true, + } +} + +export type PanelPropsType = ExtractPropTypes; \ No newline at end of file diff --git a/devui/panel/src/store/store.ts b/devui/panel/src/store/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e989228ed0d54f186a4510973c0623f22baf390 --- /dev/null +++ b/devui/panel/src/store/store.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import {ref,reactive} from 'vue'; + +export const option = reactive({}) + +class Store { + public static state() { + return option; + } + public static setData(key,value){ + option[key] = ref(value); + } +} + +export default Store \ No newline at end of file diff --git a/docs/components/panel/index.md b/docs/components/panel/index.md index 10c3ae76c4874da0e2e2fd1f7242e4af26af6c45..e25db70736cc3e564d85cac064fa98294077f713 100644 --- a/docs/components/panel/index.md +++ b/docs/components/panel/index.md @@ -7,17 +7,18 @@ 当页面内容需要进行分组显示时使用,一般包含头部、内容区域、底部三个部分。 ### 基本用法 +:::demo -
- +```vue + ``` +::: - +|参数| 类型| 默认| 说明| +|:-:|:-:|:-:|:-:| +|type| PanelType| 'default'| 可选,面板的类型| +|cssClass| string| --| 可选,自定义 class 名| +|isCollapsed| boolean| false| 可选,是否展开| +|hasLeftPadding| boolean| true| 可选,是否显示左侧填充| +|showAnimation| boolean| true| 可选,是否展示动画| +|beforeToggle| Function\|Promise\|Observable| --| 可选,面板折叠状态改变前的回调函数,返回 boolean 类型,返回 false 可以阻止面板改变折叠状态 根据条件阻止折叠| \ No newline at end of file