diff --git a/src/config.ts b/src/config.ts index 0750b81275412c8460cf4b82d2e38942e2841f15..aeae7088ed2f1da4b084094ee5b9b65a6dff2f4e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -102,6 +102,24 @@ export const nav = [ show: true, desc: '价格组件', author: 'ailululu' + }, + { + name: 'collapse', + sort: 5, + cName: '折叠面板', + type: 'component', + show: true, + desc: '折叠面板', + author: 'Ymm0008' + }, + { + name: 'collapse', + sort: 6, + cName: '折叠面板-item', + type: 'component', + show: false, + desc: '折叠面板-item', + author: 'Ymm0008' } ] }, diff --git a/src/packages/collapse/demo.vue b/src/packages/collapse/demo.vue new file mode 100644 index 0000000000000000000000000000000000000000..e62e262d47f0edbd105d021db22e4568946a3567 --- /dev/null +++ b/src/packages/collapse/demo.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/packages/collapse/doc.md b/src/packages/collapse/doc.md new file mode 100644 index 0000000000000000000000000000000000000000..cebd22176754a5ac6f0006de757257dddd653108 --- /dev/null +++ b/src/packages/collapse/doc.md @@ -0,0 +1,111 @@ +# Collapse 折叠面板 + +## 基本用法 + +通过`v-model`控制展开的面板列表,`activeNames`为数组格式 + +```html + + + 京东“厂直优品计划”首推“政府优品馆” 3年覆盖80%镇级政府 + + + 京东到家:教师节期间 创意花束销量增长53倍 + + + + +``` + +``` javascript +export default { + data() { + return { + activeNames: [1, 2] + }; + } +}; +``` + + +### 手风琴 + +通过`accordion`可以设置为手风琴模式,最多展开一个面板,此时`activeName`为字符串格式;`subTitle`可以设置副标题的内容 + +```html + + + 华为终端操作系统EMUI 11发布,9月11日正式开启 + + + 中国服务机器人市场已占全球市场超1/4 + + + QuestMobile:90后互联网用户规模超越80后达3.62亿 + + +``` + +``` javascript +export default { + data() { + return { + activeName: 1, + subTitle: '副标题' + }; + } +}; +``` + + +### 图标展示 + +通过`expandIconPosition`可以设置图标的位置,icon设置自定义图标,rotate设置图标旋转的角度 + +```html + + + 京东数科IPO将引入“绿鞋机制” + + + 世界制造业大会开幕,阿里巴巴与安徽合作再升级 + + +``` + +``` javascript +export default { + data() { + return { + activeName: 1, + expandIconPosition: 'left', + icon: 'https://img11.360buyimg.com/imagetools/jfs/t1/132849/8/9709/550/5f5f0d8aE802abee7/68bd02b3a52c3988.png' + rotate: 180, + }; + } +}; +``` + +## Collapse Prop + +| 字段 | 说明 | 类型 | 默认值 +|----- | ----- | ----- | ----- +| v-model | 当前展开面板的 name | 手风琴模式:string \| number
非手风琴模式:(string \| number)[] | - | +| accordion | 是否开启手风琴模式 | boolean | false | +| border | 是否显示外边框 | boolean | true | + +### Events + +| 事件名 | 说明 | 回调参数 | +|------|------|------| +| change | 切换面板时触发 | 类型与 v-model 绑定的值一致 | + +### CollapseItem Props +| 参数 | 说明 | 类型 | 默认值 | +|------|------|------|------| +| title | 标题栏左侧内容 | string | - | +| name | 唯一标识符,必填 | string \ number | -1 | +| expand-icon-position | 标题图标的位置 | string | right | +| sub-title | 标题栏副标题 | string | - | +| icon | 标题栏自定义图标链接 | string | - | +| rotate | 点击折叠和展开的旋转角度,在自定义图标模式下生效 | string \ number | 180 | \ No newline at end of file diff --git a/src/packages/collapse/index.scss b/src/packages/collapse/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/packages/collapse/index.vue b/src/packages/collapse/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..045b8f53f8d902c6ea78e11cdf864b85874a0ba8 --- /dev/null +++ b/src/packages/collapse/index.vue @@ -0,0 +1,91 @@ + + + + diff --git a/src/packages/collapseitem/demo.vue b/src/packages/collapseitem/demo.vue new file mode 100644 index 0000000000000000000000000000000000000000..15bbd1ae418edc26494c829d5ae2daeb83469e12 --- /dev/null +++ b/src/packages/collapseitem/demo.vue @@ -0,0 +1,4 @@ + + diff --git a/src/packages/collapseitem/doc.md b/src/packages/collapseitem/doc.md new file mode 100644 index 0000000000000000000000000000000000000000..3a98aecaa44761580d186673bab356e145483a76 --- /dev/null +++ b/src/packages/collapseitem/doc.md @@ -0,0 +1,2 @@ +# CollapseItem 折叠面板 + diff --git a/src/packages/collapseitem/index.scss b/src/packages/collapseitem/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..a4e085b44beed5bf358c38c2d954f54ab8c67979 --- /dev/null +++ b/src/packages/collapseitem/index.scss @@ -0,0 +1,94 @@ +.nut-collapse-item { + position: relative; + .collapse-item { + &::after { + position: absolute; + box-sizing: border-box; + content: ' '; + pointer-events: none; + right: 16px; + bottom: 0; + left: 16px; + border-bottom: 1px solid #ebedf0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + } + } + .collapse-item { + position: relative; + display: block; + width: 100%; + overflow: hidden; + padding: 13px 26px; + color: #666666; + font-size: 14px; + line-height: 24px; + background-color: #fff; + box-sizing: border-box; + .collapse-icon { + display: block; + position: absolute; + top: 50%; + margin-top: -6px; + right: 16px; + width: 24px; + height: 12px; + line-height: 24px; + background-image: url(https://img13.360buyimg.com/imagetools/jfs/t1/153504/23/6125/464/5fb240abE562ed1c5/382909c698fa3c1f.png); + background-repeat: no-repeat; + background-size: 100% 100%; + transition: transform 0.3s; + } + .col-expanded { + transform: rotate(-180deg); + } + .subTitle { + position: absolute; + top: 50%; + right: 60px; + margin-top: -12px; + color: #969799; + } + } + .collapse-wrapper { + display: block; + position: relative; + height: 0; + overflow: hidden; + transition: height 0.3s ease-in-out; + .collapse-content { + display: block; + padding: 12px 16px; + color: #969799; + font-size: 14px; + line-height: 1.5; + background-color: #fff; + } + } + .nut-collapse-item-disabled { + color: #c8c9cc; + cursor: not-allowed; + pointer-events: none; + .collapse-icon-disabled { + background-image: url(https://img12.360buyimg.com/imagetools/jfs/t1/150103/9/14710/474/5fb2419aE63e79ef3/8db2699c11139027.png); + background-repeat: no-repeat; + background-size: 100% 100%; + } + } + .nut-collapse-item-left { + .collapse-item { + padding: 10px 16px 10px 50px; + .collapse-icon { + left: 20px; + } + .subTitle { + right: 16px; + } + } + } + // .nut-collapse-item.nut-collapse-item-icon { + // .collapse-icon { + // transform: rotate(0deg); + // } + // } +} diff --git a/src/packages/collapseitem/index.vue b/src/packages/collapseitem/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..75fa9a6645ac4bb10c62f587d8c9ef3c9babda80 --- /dev/null +++ b/src/packages/collapseitem/index.vue @@ -0,0 +1,190 @@ + + + + diff --git a/src/utils/useRelation/useChildren.ts b/src/utils/useRelation/useChildren.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bae54c6b7808d64dbc042b2041751a9e55abee4 --- /dev/null +++ b/src/utils/useRelation/useChildren.ts @@ -0,0 +1,90 @@ +import { + VNode, + isVNode, + provide, + reactive, + getCurrentInstance, + VNodeNormalizedChildren, + ComponentPublicInstance, + ComponentInternalInstance +} from 'vue'; + +export function flattenVNodes(children: VNodeNormalizedChildren) { + const result: VNode[] = []; + + const traverse = (children: VNodeNormalizedChildren) => { + if (Array.isArray(children)) { + children.forEach(child => { + if (isVNode(child)) { + result.push(child); + + if (child.component?.subTree) { + traverse(child.component.subTree.children); + } + + if (child.children) { + traverse(child.children); + } + } + }); + } + }; + + traverse(children); + + return result; +} + +// sort children instances by vnodes order +export function sortChildren( + parent: ComponentInternalInstance, + publicChildren: ComponentPublicInstance[], + internalChildren: ComponentInternalInstance[] +) { + const vnodes = flattenVNodes(parent.subTree.children); + + internalChildren.sort((a, b) => vnodes.indexOf(a.vnode) - vnodes.indexOf(b.vnode)); + + const orderedPublicChildren = internalChildren.map(item => item.proxy!); + + publicChildren.sort((a, b) => { + const indexA = orderedPublicChildren.indexOf(a); + const indexB = orderedPublicChildren.indexOf(b); + return indexA - indexB; + }); +} + +export function useChildren(key: string | symbol) { + const publicChildren: ComponentPublicInstance[] = reactive([]); + const internalChildren: ComponentInternalInstance[] = reactive([]); + const parent = getCurrentInstance()!; + + const linkChildren = (value?: any) => { + const link = (child: ComponentInternalInstance) => { + if (child.proxy) { + internalChildren.push(child); + publicChildren.push(child.proxy); + sortChildren(parent, publicChildren, internalChildren); + } + }; + + const unlink = (child: ComponentInternalInstance) => { + const index = internalChildren.indexOf(child); + publicChildren.splice(index, 1); + internalChildren.splice(index, 1); + }; + + provide(key, { + link, + unlink, + children: publicChildren, + internalChildren, + ...value + }); + }; + + return { + children: publicChildren, + linkChildren + }; +} diff --git a/src/utils/useRelation/useParent.ts b/src/utils/useRelation/useParent.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4d557faa6d5a216f8cf464f8d7db0a1d3710384 --- /dev/null +++ b/src/utils/useRelation/useParent.ts @@ -0,0 +1,35 @@ +import { inject, computed, onUnmounted, getCurrentInstance, ComponentPublicInstance, ComponentInternalInstance } from 'vue'; + +type ParentProvide = T & { + link(child: ComponentInternalInstance): void; + unlink(child: ComponentInternalInstance): void; + children: ComponentPublicInstance[]; + internalChildren: ComponentInternalInstance[]; +}; + +export function useParent(key: string | symbol) { + const parent = inject | null>(key, null); + + if (parent) { + const instance = getCurrentInstance(); + + if (instance) { + const { link, unlink, internalChildren, ...rest } = parent; + + link(instance); + + onUnmounted(() => { + unlink(instance); + }); + + const index = computed(() => internalChildren.indexOf(instance)); + + return { + parent: rest, + index + }; + } + } + + return {}; +}