Browse Source

wip: Form改造中

kailong321200875 2 năm trước cách đây
mục cha
commit
1d0f4b4c39

+ 7 - 5
src/components/Form/src/helper.ts

@@ -1,5 +1,5 @@
 import { useI18n } from '@/hooks/web/useI18n'
-import type { Slots } from 'vue'
+import { unref, type Slots } from 'vue'
 import { getSlot } from '@/utils/tsxHelper'
 import { PlaceholderMoel } from './types'
 import { FormSchema } from '@/types/form'
@@ -74,12 +74,14 @@ export const setGridProp = (col: ColProps = {}): ColProps => {
  */
 export const setComponentProps = (item: FormSchema): Recordable => {
   // const notNeedClearable = ['ColorPicker']
-  const componentProps = {
+  const componentProps: Recordable = {
     clearable: true,
     ...item.componentProps
   }
   // 需要删除额外的属性
-  delete componentProps?.slots
+  if (componentProps.slots) {
+    delete componentProps.slots
+  }
   return componentProps
 }
 
@@ -93,8 +95,8 @@ export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = {
   for (const key in slotsProps) {
     if (slotsProps[key]) {
       if (isFunction(slotsProps[key])) {
-        slotObj[key] = () => {
-          return slotsProps[key]?.(formModel)
+        slotObj[key] = (item: any) => {
+          return slotsProps[key]?.(unref(item?.item) || undefined, formModel)
         }
       } else {
         slotObj[key] = () => {

+ 2 - 0
src/locales/en.ts

@@ -214,7 +214,9 @@ export default {
     default: 'Default',
     icon: 'Icon',
     mixed: 'Mixed',
+    password: 'Password',
     textarea: 'Textarea',
+    remoteSearch: 'Remote search',
     slot: 'Slot',
     position: 'Position',
     autocomplete: 'Autocomplete',

+ 2 - 0
src/locales/zh-CN.ts

@@ -214,7 +214,9 @@ export default {
     default: '默认',
     icon: '图标',
     mixed: '复合型',
+    password: '密码框',
     textarea: '多行文本',
+    remoteSearch: '远程搜索',
     slot: '插槽',
     position: '位置',
     autocomplete: '自动补全',

+ 72 - 15
src/types/components.d.ts

@@ -1,5 +1,5 @@
 import { CSSProperties } from 'vue'
-import { InputProps } from 'element-plus'
+import { InputProps, AutocompleteProps, InputNumberProps } from 'element-plus'
 
 export enum ComponentNameEnum {
   RADIO = 'Radio',
@@ -25,6 +25,16 @@ export enum ComponentNameEnum {
   EDITOR = 'Editor'
 }
 
+type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
+  ? K extends string
+    ? K extends `${infer A}_${infer B}`
+      ? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
+      : Capitalize<Lowercase<K>>
+    : never
+  : never
+
+export type ComponentName = CamelCaseComponentName
+
 export interface InputComponentProps {
   value?: string | number
   maxlength?: number | string
@@ -37,8 +47,8 @@ export interface InputComponentProps {
   showPassword?: boolean
   disabled?: boolean
   size?: InputProps['size']
-  prefixIcon?: string | JSX.Element | (<T>(data: T | any) => string | JSX.Element)
-  suffixIcon?: string | JSX.Element | (<T>(data: T | any) => string | JSX.Element)
+  prefixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
+  suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
   type?: InputProps['type']
   rows?: number
   autosize?: boolean | { Pows?: numer; maxRows?: number }
@@ -63,22 +73,69 @@ export interface InputComponentProps {
     input?: (value: string | number) => void
   }
   slots?: {
-    prefix?: JSX.Element | (<T>(data: T | any) => JSX.Element)
-    suffix?: JSX.Element | (<T>(data: T | any) => JSX.Element)
-    prepend?: JSX.Element | (<T>(data: T | any) => JSX.Element)
-    append?: JSX.Element | (<T>(data: T | any) => JSX.Element)
+    prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    suffix?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    append?: JSX.Element | ((item: any, data: any) => JSX.Element)
   }
 }
 
-type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
-  ? K extends string
-    ? K extends `${infer A}_${infer B}`
-      ? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
-      : Capitalize<Lowercase<K>>
-    : never
-  : never
+export interface AutocompleteComponentProps {
+  value?: string
+  placeholder?: string
+  clearable?: boolean
+  disabled?: boolean
+  valueKey?: string
+  debounce?: number
+  placement?: AutocompleteProps['placement']
+  fetchSuggestions?: (queryString: string, callback: (data: string[]) => void) => void
+  triggerOnFocus?: boolean
+  selectWhenUnmatched?: boolean
+  name?: string
+  label?: string
+  hideLoading?: boolean
+  popperClass?: string
+  popperAppendToBody?: boolean
+  teleported?: boolean
+  highlightFirstItem?: boolean
+  fitInputWidth?: boolean
+  on?: {
+    select?: (item: any) => void
+    change?: (value: string | number) => void
+  }
+  slots?: {
+    default?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    suffix?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
+    append?: JSX.Element | ((item: any, data: any) => JSX.Element)
+  }
+}
 
-export type ComponentName = CamelCaseComponentName
+export interface InputNumberComponentProps {
+  value?: number
+  min?: number
+  max?: number
+  step?: number
+  stepStrictly?: boolean
+  precision?: number
+  size?: InputNumberProps['size']
+  readonly?: boolean
+  disabled?: boolean
+  controls?: boolean
+  controlsPosition?: InputNumberProps['controlsPosition']
+  name?: string
+  label?: string
+  placeholder?: string
+  id?: string
+  valueOnClear?: number | null | 'min' | 'max'
+  validateEvent?: boolean
+  on?: {
+    change?: (currentValue: number | undefined, oldValue: number | undefined) => void
+    blur?: (event: FocusEvent) => void
+    focus?: (event: FocusEvent) => void
+  }
+}
 
 export interface ColProps {
   span?: number

+ 40 - 15
src/types/form.d.ts

@@ -3,8 +3,9 @@ import {
   ColProps,
   ComponentProps,
   ComponentName,
-  ComponentNameEnum,
-  InputComponentProps
+  InputComponentProps,
+  AutocompleteComponentProps,
+  InputNumberComponentProps
 } from '@/types/components'
 import { FormValueType, FormValueType } from '@/types/form'
 import type { AxiosPromise } from 'axios'
@@ -28,29 +29,53 @@ export type FormItemProps = {
 }
 
 export interface FormSchema {
-  // 唯一值
+  /**
+   * 唯一标识
+   */
   field: string
-  // 标题
+
+  /**
+   * 标题
+   */
   label?: string
-  // 提示
+
+  /**
+   * 提示信息
+   */
   labelMessage?: string
-  // col组件属性
+
+  /**
+   * col组件属性
+   */
   colProps?: ColProps
-  // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
-  // componentProps?: { slots?: Recordable } & ComponentProps
 
   /**
-   * 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
+   * 表单组件属性,具体可以查看element-plus文档
+   */
+  componentProps?: InputComponentProps | AutocompleteComponentProps | InputNumberComponentProps
+
+  /**
+   * formItem组件属性,具体可以查看element-plus文档
    */
-  componentProps?: InputComponentProps
-  // formItem组件属性
   formItemProps?: FormItemProps
-  // 渲染的组件
+
+  /**
+   * 渲染的组件名称
+   */
   component?: ComponentName
-  // 初始值
+
+  /**
+   * 初始值
+   */
   value?: FormValueType
-  // 是否隐藏
+
+  /**
+   * 是否隐藏
+   */
   hidden?: boolean
-  // 远程加载下拉项
+
+  /**
+   * @returns 远程加载下拉项
+   */
   api?: <T = any>() => AxiosPromise<T>
 }

+ 95 - 50
src/views/Components/Form/DefaultForm.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script setup lang="tsx">
 import { Form } from '@/components/Form'
 import { reactive, ref, onMounted, computed, unref } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
@@ -25,6 +25,17 @@ const querySearch = (queryString: string, cb: Fn) => {
   // call callback function to return suggestions
   cb(results)
 }
+let timeout: NodeJS.Timeout
+const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
+  const results = queryString
+    ? restaurants.value.filter(createFilter(queryString))
+    : restaurants.value
+
+  clearTimeout(timeout)
+  timeout = setTimeout(() => {
+    cb(results)
+  }, 3000 * Math.random())
+}
 const createFilter = (queryString: string) => {
   return (restaurant: Recordable) => {
     return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
@@ -359,7 +370,11 @@ const schema = reactive<FormSchema[]>([
   {
     field: 'field2',
     label: t('formDemo.default'),
-    component: 'Input'
+    component: 'Input',
+    componentProps: {
+      formatter: (value) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','),
+      parser: (value) => value.replace(/\$\s?|(,*)/g, '')
+    }
   },
   {
     field: 'field3',
@@ -378,7 +393,7 @@ const schema = reactive<FormSchema[]>([
     component: 'Input',
     componentProps: {
       slots: {
-        suffix: (data: any) => {
+        suffix: (_, data: any) => {
           return unref(toggle) && data.field4
             ? useIcon({ icon: 'ep:calendar' })
             : useIcon({ icon: 'ep:share' })
@@ -394,12 +409,20 @@ const schema = reactive<FormSchema[]>([
     componentProps: {
       slots: {
         prepend: useIcon({ icon: 'ep:calendar' }),
-        append: (data: any) => {
+        append: (_, data: any) => {
           return data.field5 ? useIcon({ icon: 'ep:calendar' }) : useIcon({ icon: 'ep:share' })
         }
       }
     }
   },
+  {
+    field: 'input-field7',
+    label: t('formDemo.password'),
+    component: 'Input',
+    componentProps: {
+      showPassword: true
+    }
+  },
   {
     field: 'field6',
     label: t('formDemo.textarea'),
@@ -408,54 +431,76 @@ const schema = reactive<FormSchema[]>([
       type: 'textarea',
       rows: 2
     }
+  },
+  {
+    field: 'field7',
+    label: t('formDemo.autocomplete'),
+    component: 'Divider'
+  },
+  {
+    field: 'field8',
+    label: t('formDemo.default'),
+    component: 'Autocomplete',
+    componentProps: {
+      fetchSuggestions: querySearch,
+      on: {
+        select: handleSelect
+      }
+    }
+  },
+  {
+    field: 'field9',
+    label: t('formDemo.slot'),
+    component: 'Autocomplete',
+    componentProps: {
+      fetchSuggestions: querySearch,
+      on: {
+        select: handleSelect
+      },
+      slots: {
+        default: (item: any) => {
+          return (
+            <>
+              <div class="value">{item.value}</div>
+              <span class="link">{item.link}</span>
+            </>
+          )
+        }
+      }
+    }
+  },
+  {
+    field: 'autocomplete-field10',
+    label: t('formDemo.remoteSearch'),
+    component: 'Autocomplete',
+    componentProps: {
+      fetchSuggestions: querySearchAsync,
+      on: {
+        select: handleSelect
+      }
+    }
+  },
+  {
+    field: 'field10',
+    component: 'Divider',
+    label: t('formDemo.inputNumber')
+  },
+  {
+    field: 'field11',
+    label: t('formDemo.default'),
+    component: 'InputNumber',
+    value: 0
+  },
+  {
+    field: 'field12',
+    label: t('formDemo.position'),
+    component: 'InputNumber',
+    componentProps: {
+      controlsPosition: 'right'
+    },
+    value: 10
   }
   // {
-  //   field: 'field7',
-  //   label: t('formDemo.autocomplete'),
-  //   component: 'Divider'
-  // },
-  // {
-  //   field: 'field8',
-  //   label: t('formDemo.default'),
-  //   component: 'Autocomplete',
-  //   componentProps: {
-  //     fetchSuggestions: querySearch,
-  //     onSelect: handleSelect
-  //   }
-  // },
-  // {
-  //   field: 'field9',
-  //   label: t('formDemo.slot'),
-  //   component: 'Autocomplete',
-  //   componentProps: {
-  //     fetchSuggestions: querySearch,
-  //     onSelect: handleSelect,
-  //     slots: {
-  //       default: true
-  //     }
-  //   }
-  // },
-  // {
-  //   field: 'field10',
-  //   component: 'Divider',
-  //   label: t('formDemo.inputNumber')
-  // },
-  // {
-  //   field: 'field11',
-  //   label: t('formDemo.default'),
-  //   component: 'InputNumber',
-  //   value: 0
-  // },
-  // {
-  //   field: 'field12',
-  //   label: t('formDemo.position'),
-  //   component: 'InputNumber',
-  //   componentProps: {
-  //     controlsPosition: 'right'
-  //   },
-  //   value: 0
-  // },
-  // {
   //   field: 'field13',
   //   label: t('formDemo.select'),
   //   component: 'Divider'