@firerian/fireui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/fireui.cjs +2 -0
- package/dist/fireui.cjs.map +1 -0
- package/dist/fireui.css +1 -0
- package/dist/fireui.es.mjs +3867 -0
- package/dist/fireui.es.mjs.map +1 -0
- package/dist/fireui.umd.js +2 -0
- package/dist/fireui.umd.js.map +1 -0
- package/dist/types/index.d.ts +1590 -0
- package/package.json +132 -0
- package/src/components/button/button.test.ts +357 -0
- package/src/components/button/button.vue +366 -0
- package/src/components/button/index.ts +17 -0
- package/src/components/button/types.ts +76 -0
- package/src/components/form/form-item.vue +136 -0
- package/src/components/form/form.vue +76 -0
- package/src/components/form/index.ts +16 -0
- package/src/components/grid/col.vue +99 -0
- package/src/components/grid/index.ts +16 -0
- package/src/components/grid/row.vue +85 -0
- package/src/components/grid/types.ts +66 -0
- package/src/components/index.ts +36 -0
- package/src/components/input/index.ts +8 -0
- package/src/components/input/input.test.ts +129 -0
- package/src/components/input/input.vue +256 -0
- package/src/components/input/types.ts +100 -0
- package/src/components/layout/aside.vue +89 -0
- package/src/components/layout/container.vue +53 -0
- package/src/components/layout/footer.vue +57 -0
- package/src/components/layout/header.vue +56 -0
- package/src/components/layout/index.ts +28 -0
- package/src/components/layout/main.vue +36 -0
- package/src/components/layout/types.ts +74 -0
- package/src/components/table/index.ts +16 -0
- package/src/components/table/table-column.vue +69 -0
- package/src/components/table/table.vue +354 -0
- package/src/components/tips/index.ts +12 -0
- package/src/components/tips/tips.test.ts +96 -0
- package/src/components/tips/tips.vue +206 -0
- package/src/components/tips/types.ts +56 -0
- package/src/components/tooltip/index.ts +8 -0
- package/src/components/tooltip/tooltip.test.ts +187 -0
- package/src/components/tooltip/tooltip.vue +261 -0
- package/src/components/tooltip/types.ts +60 -0
- package/src/hooks/useForm.ts +233 -0
- package/src/hooks/useTable.ts +153 -0
- package/src/index.ts +48 -0
- package/src/styles/main.scss +6 -0
- package/src/styles/mixins.scss +48 -0
- package/src/styles/reset.scss +49 -0
- package/src/styles/variables.scss +137 -0
- package/src/types/component.ts +9 -0
- package/src/types/form.ts +149 -0
- package/src/types/global.d.ts +49 -0
- package/src/types/grid.ts +76 -0
- package/src/types/index.ts +23 -0
- package/src/types/table.ts +181 -0
- package/src/types/tooltip.ts +44 -0
- package/src/utils/auto-import.ts +41 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/install.ts +20 -0
- package/src/utils/useNamespace.ts +29 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { ref, reactive, readonly, provide, inject } from 'vue'
|
|
2
|
+
import Schema from 'async-validator'
|
|
3
|
+
import type {
|
|
4
|
+
FormProps,
|
|
5
|
+
FormContext,
|
|
6
|
+
FormInstance,
|
|
7
|
+
FormItemInstance,
|
|
8
|
+
RuleItem,
|
|
9
|
+
ValidateError
|
|
10
|
+
} from '../types/form'
|
|
11
|
+
|
|
12
|
+
const formContextKey = Symbol('formContext')
|
|
13
|
+
|
|
14
|
+
export function useForm<T extends Record<string, any>>() {
|
|
15
|
+
const formItems = ref<FormItemInstance[]>([])
|
|
16
|
+
|
|
17
|
+
const formInstance = reactive<FormInstance<T>>({
|
|
18
|
+
validate: async (callback) => {
|
|
19
|
+
const errors: ValidateError[] = []
|
|
20
|
+
const validatePromises = formItems.value.map(formItem => {
|
|
21
|
+
return formItem.validate('submit').catch(errorMessage => {
|
|
22
|
+
errors.push({ message: errorMessage, field: '' } as ValidateError)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
await Promise.all(validatePromises)
|
|
27
|
+
|
|
28
|
+
const valid = errors.length === 0
|
|
29
|
+
if (callback) {
|
|
30
|
+
callback(valid, errors)
|
|
31
|
+
}
|
|
32
|
+
return valid
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
validateField: async (props, callback) => {
|
|
36
|
+
const propsArray = Array.isArray(props) ? props : [props]
|
|
37
|
+
const errors: ValidateError[] = []
|
|
38
|
+
const fields: Record<string, ValidateError[]> = {}
|
|
39
|
+
|
|
40
|
+
const validatePromises = formItems.value
|
|
41
|
+
.filter(item => {
|
|
42
|
+
const itemProp = (item as any).prop
|
|
43
|
+
return itemProp && propsArray.includes(itemProp as keyof T)
|
|
44
|
+
})
|
|
45
|
+
.map(formItem => {
|
|
46
|
+
return formItem.validate('blur').catch(errorMessage => {
|
|
47
|
+
const field = (formItem as any).prop
|
|
48
|
+
errors.push({ message: errorMessage, field } as ValidateError)
|
|
49
|
+
if (field) {
|
|
50
|
+
if (!fields[field]) {
|
|
51
|
+
fields[field] = []
|
|
52
|
+
}
|
|
53
|
+
fields[field].push({ message: errorMessage, field } as ValidateError)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
await Promise.all(validatePromises)
|
|
59
|
+
|
|
60
|
+
const valid = errors.length === 0
|
|
61
|
+
if (callback) {
|
|
62
|
+
callback(valid ? null : errors[0], fields)
|
|
63
|
+
}
|
|
64
|
+
return valid
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
resetFields: () => {
|
|
68
|
+
formItems.value.forEach(formItem => {
|
|
69
|
+
formItem.resetField()
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
clearValidate: (props) => {
|
|
74
|
+
if (props) {
|
|
75
|
+
const propsArray = Array.isArray(props) ? props : [props]
|
|
76
|
+
formItems.value
|
|
77
|
+
.filter(item => {
|
|
78
|
+
const itemProp = (item as any).prop
|
|
79
|
+
return itemProp && propsArray.includes(itemProp as keyof T)
|
|
80
|
+
})
|
|
81
|
+
.forEach(formItem => {
|
|
82
|
+
formItem.clearValidate()
|
|
83
|
+
})
|
|
84
|
+
} else {
|
|
85
|
+
formItems.value.forEach(formItem => {
|
|
86
|
+
formItem.clearValidate()
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
scrollToField: (prop) => {
|
|
92
|
+
const formItem = formItems.value.find(item => (item as any).prop === prop)
|
|
93
|
+
if (formItem) {
|
|
94
|
+
const el = (formItem as any).$el
|
|
95
|
+
if (el) {
|
|
96
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const addFormItem = (formItem: FormItemInstance) => {
|
|
103
|
+
formItems.value.push(formItem)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const removeFormItem = (formItem: FormItemInstance) => {
|
|
107
|
+
const index = formItems.value.indexOf(formItem)
|
|
108
|
+
if (index > -1) {
|
|
109
|
+
formItems.value.splice(index, 1)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
formInstance,
|
|
115
|
+
addFormItem,
|
|
116
|
+
removeFormItem
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function provideForm<T extends Record<string, any>>(props: FormProps<T>) {
|
|
121
|
+
const { formInstance, addFormItem, removeFormItem } = useForm<T>()
|
|
122
|
+
|
|
123
|
+
const formContext = reactive({
|
|
124
|
+
model: props.model || {} as T,
|
|
125
|
+
rules: props.rules || {} as Record<keyof T, RuleItem | RuleItem[]>,
|
|
126
|
+
labelWidth: props.labelWidth || '',
|
|
127
|
+
labelPosition: props.labelPosition || 'left',
|
|
128
|
+
requiredSymbol: props.requiredSymbol || '*',
|
|
129
|
+
statusIcon: props.statusIcon || false,
|
|
130
|
+
inline: props.inline || false,
|
|
131
|
+
hideRequiredAsterisk: props.hideRequiredAsterisk || false,
|
|
132
|
+
form: formInstance,
|
|
133
|
+
addFormItem,
|
|
134
|
+
removeFormItem
|
|
135
|
+
}) as FormContext<T>
|
|
136
|
+
|
|
137
|
+
provide(formContextKey, readonly(formContext))
|
|
138
|
+
|
|
139
|
+
return formContext
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function useFormContext<T extends Record<string, any>>() {
|
|
143
|
+
const formContext = inject<FormContext<T>>(formContextKey)
|
|
144
|
+
if (!formContext) {
|
|
145
|
+
throw new Error('FormItem must be used within Form')
|
|
146
|
+
}
|
|
147
|
+
return formContext
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function useFormItem(prop?: string) {
|
|
151
|
+
const formContext = useFormContext()
|
|
152
|
+
const error = ref('')
|
|
153
|
+
const validated = ref(false)
|
|
154
|
+
|
|
155
|
+
const validate = async (_trigger: string = 'blur', callback?: (valid: boolean, errorMessage: string) => void) => {
|
|
156
|
+
if (!prop || !formContext.model || !formContext.rules[prop as keyof typeof formContext.model]) {
|
|
157
|
+
const valid = true
|
|
158
|
+
if (callback) {
|
|
159
|
+
callback(valid, '')
|
|
160
|
+
}
|
|
161
|
+
return valid
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rules = formContext.rules[prop as keyof typeof formContext.model]
|
|
165
|
+
const value = getFieldValue(formContext.model, prop)
|
|
166
|
+
|
|
167
|
+
const schema = new Schema({
|
|
168
|
+
[prop]: rules
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await schema.validate({
|
|
173
|
+
[prop]: value
|
|
174
|
+
}, {
|
|
175
|
+
first: true
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
error.value = ''
|
|
179
|
+
validated.value = true
|
|
180
|
+
if (callback) {
|
|
181
|
+
callback(true, '')
|
|
182
|
+
}
|
|
183
|
+
return true
|
|
184
|
+
} catch (err: any) {
|
|
185
|
+
error.value = err.errors[0]
|
|
186
|
+
validated.value = false
|
|
187
|
+
if (callback) {
|
|
188
|
+
callback(false, err.errors[0])
|
|
189
|
+
}
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const resetField = () => {
|
|
195
|
+
if (!prop || !formContext.model) return
|
|
196
|
+
setFieldValue(formContext.model, prop, '')
|
|
197
|
+
error.value = ''
|
|
198
|
+
validated.value = false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const clearValidate = () => {
|
|
202
|
+
error.value = ''
|
|
203
|
+
validated.value = false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
formContext,
|
|
208
|
+
error,
|
|
209
|
+
validated,
|
|
210
|
+
validate,
|
|
211
|
+
resetField,
|
|
212
|
+
clearValidate
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getFieldValue(obj: any, path: string): any {
|
|
217
|
+
return path.split('.').reduce((prev, curr) => prev && prev[curr], obj)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function setFieldValue(obj: any, path: string, value: any): void {
|
|
221
|
+
const keys = path.split('.')
|
|
222
|
+
const lastKey = keys.pop()
|
|
223
|
+
if (!lastKey) return
|
|
224
|
+
|
|
225
|
+
const target = keys.reduce((prev, curr) => {
|
|
226
|
+
if (!prev[curr]) {
|
|
227
|
+
prev[curr] = {}
|
|
228
|
+
}
|
|
229
|
+
return prev[curr]
|
|
230
|
+
}, obj)
|
|
231
|
+
|
|
232
|
+
target[lastKey] = value
|
|
233
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ref, reactive, readonly, provide, inject, computed } from 'vue'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
TableProps,
|
|
5
|
+
TableContext,
|
|
6
|
+
TableInstance,
|
|
7
|
+
TableColumnInstance,
|
|
8
|
+
TableColumnProps,
|
|
9
|
+
TableColumnSortOrder
|
|
10
|
+
} from '../types/table'
|
|
11
|
+
|
|
12
|
+
const tableContextKey = Symbol('tableContext')
|
|
13
|
+
|
|
14
|
+
export function useTable<T = any>() {
|
|
15
|
+
const columns = ref<TableColumnInstance[]>([])
|
|
16
|
+
const currentRow = ref<T | null>(null)
|
|
17
|
+
const oldCurrentRow = ref<T | null>(null)
|
|
18
|
+
|
|
19
|
+
const tableInstance = reactive<TableInstance<T>>({
|
|
20
|
+
clearSort: () => {
|
|
21
|
+
columns.value.forEach(column => {
|
|
22
|
+
if (column.column.sortable) {
|
|
23
|
+
column.updateColumn({ sortOrder: null })
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
clearFilter: (columnKey) => {
|
|
29
|
+
if (columnKey) {
|
|
30
|
+
const keys = Array.isArray(columnKey) ? columnKey : [columnKey]
|
|
31
|
+
columns.value
|
|
32
|
+
.filter(column => keys.includes(column.column.prop || ''))
|
|
33
|
+
.forEach(column => {
|
|
34
|
+
column.updateColumn({ filteredValue: [] })
|
|
35
|
+
})
|
|
36
|
+
} else {
|
|
37
|
+
columns.value.forEach(column => {
|
|
38
|
+
if (column.column.filters) {
|
|
39
|
+
column.updateColumn({ filteredValue: [] })
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
setCurrentRow: (row) => {
|
|
46
|
+
oldCurrentRow.value = currentRow.value
|
|
47
|
+
currentRow.value = row
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
doLayout: () => {
|
|
51
|
+
// 触发重排
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
getRows: () => {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const addColumn = (column: TableColumnInstance) => {
|
|
60
|
+
columns.value.push(column)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const removeColumn = (column: TableColumnInstance) => {
|
|
64
|
+
const index = columns.value.indexOf(column)
|
|
65
|
+
if (index > -1) {
|
|
66
|
+
columns.value.splice(index, 1)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
tableInstance,
|
|
72
|
+
columns,
|
|
73
|
+
currentRow,
|
|
74
|
+
oldCurrentRow,
|
|
75
|
+
addColumn,
|
|
76
|
+
removeColumn
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function provideTable<T = any>(props: TableProps<T>) {
|
|
81
|
+
const { tableInstance, currentRow, oldCurrentRow, addColumn, removeColumn } = useTable<T>()
|
|
82
|
+
|
|
83
|
+
const tableContext = reactive({
|
|
84
|
+
data: props.data || [] as T[],
|
|
85
|
+
border: props.border || false,
|
|
86
|
+
stripe: props.stripe || false,
|
|
87
|
+
highlightCurrentRow: props.highlightCurrentRow || false,
|
|
88
|
+
currentRowKey: props.currentRowKey || '',
|
|
89
|
+
table: tableInstance,
|
|
90
|
+
addColumn,
|
|
91
|
+
removeColumn
|
|
92
|
+
}) as TableContext<T>
|
|
93
|
+
|
|
94
|
+
provide(tableContextKey, readonly(tableContext))
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
tableContext,
|
|
98
|
+
currentRow,
|
|
99
|
+
oldCurrentRow
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function useTableContext<T = any>() {
|
|
104
|
+
const tableContext = inject<TableContext<T>>(tableContextKey)
|
|
105
|
+
if (!tableContext) {
|
|
106
|
+
throw new Error('TableColumn must be used within Table')
|
|
107
|
+
}
|
|
108
|
+
return tableContext
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function useTableColumn(props: TableColumnProps) {
|
|
112
|
+
const tableContext = useTableContext()
|
|
113
|
+
|
|
114
|
+
const columnInstance = reactive<TableColumnInstance>({
|
|
115
|
+
column: props,
|
|
116
|
+
updateColumn: (newProps) => {
|
|
117
|
+
Object.assign(columnInstance.column, newProps)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
tableContext,
|
|
123
|
+
columnInstance
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function useSortable<T = any>(data: Ref<T[]>, sortOrder: Ref<TableColumnSortOrder>, prop: string, sortMethod?: (a: T, b: T) => number) {
|
|
128
|
+
const sortedData = computed(() => {
|
|
129
|
+
if (!sortOrder.value) {
|
|
130
|
+
return data.value
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return [...data.value].sort((a, b) => {
|
|
134
|
+
if (sortMethod) {
|
|
135
|
+
return sortMethod(a, b)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 默认排序方法
|
|
139
|
+
const aValue = a[prop as keyof T]
|
|
140
|
+
const bValue = b[prop as keyof T]
|
|
141
|
+
|
|
142
|
+
if (aValue < bValue) {
|
|
143
|
+
return sortOrder.value === 'ascending' ? -1 : 1
|
|
144
|
+
}
|
|
145
|
+
if (aValue > bValue) {
|
|
146
|
+
return sortOrder.value === 'ascending' ? 1 : -1
|
|
147
|
+
}
|
|
148
|
+
return 0
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return sortedData
|
|
153
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file FireUI 组件库入口文件
|
|
3
|
+
* @description 自动注册所有组件并导出
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* // 全局注册
|
|
7
|
+
* import { createApp } from 'vue'
|
|
8
|
+
* import FireUI from 'fireui'
|
|
9
|
+
* import 'fireui/dist/style.css'
|
|
10
|
+
*
|
|
11
|
+
* const app = createApp(App)
|
|
12
|
+
* app.use(FireUI)
|
|
13
|
+
*
|
|
14
|
+
* // 按需引入
|
|
15
|
+
* import { FButton } from 'fireui'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { App, Plugin, Component } from 'vue'
|
|
20
|
+
import { FButton, FireRow, FireCol, FireContainer, FireHeader, FireAside, FireMain, FireFooter, FireTooltip, FireTips, FireInput, FireForm, FireFormItem, FireTable, FireTableColumn } from './components'
|
|
21
|
+
|
|
22
|
+
// 所有组件列表
|
|
23
|
+
const components = [FButton, FireRow, FireCol, FireContainer, FireHeader, FireAside, FireMain, FireFooter, FireTooltip, FireTips, FireInput, FireForm, FireFormItem, FireTable, FireTableColumn]
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 安装函数 - 自动注册所有组件
|
|
27
|
+
* @param app - Vue 应用实例
|
|
28
|
+
*/
|
|
29
|
+
const install = (app: App): void => {
|
|
30
|
+
components.forEach((component: Component & { name?: string }) => {
|
|
31
|
+
if (component && component.name) {
|
|
32
|
+
app.component(component.name, component)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 导出所有组件
|
|
38
|
+
export { FButton, FireRow, FireCol, FireContainer, FireHeader, FireAside, FireMain, FireFooter, FireTooltip, FireTips, FireInput, FireForm, FireFormItem, FireTable, FireTableColumn }
|
|
39
|
+
|
|
40
|
+
// 导出组件库插件
|
|
41
|
+
export default {
|
|
42
|
+
install,
|
|
43
|
+
version: '0.1.0',
|
|
44
|
+
} as Plugin
|
|
45
|
+
|
|
46
|
+
// 导出类型
|
|
47
|
+
export * from './types'
|
|
48
|
+
export * from './utils'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@use 'variables' as *;
|
|
2
|
+
|
|
3
|
+
@mixin button-base {
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
font-family: $font-family;
|
|
8
|
+
font-weight: 500;
|
|
9
|
+
border-radius: $border-radius-base;
|
|
10
|
+
border: 1px solid transparent;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
transition: $transition-base;
|
|
13
|
+
outline: none;
|
|
14
|
+
|
|
15
|
+
&:focus {
|
|
16
|
+
box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&:active {
|
|
20
|
+
transform: scale(0.98);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@mixin button-variant($bg-color, $border-color, $text-color) {
|
|
25
|
+
background-color: $bg-color;
|
|
26
|
+
border-color: $border-color;
|
|
27
|
+
color: $text-color;
|
|
28
|
+
|
|
29
|
+
&:hover:not(&--disabled) {
|
|
30
|
+
opacity: 0.9;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:active:not(&--disabled) {
|
|
34
|
+
opacity: 0.8;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@mixin flex-center {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@mixin text-truncate {
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@use 'variables' as *;
|
|
2
|
+
|
|
3
|
+
*,
|
|
4
|
+
*::before,
|
|
5
|
+
*::after {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
html {
|
|
12
|
+
font-family: $font-family;
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
line-height: 1.5;
|
|
15
|
+
-webkit-font-smoothing: antialiased;
|
|
16
|
+
-moz-osx-font-smoothing: grayscale;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
color: #333;
|
|
21
|
+
background-color: #fff;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
button,
|
|
25
|
+
input,
|
|
26
|
+
textarea,
|
|
27
|
+
select {
|
|
28
|
+
font-family: inherit;
|
|
29
|
+
font-size: inherit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
a {
|
|
33
|
+
color: $primary-color;
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
|
|
36
|
+
&:hover {
|
|
37
|
+
color: $primary-color-hover;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ul,
|
|
42
|
+
ol {
|
|
43
|
+
list-style: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
img {
|
|
47
|
+
max-width: 100%;
|
|
48
|
+
height: auto;
|
|
49
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// 命名空间
|
|
2
|
+
$namespace: fire;
|
|
3
|
+
|
|
4
|
+
// 颜色变量
|
|
5
|
+
$primary-color: #3b82f6;
|
|
6
|
+
$primary-color-hover: #2563eb;
|
|
7
|
+
$success-color: #22c55e;
|
|
8
|
+
$warning-color: #f59e0b;
|
|
9
|
+
$danger-color: #ef4444;
|
|
10
|
+
$info-color: #6b7280;
|
|
11
|
+
|
|
12
|
+
// 字体
|
|
13
|
+
$font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
|
14
|
+
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
15
|
+
$font-size-base: 14px;
|
|
16
|
+
$font-size-small: 12px;
|
|
17
|
+
$font-size-large: 16px;
|
|
18
|
+
|
|
19
|
+
// 间距
|
|
20
|
+
$spacing-xs: 4px;
|
|
21
|
+
$spacing-sm: 8px;
|
|
22
|
+
$spacing-md: 16px;
|
|
23
|
+
$spacing-lg: 24px;
|
|
24
|
+
$spacing-xl: 32px;
|
|
25
|
+
|
|
26
|
+
// 圆角
|
|
27
|
+
$border-radius-base: 4px;
|
|
28
|
+
$border-radius-round: 20px;
|
|
29
|
+
|
|
30
|
+
// 阴影
|
|
31
|
+
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
32
|
+
$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
33
|
+
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
34
|
+
|
|
35
|
+
// 过渡
|
|
36
|
+
$transition-base: all 0.2s ease-in-out;
|
|
37
|
+
|
|
38
|
+
// Button 组件变量
|
|
39
|
+
// 默认按钮
|
|
40
|
+
$button-default-bg: #ffffff;
|
|
41
|
+
$button-default-border: #d9d9d9;
|
|
42
|
+
$button-default-text: #333333;
|
|
43
|
+
|
|
44
|
+
// 主要按钮
|
|
45
|
+
$button-primary-bg: #3b82f6;
|
|
46
|
+
$button-primary-border: #3b82f6;
|
|
47
|
+
$button-primary-text: #ffffff;
|
|
48
|
+
$button-primary-hover: #2563eb;
|
|
49
|
+
|
|
50
|
+
// 成功按钮
|
|
51
|
+
$button-success-bg: #22c55e;
|
|
52
|
+
$button-success-border: #22c55e;
|
|
53
|
+
$button-success-text: #ffffff;
|
|
54
|
+
$button-success-hover: #16a34a;
|
|
55
|
+
|
|
56
|
+
// 警告按钮
|
|
57
|
+
$button-warning-bg: #f59e0b;
|
|
58
|
+
$button-warning-border: #f59e0b;
|
|
59
|
+
$button-warning-text: #ffffff;
|
|
60
|
+
$button-warning-hover: #d97706;
|
|
61
|
+
|
|
62
|
+
// 危险按钮
|
|
63
|
+
$button-danger-bg: #ef4444;
|
|
64
|
+
$button-danger-border: #ef4444;
|
|
65
|
+
$button-danger-text: #ffffff;
|
|
66
|
+
$button-danger-hover: #dc2626;
|
|
67
|
+
|
|
68
|
+
// 信息按钮
|
|
69
|
+
$button-info-bg: #6b7280;
|
|
70
|
+
$button-info-border: #6b7280;
|
|
71
|
+
$button-info-text: #ffffff;
|
|
72
|
+
$button-info-hover: #4b5563;
|
|
73
|
+
|
|
74
|
+
// Button 尺寸
|
|
75
|
+
$button-font-size-sm: 12px;
|
|
76
|
+
$button-font-size: 14px;
|
|
77
|
+
$button-font-size-lg: 16px;
|
|
78
|
+
|
|
79
|
+
$button-padding-vertical-sm: 4px;
|
|
80
|
+
$button-padding-horizontal-sm: 12px;
|
|
81
|
+
|
|
82
|
+
$button-padding-vertical: 8px;
|
|
83
|
+
$button-padding-horizontal: 16px;
|
|
84
|
+
|
|
85
|
+
$button-padding-vertical-lg: 12px;
|
|
86
|
+
$button-padding-horizontal-lg: 24px;
|
|
87
|
+
|
|
88
|
+
$button-border-radius: 4px;
|
|
89
|
+
|
|
90
|
+
// Input 组件变量
|
|
91
|
+
$input-bg: #ffffff;
|
|
92
|
+
$input-border: #d9d9d9;
|
|
93
|
+
$input-border-focus: #3b82f6;
|
|
94
|
+
$input-text: #333333;
|
|
95
|
+
$input-placeholder: #999999;
|
|
96
|
+
$input-disabled-bg: #f5f5f5;
|
|
97
|
+
|
|
98
|
+
$input-height-sm: 24px;
|
|
99
|
+
$input-height: 32px;
|
|
100
|
+
$input-height-lg: 40px;
|
|
101
|
+
|
|
102
|
+
$input-padding-sm: 0 8px;
|
|
103
|
+
$input-padding: 0 12px;
|
|
104
|
+
$input-padding-lg: 0 12px;
|
|
105
|
+
|
|
106
|
+
// Form 组件变量
|
|
107
|
+
$form-item-margin-bottom: 16px;
|
|
108
|
+
$form-label-width: 120px;
|
|
109
|
+
$form-label-color: #333333;
|
|
110
|
+
$form-error-color: #ef4444;
|
|
111
|
+
|
|
112
|
+
// Table 组件变量
|
|
113
|
+
$table-border: #e8e8e8;
|
|
114
|
+
$table-header-bg: #fafafa;
|
|
115
|
+
$table-header-text: #333333;
|
|
116
|
+
$table-row-hover: rgba(59, 130, 246, 0.1);
|
|
117
|
+
$table-stripe-bg: #f9f9f9;
|
|
118
|
+
|
|
119
|
+
// Tooltip 组件变量
|
|
120
|
+
$tooltip-bg: rgba(0, 0, 0, 0.75);
|
|
121
|
+
$tooltip-text: #ffffff;
|
|
122
|
+
$tooltip-padding: 8px 12px;
|
|
123
|
+
$tooltip-border-radius: 4px;
|
|
124
|
+
|
|
125
|
+
// Tips 组件变量
|
|
126
|
+
$tips-padding: 16px;
|
|
127
|
+
$tips-border-radius: 4px;
|
|
128
|
+
$tips-margin-bottom: 16px;
|
|
129
|
+
|
|
130
|
+
// 通用变量
|
|
131
|
+
$border-color: #d9d9d9;
|
|
132
|
+
$text-color: #333333;
|
|
133
|
+
$text-color-secondary: #666666;
|
|
134
|
+
$text-color-light: #999999;
|
|
135
|
+
$bg-color: #fafafa;
|
|
136
|
+
$bg-color-light: #f9f9f9;
|
|
137
|
+
$disabled-bg: #f5f5f5;
|