u
This commit is contained in:
1 parent
985b42cafb
commit
5e3dccc88b
25 files changed
+931
-543
No files matched your search
@@ -0,0 +1,47 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export interface UseSelectableProps<T> {
|
||||||
|
modelValue: T[]
|
||||||
|
multiple?: boolean
|
||||||
|
options?: Record<string, any>[]
|
||||||
|
valueField?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSelectable<T>(
|
||||||
|
props: UseSelectableProps<T>,
|
||||||
|
onChange?: (selectedValues: T[], selectedOptions?: Record<string, any>[]) => void,
|
||||||
|
) {
|
||||||
|
const valueField = props.valueField || 'value'
|
||||||
|
|
||||||
|
// 修复:使用泛型参数明确指定 ref 的类型
|
||||||
|
const selectedValues = ref<T[]>(props.modelValue) as Ref<T[]>
|
||||||
|
|
||||||
|
const updateValue = (option: Record<string, any>, checked: boolean) => {
|
||||||
|
const val = option[valueField] as T
|
||||||
|
|
||||||
|
let newValues: T[]
|
||||||
|
if (props.multiple) {
|
||||||
|
newValues = checked
|
||||||
|
? selectedValues.value.includes(val)
|
||||||
|
? [...selectedValues.value]
|
||||||
|
: [...selectedValues.value, val]
|
||||||
|
: selectedValues.value.filter(v => v !== val)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newValues = checked ? [val] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedValues.value = newValues
|
||||||
|
|
||||||
|
// 如果有 options,返回完整对象数组
|
||||||
|
const selectedOptions = props.options?.filter(opt => newValues.includes(opt[valueField]))
|
||||||
|
|
||||||
|
onChange?.(newValues, selectedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedValues,
|
||||||
|
updateValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ $text-color-anti: var(--ao-text-color-anti); // 色彩-文字-反色
|
|||||||
$text-color-link: var(--ao-text-color-link); // 色彩-文字-链接
|
$text-color-link: var(--ao-text-color-link); // 色彩-文字-链接
|
||||||
|
|
||||||
$border-radius: 2px;
|
$border-radius: 2px;
|
||||||
$stroke-color: var(--ao-stroke-color); // 分割线(内容区域之间的分隔,如列表、面板内部)
|
$separator-color: var(--ao-separator-color); // 分割线(内容区域之间的分隔,如列表、面板内部)
|
||||||
$border-color: var(--ao-border-color); // 组件边框(Input、Select、Card 等容器的外部边框)
|
$border-color: var(--ao-border-color); // 组件边框(Input、Select、Card 等容器的外部边框)
|
||||||
|
|
||||||
$bg-color-control: var(
|
$bg-color-control: var(
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
--ao-text-color-link: #4582e6; /* 链接文字 */
|
--ao-text-color-link: #4582e6; /* 链接文字 */
|
||||||
--ao-text-color-watermark: rgba(255, 255, 255, 10%); /* 水印 / 背景文字 */
|
--ao-text-color-watermark: rgba(255, 255, 255, 10%); /* 水印 / 背景文字 */
|
||||||
|
|
||||||
--ao-stroke-color: #393939; // 分割线
|
--ao-separator-color: #393939; // 分割线
|
||||||
--ao-border-color: #5e5e5e; // 边框
|
--ao-border-color: #5e5e5e; // 边框
|
||||||
|
|
||||||
--ao-bg-color-control: transparent; // 特殊组件背景色,目前只用于 button、input 组件多主题场景,浅色主题下固定为白色,深色主题下为 transparent 适配背景颜色
|
--ao-bg-color-control: transparent; // 特殊组件背景色,目前只用于 button、input 组件多主题场景,浅色主题下固定为白色,深色主题下为 transparent 适配背景颜色
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
// 字体相关token
|
|
||||||
:root {
|
|
||||||
// 字体family token
|
|
||||||
--td-font-family: PingFang SC, Microsoft YaHei, Arial Regular;
|
|
||||||
--td-font-family-medium: PingFang SC, Microsoft YaHei, Arial Medium;
|
|
||||||
|
|
||||||
// 字体大小 token
|
|
||||||
--td-font-size-link-small: 12px;
|
|
||||||
--td-font-size-link-medium: 14px;
|
|
||||||
--td-font-size-link-large: 16px;
|
|
||||||
--td-font-size-mark-small: 12px;
|
|
||||||
--td-font-size-mark-medium: 14px;
|
|
||||||
--td-font-size-body-small: 12px;
|
|
||||||
--td-font-size-body-medium: 13px;
|
|
||||||
--td-font-size-body-large: 1px;
|
|
||||||
--td-font-size-title-small: 14px;
|
|
||||||
--td-font-size-title-medium: 16px;
|
|
||||||
--td-font-size-title-large: 18px;
|
|
||||||
--td-font-size-title-extraLarge: 20px;
|
|
||||||
--td-font-size-headline-small: 24px;
|
|
||||||
--td-font-size-headline-medium: 28px;
|
|
||||||
--td-font-size-headline-large: 36px;
|
|
||||||
--td-font-size-display-medium: 48px;
|
|
||||||
--td-font-size-display-large: 64px;
|
|
||||||
|
|
||||||
// 字体行高 token
|
|
||||||
--td-line-height-link-small: 20px;
|
|
||||||
--td-line-height-link-medium: 22px;
|
|
||||||
--td-line-height-link-large: 24px;
|
|
||||||
--td-line-height-mark-small: 20px;
|
|
||||||
--td-line-height-mark-medium: 22px;
|
|
||||||
--td-line-height-body-small: 20px;
|
|
||||||
--td-line-height-body-medium: 22px;
|
|
||||||
--td-line-height-body-large: 24px;
|
|
||||||
--td-line-height-title-small: 22px;
|
|
||||||
--td-line-height-title-medium: 24px;
|
|
||||||
--td-line-height-title-large: 26px;
|
|
||||||
--td-line-height-title-extraLarge: 28px;
|
|
||||||
--td-line-height-headline-small: 32px;
|
|
||||||
--td-line-height-headline-medium: 36px;
|
|
||||||
--td-line-height-headline-large: 44px;
|
|
||||||
--td-line-height-display-medium: 56px;
|
|
||||||
--td-line-height-display-large: 72px;
|
|
||||||
|
|
||||||
// font token
|
|
||||||
--td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family);
|
|
||||||
--td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family);
|
|
||||||
--td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family);
|
|
||||||
--td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family);
|
|
||||||
--td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family);
|
|
||||||
--td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family);
|
|
||||||
--td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family);
|
|
||||||
--td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family);
|
|
||||||
--td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family);
|
|
||||||
--td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family);
|
|
||||||
--td-font-title-extraLarge: 600 var(--td-font-size-title-extraLarge) / var(--td-line-height-title-extraLarge)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium)
|
|
||||||
var(--td-font-family);
|
|
||||||
--td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large)
|
|
||||||
var(--td-font-family);
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
--ao-text-color-link: #003cab; /* 链接文字 */
|
--ao-text-color-link: #003cab; /* 链接文字 */
|
||||||
--ao-text-color-watermark: rgba(0, 0, 0, 10%); /* 水印 / 背景文字 */
|
--ao-text-color-watermark: rgba(0, 0, 0, 10%); /* 水印 / 背景文字 */
|
||||||
|
|
||||||
--ao-stroke-color: #e8e8e8; // 分割线
|
--ao-separator-color: #e8e8e8; // 分割线
|
||||||
--ao-border-color: #ddd; // 边框
|
--ao-border-color: #ddd; // 边框
|
||||||
|
|
||||||
--ao-bg-color-control: #fff; // 特殊组件背景色,目前只用于 button、input 组件多主题场景,浅色主题下固定为白色,深色主题下为 transparent 适配背景颜色
|
--ao-bg-color-control: #fff; // 特殊组件背景色,目前只用于 button、input 组件多主题场景,浅色主题下固定为白色,深色主题下为 transparent 适配背景颜色
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
// border-radius相关 token
|
|
||||||
:root {
|
|
||||||
--td-radius-small: 2px;
|
|
||||||
--td-radius-default: 3px;
|
|
||||||
--td-radius-medium: 6px;
|
|
||||||
--td-radius-large: 9px;
|
|
||||||
--td-radius-extraLarge: 12px;
|
|
||||||
--td-radius-round: 999px;
|
|
||||||
--td-radius-circle: 50%;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// size/margin/padding相关 token
|
|
||||||
:root {
|
|
||||||
--td-size-2: 2px;
|
|
||||||
--td-size-4: 4px;
|
|
||||||
--td-size-6: 6px;
|
|
||||||
--td-size-8: 8px;
|
|
||||||
--td-size-12: 12px;
|
|
||||||
--td-size-16: 16px;
|
|
||||||
--td-size-20: 20px;
|
|
||||||
--td-size-24: 24px;
|
|
||||||
--td-size-28: 28px;
|
|
||||||
--td-size-32: 32px;
|
|
||||||
--td-size-36: 36px;
|
|
||||||
--td-size-40: 40px;
|
|
||||||
--td-size-48: 48px;
|
|
||||||
--td-size-56: 56px;
|
|
||||||
--td-size-64: 64px;
|
|
||||||
--td-size-72: 72px;
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,9 @@ export { default as Icon } from './icon'
|
|||||||
export { default as Input } from './input'
|
export { default as Input } from './input'
|
||||||
export { default as Popconfirm } from './popconfirm'
|
export { default as Popconfirm } from './popconfirm'
|
||||||
export { default as Popup } from './popup'
|
export { default as Popup } from './popup'
|
||||||
|
export { default as Radio } from './radio'
|
||||||
|
export { default as Separator } from './separator'
|
||||||
|
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'
|
||||||
export { default as TagGroup } from './tag-group'
|
export { default as TagGroup } from './tag-group'
|
||||||
|
|||||||
@@ -13,3 +13,6 @@
|
|||||||
@use './tooltip/index.scss' as *;
|
@use './tooltip/index.scss' as *;
|
||||||
@use './tag/index.scss' as *;
|
@use './tag/index.scss' as *;
|
||||||
@use './tag-group/index.scss' as *;
|
@use './tag-group/index.scss' as *;
|
||||||
|
@use './space/index.scss' as *;
|
||||||
|
@use './separator/index.scss' as *;
|
||||||
|
@use './radio/index.scss' as *;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropType, VNode } from 'vue'
|
import type { PropType, VNode } from 'vue'
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { defineComponent, ref } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Button from '../button/index'
|
import Button from '../button/index'
|
||||||
import Icon from '../icon/index'
|
import Icon from '../icon/index'
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
@use '../_styles/_variables.scss' as *;
|
||||||
|
|
||||||
|
$radio-prefix: '#{$prefix}-radio';
|
||||||
|
|
||||||
|
.#{$radio-prefix} {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
input[type='radio'] {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .#{$radio-prefix}-circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 勾选圆点
|
||||||
|
input[type='radio']:checked + .#{$radio-prefix}-circle::after {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #1890ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
animation: scaleIn 0.2s ease forwards;
|
||||||
|
box-shadow: 0 0 6px #1890ff33; // #33 = 20% 透明度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-clicked {
|
||||||
|
.#{$radio-prefix}-circle {
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { computed, defineComponent, ref } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AnimatedRadio',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
type: [String, Array, Object],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const prefixCls = getPrefixCls('radio')
|
||||||
|
const isClicked = ref(false)
|
||||||
|
|
||||||
|
const checked = computed(() => props.modelValue === props.value)
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
emit('update:modelValue', props.value)
|
||||||
|
// 播放点击动画
|
||||||
|
isClicked.value = true
|
||||||
|
setTimeout(() => (isClicked.value = false), 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<label
|
||||||
|
class={[
|
||||||
|
prefixCls,
|
||||||
|
props.class,
|
||||||
|
checked.value ? `${prefixCls}-checked` : '',
|
||||||
|
props.disabled ? `${prefixCls}-disabled` : '',
|
||||||
|
isClicked.value ? `${prefixCls}-clicked` : '',
|
||||||
|
]}
|
||||||
|
style={{ ...props.style }}
|
||||||
|
>
|
||||||
|
<div class={`${prefixCls}-wrapper`}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={props.value}
|
||||||
|
checked={checked.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<span class={`${prefixCls}-circle`} />
|
||||||
|
</div>
|
||||||
|
<div class={`${prefixCls}-content`}>
|
||||||
|
{slots.default ? slots.default() : null}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="padding:20px; max-width:600px;">
|
||||||
|
<h3>1. 简单水平分割线</h3>
|
||||||
|
<ASeparator />
|
||||||
|
|
||||||
|
<h3>2. 垂直分割线</h3>
|
||||||
|
<div style="display:flex; align-items:center; gap:16px;">
|
||||||
|
<span>左</span>
|
||||||
|
<ASeparator layout="vertical" />
|
||||||
|
<span>右</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>3. 虚线分割线</h3>
|
||||||
|
<ASeparator dashed />
|
||||||
|
<div style="display:flex; align-items:center; gap:16px; margin-top:8px;">
|
||||||
|
<span>左</span>
|
||||||
|
<ASeparator layout="vertical" dashed />
|
||||||
|
<span>右</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>4. 水平分割线带文字</h3>
|
||||||
|
<ASeparator content="分割文字" align="center" />
|
||||||
|
<ASeparator content="左侧文字" align="left" />
|
||||||
|
<ASeparator content="右侧文字" align="right" />
|
||||||
|
|
||||||
|
<h3>5. 使用插槽自定义内容</h3>
|
||||||
|
<ASeparator>
|
||||||
|
<strong style="color:red">自定义内容</strong>
|
||||||
|
</ASeparator>
|
||||||
|
|
||||||
|
<h3>6. 配合 ASpace 使用 separator</h3>
|
||||||
|
<ASpace size="middle" separator>
|
||||||
|
<div style="width:60px;height:40px;background:#eee;">
|
||||||
|
A
|
||||||
|
</div>
|
||||||
|
<div style="width:60px;height:40px;background:#ccc;">
|
||||||
|
B
|
||||||
|
</div>
|
||||||
|
<div style="width:60px;height:40px;background:#aaa;">
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
</ASpace>
|
||||||
|
|
||||||
|
<h3>7. 带自定义 margin</h3>
|
||||||
|
<ASeparator :margin="12" />
|
||||||
|
<div style="display:flex; align-items:center; gap:16px; margin-top:8px;">
|
||||||
|
<span>左</span>
|
||||||
|
<ASeparator layout="vertical" :margin="[0, 8]" />
|
||||||
|
<span>右</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
@use '../_styles/_variables.scss' as *;
|
||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
$separator-prefix: '#{$prefix}-separator';
|
||||||
|
|
||||||
|
.#{$separator-prefix} {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
color: $text-color-base;
|
||||||
|
box-sizing: border-box;
|
||||||
|
&.#{$separator-prefix}-layout-horizontal {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid $separator-color;
|
||||||
|
margin: 6px 0;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$separator-prefix}-layout-vertical {
|
||||||
|
border-top: 0;
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.9em;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-left: 1px solid $separator-color;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$separator-prefix}-dashed {
|
||||||
|
// 水平虚线
|
||||||
|
&.#{$separator-prefix}-layout-horizontal {
|
||||||
|
border-top-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直虚线
|
||||||
|
&.#{$separator-prefix}-layout-vertical {
|
||||||
|
border-left-style: dashed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$separator-prefix}-text {
|
||||||
|
display: flex;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 0;
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 50%;
|
||||||
|
border-top: 1px solid $separator-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .#{$separator-prefix}-text-inner:not(:empty) {
|
||||||
|
padding: 0 1em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$separator-prefix}-text-left {
|
||||||
|
&::before {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$separator-prefix}-text-right {
|
||||||
|
&::before {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import type { PropType, VNode } from 'vue'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Separator',
|
||||||
|
props: {
|
||||||
|
layout: {
|
||||||
|
type: String as PropType<'horizontal' | 'vertical'>,
|
||||||
|
default: 'horizontal',
|
||||||
|
},
|
||||||
|
dashed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
align: {
|
||||||
|
type: String as PropType<'left' | 'right' | 'center'>,
|
||||||
|
default: 'center',
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: [Number, Array] as any,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
type: [String, Array, Object],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: [],
|
||||||
|
setup(props, { slots }) {
|
||||||
|
const prefixCls = getPrefixCls('separator')
|
||||||
|
|
||||||
|
// 根据 props.margin 生成样式
|
||||||
|
const marginStyle = () => {
|
||||||
|
if (props.margin == null)
|
||||||
|
return {}
|
||||||
|
if (typeof props.margin === 'number') {
|
||||||
|
return props.layout === 'horizontal'
|
||||||
|
? { margin: `${props.margin}px 0` }
|
||||||
|
: { margin: `0 ${props.margin}px` }
|
||||||
|
}
|
||||||
|
if (Array.isArray(props.margin)) {
|
||||||
|
return { margin: props.margin.map(v => `${v}px`).join(' ') }
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染文字或默认插槽
|
||||||
|
const renderContent = (content: string | VNode | (() => VNode)) => {
|
||||||
|
if (slots.default)
|
||||||
|
return slots.default()
|
||||||
|
if (typeof content === 'function')
|
||||||
|
return content()
|
||||||
|
if (typeof content === 'string')
|
||||||
|
return <span class={`${prefixCls}-content`}>{content}</span>
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHorizontal = props.layout === 'horizontal'
|
||||||
|
const hasContent = !!(slots.default?.().length || props.content)
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
prefixCls,
|
||||||
|
`${prefixCls}-layout-${props.layout}`,
|
||||||
|
props.dashed ? `${prefixCls}-dashed` : '',
|
||||||
|
isHorizontal && hasContent ? `${prefixCls}-text` : '',
|
||||||
|
isHorizontal && hasContent ? `${prefixCls}-text-${props.align}` : '',
|
||||||
|
props.class,
|
||||||
|
]}
|
||||||
|
style={{ ...marginStyle(), ...props.style }}
|
||||||
|
>
|
||||||
|
{isHorizontal ? <span class={`${prefixCls}-text-inner`}>{renderContent(props.content)}</span> : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h3>水平 wrap + 不同间距 + align</h3>
|
||||||
|
<ASpace wrap :size="[10, 20]" align="start" style="width: 300px; border:1px solid #ccc;">
|
||||||
|
<div style="width:120px;height:40px;background:#eee;">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div style="width:120px;height:40px;background:#ccc;">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div style="width:120px;height:40px;background:#aaa;">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<div style="width:120px;height:40px;background:#f0f;">
|
||||||
|
4
|
||||||
|
</div>
|
||||||
|
</ASpace>
|
||||||
|
|
||||||
|
<h3>垂直排列 + align</h3>
|
||||||
|
<ASpace :size="[20, 10]" style="border:1px solid #ccc;" separator>
|
||||||
|
<div style="width:120px;height:40px;background:#eee;">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div style="width:120px;height:40px;background:#ccc;">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div style="width:120px;height:40px;background:#aaa;">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
</ASpace>
|
||||||
|
|
||||||
|
<h3>默认 small/middle/large 间距</h3>
|
||||||
|
<ASpace size="small" align="baseline">
|
||||||
|
<div style="width:60px;height:40px;background:#eee;">
|
||||||
|
A
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="width:60px;height:40px;background:#ccc;">
|
||||||
|
B
|
||||||
|
</div>
|
||||||
|
<div style="width:60px;height:40px;background:#aaa;">
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
</ASpace>
|
||||||
|
<div>
|
||||||
|
<AButton>测试</AButton>
|
||||||
|
<ASeparator layout="vertical" />
|
||||||
|
<AButton>测试</AButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@use '../_styles/_variables.scss' as *;
|
||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
$space-prefix: '#{$prefix}-space';
|
||||||
|
|
||||||
|
.#{$space-prefix} {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
&.#{$space-prefix}-direction-horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$space-prefix}-direction-vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$space-prefix}-align-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
&.#{$space-prefix}-align-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
&.#{$space-prefix}-align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&.#{$space-prefix}-align-baseline {
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.#{$space-prefix}-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import type { PropType, VNode } from 'vue'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
|
import Separator from '../separator/index'
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
small: 8,
|
||||||
|
middle: 16,
|
||||||
|
large: 24,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ASpace',
|
||||||
|
props: {
|
||||||
|
size: {
|
||||||
|
type: [String, Number, Array] as PropType<'small' | 'middle' | 'large' | number | [number, number]>,
|
||||||
|
default: 'middle',
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String as () => 'horizontal' | 'vertical',
|
||||||
|
default: 'horizontal',
|
||||||
|
},
|
||||||
|
align: {
|
||||||
|
type: String as () => 'start' | 'center' | 'end' | 'baseline',
|
||||||
|
default: 'center',
|
||||||
|
},
|
||||||
|
wrap: { type: Boolean, default: false },
|
||||||
|
separator: { type: Boolean, default: false },
|
||||||
|
class: { type: [String, Array, Object], default: '' },
|
||||||
|
style: { type: Object, default: () => ({}) },
|
||||||
|
},
|
||||||
|
setup(props, { slots }) {
|
||||||
|
const prefixCls = getPrefixCls('space')
|
||||||
|
|
||||||
|
const getGap = (): [number, number] => {
|
||||||
|
if (Array.isArray(props.size))
|
||||||
|
return props.size
|
||||||
|
if (typeof props.size === 'string' && sizeMap[props.size as keyof typeof sizeMap]) {
|
||||||
|
const val = sizeMap[props.size as keyof typeof sizeMap]
|
||||||
|
return [val, val]
|
||||||
|
}
|
||||||
|
return [props.size as number, props.size as number]
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const [rowGap, colGap] = getGap()
|
||||||
|
const isHorizontal = props.direction === 'horizontal'
|
||||||
|
|
||||||
|
const children = slots.default?.() || []
|
||||||
|
const items: VNode[] = []
|
||||||
|
children.forEach((child, index) => {
|
||||||
|
items.push(child)
|
||||||
|
if (props.separator && index < children.length - 1) {
|
||||||
|
items.push(
|
||||||
|
<Separator
|
||||||
|
layout={isHorizontal ? 'vertical' : 'horizontal'}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={[prefixCls, props.class, `${prefixCls}-align-${props.align}`, `${prefixCls}-direction-${props.direction}`, props.wrap && `${prefixCls}-wrap`]}
|
||||||
|
style={{
|
||||||
|
gap: isHorizontal ? `${rowGap}px ${colGap}px` : `${rowGap}px`,
|
||||||
|
...props.style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface TagOption {
|
||||||
|
label: string
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagOptions: TagOption[] = [
|
||||||
|
{ 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>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h2>ATagGroup 使用示例</h2>
|
||||||
|
|
||||||
|
<!-- 1. 多选 options 渲染 -->
|
||||||
|
<section style="margin-bottom: 20px;">
|
||||||
|
<h3>多选 - Options 渲染</h3>
|
||||||
|
<ATagGroup
|
||||||
|
v-model="multiSelected"
|
||||||
|
:options="tagOptions"
|
||||||
|
multiple
|
||||||
|
@change="handleMultiChange"
|
||||||
|
/>
|
||||||
|
<p>选中值: {{ multiSelected }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 2. 单选 options 渲染 -->
|
||||||
|
<section style="margin-bottom: 20px;">
|
||||||
|
<h3>单选 - Options 渲染</h3>
|
||||||
|
<ATagGroup
|
||||||
|
v-model="singleSelected"
|
||||||
|
: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" checked>
|
||||||
|
自定义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>
|
||||||
|
</template>
|
||||||
@@ -1,113 +1,83 @@
|
|||||||
import type { PropType, VNode } from 'vue'
|
import type { PropType, VNode } from 'vue'
|
||||||
import { cloneVNode, defineComponent, ref, watch } from 'vue'
|
import { cloneVNode, defineComponent } from 'vue'
|
||||||
|
import { useSelectable } from '../_hooks/useSelectable'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Tag from '../tag/index'
|
import Tag from '../tag/index'
|
||||||
|
|
||||||
|
interface TagOption {
|
||||||
|
label?: string
|
||||||
|
value: string | number
|
||||||
|
disabled?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TagGroup',
|
name: 'ATagGroup',
|
||||||
props: {
|
props: {
|
||||||
modelValue: { type: Array as PropType<(string | number)[]>, default: () => [] },
|
modelValue: { type: Array as PropType<(string | number)[]>, default: () => [] },
|
||||||
multiple: { type: Boolean, default: true },
|
multiple: { type: Boolean, default: true },
|
||||||
options: {
|
options: { type: Array as PropType<TagOption[]>, default: () => [] },
|
||||||
type: Array as PropType<Array<{ label: string, value: string | number }>>,
|
labelField: { type: String, default: 'label' },
|
||||||
default: () => [],
|
valueField: { type: String, default: 'value' },
|
||||||
},
|
checkedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'primary' }) },
|
||||||
checkedProps: {
|
uncheckedProps: { type: Object as PropType<Record<string, any>>, default: () => ({ type: 'solid', status: 'default' }) },
|
||||||
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',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
class: { type: [String, Array, Object], default: '' },
|
class: { type: [String, Array, Object], default: '' },
|
||||||
style: { type: Object, default: () => ({}) },
|
style: { type: Object, default: () => ({}) },
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'change'],
|
emits: ['update:modelValue', 'change'],
|
||||||
setup(props, { slots, emit }) {
|
setup(props, { slots, emit }) {
|
||||||
const prefixCls = getPrefixCls('tag-group')
|
const prefixCls = getPrefixCls('tag-group')
|
||||||
const selectedValues = ref<(string | number)[]>([...props.modelValue])
|
const { selectedValues, updateValue } = useSelectable(
|
||||||
|
{
|
||||||
// 外部 v-model 更新时同步内部值
|
modelValue: props.modelValue,
|
||||||
watch(
|
multiple: props.multiple,
|
||||||
() => props.modelValue,
|
options: props.options,
|
||||||
(val) => {
|
valueField: props.valueField,
|
||||||
selectedValues.value = [...val]
|
},
|
||||||
|
(newVal, selectedOptions) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
emit('change', selectedOptions)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateValue = (value: string | number, checked: boolean) => {
|
const renderTag = (option: TagOption, vnode?: VNode) => {
|
||||||
let newValues = [...selectedValues.value]
|
const val = option[props.valueField]
|
||||||
|
const checked = selectedValues.value.includes(val)
|
||||||
if (props.multiple) {
|
|
||||||
if (checked) {
|
|
||||||
if (!newValues.includes(value))
|
|
||||||
newValues.push(value)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newValues = newValues.filter(v => v !== value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 单选:checked 为 true → 只保留当前值
|
|
||||||
newValues = checked ? [value] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedValues.value = newValues
|
|
||||||
emit('update:modelValue', newValues)
|
|
||||||
emit('change', newValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderTag = (value: string | number, label?: string, vnode?: VNode) => {
|
|
||||||
const checked = selectedValues.value.includes(value)
|
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
'key': value,
|
'key': val,
|
||||||
value,
|
'value': val,
|
||||||
checked,
|
checked,
|
||||||
'checkable': true,
|
'checkable': true,
|
||||||
'checkedProps': props.checkedProps,
|
'checkedProps': props.checkedProps,
|
||||||
'uncheckedProps': props.uncheckedProps,
|
'uncheckedProps': props.uncheckedProps,
|
||||||
'onUpdate:checked': (val: boolean) => updateValue(value, val),
|
'disabled': option.disabled,
|
||||||
|
'onUpdate:checked': (valChecked: boolean) => updateValue(option, valChecked),
|
||||||
|
}
|
||||||
|
return vnode ? cloneVNode(vnode, baseProps) : <Tag {...baseProps}>{option[props.labelField]}</Tag>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vnode) {
|
const renderOptionsTags = () => props.options.map(opt => renderTag(opt))
|
||||||
return cloneVNode(vnode, baseProps)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return (
|
|
||||||
<Tag {...baseProps}>
|
|
||||||
{label}
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// slot 渲染
|
|
||||||
const renderSlotTags = () => {
|
const renderSlotTags = () => {
|
||||||
const slotNodes = slots.default?.() || []
|
const slotNodes = slots.default?.() || []
|
||||||
return slotNodes.map((vnode: VNode) => {
|
return slotNodes.map((vnode: VNode) => {
|
||||||
if ((vnode.type as any)?.name === 'Tag') {
|
// 判断 vnode.type 是否等于 Tag 组件本身
|
||||||
const value = (vnode.props as any)?.value
|
if (vnode.type === Tag) {
|
||||||
return renderTag(value, undefined, vnode)
|
const vnodeProps = vnode.props || {}
|
||||||
|
const option = {
|
||||||
|
value: vnodeProps.value,
|
||||||
|
label: vnodeProps.label ?? vnodeProps.value,
|
||||||
|
disabled: vnodeProps.disabled,
|
||||||
|
}
|
||||||
|
return renderTag(option, vnode)
|
||||||
}
|
}
|
||||||
return vnode
|
return vnode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// options 渲染
|
return () => {
|
||||||
const renderOptionTags = () =>
|
const children = props.options.length ? renderOptionsTags() : renderSlotTags()
|
||||||
props.options.map(opt => renderTag(opt.value, opt.label))
|
return <div class={[prefixCls, props.class]} style={props.style}>{children}</div>
|
||||||
|
}
|
||||||
return () => (
|
|
||||||
<div class={[prefixCls, props.class]} style={{ ...props.style }}>
|
|
||||||
{props.options.length ? renderOptionTags() : renderSlotTags()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="demo">
|
<div class="demo">
|
||||||
|
<!-- 正常态 -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ATag size="small">
|
<ATag size="small">
|
||||||
small
|
small
|
||||||
@@ -102,6 +103,106 @@
|
|||||||
outline-light
|
outline-light
|
||||||
</ATag>
|
</ATag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 禁用态 -->
|
||||||
|
<div class="row">
|
||||||
|
<ATag size="small" disabled>
|
||||||
|
small
|
||||||
|
</ATag>
|
||||||
|
<ATag size="medium" disabled>
|
||||||
|
medium
|
||||||
|
</ATag>
|
||||||
|
<ATag size="large" disabled>
|
||||||
|
large
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag shape="round" disabled>
|
||||||
|
round
|
||||||
|
</ATag>
|
||||||
|
<ATag shape="mark" disabled>
|
||||||
|
mark
|
||||||
|
</ATag>
|
||||||
|
<ATag shape="square" disabled>
|
||||||
|
square
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag type="solid" disabled>
|
||||||
|
solid
|
||||||
|
</ATag>
|
||||||
|
<ATag type="light" disabled>
|
||||||
|
light
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline" disabled>
|
||||||
|
outline
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline-light" disabled>
|
||||||
|
outline-light
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag type="solid" status="primary" disabled>
|
||||||
|
solid
|
||||||
|
</ATag>
|
||||||
|
<ATag type="light" status="primary" disabled>
|
||||||
|
light
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline" status="primary" disabled>
|
||||||
|
outline
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline-light" status="primary" disabled>
|
||||||
|
outline-light
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag type="solid" status="danger" disabled>
|
||||||
|
solid
|
||||||
|
</ATag>
|
||||||
|
<ATag type="light" status="danger" disabled>
|
||||||
|
light
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline" status="danger" disabled>
|
||||||
|
outline
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline-light" status="danger" disabled>
|
||||||
|
outline-light
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag type="solid" status="warning" disabled>
|
||||||
|
solid
|
||||||
|
</ATag>
|
||||||
|
<ATag type="light" status="warning" disabled>
|
||||||
|
light
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline" status="warning" disabled>
|
||||||
|
outline
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline-light" status="warning" disabled>
|
||||||
|
outline-light
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<ATag type="solid" status="success" disabled>
|
||||||
|
solid
|
||||||
|
</ATag>
|
||||||
|
<ATag type="light" status="success" disabled>
|
||||||
|
light
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline" status="success" disabled>
|
||||||
|
outline
|
||||||
|
</ATag>
|
||||||
|
<ATag type="outline-light" status="success" disabled>
|
||||||
|
outline-light
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -16,75 +16,57 @@ $tag-prefix: '#{$prefix}-tag';
|
|||||||
border-radius: $border-color;
|
border-radius: $border-color;
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
&-size-large {
|
&.#{$tag-prefix}-size-large {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
&-size-medium {
|
&.#{$tag-prefix}-size-medium {
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
&-size-small {
|
&.#{$tag-prefix}-size-small {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
&-shape-round {
|
|
||||||
|
&.#{$tag-prefix}-shape-round {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
&-shape-mark {
|
&.#{$tag-prefix}-shape-mark {
|
||||||
border-radius: 0px 12px 12px 0;
|
border-radius: 0 12px 12px 0;
|
||||||
}
|
}
|
||||||
&-icon {
|
|
||||||
|
&.#{$tag-prefix}-icon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
&-close {
|
&.#{$tag-prefix}-close {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: filter 0.2s cubic-bezier(0.38, 0, 0.24, 1);
|
transition: filter 0.2s cubic-bezier(0.38, 0, 0.24, 1);
|
||||||
|
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
filter: brightness(1.2); /* 比父色亮 20% */
|
filter: brightness(1.2);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: brightness(0.8); /* 比父色暗 20% */
|
filter: brightness(0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-check {
|
&.#{$tag-prefix}-check {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: background-color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
|
transition: background-color 0.2s cubic-bezier(0.38, 0, 0.24, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-disabled {
|
&.#{$tag-prefix}-disabled {
|
||||||
cursor: default;
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-check.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-default {
|
&.#{$tag-prefix}-type-solid {
|
||||||
background-color: $default-color-disabled;
|
|
||||||
color: $text-color-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-primary {
|
|
||||||
background-color: $primary-color-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-danger {
|
|
||||||
background-color: $danger-color-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-warning {
|
|
||||||
background-color: $warning-color-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-checked.#{$tag-prefix}-disabled.#{$tag-prefix}-type-solid.#{$tag-prefix}-status-success {
|
|
||||||
background-color: $success-color-disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-type-solid {
|
|
||||||
&.#{$tag-prefix}-status-default {
|
&.#{$tag-prefix}-status-default {
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
background-color: $default-color;
|
background-color: $default-color;
|
||||||
@@ -107,7 +89,8 @@ $tag-prefix: '#{$prefix}-tag';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-type-light {
|
// light 类型
|
||||||
|
&.#{$tag-prefix}-type-light {
|
||||||
&.#{$tag-prefix}-status-default {
|
&.#{$tag-prefix}-status-default {
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
background-color: $default-color-light;
|
background-color: $default-color-light;
|
||||||
@@ -130,35 +113,34 @@ $tag-prefix: '#{$prefix}-tag';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-type-outline {
|
// outline 类型
|
||||||
|
&.#{$tag-prefix}-type-outline {
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
&.#{$tag-prefix}-status-default {
|
&.#{$tag-prefix}-status-default {
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
background-color: transparent;
|
|
||||||
border-color: $default-color;
|
border-color: $default-color;
|
||||||
}
|
}
|
||||||
&.#{$tag-prefix}-status-primary {
|
&.#{$tag-prefix}-status-primary {
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
background-color: transparent;
|
|
||||||
border-color: $primary-color;
|
border-color: $primary-color;
|
||||||
}
|
}
|
||||||
&.#{$tag-prefix}-status-success {
|
&.#{$tag-prefix}-status-success {
|
||||||
color: $success-color;
|
color: $success-color;
|
||||||
background-color: transparent;
|
|
||||||
border-color: $success-color;
|
border-color: $success-color;
|
||||||
}
|
}
|
||||||
&.#{$tag-prefix}-status-warning {
|
&.#{$tag-prefix}-status-warning {
|
||||||
color: $warning-color;
|
color: $warning-color;
|
||||||
background-color: transparent;
|
|
||||||
border-color: $warning-color;
|
border-color: $warning-color;
|
||||||
}
|
}
|
||||||
&.#{$tag-prefix}-status-danger {
|
&.#{$tag-prefix}-status-danger {
|
||||||
color: $danger-color;
|
color: $danger-color;
|
||||||
background-color: transparent;
|
|
||||||
border-color: $danger-color;
|
border-color: $danger-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-type-outline-light {
|
// outline-light 类型
|
||||||
|
&.#{$tag-prefix}-type-outline-light {
|
||||||
&.#{$tag-prefix}-status-default {
|
&.#{$tag-prefix}-status-default {
|
||||||
color: $text-color-base;
|
color: $text-color-base;
|
||||||
background-color: $default-color-light;
|
background-color: $default-color-light;
|
||||||
|
|||||||
@@ -1,52 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const checked = ref(['1'])
|
|
||||||
|
|
||||||
const checked1 = ref(false)
|
|
||||||
const checked2 = ref(false)
|
|
||||||
const checked3 = ref(false)
|
|
||||||
|
|
||||||
const STYLE_B_UNCHECKED_PROPS = {
|
|
||||||
theme: 'default',
|
|
||||||
variant: 'outline',
|
|
||||||
}
|
|
||||||
|
|
||||||
const STYLE_C_CHECKED_PROPS = {
|
|
||||||
theme: 'primary',
|
|
||||||
variant: 'outline',
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="demo">
|
<div style="height: 800px;display: flex;align-items: center;justify-content: center;">
|
||||||
<div class="row">
|
<ARadio>
|
||||||
<ATag show-close checkable disabled>
|
123
|
||||||
测试 1
|
</ARadio>
|
||||||
</ATag>
|
|
||||||
<ATagGroup v-model="checked">
|
|
||||||
<ATag show-close value="1">
|
|
||||||
测试 1
|
|
||||||
</ATag>
|
|
||||||
<ATag show-close value="2">
|
|
||||||
测试2
|
|
||||||
</ATag>
|
|
||||||
</ATagGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.demo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -18,6 +18,10 @@ declare module 'vue' {
|
|||||||
AMessage: typeof import('@ao/ao-design')['Message']
|
AMessage: typeof import('@ao/ao-design')['Message']
|
||||||
APopconfirm: typeof import('@ao/ao-design')['Popconfirm']
|
APopconfirm: typeof import('@ao/ao-design')['Popconfirm']
|
||||||
APopup: typeof import('@ao/ao-design')['Popup']
|
APopup: typeof import('@ao/ao-design')['Popup']
|
||||||
|
ARadio: typeof import('@ao/ao-design')['Radio']
|
||||||
|
ARdio: typeof import('@ao/ao-design')['Rdio']
|
||||||
|
ASeparator: typeof import('@ao/ao-design')['Separator']
|
||||||
|
ASpace: typeof import('@ao/ao-design')['Space']
|
||||||
ASpin: typeof import('@ao/ao-design')['Spin']
|
ASpin: typeof import('@ao/ao-design')['Spin']
|
||||||
ATag: typeof import('@ao/ao-design')['Tag']
|
ATag: typeof import('@ao/ao-design')['Tag']
|
||||||
ATagGroup: typeof import('@ao/ao-design')['TagGroup']
|
ATagGroup: typeof import('@ao/ao-design')['TagGroup']
|
||||||
|
|||||||
@@ -1,278 +1,64 @@
|
|||||||
<!doctype html>
|
# 驰诺国际货运有限公司
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>12方向Tooltip</title>
|
|
||||||
<style>
|
|
||||||
/* 基础样式 */
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 2rem;
|
|
||||||
padding: 2rem;
|
|
||||||
background: #f0f2f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger {
|
<div style="text-align: center">订舱委托书</div>
|
||||||
padding: 0.8rem 1.6rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #2196f3;
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger:hover {
|
公司编号:CNNYYY230108
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tooltip 核心样式 */
|
<div style="text-align: center">
|
||||||
.tooltip {
|
<table border="1">
|
||||||
position: fixed;
|
<tbody>
|
||||||
z-index: 10000;
|
<tr>
|
||||||
min-width: 60px;
|
<td colspan="3">
|
||||||
max-width: 240px;
|
发货人 JIANGXIARTSENCHEMICALCO.,LTD SULPHUR&PHOSPHORUS CHEMICAL BASE GUIXI,JIANGXI,P.R.CHINA
|
||||||
padding: 0.8rem 1.2rem;
|
</td>
|
||||||
background: rgba(0, 0, 0, 0.8);
|
</tr>
|
||||||
color: white;
|
<tr>
|
||||||
font-size: 0.9rem;
|
<td colspan="3">
|
||||||
line-height: 1.4;
|
收货人 ARTSEN?CHEMICAL?AMERICA?LLC 19?LOVELAND?STREET MADISON,?NJ?07940 T:?908-419-0704??F:973-520-8290
|
||||||
border-radius: 4px;
|
ATTN:?WALTER?CHENG
|
||||||
visibility: hidden;
|
</td>
|
||||||
opacity: 0;
|
</tr>
|
||||||
transition:
|
<tr>
|
||||||
opacity 0.2s,
|
<td colspan="3">
|
||||||
visibility 0.2s;
|
通知人 ARTSEN?CHEMICAL?AMERICA?LLC 19?LOVELAND?STREET MADISON,?NJ?07940 T:?908-419-0704??F:973-520-8290
|
||||||
pointer-events: none;
|
ATTN:?WALTER?CHENG
|
||||||
}
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>船公司</td>
|
||||||
|
<td>船期 2023/01/20</td>
|
||||||
|
<td>航名/航次 XIN DA YANG ZHOU 086E</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>起运港 NINGBO, CHINA</td>
|
||||||
|
<td colspan="2">目的港 HOUSTON</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
/* 箭头通用样式 */
|
<div style="text-align: center">
|
||||||
.tooltip::after {
|
<table border="1">
|
||||||
content: '';
|
<tbody>
|
||||||
position: absolute;
|
<tr>
|
||||||
width: 0;
|
<td rowspan="3">唛头:NOMARK</td>
|
||||||
height: 0;
|
<td>TOTAL:</td>
|
||||||
border: 6px solid transparent;
|
<td>1</td>
|
||||||
}
|
<td></td>
|
||||||
|
<td>18000.00</td>
|
||||||
|
<td>30.00</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">箱量:1X20'TKFCL</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">运输条款:FREIGHTPREPAID</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">备注:</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
/* 不同方向的样式 */
|
联系人:裘珊珊电话:0574-87237893QQ:1597608137传真:0574-87135310宁波市海曙区药行街42号银亿环球中心B座24楼
|
||||||
/* 上方系列 */
|
|
||||||
.tooltip.top {
|
|
||||||
transform: translate(-50%, calc(-100% - 12px));
|
|
||||||
}
|
|
||||||
.tooltip.top::after {
|
|
||||||
bottom: -12px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-top-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.top-left {
|
|
||||||
transform: translate(calc(-100% + 12px), calc(-100% - 12px));
|
|
||||||
}
|
|
||||||
.tooltip.top-left::after {
|
|
||||||
bottom: -12px;
|
|
||||||
left: 12px;
|
|
||||||
border-top-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.top-right {
|
|
||||||
transform: translate(12px, calc(-100% - 12px));
|
|
||||||
}
|
|
||||||
.tooltip.top-right::after {
|
|
||||||
bottom: -12px;
|
|
||||||
right: 12px;
|
|
||||||
border-top-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 下方系列 */
|
|
||||||
.tooltip.bottom {
|
|
||||||
transform: translate(-50%, 12px);
|
|
||||||
}
|
|
||||||
.tooltip.bottom::after {
|
|
||||||
top: -12px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.bottom-left {
|
|
||||||
transform: translate(calc(-100% + 12px), 12px);
|
|
||||||
}
|
|
||||||
.tooltip.bottom-left::after {
|
|
||||||
top: -12px;
|
|
||||||
left: 12px;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.bottom-right {
|
|
||||||
transform: translate(12px, 12px);
|
|
||||||
}
|
|
||||||
.tooltip.bottom-right::after {
|
|
||||||
top: -12px;
|
|
||||||
right: 12px;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 左侧系列 */
|
|
||||||
.tooltip.left {
|
|
||||||
transform: translate(calc(-100% - 12px), -50%);
|
|
||||||
}
|
|
||||||
.tooltip.left::after {
|
|
||||||
right: -12px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
border-left-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.left-top {
|
|
||||||
transform: translate(calc(-100% - 12px), calc(-100% + 12px));
|
|
||||||
}
|
|
||||||
.tooltip.left-top::after {
|
|
||||||
right: -12px;
|
|
||||||
top: 12px;
|
|
||||||
border-left-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.left-bottom {
|
|
||||||
transform: translate(calc(-100% - 12px), 12px);
|
|
||||||
}
|
|
||||||
.tooltip.left-bottom::after {
|
|
||||||
right: -12px;
|
|
||||||
bottom: 12px;
|
|
||||||
border-left-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 右侧系列 */
|
|
||||||
.tooltip.right {
|
|
||||||
transform: translate(12px, -50%);
|
|
||||||
}
|
|
||||||
.tooltip.right::after {
|
|
||||||
left: -12px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
border-right-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.right-top {
|
|
||||||
transform: translate(12px, calc(-100% + 12px));
|
|
||||||
}
|
|
||||||
.tooltip.right-top::after {
|
|
||||||
left: -12px;
|
|
||||||
top: 12px;
|
|
||||||
border-right-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip.right-bottom {
|
|
||||||
transform: translate(12px, 12px);
|
|
||||||
}
|
|
||||||
.tooltip.right-bottom::after {
|
|
||||||
left: -12px;
|
|
||||||
bottom: 12px;
|
|
||||||
border-right-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 显示状态 */
|
|
||||||
.tooltip.visible {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div style="text-align: center;"><table border="1"><tbody><tr><td colspan="4">Shipper: CHANNEL INTERNATIONAL FREIGHT CO.,LTD 24 FLOOR, YINYI GLOBAL CENTER,B BLOCK,NO.42 YAOHANG STREET HAISHUDISTRICT,NINGBO,CHINA TEL:0086-574-87337359 FAX:0086-574-87135310</td><td rowspan="3" colspan="4">TO:宁波坤洲国际物流有限公司 ATTN: TEL: FROM:宁波驰诺/裘珊珊 TEL:0574-87237893 QQ:1597608137 业务编号:CNNBYS230201 开航日期:2023-02-0400:00:00.0 船公司:现代商船 柜量:2X2OTKFCL运费条款:FREIGHTPREPAID 运输条款:CY-CY</td></tr><tr><td colspan="4">Consignee: NSINTERNATIONAL(THAILAND)CO.LTD 75/49 RICHMOND BLDG.,15 FL SUKHUMVIT SOI 26 KLONGTOEY,BANGKOK1011O THAILAND ATTN:IMPORT@NSINTER.COM TEL: +66-2-665-6710 FAX:+66-2-665-6494</td></tr><tr><td colspan="4">Notify Party: NS INTERNATIONAL (THAILAND) CO.,LTD 75/49 RICHMOND BLDG., 15 FL SUKHUMVIT SOI 26 KLONGTOEY,BANGKOK1011O THAILAND ATTN:IMPORT@NSINTER.COM TEL:+66-2-665-6710 FAX: +66-2-665-6494</td></tr><tr><td colspan="2">起运港(PortOf Loading):</td><td colspan="3">中转港(Port of Dischargc):</td><td colspan="3">目的港(Final Destination):</td></tr><tr><td colspan="2">NINGBO,CHINA</td><td colspan="3"></td><td colspan="3">LEAM CHA BANG</td></tr><tr><td>标志和号码</td><td colspan="2">件数和包装种类</td><td colspan="3">货名</td><td>毛重(公斤)</td><td>体积(立方米)</td></tr><tr><td>SMAU9992165 SMAU9992170</td><td colspan="2">2TANKS</td><td colspan="3">BULK LIQUID ARGON CLASS:2.2 UN:1951</td><td>44000.000KGS</td><td>50.000CBM</td></tr></tbody></table></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
class TooltipManager {
|
|
||||||
constructor() {
|
|
||||||
this.tooltip = null
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// 创建tooltip元素
|
|
||||||
this.tooltip = document.createElement('div')
|
|
||||||
this.tooltip.className = 'tooltip'
|
|
||||||
document.body.appendChild(this.tooltip)
|
|
||||||
|
|
||||||
// 绑定触发事件
|
|
||||||
document.querySelectorAll('.trigger').forEach((trigger) => {
|
|
||||||
trigger.addEventListener('mouseenter', (e) => this.show(e))
|
|
||||||
trigger.addEventListener('mouseleave', () => this.hide())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
show(e) {
|
|
||||||
const { direction } = e.target.dataset
|
|
||||||
const text = e.target.dataset.tooltip
|
|
||||||
const triggerRect = e.target.getBoundingClientRect()
|
|
||||||
|
|
||||||
// 设置内容
|
|
||||||
this.tooltip.textContent = text
|
|
||||||
this.tooltip.className = `tooltip ${direction}`
|
|
||||||
|
|
||||||
// 计算初始位置
|
|
||||||
const { offsetWidth: tw, offsetHeight: th } = this.tooltip
|
|
||||||
const { left: tx, top: ty, width: trw, height: thr } = triggerRect
|
|
||||||
const offset = 12 // 基础偏移量
|
|
||||||
|
|
||||||
// 方向位置映射
|
|
||||||
const positionMap = {
|
|
||||||
top: { left: tx + trw / 2, top: ty - th - offset },
|
|
||||||
'top-left': { left: tx + trw - 12, top: ty - th - offset },
|
|
||||||
'top-right': { left: tx + 12, top: ty - th - offset },
|
|
||||||
bottom: { left: tx + trw / 2, top: ty + thr + offset },
|
|
||||||
'bottom-left': { left: tx + trw - 12, top: ty + thr + offset },
|
|
||||||
'bottom-right': { left: tx + 12, top: ty + thr + offset },
|
|
||||||
left: { left: tx - tw - offset, top: ty + thr / 2 },
|
|
||||||
'left-top': { left: tx - tw - offset, top: ty + 12 },
|
|
||||||
'left-bottom': { left: tx - tw - offset, top: ty + thr - 12 },
|
|
||||||
right: { left: tx + trw + offset, top: ty + thr / 2 },
|
|
||||||
'right-top': { left: tx + trw + offset, top: ty + 12 },
|
|
||||||
'right-bottom': { left: tx + trw + offset, top: ty + thr - 12 },
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取计算后的位置
|
|
||||||
let { left, top } = positionMap[direction]
|
|
||||||
|
|
||||||
// 边界检测(防止超出视口)
|
|
||||||
const viewportWidth = window.innerWidth
|
|
||||||
const viewportHeight = window.innerHeight
|
|
||||||
|
|
||||||
// 水平方向边界处理
|
|
||||||
if (left < 0)
|
|
||||||
left = 12 // 左边界
|
|
||||||
else if (left + tw > viewportWidth) left = viewportWidth - tw - 12 // 右边界
|
|
||||||
|
|
||||||
// 垂直方向边界处理
|
|
||||||
if (top < 0)
|
|
||||||
top = 12 // 上边界
|
|
||||||
else if (top + th > viewportHeight) top = viewportHeight - th - 12 // 下边界
|
|
||||||
|
|
||||||
// 应用位置
|
|
||||||
this.tooltip.style.left = `${left}px`
|
|
||||||
this.tooltip.style.top = `${top}px`
|
|
||||||
|
|
||||||
// 显示tooltip
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
this.tooltip.classList.add('visible')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this.tooltip.classList.remove('visible')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化Tooltip管理器
|
|
||||||
new TooltipManager()
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in new issue
Block a user