(false)
38 | watch(() => props.config.options, async() => {
39 | // 获取options下拉选项
40 | if (Array.isArray(props.config.options)) { // 传入对象数组
41 | options.value = props.config.options
42 | } else if (Object.prototype.toString.call(props.config.options) === '[object Object]') { // 字典/接口获取
43 | if (props.config?.options?.type === 'api') {
44 | optionsLoading.value = true
45 | options.value = await props.config.options.getData()
46 | optionsLoading.value = false
47 | } else if (props.config?.options?.type === 'dic') {
48 | options.value = utils.getDicByKey(props.config.options.key)
49 | }
50 | }
51 | }, { immediate: true, deep: true })
52 |
53 | /**
54 | * @description: 获取选中得item
55 | * @param {*} value 当前选择中得value ps:
56 | * 单选:'changsha' || ['hunan','changsha'](开启emitPath时)
57 | * 多选:['changsha','yiyang'] || [['hunan','changsha'], ['hunan', 'yiyang']](开启emitPath时)
58 | * @return any 选中得item
59 | */
60 | const getOption = (value: any) => {
61 | let curItem: any
62 | let valueString: any
63 | const valueIsArray = Array.isArray(value)
64 | utils.treeForeach(options.value, (node) => {
65 | if (props.config.multiple) {
66 | try {
67 | valueString = value && Array.isArray(value) ? value.map((item: any) => Array.isArray(item) ? item[item.length - 1] : item) : []
68 | if (valueString.includes(node[props?.config?.valueKey ?? 'value'])) {
69 | if (!Array.isArray(curItem)) {
70 | curItem = []
71 | }
72 | curItem.push(node)
73 | }
74 | } catch (error) {
75 | curItem = []
76 | console.log(error)
77 | }
78 | } else {
79 | try {
80 | if (valueIsArray) { // emitPath 模式
81 | valueString = value[value.length - 1]
82 | }
83 | if (node[props?.config?.valueKey ?? 'value'] === valueString) {
84 | curItem = node
85 | }
86 | } catch (error) {
87 | curItem = {}
88 | console.log(error)
89 | }
90 |
91 | }
92 | }, props.config.childrenKey ?? 'children')
93 | return curItem
94 | }
95 |
96 | const handleChange = (value: any) => {
97 | emit('update:modelValue', value)
98 | emit('update:value', value)
99 | emit('change', {
100 | prop: props.config?.prop ?? '',
101 | value,
102 | options: options.value,
103 | curItem: getOption(value),
104 | })
105 | }
106 |
107 | const getText = () => {
108 | if (Array.isArray(getOption(props.modelValue))) {
109 | return getOption(props.modelValue)?.map((item: any) => item[props.config.labelKey ?? 'label']).join('、')
110 | }
111 | return getOption(props.modelValue)?.[props.config.labelKey ?? 'label']
112 | }
113 |
114 | return () => {
115 | const dynamicComponent = new CustomDynamicComponent()
116 | const { dynamicCascader } = dynamicComponent
117 | return
118 | {textModeFilter(props.textMode, getText() ?? '', props.config.textModeRender && props.config.textModeRender({
119 | value: props.modelValue,
120 | options: options.value,
121 | curItem: getOption(props.modelValue),
122 | }), {
154 | return props.config.format && props.config.format(node, data)
155 | },
156 | } : {}}
157 | /** ele 特有属性-end */
158 |
159 | {...props.config.nativeProps}
160 | props={merge({}, {
161 | emitPath: (props.config.emitPath === true), // ===true时才会返回true
162 | label: props.config.labelKey ?? 'label',
163 | value: props.config.valueKey ?? 'value',
164 | children: props.config.childrenKey ?? 'children',
165 | multiple: props.config.multiple === true,
166 | }, props.config?.nativeProps?.props)}
167 |
168 | onChange={handleChange}
169 | >
170 | ,
171 | )}
172 |
173 | }
174 | },
175 | })
176 |
177 | export { cascaderProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsCheckbox.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-20 09:56:21
4 | * @LastEditTime: 2023-09-15 11:08:08
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, watch, ref, PropType } from 'vue'
9 | import * as utils from '@/utils/common'
10 | import type { checkboxProps } from '../interface/index'
11 | import styles from '@/components/BsForm/style.module.scss'
12 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
13 | import { textModeFilter, getOptionsLabel } from '../toolFn'
14 |
15 | export default defineComponent({
16 | name: 'BsCheckbox',
17 | props: {
18 | modelValue: {
19 | type: Array,
20 | default() {
21 | return []
22 | },
23 | },
24 | config: {
25 | type: Object as PropType,
26 | default() {
27 | return {}
28 | },
29 | },
30 | textMode: {
31 | type: Boolean,
32 | default: false,
33 | },
34 | },
35 | emits: ['update:modelValue', 'update:value', 'change'],
36 | setup(props: any, { emit }) {
37 | const options = ref([])
38 | const optionsLoading = ref(false)
39 | watch(() => props.config.options, async() => {
40 | // 获取options下拉选项
41 | if (Array.isArray(props.config.options)) { // 传入对象数组
42 | options.value = props.config.options
43 | } else if (Object.prototype.toString.call(props.config.options) === '[object Object]') { // 字典/接口获取
44 | if (props.config.options.type === 'api') {
45 | optionsLoading.value = true
46 | options.value = await props.config.options.getData()
47 | optionsLoading.value = false
48 | } else if (props.config.options.type === 'dic') {
49 | options.value = utils.getDicByKey(props.config.options.key)
50 | }
51 | }
52 | // 兼容改变
53 | options.value = options?.value?.map((v: any) => {
54 | return {
55 | ...v,
56 | label: v[props.config.labelKey || 'label'],
57 | value: v[props.config.valueKey || 'value'],
58 | }
59 | }) ?? []
60 |
61 | }, { immediate: true, deep: true })
62 |
63 | /**
64 | * @description: 获取选中得item
65 | * @param {*} value 当前选择中得value
66 | * @return any 选中得item
67 | */
68 | const getOption = (value: any) => {
69 | try {
70 | const optionArr = options.value.filter((option: any) => value?.includes(option.value)) ?? []
71 | return optionArr
72 | } catch (error) {
73 | console.log(error)
74 | return []
75 | }
76 | }
77 |
78 | function updateValue(value: any): void {
79 | emit('update:modelValue', value)
80 | emit('update:value', value)
81 | emit('change', {
82 | prop: props.config?.prop ?? '',
83 | value,
84 | options,
85 | curItem: getOption(value),
86 | })
87 | }
88 |
89 | return () => {
90 | const dynamicComponent = new CustomDynamicComponent()
91 | const { dynamicCheckBoxGroup, dynamicCheckBox, dynamicCheckBoxButton } = dynamicComponent
92 | // dynamicCheckBoxButton 只有element-plus有这个组件
93 | const componentInstance = props.config.showType === 'button' && CustomDynamicComponent.language === CustomDynamicComponent.eleLanguage ? dynamicCheckBoxButton : dynamicCheckBox
94 | return
95 | {textModeFilter(props.textMode, getOptionsLabel(getOption(props.modelValue)) ?? '', props.config.textModeRender && props.config.textModeRender({
96 | value: props.modelValue,
97 | options: options.value,
98 | curItem: getOption(props.modelValue),
99 | }),
100 |
115 | {
116 | /** 只有element-plus使用以下逻辑 */
117 | CustomDynamicComponent.language === CustomDynamicComponent.eleLanguage && options.value && Array.isArray(options.value) && options.value.map((item: any, index: number) => {
118 | return {
125 | return props.config.format(item)
126 | },
127 | } : {}}
128 | >
129 | { item.label }
130 |
131 | })
132 | }
133 | ,
134 | )}
135 |
136 | }
137 | },
138 | })
139 |
140 | export { checkboxProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsCollapse.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: chenql
3 | * @Date: 2023-04-26 10:19:48
4 | * @LastEditors: 陈宇环
5 | * @LastEditTime: 2023-09-07 10:20:46
6 | * @Descripttion: 表单手风琴
7 | */
8 | import { defineComponent, PropType, ref } from 'vue'
9 | import type { collapseProps } from '../interface'
10 | import styles from '@/components/BsForm/style.module.scss'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 |
13 | export default defineComponent({
14 | name: 'BsCollapse',
15 | props: {
16 | modelValue: {
17 | type: [Number, String, Array, Object, Boolean],
18 | default: undefined,
19 | },
20 | config: {
21 | type: Object as PropType,
22 | default() {
23 | return {}
24 | },
25 | },
26 | },
27 | setup(props: any) {
28 | const { dynamicCollapse, dynamicCollapseItem } = new CustomDynamicComponent()
29 | const activeKey = ref(props.modelValue ?? 0)
30 | return () => {
31 | return (
32 |
33 |
40 | {props.config.dataConfig?.map((item: any, index: number) =>
41 |
47 | {
48 | Array.isArray(item.desc) ?
49 | (item.desc?.map((str: any, index1: number) =>
50 |
51 | {str}
52 |
)) :
53 |
54 | {item.desc}
55 |
56 | }
57 | ,
58 | )}
59 |
60 |
61 | )
62 | }
63 | },
64 | })
65 | export { collapseProps }
66 |
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsDate.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-20 11:33:03
4 | * @LastEditTime: 2023-09-01 11:20:03
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, watch, ref, PropType } from 'vue'
9 | import type { dateProps } from '../interface/index'
10 | import styles from '@/components/BsForm/style.module.scss'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import dayjs, { Dayjs } from 'dayjs'
13 | import { textModeFilter } from '../toolFn'
14 |
15 | export default defineComponent({
16 | name: 'BsDate',
17 | props: {
18 | modelValue: {
19 | type: [String, Array],
20 | default: '',
21 | },
22 | propSecond: {
23 | type: [String],
24 | default: '',
25 | },
26 | propThird: {
27 | type: [String],
28 | default: '',
29 | },
30 | config: {
31 | type: Object as PropType,
32 | default() {
33 | return {}
34 | },
35 | },
36 | textMode: {
37 | type: Boolean,
38 | default: false,
39 | },
40 | },
41 | emits: ['update:modelValue', 'update:value', 'change', 'update:propSecond', 'update:propThird'],
42 | setup(props: any, { emit }) {
43 | function getFormat(type: string, formatType: 'format' | 'valueFormat'): string {
44 | if (['date', 'daterange'].includes(type)) {
45 | return 'YYYY-MM-DD'
46 | }
47 | if (['month', 'monthrange'].includes(type)) {
48 | return 'YYYY-MM'
49 | }
50 | if (type === 'year') {
51 | return 'YYYY'
52 | }
53 | if (type === 'week') {
54 | if (formatType === 'format') {
55 | return '第 ww 周'
56 | } else {
57 | return ''
58 | }
59 | }
60 | if (['datetime', 'datetimerange'].includes(type)) {
61 | return 'YYYY-MM-DD HH:mm:ss'
62 | }
63 | return 'YYYY-MM-DD'
64 | }
65 | const cloneModelValue = ref(dayjs())
66 | watch(() => props.modelValue, () => {
67 | cloneModelValue.value = props.modelValue
68 | }, { immediate: true })
69 |
70 | // 解决datetime类型,不点击确认按钮无法触发change事件bug
71 | watch(() => cloneModelValue.value, () => {
72 | updateValue(cloneModelValue.value)
73 | })
74 |
75 | function updateValue(value: Dayjs) {
76 | emit('update:modelValue', value)
77 | emit('update:value', value)
78 | // 当是范围时间选择器时,开始和结束时间处理
79 | if (Array.isArray(value) && value?.length === 2) {
80 | props.config.propSecond && emit('update:propSecond', value[0])
81 | props.config.propThird && emit('update:propThird', value[1])
82 | }
83 | // 时间选择器被清空时,重置propSecond和prop2
84 | if (!value) {
85 | props.config.propSecond && emit('update:propSecond', null)
86 | props.config.propThird && emit('update:propThird', null)
87 | }
88 | emit('change', {
89 | prop: props.config?.prop ?? '',
90 | value,
91 | })
92 | }
93 |
94 | const getText = () => {
95 | if (!cloneModelValue.value) {
96 | return ''
97 | }
98 | if (Array.isArray(cloneModelValue.value)) {
99 | return cloneModelValue.value.map((item: any) => {
100 | if (!item) {
101 | return '-'
102 | }
103 | return dayjs(item).format(getFormat(props.config.type, 'format'))
104 | }).join('~')
105 | }
106 | return dayjs(cloneModelValue.value).format(getFormat(props.config.type, 'format'))
107 | }
108 |
109 | return () => {
110 | const dynamicComponent = new CustomDynamicComponent()
111 | const { dynamicDatePicker, dynamicRangePicker } = dynamicComponent
112 | let dateComp = dynamicDatePicker
113 | // antd 的时间范围组件特殊处理
114 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage && props.config.type?.indexOf('range') > -1) {
115 | dateComp = dynamicRangePicker
116 | }
117 | const getPicker = (type: string) => {
118 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage && props.config.type?.indexOf('range') > -1) {
119 | return props.config.type.replace('range', '')
120 | }
121 | return type
122 | }
123 | return
124 | {textModeFilter(props.textMode, getText() ?? '', props.config.textModeRender && props.config.textModeRender({
125 | value: cloneModelValue.value,
126 | }),
127 | ,
153 | )}
154 |
155 | }
156 | },
157 | })
158 |
159 | export { dateProps }
160 |
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsDateRange.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-04-28 15:34:56
4 | * @LastEditTime: 2023-09-01 11:20:21
5 | * @LastEditors: 陈宇环
6 | * @Description: 'yearRange' | 'monthRange' | 'dateRange' | 'datetimeRange'组件
7 | */
8 | import { defineComponent, watch, ref, PropType } from 'vue'
9 | import type { dateRangeProps } from '../interface/index'
10 | import dayjs from 'dayjs'
11 | import styles from '@/components/BsForm/style.module.scss'
12 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
13 | import { textModeFilter } from '../toolFn'
14 |
15 | export default defineComponent({
16 | name: 'BsDateRange',
17 | props: {
18 | modelValue: {
19 | type: [String, Number],
20 | default: '',
21 | },
22 | propEnd: {
23 | type: [String, Number],
24 | default: '',
25 | },
26 | config: {
27 | type: Object as PropType,
28 | default() {
29 | return {}
30 | },
31 | },
32 | textMode: {
33 | type: Boolean,
34 | default: false,
35 | },
36 | },
37 | emits: ['update:modelValue', 'update:value', 'update:propEnd', 'change'],
38 | setup(props: any, { emit }) {
39 |
40 | const cloneModelValue = ref('')
41 | watch(() => props.modelValue, () => {
42 | cloneModelValue.value = props.modelValue
43 | }, { immediate: true })
44 |
45 | // 解决datetime类型,不点击确认按钮无法触发change事件bug
46 | watch(() => cloneModelValue.value, () => {
47 | updateValue(cloneModelValue.value)
48 | })
49 |
50 | function updateValue(value: number | string) {
51 | emit('update:modelValue', value)
52 | emit('update:value', value)
53 | emit('change', {
54 | type: 'start',
55 | prop: props.config?.prop ?? '',
56 | value,
57 | })
58 | }
59 |
60 | const clonePropEnd = ref('')
61 | watch(() => props.propEnd, () => {
62 | clonePropEnd.value = props.propEnd
63 | }, { immediate: true })
64 | // 解决datetime类型,不点击确认按钮无法触发change事件bug
65 | watch(() => clonePropEnd.value, () => {
66 | updateEndValue(clonePropEnd.value)
67 | })
68 | function updateEndValue(value: number | string) {
69 | emit('update:propEnd', value)
70 | emit('change', {
71 | type: 'end',
72 | prop: props.config?.propEnd ?? '',
73 | value,
74 | })
75 | }
76 |
77 | function disabledDate(date: any): boolean {
78 | if (clonePropEnd.value) {
79 | // 这里format为了解决element默认08:00:00
80 | return +new Date(dayjs(date).format('YYYY-MM-DD HH:mm:ss')) > +new Date(dayjs(clonePropEnd.value).format('YYYY-MM-DD HH:mm:ss'))
81 | }
82 | return false
83 | }
84 | function disabledDateEnd(date: any): boolean {
85 | if (cloneModelValue.value) {
86 | return +new Date(dayjs(date).format('YYYY-MM-DD HH:mm:ss')) < +new Date(dayjs(cloneModelValue.value).format('YYYY-MM-DD HH:mm:ss'))
87 | }
88 | return false
89 | }
90 |
91 |
92 | function removerRange(type: string): any {
93 | return type?.replace('Range', '')
94 | }
95 |
96 | function getFormat(type: string, formatType: 'format' | 'valueFormat'): string | undefined {
97 | if (removerRange(type) === 'date') {
98 | return 'YYYY-MM-DD'
99 | }
100 | if (removerRange(type) === 'month') {
101 | return 'YYYY-MM'
102 | }
103 | if (removerRange(type) === 'year') {
104 | return 'YYYY'
105 | }
106 | if (removerRange(type) === 'week') {
107 | if (formatType === 'format') {
108 | return '第 ww 周'
109 | } else {
110 | return undefined
111 | }
112 | }
113 | if (removerRange(type) === 'datetime') {
114 | return 'YYYY-MM-DD HH:mm:ss'
115 | }
116 | return 'YYYY-MM-DD'
117 | }
118 |
119 | const getText = () => {
120 | if (!cloneModelValue.value && !clonePropEnd.value) {
121 | return ''
122 | }
123 | const startText = cloneModelValue.value ? dayjs(cloneModelValue.value).format(getFormat(props.config.type, 'format')) : '-'
124 | const endText = clonePropEnd.value ? dayjs(clonePropEnd.value).format(getFormat(props.config.type, 'format')) : '-'
125 | return `${startText}~${endText}`
126 | }
127 |
128 | return () => {
129 | const dynamicComponent = new CustomDynamicComponent()
130 | const { dynamicDatePicker } = dynamicComponent
131 | // ant-design-vue formitem只允许一个form控件
132 | const formItem = CustomDynamicComponent.language === CustomDynamicComponent.antLanguage ? :
133 | return
134 | {textModeFilter(props.textMode, getText() ?? '', props.config.textModeRender && props.config.textModeRender({
135 | value: cloneModelValue.value,
136 | endValue: clonePropEnd.value,
137 | }),
138 |
139 |
165 | ~
166 |
167 |
193 |
194 |
,
195 | )}
196 |
197 | }
198 | },
199 | })
200 |
201 | export { dateRangeProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsInput.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-20 14:37:53
4 | * @LastEditTime: 2023-09-01 11:20:29
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType } from 'vue'
9 | import styles from '@/components/BsForm/style.module.scss'
10 | import type { inputProps } from '../interface/index'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import { textModeFilter } from '../toolFn'
13 |
14 | export default defineComponent({
15 | name: 'BsInput',
16 | props: {
17 | modelValue: {
18 | type: [String, Number],
19 | default: '',
20 | },
21 | config: {
22 | type: Object as PropType,
23 | default() {
24 | return {}
25 | },
26 | },
27 | textMode: {
28 | type: Boolean,
29 | default: false,
30 | },
31 | },
32 | emits: ['update:modelValue', 'update:value', 'change'],
33 | setup(props: any, { emit }) {
34 | const { dynamicInput } = new CustomDynamicComponent()
35 | function updateValue(value: number | string | InputEvent) {
36 | let cloneValue = value
37 |
38 | // ant-Design-vue 无input事件,value获取到的是原生input事件的e
39 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage) {
40 | cloneValue = ((value as InputEvent).target as HTMLInputElement).value
41 | }
42 |
43 | emit('update:modelValue', cloneValue)
44 | emit('update:value', cloneValue)
45 | emit('change', {
46 | prop: props.config?.prop ?? '',
47 | value: cloneValue,
48 | })
49 | }
50 |
51 | return () => {
52 | return
53 | {textModeFilter(props.textMode, props.modelValue, props.config.textModeRender && props.config.textModeRender({
54 | value: props.modelValue,
55 | }),
56 | ,
75 | )}
76 |
77 | }
78 | },
79 | })
80 | export { inputProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsNumber.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-01-03 15:19:17
4 | * @LastEditTime: 2023-09-01 11:20:44
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType } from 'vue'
9 | import styles from '@/components/BsForm/style.module.scss'
10 | import type { numberProps } from '../interface/index'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import { textModeFilter } from '../toolFn'
13 |
14 | export default defineComponent({
15 | name: 'BsNumber',
16 | props: {
17 | modelValue: {
18 | type: [String, Number],
19 | default: undefined,
20 | },
21 | config: {
22 | type: Object as PropType,
23 | default() {
24 | return {}
25 | },
26 | },
27 | textMode: {
28 | type: Boolean,
29 | default: false,
30 | },
31 | },
32 | emits: ['update:modelValue', 'update:value', 'change'],
33 | setup(props: any, { emit }) {
34 | const { dynamicNumber } = new CustomDynamicComponent()
35 | function updateValue(value: number | string | InputEvent) {
36 | emit('update:modelValue', value)
37 | emit('update:value', value)
38 | emit('change', {
39 | prop: props.config?.prop ?? '',
40 | value,
41 | })
42 | }
43 | return () => {
44 | return
45 | {textModeFilter(props.textMode, props.modelValue, props.config.textModeRender && props.config.textModeRender({
46 | value: props.modelValue,
47 | }), ,
62 | )}
63 |
64 | }
65 | },
66 | })
67 | export { numberProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsNumberRange.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-01-03 15:27:55
4 | * @LastEditTime: 2023-09-01 11:20:54
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType, ref, watch } from 'vue'
9 | import styles from '@/components/BsForm/style.module.scss'
10 | import type { numberRangeProps } from '../interface/index'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import { textModeFilter } from '../toolFn'
13 |
14 | export default defineComponent({
15 | name: 'BsNumberRange',
16 | props: {
17 | modelValue: {
18 | type: [String, Number],
19 | default: undefined,
20 | },
21 | propEnd: {
22 | type: [String, Number],
23 | default: undefined,
24 | },
25 | config: {
26 | type: Object as PropType,
27 | default() {
28 | return {}
29 | },
30 | },
31 | textMode: {
32 | type: Boolean,
33 | default: false,
34 | },
35 | },
36 | emits: ['update:modelValue', 'update:value', 'update:propEnd', 'change'],
37 | setup(props: any, { emit }) {
38 | const { dynamicNumber } = new CustomDynamicComponent()
39 | const cloneModelValue = ref('')
40 | watch(() => props.modelValue, () => {
41 | cloneModelValue.value = props.modelValue
42 | }, { immediate: true })
43 | function updateValue(value: number | string) {
44 | emit('update:modelValue', value)
45 | emit('update:value', value)
46 | emit('change', {
47 | type: 'start',
48 | prop: props.config?.prop ?? '',
49 | value,
50 | })
51 | }
52 |
53 | const clonePropEnd = ref('')
54 | watch(() => props.propEnd, () => {
55 | clonePropEnd.value = props.propEnd
56 | }, { immediate: true })
57 | function updateEndValue(value: number | string) {
58 | emit('update:propEnd', value)
59 | emit('change', {
60 | type: 'end',
61 | prop: props.config?.propEnd ?? '',
62 | value,
63 | })
64 | }
65 |
66 | const getText = () => {
67 | if ((!cloneModelValue.value && cloneModelValue.value !== 0) && (!clonePropEnd.value && clonePropEnd.value !== 0)) {
68 | return ''
69 | }
70 | const startText = cloneModelValue.value === '' || cloneModelValue.value === undefined ? cloneModelValue.value : '-'
71 | const endText = clonePropEnd.value === '' || clonePropEnd.value === undefined ? clonePropEnd.value : '-'
72 | return `${startText}~${endText}`
73 | }
74 |
75 |
76 | return () => {
77 | // ant-design-vue formitem只允许一个form控件
78 | const formItem = CustomDynamicComponent.language === CustomDynamicComponent.antLanguage ? :
79 | return
80 | {textModeFilter(props.textMode, getText() ?? '', props.config.textModeRender && props.config.textModeRender({
81 | value: cloneModelValue.value,
82 | endValue: clonePropEnd.value,
83 | }),
84 |
85 |
102 | ~
103 |
104 |
121 |
122 |
,
123 | )}
124 |
125 | }
126 | },
127 | })
128 | export { numberRangeProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsPasswod.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-03-24 14:01:06
4 | * @LastEditTime: 2023-09-01 11:21:04
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType } from 'vue'
9 | import styles from '@/components/BsForm/style.module.scss'
10 | import type { passwordProps } from '../interface/index'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 |
13 | export default defineComponent({
14 | name: 'BsPasswod',
15 | props: {
16 | modelValue: {
17 | type: [String, Number],
18 | default: '',
19 | },
20 | config: {
21 | type: Object as PropType,
22 | default() {
23 | return {}
24 | },
25 | },
26 | },
27 | emits: ['update:modelValue', 'update:value', 'change'],
28 | setup(props: any, { emit }) {
29 | const { dynamicPassword } = new CustomDynamicComponent()
30 | function updateValue(value: number | string | InputEvent) {
31 | let cloneValue = value
32 |
33 | // ant-Design-vue 无input事件,value获取到的是原生input事件的e
34 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage) {
35 | cloneValue = ((value as InputEvent).target as HTMLInputElement).value
36 | }
37 |
38 | emit('update:modelValue', cloneValue)
39 | emit('update:value', cloneValue)
40 | emit('change', {
41 | prop: props.config?.prop ?? '',
42 | value: cloneValue,
43 | })
44 | }
45 | return () => {
46 | return
47 |
69 |
70 | }
71 | },
72 | })
73 | export { passwordProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsRadio.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-18 13:40:22
4 | * @LastEditTime: 2023-09-15 11:09:38
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, watch, ref, PropType } from 'vue'
9 | import * as utils from '@/utils/common'
10 | import type { radioProps } from '../interface/index'
11 | import styles from '@/components/BsForm/style.module.scss'
12 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
13 | import { textModeFilter, getOptionsLabel } from '../toolFn'
14 |
15 | export default defineComponent({
16 | name: 'BsRadio',
17 | props: {
18 | modelValue: {
19 | type: [Number, String, Boolean],
20 | default: undefined,
21 | },
22 | config: {
23 | type: Object as PropType,
24 | default() {
25 | return {}
26 | },
27 | },
28 | textMode: {
29 | type: Boolean,
30 | default: false,
31 | },
32 | },
33 | emits: ['update:modelValue', 'update:value', 'change', 'setProp2'],
34 | setup(props: any, { emit }) {
35 | const { dynamicRadio, dynamicRadioGroup, dynamicRadioButton } = new CustomDynamicComponent()
36 | const options = ref([])
37 | const optionsLoading = ref(false)
38 | watch(() => props.config.options, async() => {
39 | // 获取options下拉选项
40 | if (Array.isArray(props.config.options)) { // 传入对象数组
41 | options.value = props.config.options
42 | } else if (Object.prototype.toString.call(props.config.options) === '[object Object]') { // 字典/接口获取
43 | if (props.config.options.type === 'api') {
44 | optionsLoading.value = true
45 | options.value = await props.config.options.getData()
46 | optionsLoading.value = false
47 | } else if (props.config.options.type === 'dic') {
48 | options.value = utils.getDicByKey(props.config.options.key)
49 | }
50 | }
51 |
52 | options.value = options?.value?.map((v: any) => {
53 | return {
54 | ...v,
55 | label: v[props.config.labelKey || 'label'],
56 | value: v[props.config.valueKey || 'value'],
57 | }
58 | }) ?? []
59 |
60 | }, { immediate: true, deep: true })
61 |
62 | /**
63 | * @description: 获取选中得item
64 | * @param {*} value 当前选择中得value
65 | * @return any 选中得item
66 | */
67 | const getOption = (value: any) => {
68 | const option = options.value.find((option: any) => option.value === value)
69 | return option
70 | }
71 |
72 | function updateValue(value: number | string | boolean | Event): any {
73 | let cloneValue = value
74 |
75 | // ant-Design-vue change返回的是 e:Event 对象
76 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage) {
77 | cloneValue = ((value as Event).target as HTMLInputElement).value
78 | }
79 |
80 | emit('update:modelValue', cloneValue)
81 | emit('update:value', cloneValue)
82 | emit('change', {
83 | prop: props.config?.prop ?? '',
84 | value: cloneValue,
85 | options,
86 | curItem: getOption(value),
87 | })
88 |
89 | }
90 |
91 | return () => {
92 | const componentInstance = props.config.showType === 'button' && CustomDynamicComponent.language === CustomDynamicComponent.eleLanguage ? dynamicRadioButton : dynamicRadio
93 | return
94 | {textModeFilter(props.textMode, getOptionsLabel(getOption(props.modelValue)) ?? '', props.config.textModeRender && props.config.textModeRender({
95 | value: props.modelValue,
96 | options,
97 | curItem: getOption(props.modelValue),
98 | }),
99 |
111 | {
112 | options.value && Array.isArray(options.value) && options.value.map((item: any, index: number) => {
113 | return {
121 | return props.config.format(item)
122 | },
123 | } : {}}
124 | >
125 | { item.label }
126 |
127 | })
128 | }
129 | ,
130 | )}
131 |
132 | }
133 | },
134 | })
135 | export { radioProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsSelect.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-15 17:30:23
4 | * @LastEditTime: 2023-09-15 11:09:04
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, watch, ref, PropType } from 'vue'
9 | import * as utils from '@/utils/common'
10 | import type { selectProps } from '../interface/index'
11 | import styles from '@/components/BsForm/style.module.scss'
12 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
13 | import { textModeFilter, getOptionsLabel } from '../toolFn'
14 |
15 | export default defineComponent({
16 | name: 'BsSelect',
17 | props: {
18 | modelValue: {
19 | type: [Number, String, Array, Object, Boolean],
20 | default: '',
21 | },
22 | config: {
23 | type: Object as PropType,
24 | default() {
25 | return {}
26 | },
27 | },
28 | textMode: {
29 | type: Boolean,
30 | default: false,
31 | },
32 | },
33 | emits: ['update:modelValue', 'update:value', 'change', 'setProp2'],
34 | setup(props: any, { emit }) {
35 | const { dynamicSelect, dynamicSelectOption } = new CustomDynamicComponent()
36 | const options = ref([])
37 | const optionsLoading = ref(false)
38 | watch(() => props.config.options, async() => {
39 | if (Array.isArray(props.config.options)) { // 传入对象数组
40 | options.value = props.config.options
41 | } else if (Object.prototype.toString.call(props.config.options) === '[object Object]') { // 字典/接口获取
42 | if (props.config.options.type === 'api') {
43 | optionsLoading.value = true
44 | options.value = await props.config.options.getData()
45 | optionsLoading.value = false
46 | } else if (props.config.options.type === 'dic') {
47 | options.value = utils.getDicByKey(props.config.options.key)
48 | }
49 | }
50 |
51 | options.value = options?.value?.map((v: any) => {
52 | return {
53 | ...v,
54 | label: v[props.config.labelKey || 'label'],
55 | value: v[props.config.valueKey || 'value'],
56 | }
57 | }) ?? []
58 |
59 | }, { immediate: true, deep: true })
60 |
61 | // watch(() => props.modelValue, () => {
62 | // updateValue(props.modelValue)
63 | // })
64 |
65 | function updateValue(value: number | string | number|string[]) {
66 | if (value === '') {
67 | emit('update:modelValue', null)
68 | emit('update:value', null)
69 | } else {
70 | emit('update:modelValue', value)
71 | emit('update:value', value)
72 | }
73 | emit('change', {
74 | prop: props.config?.prop ?? '',
75 | value: value === '' ? null : value,
76 | curItem: getOption(value),
77 | options: options.value,
78 | })
79 | // 将options中得prop2字段也设置到form中
80 | if (props?.config?.prop2) {
81 | if (value && Array.isArray(value)) { // 多选
82 | if (props?.config?.remote) { // 多选 && 远程搜索
83 | throw new Error('BsSelect组件远程搜索且多选情况下不支持绑定prop2,请检查配置')
84 | } else { // 多选 && !远程搜索
85 | const prop2List:any = []
86 | value.forEach((item) => {
87 | prop2List.push(getOption(item)[props?.config?.prop2])
88 | })
89 | emit('setProp2', prop2List)
90 | }
91 | } else { // 单选
92 | emit('setProp2', getOption(value)[props?.config?.prop2])
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * @description: 获取选中得item
99 | * @param {*} value 当前选择中得value
100 | * @return any 选中得item
101 | */
102 | const getOption = (value: any) => {
103 | if (props.config.multiple) {
104 | const optionArr = options.value.filter((option: any) => value?.includes(option.value)) ?? []
105 | return optionArr
106 | } else {
107 | const option = options.value.find((option: any) => option.value === value)
108 | return option
109 | }
110 | }
111 |
112 | // 远程搜索方法,必须将filterable、remote设置成true
113 | const remoteMethod = async(query: string) => {
114 | if (!props?.config?.remoteMethod) {
115 | return
116 | }
117 | optionsLoading.value = true
118 | options.value = await props?.config?.remoteMethod(query)
119 | optionsLoading.value = false
120 | }
121 |
122 | const defaultFilterOption = (inputValue:string, option:any) => {
123 | return option?.label?.indexOf(inputValue) >= 0
124 | }
125 |
126 | return () => {
127 | return
128 | {textModeFilter(props.textMode, getOptionsLabel(getOption(props.modelValue)) ?? '', props.config.textModeRender && props.config.textModeRender({
129 | value: props.modelValue,
130 | options,
131 | curItem: getOption(props.modelValue),
132 | }),
133 |
171 | {
172 | options.value && Array.isArray(options.value) && options.value.map((item: any) => {
173 | return {
181 | return props.config.format(item)
182 | },
183 | } : {}}
184 | >
185 | {item.label}
186 |
187 | })
188 | }
189 | ,
190 | )}
191 |
192 | }
193 | },
194 | })
195 |
196 | export { selectProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsSwitch.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-20 14:55:23
4 | * @LastEditTime: 2023-08-15 19:16:33
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType } from 'vue'
9 | import type { switchProps } from '../interface/index'
10 | import styles from '@/components/BsForm/style.module.scss'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import { textModeFilter } from '../toolFn'
13 |
14 | export default defineComponent({
15 | name: 'BsSwitch',
16 | props: {
17 | modelValue: {
18 | type: [String, Number, Boolean],
19 | default: '',
20 | },
21 | config: {
22 | type: Object as PropType,
23 | default() {
24 | return {}
25 | },
26 | },
27 | textMode: {
28 | type: Boolean,
29 | default: false,
30 | },
31 | },
32 | emits: ['update:modelValue', 'update:value', 'change'],
33 | setup(props: any, { emit }) {
34 | const { dynamicSwitch } = new CustomDynamicComponent()
35 | function updateValue(value: number | string | boolean): any {
36 | emit('update:modelValue', value)
37 | emit('update:value', value)
38 | emit('change', {
39 | prop: props.config?.prop ?? '',
40 | value,
41 | })
42 | }
43 | return () => {
44 | return
45 | {textModeFilter(props.textMode, props.modelValue !== undefined ? String(props.modelValue) : '', props.config.textModeRender && props.config.textModeRender({
46 | value: props.modelValue,
47 | }),
48 | ,
66 | )}
67 |
68 | }
69 | },
70 | })
71 | export { switchProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsText.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-18 10:35:57
4 | * @LastEditTime: 2023-08-15 10:55:57
5 | * @LastEditors: 陈宇环
6 | * @Description: 普通文本节点
7 | * 文本内容取值为 绑定formData的值
8 | */
9 | import { defineComponent, PropType } from 'vue'
10 | import styles from '@/components/BsForm/style.module.scss'
11 | import type { textProps } from '../interface/index'
12 | export default defineComponent({
13 | name: 'BsText',
14 | inheritAttrs: false,
15 | props: {
16 | modelValue: {
17 | type: [Number, String, Array, Object, Boolean],
18 | default: undefined,
19 | },
20 | config: {
21 | type: Object as PropType,
22 | default() {
23 | return {}
24 | },
25 | },
26 | },
27 | setup(props: any) {
28 | return () => {props.modelValue ?? props.config.defaultText}
29 | },
30 | })
31 |
32 | export { textProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/BsTextarea.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-20 14:37:53
4 | * @LastEditTime: 2023-09-07 10:27:03
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, PropType } from 'vue'
9 | import styles from '@/components/BsForm/style.module.scss'
10 | import type { textareaProps } from '../interface/index'
11 | import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
12 | import { textModeFilter } from '../toolFn'
13 |
14 | export default defineComponent({
15 | name: 'BsTextarea',
16 | props: {
17 | modelValue: {
18 | type: [String, Number],
19 | default: '',
20 | },
21 | config: {
22 | type: Object as PropType,
23 | default() {
24 | return {}
25 | },
26 | },
27 | textMode: {
28 | type: Boolean,
29 | default: false,
30 | },
31 | },
32 | emits: ['update:modelValue', 'update:value', 'change'],
33 | setup(props: any, { emit }) {
34 | const { dynamicTextarea } = new CustomDynamicComponent()
35 | function updateValue(value: number | string | InputEvent) {
36 | let cloneValue = value
37 |
38 | // ant-Design-vue 无input事件,value获取到的是原生input事件的e
39 | if (CustomDynamicComponent.language === CustomDynamicComponent.antLanguage) {
40 | cloneValue = ((value as InputEvent).target as HTMLInputElement).value
41 | }
42 |
43 | emit('update:modelValue', cloneValue)
44 | emit('update:value', cloneValue)
45 | emit('change', {
46 | prop: props.config?.prop ?? '',
47 | value: cloneValue,
48 | })
49 | }
50 | return () => {
51 | return
52 | {textModeFilter(props.textMode, props.modelValue, props.config.textModeRender && props.config.textModeRender({
53 | value: props.modelValue,
54 | }),
55 | ,
77 | )}
78 |
79 | }
80 | },
81 | })
82 | export { textareaProps }
--------------------------------------------------------------------------------
/src/components/BsForm/components/index.ts:
--------------------------------------------------------------------------------
1 | // import { defineAsyncComponent } from 'vue'
2 | import { columnsBase } from '../interface/index'
3 |
4 | import BsInput from './BsInput'
5 | import BsPasswod from './BsPasswod'
6 | import BsTextarea from './BsTextarea'
7 | import BsNumber from './BsNumber'
8 | import BsSelect from './BsSelect'
9 | import BsRadio from './BsRadio'
10 | import BsCheckbox from './BsCheckbox'
11 | import BsDate from './BsDate'
12 | import BsDateRange from './BsDateRange'
13 | import BsNumberRange from './BsNumberRange'
14 | import BsCascader from './BsCascader'
15 | import BsSwitch from './BsSwitch'
16 | import BsText from './BsText'
17 | import BsCollapse from './BsCollapse'
18 | import BsEditTable from '@/components/BsEditTable'
19 |
20 | // 组件注册
21 | export const getComponentByType = (type: columnsBase['type']): any => {
22 | switch (type) {
23 | case 'input':
24 | return BsInput
25 | case 'textarea':
26 | return BsTextarea
27 | case 'password':
28 | return BsPasswod
29 | case 'number':
30 | return BsNumber
31 | case 'select':
32 | return BsSelect
33 | case 'radio':
34 | return BsRadio
35 | case 'checkbox':
36 | return BsCheckbox
37 | case 'year':
38 | case 'month':
39 | case 'week':
40 | case 'date':
41 | case 'datetime':
42 | case 'dates':
43 | case 'monthrange':
44 | case 'daterange':
45 | case 'datetimerange':
46 | return BsDate
47 | case 'yearRange':
48 | case 'monthRange':
49 | case 'dateRange':
50 | case 'weekRange':
51 | case 'datetimeRange':
52 | return BsDateRange
53 | case 'numberRange':
54 | return BsNumberRange
55 | case 'cascader':
56 | return BsCascader
57 | case 'switch':
58 | return BsSwitch
59 | case 'text':
60 | return BsText
61 | case 'collapse':
62 | return BsCollapse
63 | case 'editTable':
64 | return BsEditTable
65 | default:
66 | return BsInput
67 | // throw new Error('配置项控件${col.type}不存在')
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/BsForm/style.module.scss:
--------------------------------------------------------------------------------
1 | .width100 {
2 | flex: 1;
3 | width: 100% !important;
4 | }
5 |
6 | .BsForm {
7 | // 可编辑表格校验失败时,table内所有控件都会标红,这里排除校验成功的
8 | :global(.bs-edit-table .el-form-item.is-success .el-input__wrapper) {
9 | box-shadow: 0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;
10 | }
11 | :global(.bs-edit-table .ep-form-item.is-success .ep-input__wrapper) {
12 | box-shadow: 0 0 0 1px var(--ep-input-border-color,var(--ep-border-color)) inset;
13 | }
14 | }
15 |
16 | .BsNumber, .BsDateRange {
17 | width: 100%;
18 | :global(.el-input-number), :global(.ep-input-number) {
19 | width: 100%;
20 | }
21 | :global(.el-input-number .el-input__wrapper), :global(.ep-input-number .ep-input__wrapper) {
22 | padding-left: 11px !important;
23 | padding-right: 11px !important;
24 | }
25 | :global(.textLeft .el-input__inner), :global(.textLeft .ep-input__inner) {
26 | text-align: left;
27 | }
28 | :global(.ant-form-item-control-input-content) {
29 | display: flex;
30 | }
31 | }
32 |
33 | .BsNumberRange {
34 | .noControls {
35 | :global(.el-input__inner), :global(.ep-input__inner) {
36 | text-align: left;
37 | }
38 | }
39 | :global(.ant-form-item-control-input-content) {
40 | display: flex;
41 | }
42 | }
43 |
44 | .BaseCollapse {
45 | :global{
46 | .el-collapse-item__header, .el-collapse-item__wrap, .ep-collapse-item__header, .ep-collapse-item__wrap {
47 | background-color: #f1f1f1;
48 | }
49 | .el-collapse-item__content, .el-collapse-item__header, .ep-collapse-item__content, .ep-collapse-item__header {
50 | padding: 0;
51 | padding-left: 10px;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/components/BsForm/toolFn.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-12-18 12:02:28
4 | * @LastEditTime: 2023-08-15 17:15:15
5 | * @LastEditors: 陈宇环
6 | * @Description: form相关工具函数
7 | */
8 |
9 | import { columnsBase } from './interface/index'
10 |
11 | /**
12 | * @description:
13 | * @return {*}
14 | */
15 | export const getColumnIndex = (columm: columnsBase[], key: string): number => {
16 | return columm.findIndex((item) => {
17 | return item.prop === key
18 | })
19 | }
20 |
21 | // 如果是文本模式则展示文本,否则展示defaultDom
22 | export const textModeFilter = (textMode: boolean, text: string, textModeRender: any, defaultDom: any) => {
23 | if (textMode) {
24 | if (textModeRender) {
25 | return textModeRender
26 | }
27 | return text
28 | }
29 | return defaultDom
30 | }
31 |
32 | export const getOptionsLabel = (options: {label: string, [key: string]: any} | {label: string, [key: string]: any}[]): string => {
33 | if (Array.isArray(options)) {
34 | return options?.map((item:any) => item.label).join('、')
35 | } else {
36 | return options?.label
37 | }
38 | }
--------------------------------------------------------------------------------
/src/components/BsTable/BsTableItem.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-04-08 13:49:50
4 | * @LastEditTime: 2023-07-03 15:44:38
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | import { defineComponent, toRefs, PropType } from 'vue'
9 | import BsTableItem from './BsTableItem'
10 | import { columnsItemConfig } from './interface/index'
11 | import { CustomDynamicComponent } from '../CustomDynamicComponent'
12 | export default defineComponent({
13 | name: 'BsTableItem',
14 | components: { },
15 | props: {
16 | itemData: {
17 | type: Object as PropType,
18 | default() {
19 | return {
20 | prop: 'prop',
21 | label: 'label',
22 | }
23 | },
24 | },
25 | },
26 | setup(props:any) {
27 | const { itemData } = toRefs(props)
28 | const dynamicComponent = new CustomDynamicComponent()
29 | const { dynamicTableColumn } = dynamicComponent
30 | // 多级头递归
31 | const childrenDom =
32 | itemData.value.children && itemData.value.children.length > 0
33 | ? itemData.value.children.map((item:any, index:any) => (
34 |
38 | ))
39 | : null
40 | return () => {
41 | // 序号
42 | if (itemData.value.type && itemData.value.type === 'index') {
43 | return
51 | }
52 | // const itemDataProps: columnsItemConfig = _.cloneDeep(itemData.value)
53 | // 解决element-ui报错问题
54 | itemData.value.children = undefined
55 | return (
56 | {
66 | return <>
67 | {
68 | itemData.value.render ?
69 | (typeof itemData.value.render === 'function' ? itemData.value.render(scope) : itemData.value.render) :
70 | scope.row[itemData.value.prop]
71 | }
72 | {/* 多级头部 */}
73 | {childrenDom}
74 | >
75 | },
76 | }}
77 | >
78 | )
79 | }
80 | },
81 | })
82 |
--------------------------------------------------------------------------------
/src/components/BsTable/interface/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-01-03 10:56:12
4 | * @LastEditTime: 2023-08-25 11:45:01
5 | * @LastEditors: 陈宇环
6 | * @Description: table+paging 接口定义
7 | */
8 |
9 | /** table配置参数 */
10 |
11 | /** table列配置 */
12 | export interface columnsConfigFace {
13 | [index: number]: columnsItemConfig;
14 | }
15 |
16 | /** table数据获取函数接口 */
17 | export interface loadDataFace {
18 | ({ pageIndex, pageSize }: { pageIndex: number, pageSize: number }): Promise
19 | }
20 |
21 | export interface tableConfigFace {
22 | /** 是否需要边框
23 | * @defaultValue false
24 | */
25 | border?: boolean,
26 | /** 是否斑马纹 */
27 | stripe?: boolean,
28 | /** 是否初始调用getList方法 */
29 | ifInitLoadData?: boolean,
30 | /** 选择行配置 */
31 | rowSelection?: rowSelectionFace
32 | /** 行对应key值,选择行功能开启时必传 */
33 | rowKey?: string,
34 | /** ui框架原生属性 */
35 | nativeProps?: {
36 | [key: string]: any
37 | }
38 | }
39 |
40 |
41 | /** 分页配置参数 */
42 | export interface pagingConfigFace {
43 | /** 是否需要分页 */
44 | open?: boolean,
45 | /** 默认pageIndex */
46 | pageIndex?: number,
47 | /** 默认pageSize */
48 | pageSize?: number,
49 | /** 默认total */
50 | total?: number,
51 | /** ant-design-vue 是否展示总数 */
52 | showTotal?: (total: number)=> string,
53 | /** ant-design-vue 是否需要分页大小切换组件 */
54 | showSizeChanger?: boolean,
55 | /** 分页组件需要控件 */
56 | layout?: string,
57 | /** 分页index change函数 */
58 | pageIndexChange?: (val: number) => any
59 | /** 分页size change函数 */
60 | pageSizeChange?: (val: number) => any
61 | /** ui框架原生属性 */
62 | nativeProps?: {
63 | [key: string]: any
64 | }
65 | }
66 |
67 | /** table多选配置项 */
68 | export type rowSelectionFace = {
69 | /** 多选或者单选 */
70 | type: 'checkout' | 'radio',
71 | /** 选择变化勾选变化事件 */
72 | onChange?:(selection?: any[]) => any,
73 | /** 当前行勾选是否禁用 */
74 | selectable?: (row:any, index:number) => boolean
75 | /** ant-design-vue 属性兼容 */
76 | [key: string]: any,
77 | }
78 |
79 | /** table列配置项item */
80 | export type columnsItemConfig = {
81 | /** key */
82 | prop?: string,
83 | /** 中文名称 */
84 | label?: string,
85 | /** 类型 */
86 | type?: 'selection' | 'index' | 'expand'
87 | /** 宽度 */
88 | width?: string | number,
89 | /** 最小宽度 */
90 | minWidth?: string | number,
91 | /** 列align布局 */
92 | align?: 'left' | 'center' | 'right',
93 | /** 列是否固定在左侧或者右侧,true 表示固定在左侧 */
94 | fixed?: 'left' | 'right' | true,
95 | /** 自定义渲染函数 */
96 | render?: (scope: any) => any,
97 | /** 多级头定义 */
98 | children?: columnsItemConfig[],
99 | /** ant-design-vue columns属性兼容 */
100 | [key: string]: any,
101 | /** ui框架原生属性 */
102 | nativeProps?: {
103 | [key: string]: any
104 | }
105 | }
106 |
107 | /** table数据获取函数返回值校验 */
108 | export type resultInt = {
109 | /** 接口返回状态 */
110 | success: boolean,
111 | /** table数据列表 */
112 | list?: any[],
113 | /** table数据总数 */
114 | total?: number
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/BsTable/style.module.scss:
--------------------------------------------------------------------------------
1 | .BsTable {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: space-between;
7 | background-color: #fff;
8 | // padding: 0 15px;
9 | border-radius: 3px;
10 | .table {
11 | flex: 1;
12 | .rowRadio {
13 | :global(.el-radio__label), :global(.ep-radio__label) {
14 | display: none;
15 | }
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/components/CustomDynamicComponent.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-06-05 15:12:47
4 | * @LastEditTime: 2023-08-18 11:32:00
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | // window.uiLanguage = 'ant'
9 | // 自定义动态组件
10 | export class CustomDynamicComponent {
11 | // 定义this属性的类型
12 | [x: string]: JSX.Element | ((type: string) => JSX.Element)
13 | // 默认element-plus样式,在window下面进行注册
14 | static language = window.uiLanguage || 'ele'
15 | // element-plus对比基准
16 | static eleLanguage = 'ele'
17 | // ant-design-vue对比基准
18 | static antLanguage = 'ant'
19 | // 多种ui定义字典值
20 | static dicts: {
21 | [x in 'ele' | 'ant']: () => {
22 | [x in string]: JSX.Element
23 | }
24 | } = {
25 | ant: () => {
26 | return {
27 | row: ,
28 | col: ,
29 | form: ,
30 | formItem: ,
31 | button: ,
32 | table: ,
33 | tableColumn: ,
34 | radio: ,
35 | pagination: ,
36 | input: ,
37 | password: ,
38 | textarea: ,
39 | number: ,
40 | radioGroup: ,
41 | radioButton: ,
42 | select: ,
43 | selectOption: ,
44 | switch: ,
45 | cascader: ,
46 | checkBox: ,
47 | checkBoxGroup: ,
48 | datePicker: ,
49 | rangePicker: ,
50 | checkBoxButton: ,
51 | popconfirm: ,
52 | collapse: ,
53 | collapseItem: ,
54 | dialog: ,
55 | }
56 | },
57 | ele: () => {
58 | return {
59 | row: ,
60 | col: ,
61 | form: ,
62 | formItem: ,
63 | button: ,
64 | table: ,
65 | tableColumn: ,
66 | radio: ,
67 | pagination: ,
68 | input: ,
69 | password: ,
70 | textarea: ,
71 | number: ,
72 | radioGroup: ,
73 | radioButton: ,
74 | select: ,
75 | selectOption: ,
76 | switch: ,
77 | cascader: ,
78 | checkBox: ,
79 | checkBoxGroup: ,
80 | datePicker: ,
81 | rangePicker: ,
82 | checkBoxButton: ,
83 | popconfirm: ,
84 | collapse: ,
85 | collapseItem: ,
86 | dialog: ,
87 | }
88 | },
89 | }
90 | constructor() {
91 | // 首字母大写函数
92 | function ucFirst(str:string):string {
93 | const str1 = str[0].toUpperCase() + str.slice(1)
94 | return str1
95 | }
96 | const map = CustomDynamicComponent.dicts[window?.uiLanguage ?? 'ele']()
97 | for (const key in map) {
98 | this['dynamic' + ucFirst(key)] = map[key]
99 | }
100 | }
101 | // 获取动态组件函数
102 | getComponent(type: string): JSX.Element {
103 | const methodMap = CustomDynamicComponent.dicts[window?.uiLanguage ?? 'ele']
104 | // 判断是否有ui主题组件库
105 | if (methodMap) {
106 | // 判断是否有定义的组件类型,没有返回div
107 | return methodMap()[type] ||
108 | }
109 | return
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/examples/api/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
20 |
21 |
27 |
--------------------------------------------------------------------------------
/src/examples/dialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
74 |
75 |
76 |
213 |
214 |
217 |
--------------------------------------------------------------------------------
/src/layout/FullPageLayout.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/layout/LRLayout.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
--------------------------------------------------------------------------------
/src/local/components/BaseMenu/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2023-03-03 11:34:55
4 | * @LastEditTime: 2023-09-01 17:06:50
5 | * @LastEditors: 陈宇环
6 | * @Description: 页面菜单组件
7 | */
8 |
9 | import { defineComponent } from 'vue'
10 | import { useRoute, useRouter } from 'vue-router'
11 | import styles from './style.module.scss'
12 | import * as icons from '@element-plus/icons-vue'
13 | import menus from '@/router/route'
14 |
15 | export default defineComponent({
16 | setup() {
17 | const route = useRoute()
18 | const router = useRouter()
19 | const recursion = (menus: any[]) => {
20 | return menus.map((menu:any) => {
21 | // icon取的是pms配置菜单是的icon字段 - 对应elementplus的icon组件https://element-plus.org/zh-CN/component/icon.html#%E5%9B%BE%E6%A0%87%E9%9B%86%E5%90%88
22 | const iconDom = menu?.meta?.moduleIcon && (icons as any)[menu?.meta?.moduleIcon] ? (icons as any)[menu?.meta?.moduleIcon] : icons.Failed
23 | return (<>
24 | {(!menu.children && menu?.meta?.hide !== true) && }
31 | {menu.children && {
33 | return <>
34 | {iconDom ? : null}
35 | {menu?.meta?.moduleName}
36 | >
37 | },
38 | }}>
39 | {recursion(menu.children)}
40 | }
41 | >)
42 | })
43 | }
44 |
45 | const goApi = () => {
46 | const newpage = router.resolve({
47 | name: 'api',
48 | })
49 | window.open(newpage.href, '_blank')
50 | }
51 |
52 | return () => {
53 | return
71 | }
72 | },
73 | })
--------------------------------------------------------------------------------
/src/local/components/BaseMenu/style.module.scss:
--------------------------------------------------------------------------------
1 | .BaseMenu {
2 | width: auto;
3 | height: 100%;
4 | background-color: #30364c;
5 | overflow-y: auto;
6 | overflow-x: hidden;
7 | &:global(.width230) {
8 | width: 180px;
9 | }
10 | :global(.el-menu-vertical:not(.ep-menu--collapse)) {
11 | height: 100%;
12 | width: 180px;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/local/utils/index.tsx:
--------------------------------------------------------------------------------
1 | import router from '@/router/index'
2 | export const goApi = (url: string) => {
3 | const newpage = router.resolve({
4 | path: '/api',
5 | query: {
6 | url,
7 | },
8 | })
9 | window.open(newpage.href)
10 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import './registerServiceWorker'
4 | import '@/assets/css/reset.css'
5 | import ElementPlus from 'element-plus'
6 | import zhCn from 'element-plus/es/locale/lang/zh-cn'
7 | import 'element-plus/theme-chalk/index.css'
8 | import AntDesignVue from 'ant-design-vue'
9 | import 'ant-design-vue/dist/antd.css'
10 | import router from '@/router'
11 | createApp(App).use(router).use(ElementPlus, {
12 | locale: zhCn,
13 | // namespace: 'ep',
14 | }).use(AntDesignVue).mount('#app')
15 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { register } from 'register-service-worker'
4 |
5 | if (process.env.NODE_ENV === 'production') {
6 | register(`${process.env.BASE_URL}service-worker.js`, {
7 | ready() {
8 | console.log(
9 | 'App is being served from cache by a service worker.\n' +
10 | 'For more details, visit https://goo.gl/AFskqB',
11 | )
12 | },
13 | registered() {
14 | console.log('Service worker has been registered.')
15 | },
16 | cached() {
17 | console.log('Content has been cached for offline use.')
18 | },
19 | updatefound() {
20 | console.log('New content is downloading.')
21 | },
22 | updated() {
23 | console.log('New content is available; please refresh.')
24 | },
25 | offline() {
26 | console.log('No internet connection found. App is running in offline mode.')
27 | },
28 | error(error) {
29 | console.error('Error during service worker registration:', error)
30 | },
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouterOptions, Router } from 'vue-router'
2 | import routes from './route'
3 |
4 | // RouterOptions是路由选项类型
5 | const options: RouterOptions = {
6 | history: createWebHashHistory(),
7 | routes,
8 | }
9 |
10 | // Router是路由对象类型
11 | const router: Router = createRouter(options)
12 |
13 | export default router
14 |
--------------------------------------------------------------------------------
/src/router/route.ts:
--------------------------------------------------------------------------------
1 | import { RouteRecordRaw } from 'vue-router'
2 | import LRLayout from '@/layout/LRLayout.vue'
3 | const routes: RouteRecordRaw[] = [
4 | {
5 | path: '/',
6 | name: 'index',
7 | component: LRLayout,
8 | redirect: 'home',
9 | children: [
10 | {
11 | path: 'home',
12 | name: 'home',
13 | component: () => import('@/examples/home/index.vue'),
14 | meta: {
15 | moduleName: '示例页面',
16 | moduleIcon: 'HomeFilled',
17 | },
18 | },
19 | {
20 | path: 'form',
21 | name: 'form',
22 | component: () => import('@/examples/form/index.vue'),
23 | meta: {
24 | moduleName: '表单',
25 | moduleIcon: 'Postcard',
26 | },
27 | },
28 | {
29 | path: 'editTable',
30 | name: 'editTable',
31 | component: () => import('@/examples/editTable/index.vue'),
32 | meta: {
33 | moduleName: '行内编辑table',
34 | moduleIcon: 'List',
35 | },
36 | },
37 | {
38 | path: 'dialog',
39 | name: 'dialog',
40 | component: () => import('@/examples/dialog/index.vue'),
41 | meta: {
42 | moduleName: '弹窗',
43 | moduleIcon: 'Money',
44 | },
45 | },
46 | ],
47 | },
48 | {
49 | path: '/api',
50 | name: 'api',
51 | component: () => import('@/examples/api/index.vue'),
52 | meta: {
53 | hide: true,
54 | moduleName: 'API文档',
55 | moduleIcon: 'Reading',
56 | },
57 | },
58 | ]
59 |
60 | export default routes
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | import type { DefineComponent } from 'vue'
4 | const component: DefineComponent<{}, {}, any>
5 | export default component
6 | }
7 |
8 | declare module '*.module.scss' {
9 | const classes: { readonly [key: string]: string }
10 | export default classes
11 | }
12 | interface Window {
13 | Vue: any;
14 | uiLanguage: 'ele' | 'ant' | undefined
15 | }
--------------------------------------------------------------------------------
/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | // sessionStorage
2 | export const session = function(key: string, value?: any): any {
3 | // debugger
4 | if (value === void 0) {
5 | const lsVal:string = sessionStorage.getItem(key) as string
6 | return JSON.parse(lsVal)
7 | }
8 | return sessionStorage.setItem(key, JSON.stringify(value))
9 | }
10 |
11 | // 获取字典
12 | export function getDicByKey(key: string):any {
13 | const dicList = session('sysCodeList')
14 | if (dicList && Array.isArray(dicList)) {
15 | const dic = dicList?.find((item: any) => {
16 | return item?.bizTypeCode === key
17 | })
18 | return dic?.codeValues ?? null
19 | }
20 | return dicList?.[key] ?? null
21 | }
22 |
23 | // 树遍历
24 | export function treeForeach(tree: any[], func: (node: any)=> any | void, childrenKey = 'children') {
25 | tree.forEach((data) => {
26 | func(data)
27 | data[childrenKey] && treeForeach(data[childrenKey], func) // 遍历子树
28 | })
29 | }
30 |
31 | /**
32 | * 根据code判断是否要显示按钮(按钮权限控制)
33 | * @param code
34 | * @returns {*}
35 | */
36 | export const isHavePermission = function(code: string) {
37 | const { permissionInfo } = session('permissionInfo')
38 | const userInfo = session('userInfo')?.data
39 | if (userInfo !== null) {
40 | if (userInfo?.userType === 1) { // 超级管理员
41 | return true
42 | } else {
43 | const permission = permissionInfo?.find((value: string) => value === code)
44 | if (permission) {
45 | return true
46 | }
47 | return false
48 | }
49 | } else {
50 | return false
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-01-17 14:01:46
4 | * @LastEditTime: 2022-01-17 14:01:46
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | export * from './common'
9 |
--------------------------------------------------------------------------------
/tests/unit/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import HelloWorld from '@/components/HelloWorld.vue'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('renders props.msg when passed', () => {
6 | const msg = 'new message'
7 | const wrapper = shallowMount(HelloWorld, {
8 | props: { msg }
9 | })
10 | expect(wrapper.text()).toMatch(msg)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/ts-shim.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 陈宇环
3 | * @Date: 2022-05-30 16:16:29
4 | * @LastEditTime: 2023-06-14 11:08:37
5 | * @LastEditors: 陈宇环
6 | * @Description:
7 | */
8 | // eslint-disable-next-line no-unused-expressions, space-infix-ops
9 | declare module '*.jsx'
10 |
11 | interface Window {
12 | Vue: any;
13 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "moduleResolution": "node",
8 | "skipLibCheck": true,
9 | "declaration": true,
10 | "emitDeclarationOnly": true,
11 | "declarationMap": false,
12 | "declarationDir": "types",
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "useDefineForClassFields": true,
17 | "sourceMap": false,
18 | "baseUrl": ".",
19 | "types": [
20 | "webpack-env",
21 | "jest",
22 | "element-plus/global"
23 | ],
24 | "paths": {
25 | "@/*": [
26 | "src/*"
27 | ]
28 | },
29 | "plugins": [
30 | // 转换输出 js 文件中别名的路径
31 | // { "transform": "typescript-transform-paths" },
32 | // 转换输出 .d.ts 文件中别名的路径
33 | { "transform": "typescript-transform-paths", "afterDeclarations": true }
34 | ],
35 | "lib": [
36 | "esnext",
37 | "dom",
38 | "dom.iterable",
39 | "scripthost"
40 | ]
41 | },
42 | "include": [
43 | "src/**/*.ts",
44 | "src/**/*.tsx",
45 | "src/**/*.jsx",
46 | "src/**/*.vue",
47 | "packages/**/*.ts",
48 | "packages/**/*.tsx",
49 | "packages/**/*.jsx",
50 | "packages/**/*.vue",
51 | "tests/**/*.ts",
52 | "tests/**/*.tsx",
53 | "src/types/index.d.ts",
54 | ],
55 | "exclude": [
56 | "node_modules", "public"
57 | ],
58 | }
59 |
--------------------------------------------------------------------------------
/typedoc.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 |
4 | const resolve = (dir) => {
5 | return path.resolve(__dirname, './' + dir)
6 | }
7 |
8 |
9 | function getComponentEntries() {
10 | let mainComponentsPath = []
11 |
12 | // 将src/components所有子目录中的index.tsx传入, 仅子节点,不包括孙子节点
13 | const files = fs.readdirSync(resolve('src/components'))
14 | mainComponentsPath = files.filter((fileName) => {
15 | return fileName !== 'CustomDynamicComponent.tsx'
16 | }).map((fileName) => 'src/components/' + fileName + '/index.tsx')
17 |
18 | // src/components/BsForm/components组件处理
19 | let formComponentsPath = []
20 | const formFiles = fs.readdirSync(resolve('src/components/BsForm/components'))
21 | formComponentsPath = formFiles.filter((fileName) => {
22 | return fileName !== 'index.ts'
23 | }).map((fileName) => 'src/components/BsForm/components/' + fileName)
24 |
25 | const extraComponents = ['src/components/BsDialog/BsFormDialog/index.tsx', 'src/components/BsDialog/BsListDialog/index.tsx']
26 |
27 | return [...mainComponentsPath, ...formComponentsPath, ...extraComponents]
28 | }
29 |
30 | module.exports = {
31 | entryPoints: getComponentEntries(),
32 | out: 'public/static/apiDocs2',
33 | disableSources: true,
34 | }
35 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const build = require('./buildConfig/build.js')
2 | const npmBuild = require('./buildConfig/npmBuild.js')
3 |
4 | module.exports = process.env.ENV === 'npm' ? npmBuild : build
5 |
6 |
7 |
--------------------------------------------------------------------------------
table列配置
3 |