20261007220117
This commit is contained in:
1 parent
9217b3fa42
commit
8b1ec67609
275 files changed
+20026
-49299
No files matched your search
@@ -0,0 +1,213 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ButtonProps } from './types'
|
||||
|
||||
/**
|
||||
* 常用的操作按钮。
|
||||
*
|
||||
* 支持 7 种视觉类型、3 种尺寸,以及加载、禁用、撑满、纯图标等状态。
|
||||
*/
|
||||
defineOptions({ name: 'G3Button' })
|
||||
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
type: 'default',
|
||||
size: 'medium',
|
||||
htmlType: 'button',
|
||||
disabled: false,
|
||||
loading: false,
|
||||
block: false,
|
||||
iconOnly: false,
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击按钮时触发。
|
||||
* 禁用或加载中不会触发。
|
||||
*/
|
||||
const emit = defineEmits<{ click: [ev: MouseEvent] }>()
|
||||
|
||||
const classes = computed(() => [
|
||||
'g3-button',
|
||||
`g3-button--${props.type}`,
|
||||
`g3-button--${props.size}`,
|
||||
{
|
||||
'is-disabled': props.disabled,
|
||||
'is-loading': props.loading,
|
||||
'is-block': props.block,
|
||||
'is-icon-only': props.iconOnly,
|
||||
},
|
||||
])
|
||||
|
||||
function handleClick(ev: MouseEvent) {
|
||||
if (props.disabled || props.loading) {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
return
|
||||
}
|
||||
emit('click', ev)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:class="classes"
|
||||
:type="htmlType"
|
||||
:disabled="disabled || loading"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span v-if="loading" class="g3-button__spinner" aria-hidden="true" />
|
||||
<span v-if="$slots.icon" class="g3-button__icon"><slot name="icon" /></span>
|
||||
<span v-if="$slots.default" class="g3-button__content"><slot /></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/**
|
||||
* 不写 scoped:组件库用 g3- 前缀 + BEM 做隔离,便于消费方覆盖样式。
|
||||
* 每个变量都带兜底值,保证未引入 @g3soft/tokens 时组件仍可正常显示。
|
||||
*/
|
||||
.g3-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
height: var(--g3-control-height, 32px);
|
||||
padding: 0 15px;
|
||||
border: 1px solid var(--g3-border-color, #d9d9d9);
|
||||
border-radius: var(--g3-radius, 6px);
|
||||
background: var(--g3-bg-container, #fff);
|
||||
color: var(--g3-text-1, rgba(0, 0, 0, 0.88));
|
||||
font-family: inherit;
|
||||
font-size: var(--g3-font-size, 14px);
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all var(--g3-duration, 0.2s) var(--g3-ease, ease);
|
||||
}
|
||||
|
||||
.g3-button:hover:not(:disabled) {
|
||||
border-color: var(--g3-color-primary, #1677ff);
|
||||
color: var(--g3-color-primary, #1677ff);
|
||||
}
|
||||
|
||||
.g3-button:focus-visible {
|
||||
outline: 2px solid var(--g3-color-primary, #1677ff);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.g3-button:disabled {
|
||||
background: var(--g3-bg-disabled, rgba(0, 0, 0, 0.04));
|
||||
border-color: var(--g3-border-color, #d9d9d9);
|
||||
color: var(--g3-text-disabled, rgba(0, 0, 0, 0.25));
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ---------- 尺寸 ---------- */
|
||||
.g3-button--small {
|
||||
height: var(--g3-control-height-sm, 24px);
|
||||
padding: 0 7px;
|
||||
font-size: var(--g3-font-size-sm, 12px);
|
||||
}
|
||||
|
||||
.g3-button--large {
|
||||
height: var(--g3-control-height-lg, 40px);
|
||||
padding: 0 19px;
|
||||
font-size: var(--g3-font-size-lg, 16px);
|
||||
}
|
||||
|
||||
.g3-button.is-block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.g3-button.is-icon-only {
|
||||
width: var(--g3-control-height, 32px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.g3-button.is-icon-only.g3-button--small {
|
||||
width: var(--g3-control-height-sm, 24px);
|
||||
}
|
||||
|
||||
.g3-button.is-icon-only.g3-button--large {
|
||||
width: var(--g3-control-height-lg, 40px);
|
||||
}
|
||||
|
||||
/* ---------- primary ---------- */
|
||||
.g3-button--primary {
|
||||
background: var(--g3-color-primary, #1677ff);
|
||||
border-color: var(--g3-color-primary, #1677ff);
|
||||
color: var(--g3-text-inverse, #fff);
|
||||
}
|
||||
|
||||
.g3-button--primary:hover:not(:disabled) {
|
||||
background: var(--g3-color-primary-hover, #4096ff);
|
||||
border-color: var(--g3-color-primary-hover, #4096ff);
|
||||
color: var(--g3-text-inverse, #fff);
|
||||
}
|
||||
|
||||
.g3-button--primary:active:not(:disabled) {
|
||||
background: var(--g3-color-primary-active, #0958d9);
|
||||
border-color: var(--g3-color-primary-active, #0958d9);
|
||||
}
|
||||
|
||||
.g3-button--primary:disabled {
|
||||
background: var(--g3-bg-disabled, rgba(0, 0, 0, 0.04));
|
||||
border-color: var(--g3-border-color, #d9d9d9);
|
||||
color: var(--g3-text-disabled, rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
|
||||
/* ---------- success / warning / danger ---------- */
|
||||
.g3-button--success {
|
||||
background: var(--g3-color-success, #52c41a);
|
||||
border-color: var(--g3-color-success, #52c41a);
|
||||
color: var(--g3-text-inverse, #fff);
|
||||
}
|
||||
|
||||
.g3-button--warning {
|
||||
background: var(--g3-color-warning, #faad14);
|
||||
border-color: var(--g3-color-warning, #faad14);
|
||||
color: var(--g3-text-inverse, #fff);
|
||||
}
|
||||
|
||||
.g3-button--danger {
|
||||
background: var(--g3-color-danger, #ff4d4f);
|
||||
border-color: var(--g3-color-danger, #ff4d4f);
|
||||
color: var(--g3-text-inverse, #fff);
|
||||
}
|
||||
|
||||
/* ---------- text / link ---------- */
|
||||
.g3-button--text,
|
||||
.g3-button--link {
|
||||
height: auto;
|
||||
padding: 0 4px;
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.g3-button--link {
|
||||
color: var(--g3-color-primary, #1677ff);
|
||||
}
|
||||
|
||||
/* ---------- loading ---------- */
|
||||
.g3-button.is-loading {
|
||||
cursor: default;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.g3-button__spinner {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 2px solid currentColor;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: g3-button-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes g3-button-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as G3Button } from './Button.vue'
|
||||
export type { ButtonProps, ButtonSize, ButtonType } from './types'
|
||||
@@ -0,0 +1,29 @@
|
||||
/** 按钮视觉类型 */
|
||||
export type ButtonType =
|
||||
| 'default'
|
||||
| 'primary'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'text'
|
||||
| 'link'
|
||||
|
||||
/** 按钮尺寸 */
|
||||
export type ButtonSize = 'small' | 'medium' | 'large'
|
||||
|
||||
export interface ButtonProps {
|
||||
/** 视觉类型 */
|
||||
type?: ButtonType
|
||||
/** 尺寸 */
|
||||
size?: ButtonSize
|
||||
/** 原生 button 的 type 属性 */
|
||||
htmlType?: 'button' | 'submit' | 'reset'
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean
|
||||
/** 是否加载中:同时禁止点击并展示加载图标 */
|
||||
loading?: boolean
|
||||
/** 是否撑满父容器宽度 */
|
||||
block?: boolean
|
||||
/** 是否仅图标:渲染为正方形按钮 */
|
||||
iconOnly?: boolean
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { App, Plugin } from 'vue'
|
||||
import G3Button from './button/Button.vue'
|
||||
|
||||
export { G3Button }
|
||||
export type { ButtonProps, ButtonSize, ButtonType } from './button/types'
|
||||
|
||||
/**
|
||||
* 可选的全量注册插件。
|
||||
* 按需 import 是主路径;这里只是为不熟悉按需引入的团队提供便利入口。
|
||||
*/
|
||||
const components = { G3Button }
|
||||
|
||||
export const G3UI: Plugin = {
|
||||
install(app: App) {
|
||||
Object.entries(components).forEach(([name, component]) => {
|
||||
app.component(name, component)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default G3UI
|
||||
Reference in new issue
Block a user