陈凯龙 3 жил өмнө
parent
commit
d7d0ada558

+ 2 - 1
.vscode/settings.json

@@ -14,5 +14,6 @@
   "i18n-ally.enabledParsers": ["ts"],
   "i18n-ally.sourceLanguage": "en",
   "i18n-ally.displayLanguage": "zh-CN",
-  "i18n-ally.enabledFrameworks": ["vue", "react"]
+  "i18n-ally.enabledFrameworks": ["vue", "react"],
+  "god.tsconfig": "./tsconfig.json"
 }

+ 1 - 0
package.json

@@ -44,6 +44,7 @@
     "@typescript-eslint/parser": "^5.6.0",
     "@vitejs/plugin-vue": "^1.9.3",
     "@vitejs/plugin-vue-jsx": "^1.3.0",
+    "async-validator": "^4.0.7",
     "autoprefixer": "^10.4.0",
     "commitizen": "^4.2.4",
     "eslint": "^8.4.1",

+ 8 - 7
pnpm-lock.yaml

@@ -11,6 +11,7 @@ specifiers:
   '@vitejs/plugin-vue': ^1.9.3
   '@vitejs/plugin-vue-jsx': ^1.3.0
   '@vueuse/core': ^7.1.2
+  async-validator: ^4.0.7
   autoprefixer: ^10.4.0
   commitizen: ^4.2.4
   element-plus: 1.2.0-beta.6
@@ -69,6 +70,7 @@ devDependencies:
   '@typescript-eslint/parser': registry.npmmirror.com/@typescript-eslint/parser/5.6.0_eslint@8.4.1+typescript@4.5.2
   '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/1.10.1_vite@2.6.14
   '@vitejs/plugin-vue-jsx': registry.npmmirror.com/@vitejs/plugin-vue-jsx/1.3.0
+  async-validator: registry.npmmirror.com/async-validator/4.0.7
   autoprefixer: registry.npmmirror.com/autoprefixer/10.4.0_postcss@8.4.4
   commitizen: registry.npmmirror.com/commitizen/4.2.4
   eslint: registry.npmmirror.com/eslint/8.4.1
@@ -4320,7 +4322,7 @@ packages:
       {
         integrity: sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz
+        tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz
       }
     name: semver
     version: 5.7.1
@@ -4332,7 +4334,7 @@ packages:
       {
         integrity: sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz
+        tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz
       }
     name: semver
     version: 6.3.0
@@ -6959,7 +6961,7 @@ packages:
       {
         integrity: sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.npmmirror.com/acorn/download/acorn-7.4.1.tgz
+        tarball: https://registry.npmmirror.com/acorn/download/acorn-7.4.1.tgz?cache=0&sync_timestamp=1637226362293&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Facorn%2Fdownload%2Facorn-7.4.1.tgz
       }
     name: acorn
     version: 7.4.1
@@ -6972,7 +6974,7 @@ packages:
       {
         integrity: sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.npmmirror.com/acorn/download/acorn-8.6.0.tgz
+        tarball: https://registry.npmmirror.com/acorn/download/acorn-8.6.0.tgz?cache=0&sync_timestamp=1637226362293&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Facorn%2Fdownload%2Facorn-8.6.0.tgz
       }
     name: acorn
     version: 8.6.0
@@ -7021,7 +7023,6 @@ packages:
       }
     name: async-validator
     version: 4.0.7
-    dev: false
 
   registry.npmmirror.com/autoprefixer/10.4.0_postcss@8.4.4:
     resolution:
@@ -7911,7 +7912,7 @@ packages:
       {
         integrity: sha1-MOvR73wv3/AcOk8VEESvJfqwUj4=,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz
+        tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz?cache=0&sync_timestamp=1636378498011&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-1.3.0.tgz
       }
     name: eslint-visitor-keys
     version: 1.3.0
@@ -7923,7 +7924,7 @@ packages:
       {
         integrity: sha1-9lMoJZMFknOSyTjtROsKXJsr0wM=,
         registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz
+        tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz?cache=0&sync_timestamp=1636378498011&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-2.1.0.tgz
       }
     name: eslint-visitor-keys
     version: 2.1.0

+ 19 - 11
src/App.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, onMounted, unref } from 'vue'
+import { ref, onMounted, unref, reactive } from 'vue'
 import { ElConfigProvider } from 'element-plus'
 import zhCn from 'element-plus/lib/locale/lang/zh-cn'
 // import en from 'element-plus/lib/locale/lang/en'
@@ -9,21 +9,29 @@ const formRef = ref<ComponentRef<typeof VFrom> & VFormExpose>()
 onMounted(() => {
   const form = unref(formRef.value)
   console.log(form?.$el)
-
-  const schema: VFormSchema = [
-    {
-      field: '1',
-      colProps: {}
-    }
-  ]
-  console.log(schema)
 })
