u
This commit is contained in:
commit
1b73170f78
16 files changed
+1197
-229
No files matched your search
@@ -94,6 +94,10 @@ $scroll-track-color: var(--ao-scroll-track-color);
|
|||||||
$bg-color-container: var(--ao-bg-color-container);
|
$bg-color-container: var(--ao-bg-color-container);
|
||||||
$bg-color-container-hover: var(--ao-bg-color-container-hover);
|
$bg-color-container-hover: var(--ao-bg-color-container-hover);
|
||||||
$bg-color-container-active: var(--ao-bg-color-container-active);
|
$bg-color-container-active: var(--ao-bg-color-container-active);
|
||||||
|
$bg-color-container-select: var(--ao-bg-color-container-select);
|
||||||
|
|
||||||
|
$bg-color-component: var(--ao-bg-color-component);
|
||||||
|
$border-color-component: var(--ao-border-color-component); // 组件边框(Input、Select、Card 等容器的外部边框)
|
||||||
|
|
||||||
// shadow
|
// shadow
|
||||||
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
||||||
|
|||||||
@@ -72,6 +72,10 @@
|
|||||||
--ao-bg-color-container: #242424;
|
--ao-bg-color-container: #242424;
|
||||||
--ao-bg-color-container-hover: #2c2c2c;
|
--ao-bg-color-container-hover: #2c2c2c;
|
||||||
--ao-bg-color-container-active: #4b4b4b;
|
--ao-bg-color-container-active: #4b4b4b;
|
||||||
|
--ao-bg-color-container-select: #5e5e5e;
|
||||||
|
|
||||||
|
--ao-bg-color-component: #393939;
|
||||||
|
--ao-border-color-component: #5e5e5e;
|
||||||
|
|
||||||
// shadow阴影
|
// shadow阴影
|
||||||
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
||||||
|
|||||||
@@ -73,6 +73,10 @@
|
|||||||
--ao-bg-color-container: #fff;
|
--ao-bg-color-container: #fff;
|
||||||
--ao-bg-color-container-hover: #f3f3f3;
|
--ao-bg-color-container-hover: #f3f3f3;
|
||||||
--ao-bg-color-container-active: #e8e8e8;
|
--ao-bg-color-container-active: #e8e8e8;
|
||||||
|
--ao-bg-color-container-select: #fff;
|
||||||
|
|
||||||
|
--ao-bg-color-component: #e8e8e8;
|
||||||
|
--ao-border-color-component: #ddd;
|
||||||
|
|
||||||
// shadow阴影
|
// shadow阴影
|
||||||
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
// 基础/下层 投影 hover 使用的组件包括:表格 /
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export { default as RadioGroup } from './radio/radio-group'
|
|||||||
export { default as Separator } from './separator'
|
export { default as Separator } from './separator'
|
||||||
export { default as Space } from './space'
|
export { default as Space } from './space'
|
||||||
export { default as Spin } from './spin'
|
export { default as Spin } from './spin'
|
||||||
export { default as Tag } from './tag'
|
export { default as Tag } from './tag/tag'
|
||||||
export { default as TagGroup } from './tag-group'
|
export { default as TagGroup } from './tag/tag-group'
|
||||||
export { default as Textarea } from './textarea'
|
export { default as Textarea } from './textarea'
|
||||||
export { default as Tooltip } from './tooltip'
|
export { default as Tooltip } from './tooltip'
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
[class*='#{variables.$prefix}-'] {
|
[class*='#{variables.$prefix}-'] {
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
font-family:
|
font-family:
|
||||||
Inter,
|
Inter,
|
||||||
-apple-system,
|
-apple-system,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface RadioGroupContext {
|
|||||||
value: string | number | boolean
|
value: string | number | boolean
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
slots: Slots
|
slots: Slots
|
||||||
|
type?: 'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'
|
||||||
handleChange: (value: string | number | boolean, e: Event) => void
|
handleChange: (value: string | number | boolean, e: Event) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const selectedValue = ref()
|
||||||
|
|
||||||
|
// 自定义字段名
|
||||||
|
const options = [
|
||||||
|
{ name: '选项一', id: 1, note: '这是额外信息 1' },
|
||||||
|
{ name: '选项二', id: 2, note: '这是额外信息 2' },
|
||||||
|
{ name: '选项三', id: 3 },
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleChange(val: string | number | boolean) {
|
||||||
|
console.log('当前选中值:', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleThemeLight() {
|
||||||
|
document.documentElement.setAttribute('theme-mode', 'light')
|
||||||
|
}
|
||||||
|
function toggleThemeDark() {
|
||||||
|
document.documentElement.setAttribute('theme-mode', 'dark')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AButton status="primary" @click="toggleThemeLight">
|
||||||
|
白天
|
||||||
|
</AButton>
|
||||||
|
<AButton status="primary" @click="toggleThemeDark">
|
||||||
|
黑天
|
||||||
|
</AButton>
|
||||||
|
|
||||||
|
<div class="demo-wrapper">
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>Default</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="default"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>filled</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="filled"
|
||||||
|
size="small"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>filled</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="filled"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>filled</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="filled"
|
||||||
|
size="large"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>primaryFilled</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="primaryFilled"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>outline</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="outline"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>card</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="card"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>pureCard</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue"
|
||||||
|
:options="options"
|
||||||
|
label-field="name"
|
||||||
|
value-field="id"
|
||||||
|
extra-field="note"
|
||||||
|
type="pureCard"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<h3>自定义插槽radio</h3>
|
||||||
|
<ARadioGroup
|
||||||
|
v-model="selectedValue" :options="options"
|
||||||
|
value-field="id"
|
||||||
|
>
|
||||||
|
<template #radio="{ data, checked }">
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
padding: '4px 8px',
|
||||||
|
border: checked ? '2px solid #409eff' : '1px solid #dcdfe6',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginRight: '4px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ checked ? '选中' : '未选中' }} - {{ data.name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ARadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 24px 16px;
|
||||||
|
min-height: 100vh;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主题背景色 */
|
||||||
|
:root[theme-mode='light'] .demo-wrapper {
|
||||||
|
}
|
||||||
|
:root[theme-mode='dark'] .demo-wrapper {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,14 +3,13 @@
|
|||||||
$radio-prefix: '#{$prefix}-radio';
|
$radio-prefix: '#{$prefix}-radio';
|
||||||
$radio-group-prefix: '#{$prefix}-radio-group';
|
$radio-group-prefix: '#{$prefix}-radio-group';
|
||||||
|
|
||||||
.#{$radio-prefix} {
|
.#{$radio-prefix}-default {
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
line-height: 18px;
|
|
||||||
&.#{$radio-prefix}-disabled {
|
&.#{$radio-prefix}-disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@@ -20,13 +19,20 @@ $radio-group-prefix: '#{$prefix}-radio-group';
|
|||||||
border-color: $primary-color;
|
border-color: $primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-checked .#{$radio-prefix}-main .#{$radio-prefix}-inner {
|
||||||
|
border-color: $primary-color;
|
||||||
|
&::after {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
background-color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.#{$radio-prefix}-main {
|
.#{$radio-prefix}-main {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
margin-top: 1px;
|
|
||||||
.#{$radio-prefix}-input {
|
.#{$radio-prefix}-input {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
@@ -66,7 +72,343 @@ $radio-group-prefix: '#{$prefix}-radio-group';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-content {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-label {
|
||||||
|
line-height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: $text-color-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-extra {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-group-prefix}-filled {
|
||||||
|
background-color: $bg-color-component;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
padding: 1.5px;
|
||||||
|
line-height: 26px;
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-small {
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-filled {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-medium {
|
||||||
|
line-height: 22px;
|
||||||
|
.#{$radio-prefix}-filled {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-large {
|
||||||
|
line-height: 26px;
|
||||||
|
.#{$radio-prefix}-filled {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-filled {
|
||||||
|
margin: 1.5px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 26px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-bottom-left-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-content {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-checked {
|
||||||
|
color: $text-color-base;
|
||||||
|
background-color: $bg-color-container-select;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.#{$radio-prefix}-disabled):not(.#{$radio-prefix}-checked):hover {
|
||||||
|
color: $text-color-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 1px;
|
||||||
|
height: 80%;
|
||||||
|
background-color: $border-color-component;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0, 0, 0.15, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-group-prefix}-primaryFilled {
|
||||||
|
background-color: $bg-color-component;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
padding: 1.5px;
|
||||||
|
line-height: 26px;
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-small {
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-primaryFilled {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-medium {
|
||||||
|
line-height: 22px;
|
||||||
|
.#{$radio-prefix}-primaryFilled {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-large {
|
||||||
|
line-height: 26px;
|
||||||
|
.#{$radio-prefix}-primaryFilled {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-primaryFilled {
|
||||||
|
margin: 1.5px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 26px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-bottom-left-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-content {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-checked {
|
||||||
|
color: $text-color-anti;
|
||||||
|
background-color: $primary-color;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.#{$radio-prefix}-disabled):not(.#{$radio-prefix}-checked):hover {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 1px;
|
||||||
|
height: 80%;
|
||||||
|
background-color: $border-color-component;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0, 0, 0.15, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-group-prefix}-outline {
|
||||||
|
padding: 1.5px;
|
||||||
|
line-height: 26px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-small {
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-outline {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-medium {
|
||||||
|
line-height: 22px;
|
||||||
|
.#{$radio-prefix}-outline {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-group-prefix}-large {
|
||||||
|
line-height: 26px;
|
||||||
|
.#{$radio-prefix}-outline {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-outline:last-child {
|
||||||
|
border-right-color: $border-color;
|
||||||
|
}
|
||||||
|
.#{$radio-prefix}-outline.#{$radio-prefix}-checked.#{$radio-prefix}-outline:last-child {
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
.#{$radio-prefix}-outline.#{$radio-prefix}-checked + .#{$radio-prefix}-outline {
|
||||||
|
border-left-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-outline {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 26px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-right-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-bottom-left-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.#{$radio-prefix}-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.#{$radio-prefix}-content {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
&:not(.#{$radio-prefix}-disabled):hover {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
&.#{$radio-prefix}-checked {
|
||||||
|
color: $primary-color;
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-card {
|
||||||
|
color: $text-color-base;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:hover .#{$radio-prefix}-main {
|
||||||
|
background-color: $bg-color-component;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-checked {
|
||||||
|
border-color: $primary-color;
|
||||||
|
background-color: $status-info-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .#{$radio-prefix}-main .#{$radio-prefix}-inner {
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
&.#{$radio-prefix}-checked .#{$radio-prefix}-main .#{$radio-prefix}-inner {
|
&.#{$radio-prefix}-checked .#{$radio-prefix}-main .#{$radio-prefix}-inner {
|
||||||
border-color: $primary-color;
|
border-color: $primary-color;
|
||||||
&::after {
|
&::after {
|
||||||
@@ -75,12 +417,118 @@ $radio-group-prefix: '#{$prefix}-radio-group';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-main {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-input {
|
||||||
|
opacity: 0;
|
||||||
|
outline: 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
margin: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-inner {
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: $bg-color-container;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: 0;
|
||||||
|
&::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background-color: transparent;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
transition:
|
||||||
|
transform 0.1s ease-in,
|
||||||
|
background-color 0.1s ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.#{$radio-prefix}-content {
|
.#{$radio-prefix}-content {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
vertical-align: middle;
|
display: inline-block;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-label {
|
||||||
|
line-height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: $text-color-base;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-extra {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$radio-group-prefix} {
|
.#{$radio-prefix}-pureCard {
|
||||||
|
color: $text-color-base;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $bg-color-component;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$radio-prefix}-checked {
|
||||||
|
border-color: $primary-color;
|
||||||
|
background-color: $status-info-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-content {
|
||||||
|
font-size: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.#{$radio-prefix}-label {
|
||||||
|
line-height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: $text-color-base;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$radio-prefix}-extra {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-color-secondary;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,10 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'value',
|
default: 'value',
|
||||||
},
|
},
|
||||||
|
extraField: {
|
||||||
|
type: String,
|
||||||
|
default: 'extra',
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Array as PropType<Array<string | number | RadioOption>>,
|
type: Array as PropType<Array<string | number | RadioOption>>,
|
||||||
default: [],
|
default: [],
|
||||||
@@ -44,6 +48,14 @@ export default defineComponent({
|
|||||||
type: Object as PropType<Record<string, any>>,
|
type: Object as PropType<Record<string, any>>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
|
type: {
|
||||||
|
type: String as PropType<'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'>,
|
||||||
|
default: 'default',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String as PropType<'small' | 'medium' | 'large'>,
|
||||||
|
default: 'medium',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'change'],
|
emits: ['update:modelValue', 'change'],
|
||||||
setup(props, { emit, slots }) {
|
setup(props, { emit, slots }) {
|
||||||
@@ -64,12 +76,13 @@ export default defineComponent({
|
|||||||
emit('change', val, e)
|
emit('change', val, e)
|
||||||
}
|
}
|
||||||
const groupDisabled = computed(() => props.disabled)
|
const groupDisabled = computed(() => props.disabled)
|
||||||
|
const groupType = computed(() => props.type)
|
||||||
// 提供 context 给 Radio 使用
|
// 提供 context 给 Radio 使用
|
||||||
provide(radioGroupKey, reactive({
|
provide(radioGroupKey, reactive({
|
||||||
name: 'AoRadioGroup' as const,
|
name: 'AoRadioGroup' as const,
|
||||||
value: _value,
|
value: _value,
|
||||||
disabled: groupDisabled,
|
disabled: groupDisabled,
|
||||||
|
type: groupType,
|
||||||
slots,
|
slots,
|
||||||
handleChange,
|
handleChange,
|
||||||
}))
|
}))
|
||||||
@@ -88,12 +101,31 @@ export default defineComponent({
|
|||||||
? opt[props.labelField]()
|
? opt[props.labelField]()
|
||||||
: opt[props.labelField]
|
: opt[props.labelField]
|
||||||
|
|
||||||
|
const extraContent = slots.extra
|
||||||
|
? slots.extra({ data: opt })
|
||||||
|
: typeof opt[props.extraField] === 'function'
|
||||||
|
? opt[props.extraField]()
|
||||||
|
: opt[props.extraField]
|
||||||
|
|
||||||
|
if (slots.radio) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={opt[props.valueField]}
|
||||||
|
onClick={e => handleChange(opt[props.valueField], e)} // 内部统一处理点击
|
||||||
|
>
|
||||||
|
{slots.radio({ data: opt, checked: _value.value === opt[props.valueField] })}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Radio
|
<Radio
|
||||||
key={opt[props.valueField]}
|
key={opt[props.valueField]}
|
||||||
value={opt[props.valueField]}
|
value={opt[props.valueField]}
|
||||||
disabled={opt.disabled ?? props.disabled}
|
disabled={opt.disabled ?? props.disabled}
|
||||||
modelValue={_value.value === opt[props.valueField]}
|
modelValue={_value.value === opt[props.valueField]}
|
||||||
|
extra={extraContent}
|
||||||
|
type={props.type}
|
||||||
>
|
>
|
||||||
{labelContent}
|
{labelContent}
|
||||||
</Radio>
|
</Radio>
|
||||||
@@ -101,12 +133,30 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const spaceSize
|
||||||
|
= ['default', 'card', 'pureCard'].includes(groupType.value)
|
||||||
|
? [6, 6]
|
||||||
|
: props.spaceProps.size ?? 0
|
||||||
|
|
||||||
|
const sizeClass = computed(() => {
|
||||||
|
if (['filled', 'primaryFilled', 'outline'].includes(groupType.value)) {
|
||||||
|
return `${prefixCls}-${props.size}`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class={[prefixCls]}>
|
<Space
|
||||||
<Space {...props.spaceProps}>
|
align="start"
|
||||||
{props.options.length > 0 ? renderOptions() : slots.default?.()}
|
size={spaceSize}
|
||||||
</Space>
|
{...props.spaceProps}
|
||||||
</div>
|
class={[
|
||||||
|
`${prefixCls}-${groupType.value}`,
|
||||||
|
sizeClass.value,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{props.options.length > 0 ? renderOptions() : slots.default?.()}
|
||||||
|
</Space>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -31,6 +31,10 @@ export default defineComponent({
|
|||||||
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
type: {
|
||||||
|
type: String as PropType<'default' | 'filled' | 'primaryFilled' | 'card' | 'pureCard' | 'outline'>,
|
||||||
|
default: 'default',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'change'],
|
emits: ['update:modelValue', 'change'],
|
||||||
setup(props, { slots, emit }) {
|
setup(props, { slots, emit }) {
|
||||||
@@ -42,11 +46,10 @@ export default defineComponent({
|
|||||||
const isDisabled = computed(() => props.disabled || radioGroupCtx?.disabled)
|
const isDisabled = computed(() => props.disabled || radioGroupCtx?.disabled)
|
||||||
const _checked = ref(props.defaultChecked)
|
const _checked = ref(props.defaultChecked)
|
||||||
|
|
||||||
// 判断是否选中
|
|
||||||
const isChecked = computed(() => {
|
const isChecked = computed(() => {
|
||||||
if (inGroup.value)
|
if (inGroup.value)
|
||||||
return props.value === radioGroupCtx!.value
|
return props.value === radioGroupCtx!.value
|
||||||
return _checked.value
|
return props.modelValue ?? _checked.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = (e: Event) => {
|
const handleClick = (e: Event) => {
|
||||||
@@ -61,17 +64,16 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const newValue = props.allowUncheck ? !isChecked.value : true
|
const newValue = props.allowUncheck ? !isChecked.value : true
|
||||||
|
|
||||||
_checked.value = newValue
|
_checked.value = newValue
|
||||||
emit('update:modelValue', newValue)
|
emit('update:modelValue', newValue)
|
||||||
emit('change', props.value, e)
|
emit('change', props.value, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => (
|
const renderDefault = () => (
|
||||||
<label
|
<label
|
||||||
class={[
|
class={[
|
||||||
prefixCls,
|
`${prefixCls}-default`,
|
||||||
isDisabled.value ? `${prefixCls}-disabled` : '',
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
isChecked.value ? `${prefixCls}-checked` : '',
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
]}
|
]}
|
||||||
@@ -87,10 +89,140 @@ export default defineComponent({
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div class={`${prefixCls}-content`}>
|
<div class={`${prefixCls}-content`}>
|
||||||
{slots.default?.()}
|
<div class={`${prefixCls}-label`}>{slots.default?.()}</div>
|
||||||
|
{props.extra && (
|
||||||
|
<div class={`${prefixCls}-extra`}>
|
||||||
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const renderFilled = () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
`${prefixCls}-filled`,
|
||||||
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
|
]}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
class={`${prefixCls}-input`}
|
||||||
|
/>
|
||||||
|
<span class={`${prefixCls}-content`}>
|
||||||
|
{slots.default?.()}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderPrimaryFilled = () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
`${prefixCls}-primaryFilled`,
|
||||||
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
|
]}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
class={`${prefixCls}-input`}
|
||||||
|
/>
|
||||||
|
<span class={`${prefixCls}-content`}>
|
||||||
|
{slots.default?.()}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderCard = () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
`${prefixCls}-card`,
|
||||||
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
|
]}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<span class={`${prefixCls}-main`}>
|
||||||
|
<span class={`${prefixCls}-inner`} />
|
||||||
|
<input
|
||||||
|
class={`${prefixCls}-input`}
|
||||||
|
type="radio"
|
||||||
|
disabled={isDisabled.value}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div class={`${prefixCls}-content`}>
|
||||||
|
<div class={`${prefixCls}-label`}>{slots.default?.()}</div>
|
||||||
|
{props.extra && (
|
||||||
|
<div class={`${prefixCls}-extra`}>
|
||||||
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderPureCard = () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
`${prefixCls}-pureCard`,
|
||||||
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
|
]}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div class={`${prefixCls}-content`}>
|
||||||
|
<div class={`${prefixCls}-label`}>{slots.default?.()}</div>
|
||||||
|
{props.extra && (
|
||||||
|
<div class={`${prefixCls}-extra`}>
|
||||||
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderOutline = () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
`${prefixCls}-outline`,
|
||||||
|
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||||
|
isChecked.value ? `${prefixCls}-checked` : '',
|
||||||
|
]}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
class={`${prefixCls}-input`}
|
||||||
|
/>
|
||||||
|
<span class={`${prefixCls}-content`}>
|
||||||
|
{slots.default?.()}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'filled':
|
||||||
|
return renderFilled()
|
||||||
|
case 'primaryFilled':
|
||||||
|
return renderPrimaryFilled()
|
||||||
|
case 'card':
|
||||||
|
return renderCard()
|
||||||
|
case 'pureCard':
|
||||||
|
return renderPureCard()
|
||||||
|
case 'outline':
|
||||||
|
return renderOutline()
|
||||||
|
default:
|
||||||
|
return renderDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -2,7 +2,7 @@ import type { PropType, VNode } from 'vue'
|
|||||||
import { cloneVNode, defineComponent } from 'vue'
|
import { cloneVNode, defineComponent } from 'vue'
|
||||||
import { useSelectable } from '../_hooks/useSelectable'
|
import { useSelectable } from '../_hooks/useSelectable'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Tag from '../tag/index'
|
import Tag from '../tag/tag'
|
||||||
|
|
||||||
interface TagOption {
|
interface TagOption {
|
||||||
label?: string
|
label?: string
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { InjectionKey, Slots } from 'vue'
|
||||||
|
|
||||||
|
export interface TagGroupContext {
|
||||||
|
name: 'AoTagGroup'
|
||||||
|
value: Array<string | number | boolean>
|
||||||
|
disabled: boolean
|
||||||
|
checkable: boolean
|
||||||
|
slots: Slots
|
||||||
|
multiple: boolean
|
||||||
|
handleChange: (value: string | number | boolean, e: Event) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagGroupKey: InjectionKey<TagGroupContext> = Symbol('TagGroup')
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import type { PropType } from 'vue'
|
|
||||||
import { defineComponent, ref } from 'vue'
|
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
|
||||||
import Icon from '../icon/index'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Tag',
|
|
||||||
props: {
|
|
||||||
shape: {
|
|
||||||
type: String as PropType<'square' | 'round' | 'mark'>,
|
|
||||||
default: 'square',
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: String as PropType<'small' | 'medium' | 'large'>,
|
|
||||||
default: 'medium',
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String as () => 'solid' | 'light' | 'outline' | 'outline-light',
|
|
||||||
default: 'solid',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default',
|
|
||||||
default: 'default',
|
|
||||||
},
|
|
||||||
checkable: { type: Boolean, default: false },
|
|
||||||
checked: { type: Boolean, default: false },
|
|
||||||
value: { type: [String, Number], required: false },
|
|
||||||
checkedProps: {
|
|
||||||
type: Object as PropType<{ type?: 'solid' | 'light' | 'outline' | 'outline-light', status?: 'primary' | 'danger' | 'warning' | 'success' | 'default' }>,
|
|
||||||
default: () => ({
|
|
||||||
type: 'solid',
|
|
||||||
status: 'primary',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
uncheckedProps: {
|
|
||||||
type: Object as PropType<{ type?: 'solid' | 'light' | 'outline' | 'outline-light', status?: 'primary' | 'danger' | 'warning' | 'success' | 'default' }>,
|
|
||||||
default: () => ({
|
|
||||||
type: 'solid',
|
|
||||||
status: 'default',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
showClose: { type: Boolean, default: false },
|
|
||||||
icon: { type: String, default: '' },
|
|
||||||
disabled: { type: Boolean, default: false },
|
|
||||||
class: { type: [String, Array, Object], default: '' },
|
|
||||||
style: { type: Object, default: () => ({}) },
|
|
||||||
},
|
|
||||||
emits: ['click', 'close', 'update:checked'],
|
|
||||||
setup(props, { slots, emit }) {
|
|
||||||
const prefixCls = getPrefixCls('tag')
|
|
||||||
const visible = ref(true)
|
|
||||||
|
|
||||||
const handleClick = (event: MouseEvent) => {
|
|
||||||
if (props.disabled)
|
|
||||||
return
|
|
||||||
|
|
||||||
// 如果 checkable,触发 update:checked
|
|
||||||
if (props.checkable) {
|
|
||||||
emit('update:checked', !props.checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('click', { value: props.value, checked: props.checked, event })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = (event: MouseEvent) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
visible.value = false
|
|
||||||
emit('close', { value: props.value, event })
|
|
||||||
}
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
visible.value && (() => {
|
|
||||||
const renderType = props.checkable
|
|
||||||
? props.checked
|
|
||||||
? props.checkedProps.type
|
|
||||||
: props.uncheckedProps.type
|
|
||||||
: props.type
|
|
||||||
|
|
||||||
const renderStatus = props.checkable
|
|
||||||
? props.checked
|
|
||||||
? props.checkedProps.status
|
|
||||||
: props.uncheckedProps.status
|
|
||||||
: props.status
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={[
|
|
||||||
prefixCls,
|
|
||||||
`${prefixCls}-size-${props.size}`,
|
|
||||||
`${prefixCls}-shape-${props.shape}`,
|
|
||||||
`${prefixCls}-type-${renderType}`,
|
|
||||||
`${prefixCls}-status-${renderStatus}`,
|
|
||||||
{ [`${prefixCls}-check`]: props.checkable },
|
|
||||||
{ [`${prefixCls}-checked`]: props.checked },
|
|
||||||
{ [`${prefixCls}-disabled`]: props.disabled },
|
|
||||||
props.class,
|
|
||||||
]}
|
|
||||||
style={{ ...props.style }}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
{props.icon && <Icon icon={props.icon} class={`${prefixCls}-icon`} />}
|
|
||||||
<span>{slots.default?.()}</span>
|
|
||||||
{!props.disabled && props.showClose && (
|
|
||||||
<Icon icon="ix:close" class={`${prefixCls}-close`} onClick={handleClose} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import type { PropType, RenderFunction } from 'vue'
|
||||||
|
import { computed, defineComponent, provide, reactive, ref, watch } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
import Tag from '../tag/tag'
|
||||||
|
import { tagGroupKey } from './context'
|
||||||
|
|
||||||
|
interface TagOption {
|
||||||
|
label?: string | RenderFunction
|
||||||
|
value?: string | boolean | number
|
||||||
|
disabled?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ATagGroup',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Array as PropType<Array<string | number | boolean>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
type: Array as PropType<Array<string | number | boolean>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
multiple: { type: Boolean, default: true }, // 单选也用数组,只限制长度为1即可
|
||||||
|
options: { type: Array as PropType<TagOption[]>, default: () => [] },
|
||||||
|
labelField: { type: String, default: 'label' },
|
||||||
|
valueField: { type: String, default: 'value' },
|
||||||
|
checkedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'primary' }) },
|
||||||
|
uncheckedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'default' }) },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
class: { type: [String, Array, Object], default: '' },
|
||||||
|
style: { type: Object, default: () => ({}) },
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
// 统一为数组
|
||||||
|
const _value = ref<Array<string | number | boolean>>(
|
||||||
|
Array.isArray(props.modelValue) ? props.modelValue : props.defaultValue ?? [],
|
||||||
|
)
|
||||||
|
|
||||||
|
const prefixCls = getPrefixCls('tag-group')
|
||||||
|
const groupDisabled = computed(() => props.disabled)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if (Array.isArray(newVal)) {
|
||||||
|
_value.value = newVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChange = (val: string | number | boolean, e: Event) => {
|
||||||
|
const arr = [..._value.value]
|
||||||
|
|
||||||
|
const idx = arr.indexOf(val)
|
||||||
|
if (idx >= 0) {
|
||||||
|
arr.splice(idx, 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!props.multiple)
|
||||||
|
arr.length = 0 // 单选清空已有值
|
||||||
|
arr.push(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
_value.value = arr
|
||||||
|
emit('update:modelValue', _value.value)
|
||||||
|
emit('change', _value.value, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(tagGroupKey, reactive({
|
||||||
|
name: 'AoTagGroup' as const,
|
||||||
|
value: _value,
|
||||||
|
disabled: groupDisabled,
|
||||||
|
slots,
|
||||||
|
checkable: true,
|
||||||
|
multiple: props.multiple,
|
||||||
|
handleChange,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const renderOptions = () => {
|
||||||
|
return props.options.map((option) => {
|
||||||
|
const opt = typeof option === 'string' || typeof option === 'number'
|
||||||
|
? { [props.labelField]: String(option), [props.valueField]: option }
|
||||||
|
: option
|
||||||
|
|
||||||
|
const labelContent = slots.label
|
||||||
|
? slots.label({ data: opt })
|
||||||
|
: typeof opt[props.labelField] === 'function'
|
||||||
|
? opt[props.labelField]()
|
||||||
|
: opt[props.labelField]
|
||||||
|
|
||||||
|
const isChecked = _value.value.includes(opt[props.valueField])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
key={opt[props.valueField]}
|
||||||
|
value={opt[props.valueField]}
|
||||||
|
disabled={opt.disabled ?? props.disabled}
|
||||||
|
checkable
|
||||||
|
checked={isChecked}
|
||||||
|
checkedProps={props.checkedProps}
|
||||||
|
uncheckedProps={props.uncheckedProps}
|
||||||
|
>
|
||||||
|
{labelContent}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class={[prefixCls, props.class]} style={props.style}>
|
||||||
|
{props.options.length > 0 ? renderOptions() : slots.default?.()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { TagGroupContext } from './context'
|
||||||
|
import { computed, defineComponent, inject, ref } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
import Icon from '../icon/index'
|
||||||
|
import { tagGroupKey } from './context'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Tag',
|
||||||
|
props: {
|
||||||
|
shape: { type: String as PropType<'square' | 'round' | 'mark'>, default: 'square' },
|
||||||
|
size: { type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium' },
|
||||||
|
type: { type: String as () => 'solid' | 'light' | 'outline' | 'outline-light', default: 'solid' },
|
||||||
|
status: { type: String as () => 'primary' | 'danger' | 'warning' | 'success' | 'default', default: 'default' },
|
||||||
|
checkable: { type: Boolean, default: false },
|
||||||
|
value: { type: [String, Number, Boolean] as PropType<string | number | boolean>, default: true },
|
||||||
|
checked: { type: Boolean, default: undefined },
|
||||||
|
modelValue: { type: Boolean, default: undefined },
|
||||||
|
defaultChecked: { type: Boolean, default: false },
|
||||||
|
checkedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'primary' }) },
|
||||||
|
uncheckedProps: { type: Object as PropType<any>, default: () => ({ type: 'solid', status: 'default' }) },
|
||||||
|
showClose: { type: Boolean, default: false },
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
class: { type: [String, Array, Object], default: '' },
|
||||||
|
style: { type: Object, default: () => ({}) },
|
||||||
|
},
|
||||||
|
emits: ['click', 'close', 'update:checked', 'update:modelValue'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const prefixCls = getPrefixCls('tag')
|
||||||
|
const tagGroupCtx = inject<TagGroupContext | undefined>(tagGroupKey, undefined)
|
||||||
|
const inGroup = computed(() => !!tagGroupCtx)
|
||||||
|
const _checked = ref(props.defaultChecked)
|
||||||
|
const visible = ref(true)
|
||||||
|
|
||||||
|
const isDisabled = computed(() => props.disabled || tagGroupCtx?.disabled)
|
||||||
|
const isChecked = computed(() => {
|
||||||
|
if (inGroup.value) {
|
||||||
|
const groupValue = tagGroupCtx!.value
|
||||||
|
return groupValue.includes(props.value)
|
||||||
|
}
|
||||||
|
return props.checked ?? props.modelValue ?? _checked.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCheckable = computed(() => props.checkable || tagGroupCtx?.checkable)
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
if (isDisabled.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (inGroup.value) {
|
||||||
|
// 多选:切换
|
||||||
|
if (tagGroupCtx!.multiple) {
|
||||||
|
tagGroupCtx!.handleChange(props.value, event)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 单选:清空已有值或选中新值
|
||||||
|
const newValue = isChecked.value ? '' : props.value
|
||||||
|
tagGroupCtx!.handleChange(newValue, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 独立 Tag 自己控制
|
||||||
|
const newChecked = !isChecked.value
|
||||||
|
_checked.value = newChecked
|
||||||
|
emit('update:modelValue', newChecked)
|
||||||
|
emit('update:checked', newChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = (event: MouseEvent) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
visible.value = false
|
||||||
|
emit('close', { value: props.value, event })
|
||||||
|
}
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
visible.value && (() => {
|
||||||
|
const renderType = isCheckable.value
|
||||||
|
? isChecked.value
|
||||||
|
? props.checkedProps.type
|
||||||
|
: props.uncheckedProps.type
|
||||||
|
: props.type
|
||||||
|
|
||||||
|
const renderStatus = isCheckable.value
|
||||||
|
? isChecked.value
|
||||||
|
? props.checkedProps.status
|
||||||
|
: props.uncheckedProps.status
|
||||||
|
: props.status
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
prefixCls,
|
||||||
|
`${prefixCls}-size-${props.size}`,
|
||||||
|
`${prefixCls}-shape-${props.shape}`,
|
||||||
|
`${prefixCls}-type-${renderType}`,
|
||||||
|
`${prefixCls}-status-${renderStatus}`,
|
||||||
|
{ [`${prefixCls}-check`]: isCheckable.value },
|
||||||
|
{ [`${prefixCls}-checked`]: isChecked.value },
|
||||||
|
{ [`${prefixCls}-disabled`]: isDisabled.value },
|
||||||
|
props.class,
|
||||||
|
]}
|
||||||
|
style={{ ...props.style }}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{props.icon && <Icon icon={props.icon} class={`${prefixCls}-icon`} />}
|
||||||
|
<span>{slots.default?.()}</span>
|
||||||
|
{!props.disabled && props.showClose && (
|
||||||
|
<Icon icon="ix:close" class={`${prefixCls}-close`} onClick={handleClose} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,110 +1,101 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// Group 示例 1: 基本使用 + v-model
|
interface TagOption {
|
||||||
const selectedValue = ref('B')
|
label: string
|
||||||
|
value: string | number
|
||||||
// Group 示例 2: options 数组渲染
|
|
||||||
const options = [
|
|
||||||
{ label: 'XXX', value: 'X' },
|
|
||||||
{ label: '选项 Y', value: 'Y' },
|
|
||||||
{ label: '选项 Z', value: 'Z' },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 单独 Radio 使用
|
|
||||||
const singleChecked = ref(false)
|
|
||||||
|
|
||||||
// Group Disabled
|
|
||||||
const groupDisabled = ref(false)
|
|
||||||
|
|
||||||
function toggleGroupDisabled() {
|
|
||||||
|
|
||||||
groupDisabled.value = !groupDisabled.value
|
|
||||||
console.log(groupDisabled.value);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChange(val) {
|
const tagOptions: TagOption[] = [
|
||||||
console.log('选中值:', val)
|
{ label: '标签1', value: 1 },
|
||||||
|
{ label: '标签2', value: 2 },
|
||||||
|
{ label: '标签3', value: 3 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const customOptions: TagOption[] = [
|
||||||
|
{ label: '成功', value: 'success' },
|
||||||
|
{ label: '警告', value: 'warning' },
|
||||||
|
{ label: '错误', value: 'error' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 多选示例
|
||||||
|
const multiSelected = ref<(string | number)[]>([1, 2])
|
||||||
|
function handleMultiChange(selectedOptions: TagOption[]) {
|
||||||
|
console.log('多选 change:', selectedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单选示例
|
||||||
|
const singleSelected = ref<(string | number)[]>([2])
|
||||||
|
function handleSingleChange(selectedOptions: TagOption[]) {
|
||||||
|
console.log('单选 change:', selectedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// slot 渲染示例
|
||||||
|
const slotSelected = ref<(string | number)[]>(['b'])
|
||||||
|
|
||||||
|
// 自定义样式示例
|
||||||
|
const customSelected = ref<(string | number)[]>(['success'])
|
||||||
|
function handleCustomChange(selectedOptions: TagOption[]) {
|
||||||
|
console.log('自定义样式 change:', selectedOptions)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="demo-wrapper">
|
<div style="padding: 20px;">
|
||||||
<div class="demo-container">
|
<h2>ATagGroup 使用示例</h2>
|
||||||
<h3>ARadioGroup - 基本使用</h3>
|
|
||||||
<ARadioGroup
|
|
||||||
v-model="selectedValue"
|
|
||||||
default-value="B"
|
|
||||||
:disabled="groupDisabled"
|
|
||||||
class="demo-radio-group"
|
|
||||||
>
|
|
||||||
<ARadio value="A" extra="额外信息">选项 A</ARadio>
|
|
||||||
<ARadio value="B" >选项 B</ARadio>
|
|
||||||
<ARadio value="C">选项 C</ARadio>
|
|
||||||
</ARadioGroup>
|
|
||||||
<p>当前选中: {{ selectedValue }}</p>
|
|
||||||
<button @click="toggleGroupDisabled" class="demo-btn">
|
|
||||||
切换 Group Disabled
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<h3>ARadioGroup - 使用 options 数组渲染</h3>
|
<!-- 1. 多选 options 渲染 -->
|
||||||
<ARadioGroup
|
<section style="margin-bottom: 20px;">
|
||||||
v-model="selectedValue"
|
<h3>多选 - Options 渲染</h3>
|
||||||
:options="options"
|
<ATagGroup
|
||||||
@change="handleChange"
|
v-model="multiSelected"
|
||||||
class="demo-radio-group"
|
:options="tagOptions"
|
||||||
|
multiple
|
||||||
|
@change="handleMultiChange"
|
||||||
/>
|
/>
|
||||||
<p>当前选中: {{ selectedValue }}</p>
|
<p>选中值: {{ multiSelected }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<h3>单独 ARadio 使用</h3>
|
<!-- 2. 单选 options 渲染 -->
|
||||||
<ARadio v-model="singleChecked" value="X" :allow-uncheck="true">
|
<section style="margin-bottom: 20px;">
|
||||||
单选 X
|
<h3>单选 - Options 渲染</h3>
|
||||||
</ARadio>
|
<ATagGroup
|
||||||
<p>是否选中: {{ singleChecked }}</p>
|
v-model="singleSelected"
|
||||||
</div>
|
:options="tagOptions"
|
||||||
|
:multiple="false"
|
||||||
|
@change="handleSingleChange"
|
||||||
|
/>
|
||||||
|
<p>选中值: {{ singleSelected }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 3. Slot 渲染 -->
|
||||||
|
<section style="margin-bottom: 20px;">
|
||||||
|
<h3>Slot 渲染</h3>
|
||||||
|
<ATagGroup v-model="slotSelected">
|
||||||
|
<ATag value="a">
|
||||||
|
自定义A
|
||||||
|
</ATag>
|
||||||
|
<ATag value="b">
|
||||||
|
自定义B
|
||||||
|
</ATag>
|
||||||
|
<ATag value="c" disabled>
|
||||||
|
自定义C
|
||||||
|
</ATag>
|
||||||
|
</ATagGroup>
|
||||||
|
<p>选中值: {{ slotSelected }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 4. 自定义 checkedProps / uncheckedProps -->
|
||||||
|
<section>
|
||||||
|
<h3>自定义样式</h3>
|
||||||
|
<ATagGroup
|
||||||
|
v-model="customSelected"
|
||||||
|
:options="customOptions"
|
||||||
|
:checked-props="{ type: 'solid', status: 'success' }"
|
||||||
|
:unchecked-props="{ type: 'solid', status: 'default' }"
|
||||||
|
@change="handleCustomChange"
|
||||||
|
/>
|
||||||
|
<p>选中值: {{ customSelected }}</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.demo-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 40px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px; /* 各部分垂直间距 */
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px; /* 页面居中显示 */
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 24px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.demo-btn {
|
|
||||||
padding: 6px 16px;
|
|
||||||
border: 1px solid #409eff;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #409eff;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-btn:hover {
|
|
||||||
background-color: #409eff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in new issue
Block a user