浏览代码

refactor(架构调整): 全局引入方法变更,初步移植Dailog与调整

gemercheung 2 年之前
父节点
当前提交
13123660ff

+ 1 - 1
.cz-config.js

@@ -16,7 +16,7 @@ module.exports = {
         { value: 'update', name: 'update ⬆:    第三方库升级 ' },
     ],
 
-    scopes: [{ name: '组件' }, { name: '样式' }, { name: '文档更改' }, { name: '其它变更' }],
+    scopes: [{ name: '组件' }, { name: '样式' }, { name: '文档更改' }, { name: '其它变更' }, { name: '架构调整' }],
     allowTicketNumber: false,
     isTicketNumberRequired: false,
     ticketNumberPrefix: 'TICKET-',

+ 2 - 1
package.json

@@ -1,5 +1,5 @@
 {
-    "name": "4dkankan-components",
+    "name": "kankan-components",
     "description": "a collection of 4dkankan components",
     "private": true,
     "workspaces": [
@@ -24,6 +24,7 @@
         "vue": "^3.2.0"
     },
     "dependencies": {
+        "@kankan/components": "workspace:*",
         "@vueuse/core": "^9.1.0",
         "lodash": "^4.17.21",
         "lodash-es": "^4.17.21",

+ 3 - 1
packages/components/README.md

@@ -1,10 +1,12 @@
 # Components 组件库说明
 
-packages/components
+packages/components 组件目录结构
+```
 ├── basic // 基础 UI 组件
 ├── editor // 与编辑器相关
 ├── advance // 高阶复杂
 
+```
 
 ## 文件开发目录规范
 

+ 2 - 1
packages/components/basic/button/src/button.vue

@@ -8,7 +8,8 @@
 <script lang="ts" setup>
 import { defineProps, computed } from 'vue'
 import { normalizeUnitToStyle } from '@kankan/utils'
-import UIIcon from '../../icon/src/icon.vue'
+// import UIIcon from '../../icon/src/icon.vue'
+import UIIcon from '@kankan/components/basic/icon'
 
 const props = defineProps({
     type: {

+ 162 - 0
packages/components/basic/floating/index.vue

@@ -0,0 +1,162 @@
+<template>
+    <teleport :to="mount">
+        <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
+            <slot></slot>
+        </div>
+    </teleport>
+</template>
+
+<script setup>
+// onUpdated
+import { defineProps, defineExpose, onUnmounted, reactive, watch, computed, onActivated } from 'vue'
+import { getPostionByTarget, getScrollParents, getZIndex } from '../../utils'
+
+const Horizontal = {
+    center: 'center',
+    right: 'right',
+    left: 'left',
+}
+const Vertical = {
+    center: 'center',
+    top: 'top',
+    bottom: 'bottom',
+}
+const Divide = '-'
+
+const props = defineProps({
+    mount: {
+        require: true,
+        default: document.body,
+    },
+    class: { type: String },
+    refer: { type: Object },
+    dire: { type: String },
+    width: { type: [Number, String] },
+    height: { type: [Number, String] },
+})
+const emit = defineEmits(['leave', 'enter'])
+
+// 确定方向
+const dires = computed(() => {
+    const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`
+    const isPreset = (preset, val) => Object.keys(preset).some(key => preset[key] === val)
+
+    let [horizontal, vertical] = dire.split(Divide)
+
+    if (!horizontal || !isPreset(Horizontal, horizontal)) {
+        horizontal = Horizontal.left
+    }
+    if (!vertical || !isPreset(Vertical, vertical)) {
+        vertical = Vertical.bottom
+    }
+    return [horizontal, vertical]
+})
+
+const normalizeUnit = (unit, total) => {
+    if (unit === void 0) {
+        return void 0
+    } else if (typeof unit === 'number') {
+        return unit ? ((unit <= 1) & (unit >= 0) ? total * unit : unit) : void 0
+    } else if (unit.includes('px')) {
+        return normalizeUnit(parseFloat(unit), total)
+    } else if (unit.includes('%')) {
+        return normalizeUnit(parseFloat(unit) / 100, total)
+    }
+}
+
+const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth))
+const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight))
+
+const location = reactive({ x: 0, y: 0 })
+const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []))
+
+watch(
+    [scrollParents, props],
+    ([newParents], [oldParents]) => {
+        oldParents && oldParents.forEach(dom => dom.removeEventListener('scroll', updateLocation))
+        newParents.forEach(dom => dom.addEventListener('scroll', updateLocation))
+        if (props.refer) {
+            setTimeout(() => updateLocation())
+        }
+    },
+    { immediate: true }
+)
+
+const zIndex = getZIndex()
+
+const style = computed(() => {
+    let style = {
+        width: width.value && width.value + 'px',
+        height: height.value && height.value + 'px',
+        left: location.x + 'px',
+        top: location.y + 'px',
+        zIndex: zIndex,
+    }
+    if (location.x > 0 && location.y > 0) {
+        return style
+    }
+    return {}
+})
+
+const updateLocation = () => {
+    const pos = getPostionByTarget(props.refer, props.mount)
+    const screenInfo = scrollParents.value.reduce(
+        (t, c) => {
+            t.y += c.scrollTop
+            t.x += c.scrollLeft
+            return t
+        },
+        { x: 0, y: 0 }
+    )
+
+    const [horizontal, vertical] = dires.value
+    const start = {
+        x: pos.x - screenInfo.x,
+        y: pos.y - screenInfo.y,
+    }
+
+    switch (horizontal) {
+        case Horizontal.left:
+            location.x = start.x
+            break
+        case Horizontal.right:
+            location.x = start.x + pos.width
+            break
+        case Horizontal.center:
+            location.x = start.x + pos.width / 2
+            break
+    }
+
+    switch (vertical) {
+        case Vertical.top:
+            location.y = start.y
+            break
+        case Vertical.bottom:
+            location.y = start.y + pos.height
+            break
+        case Vertical.center:
+            location.y = start.y + pos.height / 2
+            break
+    }
+}
+
+window.addEventListener('resize', updateLocation)
+onUnmounted(() => {
+    scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
+    window.removeEventListener('resize', updateLocation)
+})
+
+onActivated(() => {
+    if (props.refer) {
+        updateLocation()
+    }
+})
+
+defineExpose({
+    updateLocation,
+})
+</script>
+
+<script>
+export default { name: 'UiFloating' }
+</script>

+ 1 - 0
packages/components/basic/index.ts

@@ -1,3 +1,4 @@
 export * from './audio'
 export * from './icon'
 export * from './button'
+// export * from './input'

+ 6 - 0
packages/components/basic/input/index.ts

@@ -0,0 +1,6 @@
+import { withInstall } from '@kankan/utils'
+import Input from './src/index.vue'
+
+export const UIInput = withInstall(Input)
+
+export default UIInput

+ 121 - 0
packages/components/basic/input/src/index copy.vue

@@ -0,0 +1,121 @@
+<template>
+    <div class="ui-input" v-if="types[type]" :style="style" :class="{ require: props.require, error: props.error }">
+        <component :is="types[type].component" v-bind="childProps" :modelValue="props.modelValue" @update:model-value="newValue => emit('update:modelValue', newValue)">
+            <template v-for="(slot, name) in $slots" #[name]="raw">
+                <slot :name="name" v-bind="raw"></slot>
+            </template>
+        </component>
+        <slot></slot>
+
+        <p class="error-msg" v-if="error">{{ error }}</p>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import radio from './radio.vue'
+import checkbox from './checkbox.vue'
+import text from './text.vue'
+import select from './select.vue'
+import range from './range.vue'
+import textarea from './textarea.vue'
+import number from './number.vue'
+import uiSwitch from './switch.vue'
+import file from './file.vue'
+import search from './search.vue'
+import richtext from './richtext.vue'
+import {
+    inputPropsDesc,
+    textPropsDesc,
+    selectPropsDesc,
+    checkboxPropsDesc,
+    radioPropsDesc,
+    rangePropsDesc,
+    numberPropsDesc,
+    switchPropsDesc,
+    textareaPropsDesc,
+    filePropsDesc,
+    searchPropsDesc,
+    richtextPropsDesc,
+} from './state.js.bk'
+
+const types = {
+    checkbox: {
+        component: checkbox,
+        propsDesc: checkboxPropsDesc,
+    },
+    text: {
+        component: text,
+        propsDesc: textPropsDesc,
+    },
+    select: {
+        component: select,
+        propsDesc: selectPropsDesc,
+    },
+    radio: {
+        component: radio,
+        propsDesc: radioPropsDesc,
+    },
+    range: {
+        component: range,
+        propsDesc: rangePropsDesc,
+    },
+    number: {
+        component: number,
+        propsDesc: numberPropsDesc,
+    },
+    switch: {
+        component: uiSwitch,
+        propsDesc: switchPropsDesc,
+    },
+    textarea: {
+        component: textarea,
+        propsDesc: textareaPropsDesc,
+    },
+    file: {
+        component: file,
+        propsDesc: filePropsDesc,
+    },
+    search: {
+        component: search,
+        propsDesc: searchPropsDesc,
+    },
+    richtext: {
+        component: richtext,
+        propsDesc: richtextPropsDesc,
+    },
+}
+
+const props = defineProps(inputPropsDesc)
+
+const emit = defineEmits(['update:modelValue'])
+const type = computed(() => (types[props.type] ? props.type : 'text'))
+const childProps = computed(() => {
+    const retain = Object.keys(types[type.value].propsDesc)
+    const childProps = {}
+    for (let key in props) {
+        if (retain.includes(key)) {
+            childProps[key] = props[key]
+        }
+    }
+    if (!types[props.type]) {
+        childProps.type = props.type
+    }
+    return childProps
+})
+
+const style = computed(() => {
+    const style = {}
+    const keys = Object.keys(childProps.value)
+
+    if (!keys.includes('width')) {
+        style.width = props.width
+    }
+
+    if (!keys.includes('height')) {
+        style.height = props.height
+    }
+
+    return style
+})
+</script>

+ 3 - 3
packages/components/basic/input/src/index.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="ui-input" v-if="types[type]" :style="style" :class="{ require: props.require, error: props.error }">
-        <component :is="types[type].component" v-bind="childProps" :modelValue="props.modelValue" @update:modelValue="newValue => emit('update:modelValue', newValue)">
+        <component :is="types[type].component" v-bind="childProps" :modelValue="props.modelValue" @update:model-value="newValue => emit('update:modelValue', newValue)">
             <template v-for="(slot, name) in $slots" #[name]="raw">
                 <slot :name="name" v-bind="raw"></slot>
             </template>
@@ -11,7 +11,7 @@
     </div>
 </template>
 
-<script setup>
+<script setup lang="ts">
 import { computed } from 'vue'
 import radio from './radio.vue'
 import checkbox from './checkbox.vue'
@@ -37,7 +37,7 @@ import {
     filePropsDesc,
     searchPropsDesc,
     richtextPropsDesc,
-} from './state'
+} from './state.js.bk'
 
 const types = {
     checkbox: {

+ 17 - 17
packages/components/basic/input/src/radio/radio.ts

@@ -1,18 +1,18 @@
 import { buildProps, definePropType } from '@kankan/utils'
 import type { ExtractPropTypes } from 'vue'
 
-export const switchProps = buildProps({
-    type: {
+export const radioProps = buildProps({
+    icon: {
         type: String,
     },
-    name: {
+    tip: {
         type: String,
     },
-    width: {
-        type: definePropType<number | string>([Number, String]),
+    type: {
+        type: String,
     },
-    height: {
-        type: definePropType<number | string>([Number, String]),
+    name: {
+        type: String,
     },
     disabled: {
         type: Boolean,
@@ -31,7 +31,7 @@ export const switchProps = buildProps({
     },
     readonly: {
         type: Boolean,
-        default: true,
+        default: false,
     },
     other: {
         type: Object,
@@ -40,17 +40,17 @@ export const switchProps = buildProps({
     right: {
         type: Boolean,
     },
-    className: {
-        type: String,
+    width: {
+        type: definePropType<number | string>([Number, String]),
+        default: '',
     },
-    labelValue: {
-        type: String,
-        require: false,
+    height: {
+        type: definePropType<number | string>([Number, String]),
+        default: '',
     },
-    dbhide: {
-        type: Boolean,
-        default: true,
+    label: {
+        type: String,
     },
 })
 
-export type SelectProps = ExtractPropTypes<typeof selectProps>
+export type RadioProps = ExtractPropTypes<typeof radioProps>

+ 5 - 5
packages/components/basic/input/src/radio/radio.vue

@@ -4,17 +4,17 @@
         <span class="replace"></span>
     </div>
     <label class="label" v-if="props.label || props.icon" :for="id">
-        <Icon :type="props.icon" :tip="props.tip" v-if="props.icon" />
+        <UIIcon :type="props.icon" :tip="props.tip" v-if="props.icon" />
         {{ props.label }}
     </label>
 </template>
 
 <script setup lang="ts">
-import Icon from '../icon'
-import { radioPropsDesc } from '../state'
-import { randomId } from '../../utils'
+import UIIcon from '../../../icon/src/icon.vue'
+import { radioProps } from './radio'
+import { randomId } from '@kankan/utils'
 import { defineProps, defineEmits } from 'vue'
-const props = defineProps(radioPropsDesc)
+const props = defineProps(radioProps)
 const emit = defineEmits(['update:modelValue'])
 const id = randomId(4)
 </script>

+ 57 - 0
packages/components/basic/input/src/richtext/richtext.ts

@@ -0,0 +1,57 @@
+import { buildProps, definePropType } from '@kankan/utils'
+import type { ExtractPropTypes } from 'vue'
+
+export const richTextProps = buildProps({
+    type: {
+        type: String,
+    },
+    name: {
+        type: String,
+    },
+    width: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    height: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    disabled: {
+        type: Boolean,
+        default: false,
+    },
+    modelValue: {
+        type: String,
+        required: false,
+        default: '',
+    },
+    placeholder: {
+        type: String,
+        default: '请输入',
+    },
+    maxlength: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    readonly: {
+        type: Boolean,
+        default: true,
+    },
+    other: {
+        type: Object,
+        default: () => ({}),
+    },
+    right: {
+        type: Boolean,
+    },
+    className: {
+        type: String,
+    },
+    labelValue: {
+        type: String,
+        require: false,
+    },
+    dbhide: {
+        type: Boolean,
+        default: true,
+    },
+})
+
+export type RichTextProps = ExtractPropTypes<typeof richTextProps>

+ 2 - 4
packages/components/basic/input/src/richtext/richtext.vue

@@ -26,11 +26,9 @@
 </template>
 
 <script setup lang="ts">
-import { richtextPropsDesc } from './state'
+import { richTextProps } from './richtext'
 import { defineProps, defineEmits, defineExpose, nextTick, ref, watchEffect } from 'vue'
-const props = defineProps({
-    ...richtextPropsDesc,
-})
+const props = defineProps(richTextProps)
 
 const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click', ''])
 const textRef = ref(null)

+ 57 - 0
packages/components/basic/input/src/search/search.ts

@@ -0,0 +1,57 @@
+import { buildProps, definePropType } from '@kankan/utils'
+import type { ExtractPropTypes } from 'vue'
+
+export const searchProps = buildProps({
+    type: {
+        type: String,
+    },
+    name: {
+        type: String,
+    },
+    width: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    height: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    disabled: {
+        type: Boolean,
+        default: false,
+    },
+    modelValue: {
+        type: String,
+        required: false,
+        default: '',
+    },
+    placeholder: {
+        type: String,
+        default: '请输入',
+    },
+    maxlength: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    readonly: {
+        type: Boolean,
+        default: true,
+    },
+    other: {
+        type: Object,
+        default: () => ({}),
+    },
+    right: {
+        type: Boolean,
+    },
+    className: {
+        type: String,
+    },
+    labelValue: {
+        type: String,
+        require: false,
+    },
+    dbhide: {
+        type: Boolean,
+        default: true,
+    },
+})
+
+export type SearchProps = ExtractPropTypes<typeof searchProps>

+ 2 - 2
packages/components/basic/input/src/search/search.vue

@@ -14,10 +14,10 @@
 
 <script setup lang="ts">
 import { ref, watchEffect, defineEmits, onUnmounted } from 'vue'
-import { searchPropsDesc } from '../state'
+import { searchProps } from './search'
 import UISelect from '../select/select.vue'
 
-const props = defineProps(searchPropsDesc)
+const props = defineProps(searchProps)
 const labelValue = ref('')
 const options = ref([])
 const selectVM = ref()

packages/components/basic/input/src/state.js → packages/components/basic/input/src/state.js.bk


+ 0 - 73
packages/components/basic/input/src/test.vue

@@ -1,73 +0,0 @@
-<template>
-    <UItext
-        :disabled="props.disabled"
-        class="select"
-        :class="{ ready, focus: showOptions }"
-        ref="vmRef"
-        v-model="inputValue"
-        :width="props.width"
-        :height="props.height"
-        readonly
-        :placeholder="props.placeholder"
-        @blur="blurHandler"
-        @click="changShow(!showOptions)"
-    >
-        <template #icon>
-            <icon type="pull-down" small />
-        </template>
-    </UItext>
-
-    <UIFloating :mount="mountEl" :refer="vmRef && vmRef.root">
-        <ul class="select-replace" :class="{ ready }" :style="originHeight && { 'max-height': maxHeight + 'px' }" ref="contentRef">
-            <li v-for="option in props.options" :key="option.value" :class="{ active: props.modelValue === option.value }" @click="optionClickHandler(option)">
-                {{ option.label }}
-            </li>
-        </ul>
-    </UIFloating>
-</template>
-
-<script setup lang="ts">
-import UItext from './text.vue'
-import UIFloating from '../floating/index.vue'
-import { ref, onUnmounted, computed } from 'vue'
-import { selectPropsDesc } from './state'
-import icon from '../icon'
-import { changeWHFactory } from '../../utils'
-
-const props = defineProps(selectPropsDesc)
-const emit = defineEmits(['update:modelValue'])
-const vmRef = ref(null)
-const mountEl = document.body
-let timeout: number
-
-const inputValue = computed(() => {
-    const selectOption = props.options.find(({ value }) => value === props.modelValue)
-    return selectOption ? selectOption.label : ''
-})
-
-const [contentRef, changShow, maxHeight, originHeight, showOptions, ready] = changeWHFactory()
-
-const optionClickHandler = option => {
-    emit('update:modelValue', option.value)
-    vmRef.value.input.focus()
-    changShow(false)
-}
-
-const blurHandler = () => {
-    timeout = setTimeout(() => changShow(false), 500)
-}
-
-const attach = document.documentElement
-const htmlClickHandler = ev => {
-    if (vmRef.value.root.contains(ev.target)) {
-        clearTimeout(timeout)
-    } else {
-        changShow(false)
-    }
-}
-
-attach.addEventListener('click', htmlClickHandler)
-onUnmounted(() => {
-    attach.removeEventListener('click', htmlClickHandler)
-})
-</script>

+ 38 - 38
packages/components/package.json

@@ -1,40 +1,40 @@
 {
-  "name": "@kankan/components",
-  "version": "1.0.0",
-  "main": "index.ts",
-  "module": "index.ts",
-  "unpkg": "index.js",
-  "types": "index.d.ts",
-  "files": [
-    "dist"
-  ],
-  "author": {
-    "name": "gemercheung",
-    "email": "gemercheung@gmail.com",
-    "url": "http://gemer.xyz/"
-  },
-  "scripts": {
-    "build": "pnpm run clean && pnpm run compile",
-    "clean": "rimraf -rf ./dist",
-    "compile": "rollup -c",
-    "prepublishOnly": "pnpm run build"
-  },
-  "peerDependencies": {
-    "@kankan/utils": "workspace:^1.0.0",
-    "vue": "^3.2.0"
-  },
-  "devDependencies": {
-    "@kankan/utils": "workspace:^1.0.0",
-    "@rollup/plugin-typescript": "~8.3.4",
-    "rimraf": "~3.0.2",
-    "rollup": "~2.77.3",
-    "rollup-plugin-terser": "^7.0.2",
-    "tslib": "^2.4.0",
-    "typescript": "~4.7.4"
-  },
-  "typedoc": {
-    "entryPoint": "./src/index.ts",
-    "readmeFile": "./README.md",
-    "displayName": "@kankan/components"
-  }
+    "name": "@kankan/components",
+    "version": "1.0.0",
+    "main": "index.ts",
+    "module": "index.ts",
+    "unpkg": "index.js",
+    "types": "index.d.ts",
+    "files": [
+        "dist"
+    ],
+    "author": {
+        "name": "gemercheung",
+        "email": "gemercheung@gmail.com",
+        "url": "http://gemer.xyz/"
+    },
+    "scripts": {
+        "build": "pnpm run clean && pnpm run compile",
+        "clean": "rimraf -rf ./dist",
+        "compile": "rollup -c",
+        "prepublishOnly": "pnpm run build"
+    },
+    "peerDependencies": {
+        "@kankan/utils": "workspace:^1.0.0",
+        "vue": "^3.2.0"
+    },
+    "devDependencies": {
+        "@kankan/utils": "workspace:^1.0.0",
+        "@rollup/plugin-typescript": "~8.3.4",
+        "rimraf": "~3.0.2",
+        "rollup": "~2.77.3",
+        "rollup-plugin-terser": "^7.0.2",
+        "tslib": "^2.4.0",
+        "typescript": "~4.7.4"
+    },
+    "typedoc": {
+        "entryPoint": "./src/index.ts",
+        "readmeFile": "./README.md",
+        "displayName": "@kankan/components"
+    }
 }

+ 2 - 0
pnpm-lock.yaml

@@ -6,6 +6,7 @@ importers:
     specifiers:
       '@changesets/cli': ^2.24.4
       '@commitlint/cli': ^17.1.2
+      '@kankan/components': workspace:*
       '@types/jsdom': ^16.2.14
       '@types/node': '*'
       '@typescript-eslint/eslint-plugin': ^5.38.1
@@ -37,6 +38,7 @@ importers:
       vitest: ^0.23.4
       vue: ^3.2.0
     dependencies:
+      '@kankan/components': link:packages/components
       '@vueuse/core': 9.3.0_vue@3.2.39
       lodash: 4.17.21
       lodash-es: 4.17.21