tang-ui-x 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/components/TActionSheet/index.uvue +170 -0
- package/components/TActionSheet/type.uts +29 -0
- package/components/TAvatar/index.uvue +156 -0
- package/components/TAvatar/type.uts +54 -0
- package/components/TBadge/index.uvue +152 -0
- package/components/TBadge/type.uts +48 -0
- package/components/TButton/README.md +111 -0
- package/components/TButton/index.uvue +380 -0
- package/components/TButton/type.uts +95 -0
- package/components/TCard/index.uvue +174 -0
- package/components/TCard/type.uts +50 -0
- package/components/TCell/index.uvue +49 -0
- package/components/TCheckbox/index.uvue +187 -0
- package/components/TCheckboxGroup/index.uvue +139 -0
- package/components/TCheckboxGroup/type.uts +26 -0
- package/components/TCol/index.uvue +82 -0
- package/components/TCol/type.uts +30 -0
- package/components/TCollapse/index.uvue +93 -0
- package/components/TCollapse/type.uts +36 -0
- package/components/TCollapseItem/index.uvue +194 -0
- package/components/TCollapseItem/type.uts +25 -0
- package/components/TDialog/index.uvue +386 -0
- package/components/TDialog/type.uts +84 -0
- package/components/TDivider/index.uvue +235 -0
- package/components/TDivider/type.uts +91 -0
- package/components/TEmpty/index.uvue +128 -0
- package/components/TErrorState/index.uvue +57 -0
- package/components/TGrid/index.uvue +115 -0
- package/components/TGrid/type.uts +77 -0
- package/components/TGridItem/index.uvue +243 -0
- package/components/TGridItem/type.uts +64 -0
- package/components/TIcon/index.uvue +96 -0
- package/components/TImage/index.uvue +255 -0
- package/components/TImage/type.uts +146 -0
- package/components/TInput/README.md +119 -0
- package/components/TInput/index.uvue +376 -0
- package/components/TInput/type.uts +138 -0
- package/components/TList/index.uvue +82 -0
- package/components/TList/type.uts +68 -0
- package/components/TListItem/index.uvue +161 -0
- package/components/TListItem/type.uts +49 -0
- package/components/TLoading/index.uvue +153 -0
- package/components/TLoading/type.uts +43 -0
- package/components/TNavBar/index.uvue +120 -0
- package/components/TNavBar/type.uts +22 -0
- package/components/TNoticeBar/index.uvue +106 -0
- package/components/TNoticeBar/type.uts +21 -0
- package/components/TNumberInput/index.uvue +226 -0
- package/components/TPicker/index.uvue +276 -0
- package/components/TPicker/type.uts +105 -0
- package/components/TPopup/index.uvue +442 -0
- package/components/TProgress/index.uvue +103 -0
- package/components/TProgress/type.uts +64 -0
- package/components/TRadioButton/index.uvue +232 -0
- package/components/TRadioGroup/index.uvue +117 -0
- package/components/TRadioGroup/type.uts +25 -0
- package/components/TRate/index.uvue +182 -0
- package/components/TRow/index.uvue +105 -0
- package/components/TRow/type.uts +52 -0
- package/components/TSearchBar/index.uvue +255 -0
- package/components/TSearchBar/type.uts +140 -0
- package/components/TSelect/index.uvue +655 -0
- package/components/TSelect/type.uts +57 -0
- package/components/TSlider/index.uvue +72 -0
- package/components/TSlider/type.uts +21 -0
- package/components/TSwiper/index.uvue +222 -0
- package/components/TSwiper/type.uts +77 -0
- package/components/TSwitch/index.uvue +177 -0
- package/components/TSwitch/type.uts +52 -0
- package/components/TText/README.md +124 -0
- package/components/TText/index.uvue +257 -0
- package/components/TText/type.uts +114 -0
- package/components/TTextarea/index.uvue +239 -0
- package/components/TTextarea/type.uts +106 -0
- package/components/TToast/type.uts +14 -0
- package/components/Tabs/README.md +297 -0
- package/components/Tabs/index.uvue +383 -0
- package/components/Tabs/type.uts +10 -0
- package/components/Tags/README.md +297 -0
- package/components/Tags/index.uvue +383 -0
- package/components/Tags/type.uts +10 -0
- package/components/VbenFrom/index.uvue +392 -0
- package/composables/useModal.uts +294 -0
- package/composables/useTheme.uts +235 -0
- package/composables/useToast.uts +322 -0
- package/index.js +62 -0
- package/package.json +48 -0
- package/style/colors/index.scss +157 -0
- package/style/index.scss +399 -0
- package/types/index.uts +52 -0
- package/uni.scss +79 -0
- package/utils/color.uts +92 -0
- package/utils/common.uts +245 -0
- package/utils/dom.uts +275 -0
- package/utils/index.uts +10 -0
- package/utils/validator.uts +155 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# UniApp X UI 组件库
|
|
2
|
+
|
|
3
|
+
基于 uni-app x 的移动端 UI 组件库,提供 42+ 个高质量组件。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 🎨 42+ 个精美组件
|
|
8
|
+
- 💪 使用 TypeScript 开发,提供完整的类型定义
|
|
9
|
+
- 📱 专为 uni-app x 优化
|
|
10
|
+
- 🔥 使用 Vue 3 Composition API
|
|
11
|
+
- 🎯 支持 defineModel 双向绑定
|
|
12
|
+
- 📦 支持按需引入
|
|
13
|
+
- 🌈 统一的设计规范
|
|
14
|
+
|
|
15
|
+
## 📦 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install tang-ui-x
|
|
19
|
+
# 或
|
|
20
|
+
yarn add tang-ui-x
|
|
21
|
+
# 或
|
|
22
|
+
pnpm add tang-ui-x
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🔨 使用
|
|
26
|
+
|
|
27
|
+
### 方式一:easycom 自动导入(推荐)
|
|
28
|
+
|
|
29
|
+
在 `pages.json` 中配置 easycom:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"easycom": {
|
|
34
|
+
"autoscan": true,
|
|
35
|
+
"custom": {
|
|
36
|
+
"^T(.*)": "tang-ui-x/components/T$1/T$1.vue"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
然后在页面中直接使用:
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<template>
|
|
46
|
+
<view>
|
|
47
|
+
<TButton type="primary">按钮</TButton>
|
|
48
|
+
<TInput v-model="value" placeholder="请输入" />
|
|
49
|
+
</view>
|
|
50
|
+
</template>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 方式二:按需引入
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup>
|
|
57
|
+
import { TButton, TInput } from 'tang-ui-x'
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<view>
|
|
62
|
+
<TButton type="primary">按钮</TButton>
|
|
63
|
+
<TInput v-model="value" placeholder="请输入" />
|
|
64
|
+
</view>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 📚 组件列表
|
|
69
|
+
|
|
70
|
+
### 基础组件 (5个)
|
|
71
|
+
- TButton - 按钮
|
|
72
|
+
- TIcon - 图标
|
|
73
|
+
- TText - 文本
|
|
74
|
+
- TImage - 图片
|
|
75
|
+
- TDivider - 分割线
|
|
76
|
+
|
|
77
|
+
### 布局组件 (8个)
|
|
78
|
+
- TCard - 卡片
|
|
79
|
+
- TList - 列表
|
|
80
|
+
- TListItem - 列表项
|
|
81
|
+
- TCell - 单元格
|
|
82
|
+
- TGrid - 宫格
|
|
83
|
+
- TGridItem - 宫格项
|
|
84
|
+
- TRow - 栅格行
|
|
85
|
+
- TCol - 栅格列
|
|
86
|
+
|
|
87
|
+
### 表单组件 (13个)
|
|
88
|
+
- TInput - 输入框
|
|
89
|
+
- TNumberInput - 数字输入
|
|
90
|
+
- TTextarea - 多行文本输入
|
|
91
|
+
- TSearchBar - 搜索框
|
|
92
|
+
- TSwitch - 开关
|
|
93
|
+
- TCheckbox - 复选框
|
|
94
|
+
- TCheckboxGroup - 复选组
|
|
95
|
+
- TRadioButton - 单选按钮
|
|
96
|
+
- TRadioGroup - 单选组
|
|
97
|
+
- TSelect - 选择器
|
|
98
|
+
- TSlider - 滑块
|
|
99
|
+
- TRate - 评分
|
|
100
|
+
- TPicker - 选择器
|
|
101
|
+
|
|
102
|
+
### 数据展示 (9个)
|
|
103
|
+
- Tags - 标签
|
|
104
|
+
- TBadge - 徽标
|
|
105
|
+
- TAvatar - 头像
|
|
106
|
+
- TProgress - 进度条
|
|
107
|
+
- TNoticeBar - 通知栏
|
|
108
|
+
- TCollapse - 折叠面板
|
|
109
|
+
- TCollapseItem - 折叠面板项
|
|
110
|
+
- TEmpty - 空状态
|
|
111
|
+
- TErrorState - 错误状态
|
|
112
|
+
- TSwiper - 轮播图
|
|
113
|
+
|
|
114
|
+
### 反馈组件 (5个)
|
|
115
|
+
- TLoading - 加载
|
|
116
|
+
- TToast - 轻提示
|
|
117
|
+
- TDialog - 对话框
|
|
118
|
+
- TPopup - 弹出层
|
|
119
|
+
- TActionSheet - 动作面板
|
|
120
|
+
|
|
121
|
+
### 导航组件 (2个)
|
|
122
|
+
- Tabs - 标签页
|
|
123
|
+
- TNavBar - 导航栏
|
|
124
|
+
|
|
125
|
+
## 📖 文档
|
|
126
|
+
|
|
127
|
+
详细文档请访问:[https://github.com/sugar258596/tang-ui](https://github.com/sugar258596/tang-ui)
|
|
128
|
+
|
|
129
|
+
## 🤝 贡献
|
|
130
|
+
|
|
131
|
+
欢迎提交 Issue 和 Pull Request!
|
|
132
|
+
|
|
133
|
+
## � 链接
|
|
134
|
+
|
|
135
|
+
- [GitHub](https://github.com/sugar258596/tang-ui)
|
|
136
|
+
- [Issues](https://github.com/sugar258596/tang-ui/issues)
|
|
137
|
+
- [NPM](https://www.npmjs.com/package/tang-ui-x)
|
|
138
|
+
|
|
139
|
+
## 📄 License
|
|
140
|
+
|
|
141
|
+
MIT License
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script setup lang="uts">
|
|
2
|
+
import TPopup from '@/components/TPopup/index.uvue'
|
|
3
|
+
import type { ActionSheetAction, TActionSheetProps } from './type.uts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TActionSheet 动作面板组件
|
|
7
|
+
* @description 底部弹出的动作菜单,基于 TPopup 组件实现
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Props 定义 (从 type.uts 导入,排除 show)
|
|
11
|
+
type Props = Omit<TActionSheetProps, 'show'>
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
+
actions: () => [] as ActionSheetAction[],
|
|
15
|
+
title: '',
|
|
16
|
+
description: '',
|
|
17
|
+
cancelText: '取消',
|
|
18
|
+
closeOnClickAction: true,
|
|
19
|
+
closeOnClickOverlay: true,
|
|
20
|
+
customClass: '',
|
|
21
|
+
customStyle: ''
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// 使用 defineModel 管理显示状态 (v-model)
|
|
25
|
+
const visible = defineModel<boolean>({ default: false })
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
select: [action: ActionSheetAction, index: number]
|
|
29
|
+
cancel: []
|
|
30
|
+
close: []
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
const handleSelect = (action: ActionSheetAction, index: number): void => {
|
|
34
|
+
if (action.disabled || action.loading) return
|
|
35
|
+
|
|
36
|
+
emit('select', action, index)
|
|
37
|
+
if (props.closeOnClickAction) {
|
|
38
|
+
visible.value = false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleCancel = (): void => {
|
|
43
|
+
emit('cancel')
|
|
44
|
+
visible.value = false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const handleClose = (): void => {
|
|
48
|
+
emit('close')
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<TPopup
|
|
54
|
+
v-model="visible"
|
|
55
|
+
position="bottom"
|
|
56
|
+
:show-title="false"
|
|
57
|
+
:show-close="false"
|
|
58
|
+
:close-on-click-overlay="closeOnClickOverlay"
|
|
59
|
+
:border-radius="16"
|
|
60
|
+
height="auto"
|
|
61
|
+
@close="handleClose"
|
|
62
|
+
>
|
|
63
|
+
<view class="t-action-sheet" :class="customClass" :style="customStyle">
|
|
64
|
+
<!-- 头部:标题和描述 -->
|
|
65
|
+
<view v-if="title || description" class="t-action-sheet__header">
|
|
66
|
+
<text v-if="title" class="t-action-sheet__title">{{ title }}</text>
|
|
67
|
+
<text v-if="description" class="t-action-sheet__description">{{ description }}</text>
|
|
68
|
+
</view>
|
|
69
|
+
|
|
70
|
+
<!-- 操作列表 -->
|
|
71
|
+
<view class="t-action-sheet__actions">
|
|
72
|
+
<view
|
|
73
|
+
v-for="(action, index) in actions"
|
|
74
|
+
:key="index"
|
|
75
|
+
class="t-action-sheet__action"
|
|
76
|
+
:class="{
|
|
77
|
+
't-action-sheet__action--disabled': action.disabled,
|
|
78
|
+
't-action-sheet__action--loading': action.loading
|
|
79
|
+
}"
|
|
80
|
+
@click="() => handleSelect(action, index)"
|
|
81
|
+
>
|
|
82
|
+
<text
|
|
83
|
+
class="t-action-sheet__action-text"
|
|
84
|
+
:style="action.color ? `color: ${action.color}` : ''"
|
|
85
|
+
>
|
|
86
|
+
{{ action.name }}
|
|
87
|
+
</text>
|
|
88
|
+
</view>
|
|
89
|
+
</view>
|
|
90
|
+
|
|
91
|
+
<!-- 取消按钮 -->
|
|
92
|
+
<view class="t-action-sheet__cancel" @click="handleCancel">
|
|
93
|
+
<text class="t-action-sheet__cancel-text">{{ cancelText }}</text>
|
|
94
|
+
</view>
|
|
95
|
+
</view>
|
|
96
|
+
</TPopup>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<style lang="scss" scoped>
|
|
100
|
+
.t-action-sheet {
|
|
101
|
+
background-color: transparent;
|
|
102
|
+
padding: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.t-action-sheet__header {
|
|
106
|
+
padding: 16px;
|
|
107
|
+
text-align: center;
|
|
108
|
+
border-bottom: 1px solid #ebedf0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.t-action-sheet__title {
|
|
112
|
+
font-size: 16px;
|
|
113
|
+
font-weight: 500;
|
|
114
|
+
color: #323233;
|
|
115
|
+
display: block;
|
|
116
|
+
margin-bottom: 8px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.t-action-sheet__description {
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
color: #969799;
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.t-action-sheet__actions {
|
|
126
|
+
max-height: 400px;
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.t-action-sheet__action {
|
|
131
|
+
padding: 16px;
|
|
132
|
+
display: flex;;
|
|
133
|
+
align-items: center;
|
|
134
|
+
text-align: center;
|
|
135
|
+
border-bottom: 1px solid #ebedf0;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
|
|
138
|
+
&:active {
|
|
139
|
+
background-color: #f5f5f5;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&--disabled {
|
|
143
|
+
opacity: 0.5;
|
|
144
|
+
cursor: not-allowed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.t-action-sheet__action-text {
|
|
149
|
+
font-size: 16px;
|
|
150
|
+
color: #323233;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.t-action-sheet__cancel {
|
|
154
|
+
padding: 16px;
|
|
155
|
+
display: flex;;
|
|
156
|
+
align-items: center;
|
|
157
|
+
text-align: center;
|
|
158
|
+
border-top: 8px solid #f5f5f5;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
|
|
161
|
+
&:active {
|
|
162
|
+
background-color: #f5f5f5;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.t-action-sheet__cancel-text {
|
|
167
|
+
font-size: 16px;
|
|
168
|
+
color: #323233;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TActionSheet 组件类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ActionSheetAction {
|
|
6
|
+
name: string
|
|
7
|
+
color?: string
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
loading?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TActionSheetProps {
|
|
13
|
+
show?: boolean
|
|
14
|
+
actions?: ActionSheetAction[]
|
|
15
|
+
title?: string
|
|
16
|
+
description?: string
|
|
17
|
+
cancelText?: string
|
|
18
|
+
closeOnClickAction?: boolean
|
|
19
|
+
closeOnClickOverlay?: boolean
|
|
20
|
+
customClass?: string
|
|
21
|
+
customStyle?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TActionSheetEmits {
|
|
25
|
+
'update:show': (value: boolean) => void
|
|
26
|
+
select: (action: ActionSheetAction, index: number) => void
|
|
27
|
+
cancel: () => void
|
|
28
|
+
close: () => void
|
|
29
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<script setup lang="uts" >
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { TAvatarProps } from './type.uts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TAvatar 头像组件
|
|
7
|
+
* @description 用于展示用户头像或图标
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// 定义 props
|
|
11
|
+
const props = withDefaults(defineProps<TAvatarProps>(), {
|
|
12
|
+
src: '',
|
|
13
|
+
size: 'medium',
|
|
14
|
+
shape: 'circle',
|
|
15
|
+
alt: '',
|
|
16
|
+
bgColor: '#c0c4cc',
|
|
17
|
+
color: '#ffffff',
|
|
18
|
+
fit: 'cover'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// 定义 emits
|
|
22
|
+
const emit = defineEmits<{
|
|
23
|
+
click: []
|
|
24
|
+
error: []
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
// 图片加载失败标记
|
|
28
|
+
const hasLoadError = ref<boolean>(false)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 计算头像大小
|
|
32
|
+
*/
|
|
33
|
+
const avatarSize = computed<number>(() => {
|
|
34
|
+
if (typeof props.size === 'number') {
|
|
35
|
+
return props.size
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sizeMap = {
|
|
39
|
+
small: 32,
|
|
40
|
+
medium: 40,
|
|
41
|
+
large: 56
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return sizeMap[props.size] || 40
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 计算头像样式类
|
|
49
|
+
*/
|
|
50
|
+
const avatarClass = computed<string>(() => {
|
|
51
|
+
const classes: string[] = ['t-avatar']
|
|
52
|
+
|
|
53
|
+
classes.push(`t-avatar--${props.shape}`)
|
|
54
|
+
|
|
55
|
+
return classes.join(' ')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 计算头像样式
|
|
60
|
+
*/
|
|
61
|
+
const avatarStyle = computed<string>(() => {
|
|
62
|
+
const styles: string[] = []
|
|
63
|
+
|
|
64
|
+
const size = avatarSize.value
|
|
65
|
+
styles.push(`width: ${size}px`)
|
|
66
|
+
styles.push(`height: ${size}px`)
|
|
67
|
+
|
|
68
|
+
if (!props.src || hasLoadError.value) {
|
|
69
|
+
styles.push(`background-color: ${props.bgColor}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return styles.join('; ')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 计算文字样式
|
|
77
|
+
*/
|
|
78
|
+
const textStyle = computed<string>(() => {
|
|
79
|
+
const styles: string[] = []
|
|
80
|
+
|
|
81
|
+
styles.push(`color: ${props.color}`)
|
|
82
|
+
styles.push(`font-size: ${avatarSize.value / 2}px`)
|
|
83
|
+
|
|
84
|
+
return styles.join('; ')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 处理点击事件
|
|
89
|
+
*/
|
|
90
|
+
const handleClick = (): void => {
|
|
91
|
+
emit('click')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 处理图片加载失败
|
|
96
|
+
*/
|
|
97
|
+
const handleError = (): void => {
|
|
98
|
+
hasLoadError.value = true
|
|
99
|
+
emit('error')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 获取显示的文字(取首字符)
|
|
104
|
+
*/
|
|
105
|
+
const displayText = computed<string>(() => {
|
|
106
|
+
if (props.alt) {
|
|
107
|
+
return props.alt.charAt(0).toUpperCase()
|
|
108
|
+
}
|
|
109
|
+
return ''
|
|
110
|
+
})
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<view :class="avatarClass" :style="avatarStyle" @click="handleClick">
|
|
115
|
+
<image
|
|
116
|
+
v-if="src && !hasLoadError"
|
|
117
|
+
class="t-avatar__image"
|
|
118
|
+
:src="src"
|
|
119
|
+
:mode="fit"
|
|
120
|
+
@error="handleError"
|
|
121
|
+
/>
|
|
122
|
+
<text v-else-if="alt" class="t-avatar__text" :style="textStyle">
|
|
123
|
+
{{ displayText }}
|
|
124
|
+
</text>
|
|
125
|
+
<slot v-else></slot>
|
|
126
|
+
</view>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<style lang="scss" scoped>
|
|
130
|
+
.t-avatar {
|
|
131
|
+
display: inline-flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
position: relative;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
|
|
138
|
+
&--circle {
|
|
139
|
+
border-radius: 50%;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&--square {
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__image {
|
|
147
|
+
width: 100%;
|
|
148
|
+
height: 100%;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&__text {
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
text-align: center;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAvatar 头像组件属性类型定义
|
|
3
|
+
*/
|
|
4
|
+
export type TAvatarProps = {
|
|
5
|
+
/**
|
|
6
|
+
* 头像图片地址
|
|
7
|
+
*/
|
|
8
|
+
src?: string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 头像大小
|
|
12
|
+
* @default "medium"
|
|
13
|
+
*/
|
|
14
|
+
size?: 'small' | 'medium' | 'large' | number
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 头像形状
|
|
18
|
+
* @default "circle"
|
|
19
|
+
*/
|
|
20
|
+
shape?: 'circle' | 'square'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 替代文字(无图片时显示)
|
|
24
|
+
*/
|
|
25
|
+
alt?: string
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 背景色
|
|
29
|
+
*/
|
|
30
|
+
bgColor?: string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 文字颜色
|
|
34
|
+
*/
|
|
35
|
+
color?: string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 图片填充模式
|
|
39
|
+
* @default "cover"
|
|
40
|
+
*/
|
|
41
|
+
fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type TAvatarEmits = {
|
|
45
|
+
/**
|
|
46
|
+
* 点击头像时触发
|
|
47
|
+
*/
|
|
48
|
+
click: () => void
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 图片加载失败时触发
|
|
52
|
+
*/
|
|
53
|
+
error: () => void
|
|
54
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<script setup lang="uts" >
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { TBadgeProps } from './type.uts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TBadge 徽标组件
|
|
7
|
+
* @description 用于显示数字或状态标记
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// 定义 props
|
|
11
|
+
const props = withDefaults(defineProps<TBadgeProps>(), {
|
|
12
|
+
value: '',
|
|
13
|
+
max: 99,
|
|
14
|
+
isDot: false,
|
|
15
|
+
hidden: false,
|
|
16
|
+
type: 'danger',
|
|
17
|
+
bgColor: '',
|
|
18
|
+
color: '',
|
|
19
|
+
offset: () => [0, 0] as [number, number]
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 计算显示的内容
|
|
24
|
+
*/
|
|
25
|
+
const displayValue = computed<string>(() => {
|
|
26
|
+
if (props.isDot) {
|
|
27
|
+
return ''
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const val = props.value
|
|
31
|
+
if (typeof val === 'number' && val > props.max) {
|
|
32
|
+
return `${props.max}+`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return String(val)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 计算徽标样式类
|
|
40
|
+
*/
|
|
41
|
+
const badgeClass = computed<string>(() => {
|
|
42
|
+
const classes: string[] = ['t-badge__content']
|
|
43
|
+
|
|
44
|
+
if (props.isDot) {
|
|
45
|
+
classes.push('t-badge__content--dot')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
classes.push(`t-badge__content--${props.type}`)
|
|
49
|
+
|
|
50
|
+
return classes.join(' ')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 计算徽标样式
|
|
55
|
+
*/
|
|
56
|
+
const badgeStyle = computed<string>(() => {
|
|
57
|
+
const styles: string[] = []
|
|
58
|
+
|
|
59
|
+
if (props.bgColor != '') {
|
|
60
|
+
styles.push(`background-color: ${props.bgColor}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (props.color != '') {
|
|
64
|
+
styles.push(`color: ${props.color}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (props.offset && props.offset.length === 2) {
|
|
68
|
+
const [x, y] = props.offset
|
|
69
|
+
if (x !== 0) {
|
|
70
|
+
styles.push(`right: ${-x}px`)
|
|
71
|
+
}
|
|
72
|
+
if (y !== 0) {
|
|
73
|
+
styles.push(`top: ${y}px`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return styles.join('; ')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 是否显示徽标
|
|
82
|
+
*/
|
|
83
|
+
const showBadge = computed<boolean>(() => {
|
|
84
|
+
return !props.hidden && (props.isDot || props.value !== '' && props.value !== 0)
|
|
85
|
+
})
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<view class="t-badge">
|
|
90
|
+
<slot></slot>
|
|
91
|
+
<view v-if="showBadge" :class="badgeClass" :style="badgeStyle">
|
|
92
|
+
<text v-if="!isDot" class="t-badge__text">{{ displayValue }}</text>
|
|
93
|
+
</view>
|
|
94
|
+
</view>
|
|
95
|
+
</template>
|
|
96
|
+
|
|
97
|
+
<style lang="scss" scoped>
|
|
98
|
+
.t-badge {
|
|
99
|
+
position: relative;
|
|
100
|
+
display: inline-block;
|
|
101
|
+
overflow:visible;
|
|
102
|
+
|
|
103
|
+
&__content {
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: 0;
|
|
106
|
+
right: 0;
|
|
107
|
+
transform: translate(50%, -50%);
|
|
108
|
+
border-radius: 10px;
|
|
109
|
+
padding: 2px 6px;
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
line-height: 1.2;
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
z-index: 1;
|
|
114
|
+
|
|
115
|
+
&--dot {
|
|
116
|
+
width: 8px;
|
|
117
|
+
height: 8px;
|
|
118
|
+
padding: 0;
|
|
119
|
+
border-radius: 50%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&--primary {
|
|
123
|
+
background-color: #409eff;
|
|
124
|
+
color: #ffffff;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&--success {
|
|
128
|
+
background-color: #67c23a;
|
|
129
|
+
color: #ffffff;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&--warning {
|
|
133
|
+
background-color: #e6a23c;
|
|
134
|
+
color: #ffffff;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
&--danger {
|
|
138
|
+
background-color: #f56c6c;
|
|
139
|
+
color: #ffffff;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&--info {
|
|
143
|
+
background-color: #909399;
|
|
144
|
+
color: #ffffff;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&__text {
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
</style>
|