+const schema = reactive<VFormSchema[]>([
+  {
+    field: 'field1',
+    label: '字段1',
+    component: 'Input'
+  }
+])
+// setTimeout(() => {
+//   schema.push({
+//     field: '2'
+//   })
+// }, 3000)
 </script>
 
 <template>
   <ElConfigProvider :locale="zhCn">
-    <VFrom ref="formRef" />
-    <Component :is="VFrom" />
+    <!-- <VFrom ref="formRef" is-custom>
+      <template #default> hahahah </template>
+    </VFrom> -->
+    <VFrom :schema="schema" />
+    <!-- <VFrom :is-col="false" :schema="schema" /> -->
+    <!-- <Component :is="VFrom" /> -->
     <!-- <RouterView class="app" /> -->
   </ElConfigProvider>
 </template>

+ 72 - 10
src/components/Form/src/VForm.vue

@@ -1,8 +1,10 @@
 <script lang="tsx">
-import { PropType, defineComponent, onMounted, ref, unref } from 'vue'
-import { ElForm } from 'element-plus'
-// import { COMPONENT_MAP } from './componentMap'
+import { PropType, defineComponent, ref, computed, unref, watch } from 'vue'
+import { ElForm, ElFormItem, ElRow, ElCol } from 'element-plus'
+import { COMPONENT_MAP } from './componentMap'
 import { propTypes } from '@/utils/propTypes'
+import { getSlot } from '@/utils/tsxHelper'
+import { setTextPlaceholder } from './helper'
 
 export default defineComponent({
   name: 'VForm',
@@ -25,17 +27,77 @@ export default defineComponent({
     // 是否自定义内容
     isCustom: propTypes.bool.def(false)
   },
-  setup() {
+  setup(props, { slots }) {
     const formRef = ref<ComponentRef<typeof ElForm>>()
-    onMounted(() => {
-      console.log(unref(formRef)?.clearValidate)
-    })
+    const getProps = computed(() => props)
+    const { schema, isCol, isCustom, autoSetPlaceholder } = unref(getProps)
+    const test = ref('')
 
-    // function renderWrap() {}
+    watch(
+      () => test.value,
+      (val) => {
+        console.log(val)
+      }
+    )
 
-    // function renderFormItem() {}
+    // 渲染包裹标签,是否使用栅格布局
+    function renderWrap() {
+      const content = isCol ? (
+        <ElRow gutter={20}>
+          <ElCol>{renderFormItem()}</ElCol>
+        </ElRow>
+      ) : (
+        renderFormItem()
+      )
+      console.log(content)
+      return content
+    }
 
-    return () => <ElForm ref={formRef}></ElForm>
+    // 渲染formItem
+    function renderFormItem() {
+      // hidden属性表示隐藏
+      return schema
+        .filter((v) => !v.hidden)
+        .map((item) => {
+          return (
+            <ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label}>
+              {() => {
+                const Com = COMPONENT_MAP[item.component as string] as ReturnType<
+                  typeof defineComponent
+                >
+                return (
+                  <Com
+                    v-model={test.value}
+                    {...(autoSetPlaceholder && setTextPlaceholder(item))}
+                  ></Com>
+                )
+              }}
+            </ElFormItem>
+          )
+        })
+      // return <div>{schema[0]?.field}</div>
+    }
+
+    // 过滤传入Form组件的属性
+    function getFormBindValue() {
+      // 避免在标签上出现多余的属性
+      const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom']
+      const props = { ...unref(getProps) }
+      for (const key in props) {
+        if (delKeys.indexOf(key) !== -1) {
+          delete props[key]
+        }
+      }
+      return props
+    }
+
+    return () => (
+      <ElForm ref={formRef} {...getFormBindValue()}>
+        {{
+          default: () => (isCustom ? getSlot(slots, 'default') : renderWrap())
+        }}
+      </ElForm>
+    )
   }
 })
 </script>

+ 32 - 1
src/components/Form/src/helper.ts

@@ -1,8 +1,39 @@
+// import { useI18n } from '@/hooks/web/useI18n'
+// const { t } = useI18n()
 /**
  *
  * @param schema 对应组件数据
  * @description 用于自动设置placeholder
  */
-export function setTextPlaceholder(schema: VFormSchema) {
+export function setTextPlaceholder(schema: VFormSchema): {
+  placeholder?: string
+  startPlaceholder?: string
+  endPlaceholder?: string
+} {
   console.log(schema)
+  // const textMap = ['Input', 'Autocomplete', 'InputNumber']
+  // const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
+  // if (textMap.includes(schema?.component as string)) {
+  //   return {
+  //     placeholder: t('common.inputText')
+  //   }
+  // }
+  // if (selectMap.includes(schema?.component as string)) {
+  //   // 一些范围选择器
+  //   const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']
+  //   if (
+  //     twoTextMap.includes(schema?.componentProps?.type || schema?.componentProps?.isRange) as string
+  //   ) {
+  //     return {
+  //       startPlaceholder: t('common.startTimeText'),
+  //       endPlaceholder: t('common.endTimeText'),
+  //       rangeSeparator: '-'
+  //     }
+  //   } else {
+  //     return {
+  //       placeholder: t('common.selectText')
+  //     }
+  //   }
+  // }
+  return {}
 }

+ 50 - 0
src/hooks/web/useI18n.ts

@@ -0,0 +1,50 @@
+// // import { i18n } from '@/plugins/i18n'
+
+// type I18nGlobalTranslation = {
+//   (key: string): string
+//   (key: string, locale: string): string
+//   (key: string, locale: string, list: unknown[]): string
+//   (key: string, locale: string, named: Record<string, unknown>): string
+//   (key: string, list: unknown[]): string
+//   (key: string, named: Record<string, unknown>): string
+// }
+
+// type I18nTranslationRestParameters = [string, any]
+
+// function getKey(namespace: string | undefined, key: string) {
+//   if (!namespace) {
+//     return key
+//   }
+//   if (key.startsWith(namespace)) {
+//     return key
+//   }
+//   return `${namespace}.${key}`
+// }
+
+// export function useI18n(namespace?: string): {
+//   t: I18nGlobalTranslation
+// } {
+//   const normalFn = {
+//     t: (key: string) => {
+//       return getKey(namespace, key)
+//     }
+//   }
+
+//   if (!i18n) {
+//     return normalFn
+//   }
+
+//   const { t, ...methods } = i18n.global
+
+//   const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
+//     if (!key) return ''
+//     if (!key.includes('.') && !namespace) return key
+//     return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters))
+//   }
+//   return {
+//     ...methods,
+//     t: tFn
+//   }
+// }
+
+// export const t = (key: string) => key

+ 8 - 10
src/main.ts

@@ -1,18 +1,16 @@
+// 引入windi css
+import '@/plugins/windicss'
 import { createApp } from 'vue'
-
 import App from './App.vue'
-
 const app = createApp(App)
-
-// 引入windi css
-import '@/plugins/windicss'
-
 // 引入多语言
 import { setupI18n } from '@/plugins/i18n'
-setupI18n(app)
-
 // 引入状态管理
 import { setupStore } from '@/store'
-setupStore(app)
+;(async () => {
+  await setupI18n(app)
+
+  setupStore(app)
 
-app.mount('#app')
+  app.mount('#app')
+})()

+ 3 - 1
src/plugins/i18n/index.ts

@@ -1,13 +1,15 @@
 import { createI18n } from 'vue-i18n'
 import type { App } from 'vue'
 
+// export let i18n: ReturnType<typeof createI18n>
+
 const messages = Object.fromEntries(
   Object.entries(import.meta.globEager('../../locales/*.ts')).map(([key, value]) => {
     return [key.slice(14, -3), value.default]
   })
 )
 
-export function setupI18n(app: App<Element>): void {
+export function setupI18n(app: App): void {
   const i18n = createI18n({
     legacy: false,
     locale: 'zh-CN',

+ 9 - 9
src/types/componentType.d.ts

@@ -1,4 +1,5 @@
 import type { Component, RendererNode, VNode, CSSProperties } from 'vue'
+import type { RuleItem } from 'async-validator'
 
 declare global {
   // BfForm types start
@@ -33,18 +34,18 @@ declare global {
 
   declare type FormValueTypes = string | number | string[] | number[] | boolean | undefined
 
-  declare type FormRules = {
-    required?: boolean
-    message?: string
-    type?: string
-    trigger?: 'blur' | 'change' | ['change', 'blur']
-    validator?: (rule: any, value: FormValueTypes, callback: Fn) => void | boolean
+  declare interface FormItemRule extends RuleItem {
+    trigger?: string
   }
 
+  declare type FormRulesMap<T extends string = string> = Partial<
+    Record<T, FormItemRule | FormItemRule[]>
+  >
+
   declare type FormItemProps = {
     labelWidth?: string | number
     required?: boolean
-    rules?: FormRules | FormRules[]
+    rules?: FormRulesMap
     error?: string
     showMessage?: boolean
     inlineMessage?: boolean
@@ -471,7 +472,7 @@ declare global {
     }
   }
 
-  declare type FormSchema = {
+  declare type VFormSchema = {
     field: string
     label?: string
     colProps?: ColProps
@@ -502,5 +503,4 @@ declare global {
   }
 
   // VForm types end
-  declare type VFormSchema = FormSchema[]
 }

+ 16 - 0
src/utils/tsxHelper.ts

@@ -0,0 +1,16 @@
+import { Slots } from 'vue'
+import { isFunction } from '@/utils/is'
+
+export function getSlot(slots: Slots, slot = 'default', data?: Recordable) {
+  // Reflect.has 判断一个对象是否存在某个属性
+  if (!slots || !Reflect.has(slots, slot)) {
+    return null
+  }
+  if (!isFunction(slots[slot])) {
+    console.error(`${slot} is not a function!`)
+    return null
+  }
+  const slotFn = slots[slot]
+  if (!slotFn) return null
+  return slotFn(data)
+}