oxy-uni-ui 1.1.0 → 1.2.3
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/attributes.json +1 -1
- package/components/common/abstracts/variable.scss +59 -1
- package/components/common/path.ts +9 -0
- package/components/common/util.ts +42 -0
- package/components/composables/index.ts +1 -0
- package/components/composables/useGlobalLoading.ts +42 -0
- package/components/composables/useGlobalMessage.ts +48 -0
- package/components/composables/useGlobalToast.ts +84 -0
- package/components/composables/useVirtualScroll.ts +173 -0
- package/components/oxy-cell/oxy-cell.vue +15 -2
- package/components/oxy-cell/types.ts +4 -0
- package/components/oxy-checkbox/index.scss +1 -1
- package/components/oxy-checkbox/oxy-checkbox.vue +2 -2
- package/components/oxy-col-picker/oxy-col-picker.vue +3 -0
- package/components/oxy-col-picker/types.ts +5 -1
- package/components/oxy-corner/index.scss +121 -1
- package/components/oxy-corner/oxy-corner.vue +18 -5
- package/components/oxy-corner/types.ts +24 -3
- package/components/oxy-date-strip/index.scss +10 -0
- package/components/oxy-date-strip/oxy-date-strip.vue +198 -0
- package/components/oxy-date-strip/types.ts +98 -0
- package/components/oxy-date-strip/utils.ts +67 -0
- package/components/oxy-date-strip-item/index.scss +94 -0
- package/components/oxy-date-strip-item/oxy-date-strip-item.vue +102 -0
- package/components/oxy-date-strip-item/types.ts +53 -0
- package/components/oxy-datetime-picker/oxy-datetime-picker.vue +3 -1
- package/components/oxy-datetime-picker/types.ts +5 -1
- package/components/oxy-echarts/index.scss +17 -0
- package/components/oxy-echarts/index.ts +1 -0
- package/components/oxy-echarts/oxy-echarts.vue +32 -0
- package/components/oxy-echarts/types.ts +12 -0
- package/components/oxy-file-list/index.scss +26 -0
- package/components/oxy-file-list/oxy-file-list.vue +208 -34
- package/components/oxy-file-list/types.ts +58 -2
- package/components/oxy-global-loading/oxy-global-loading.vue +53 -0
- package/components/oxy-global-message/oxy-global-message.vue +64 -0
- package/components/oxy-global-toast/oxy-global-toast.vue +53 -0
- package/components/oxy-img-lazy/index.scss +17 -0
- package/components/oxy-img-lazy/oxy-img-lazy.vue +332 -0
- package/components/oxy-img-lazy/types.ts +69 -0
- package/components/oxy-link/index.scss +57 -0
- package/components/oxy-link/oxy-link.vue +130 -0
- package/components/oxy-link/types.ts +81 -0
- package/components/oxy-list/index.scss +8 -1
- package/components/oxy-list/oxy-list.vue +121 -40
- package/components/oxy-list/types.ts +3 -15
- package/components/oxy-picker/oxy-picker.vue +3 -0
- package/components/oxy-picker/types.ts +5 -1
- package/components/oxy-radio/index.scss +3 -3
- package/components/oxy-radio/oxy-radio.vue +1 -1
- package/components/oxy-rich-text/icon/emjio.svg +1 -0
- package/components/oxy-rich-text/icon/quote.svg +1 -0
- package/components/oxy-rich-text/icon/text.svg +1 -0
- package/components/oxy-rich-text/icon/title.svg +1 -0
- package/components/oxy-rich-text/index.scss +159 -0
- package/components/oxy-rich-text/mp-html/card/card.vue +122 -0
- package/components/oxy-rich-text/mp-html/card/index.js +7 -0
- package/components/oxy-rich-text/mp-html/editable/config.js +15 -0
- package/components/oxy-rich-text/mp-html/editable/index.js +553 -0
- package/components/oxy-rich-text/mp-html/emoji/index.js +203 -0
- package/components/oxy-rich-text/mp-html/highlight/config.js +5 -0
- package/components/oxy-rich-text/mp-html/highlight/index.js +96 -0
- package/components/oxy-rich-text/mp-html/highlight/prism.css +1 -0
- package/components/oxy-rich-text/mp-html/highlight/prism.min.js +7 -0
- package/components/oxy-rich-text/mp-html/img-cache/index.js +138 -0
- package/components/oxy-rich-text/mp-html/latex/index.js +80 -0
- package/components/oxy-rich-text/mp-html/latex/katex.css +1 -0
- package/components/oxy-rich-text/mp-html/latex/katex.min.js +1 -0
- package/components/oxy-rich-text/mp-html/markdown/index.js +50 -0
- package/components/oxy-rich-text/mp-html/markdown/marked.min.js +71 -0
- package/components/oxy-rich-text/mp-html/mp-html.d.ts +184 -0
- package/components/oxy-rich-text/mp-html/mp-html.vue +675 -0
- package/components/oxy-rich-text/mp-html/node/node.vue +1161 -0
- package/components/oxy-rich-text/mp-html/parser.js +1428 -0
- package/components/oxy-rich-text/mp-html/search/index.js +132 -0
- package/components/oxy-rich-text/mp-html/style/index.js +129 -0
- package/components/oxy-rich-text/mp-html/style/parser.js +175 -0
- package/components/oxy-rich-text/mp-html/template/index.js +67 -0
- package/components/oxy-rich-text/mp-html/txv-video/index.js +46 -0
- package/components/oxy-rich-text/oxy-rich-text.vue +642 -0
- package/components/oxy-rich-text/types.ts +71 -0
- package/components/oxy-select/index.scss +255 -0
- package/components/oxy-select/oxy-select.vue +421 -0
- package/components/oxy-select/types.ts +71 -0
- package/components/oxy-select-picker/oxy-select-picker.vue +3 -0
- package/components/oxy-select-picker/types.ts +5 -1
- package/components/oxy-stream-render/index.scss +6 -0
- package/components/oxy-stream-render/oxy-stream-render.vue +204 -0
- package/components/oxy-stream-render/types.ts +5 -0
- package/components/oxy-tree/index.scss +43 -5
- package/components/oxy-tree/oxy-tree.vue +233 -35
- package/components/oxy-tree/types.ts +54 -7
- package/components/oxy-tree/utils.ts +51 -0
- package/components/oxy-virtual-scroll/index.scss +1 -1
- package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +69 -110
- package/components/oxy-virtual-scroll/types.ts +95 -5
- package/components/oxy-waterfall/index.scss +18 -0
- package/components/oxy-waterfall/oxy-waterfall.vue +218 -0
- package/components/oxy-waterfall/types.ts +90 -0
- package/components/oxy-waterfall-item/index.scss +8 -0
- package/components/oxy-waterfall-item/oxy-waterfall-item.vue +89 -0
- package/components/oxy-waterfall-item/types.ts +16 -0
- package/global.d.ts +7 -0
- package/index.ts +3 -0
- package/locale/lang/en-US.ts +35 -9
- package/locale/lang/zh-CN.ts +31 -5
- package/oxy-uni-ui.zip +0 -0
- package/package.json +1 -1
- package/tags.json +1 -1
- package/uni-echarts/changelog.md +2 -0
- package/uni-echarts/components/index.js +1 -0
- package/uni-echarts/components/uni-echarts/events.js +95 -0
- package/uni-echarts/components/uni-echarts/types.d.ts +183 -0
- package/uni-echarts/components/uni-echarts/types.js +1 -0
- package/uni-echarts/components/uni-echarts/uni-echarts.vue +530 -0
- package/uni-echarts/components/uni-echarts/uni-echarts.vue.d.ts +19 -0
- package/uni-echarts/global.d.ts +7 -0
- package/uni-echarts/index.d.ts +440 -0
- package/uni-echarts/index.js +2 -0
- package/uni-echarts/package.json +105 -0
- package/uni-echarts/shared-core.d.ts +269 -0
- package/uni-echarts/shared-core.js +900 -0
- package/web-types.json +1 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view :class="`oxy-img-lazy ${customClass}`" :style="rootStyle">
|
|
3
|
+
<!-- 加载中 -->
|
|
4
|
+
<image
|
|
5
|
+
:src="loadSrc"
|
|
6
|
+
class="oxy-img-lazy__image oxy-img-lazy__load"
|
|
7
|
+
@load="init"
|
|
8
|
+
:show-menu-by-longpress="true"
|
|
9
|
+
:mode="mode"
|
|
10
|
+
:style="{
|
|
11
|
+
opacity: isShow ? '0' : '1',
|
|
12
|
+
borderRadius,
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
transition: `opacity ${duration / 1000}s ${effect}`
|
|
16
|
+
}"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<!-- 加载成功 -->
|
|
20
|
+
<image
|
|
21
|
+
class="oxy-img-lazy__image"
|
|
22
|
+
@load="load"
|
|
23
|
+
@error="error"
|
|
24
|
+
:show-menu-by-longpress="true"
|
|
25
|
+
:webp="true"
|
|
26
|
+
v-if="status === 1"
|
|
27
|
+
:src="imageSrc"
|
|
28
|
+
:mode="mode"
|
|
29
|
+
:style="{
|
|
30
|
+
opacity: isShow ? '1' : '0',
|
|
31
|
+
borderRadius,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
transition: `opacity ${duration / 1000}s ${effect}`
|
|
35
|
+
}"
|
|
36
|
+
/>
|
|
37
|
+
<!-- 加载失败 -->
|
|
38
|
+
<image
|
|
39
|
+
class="oxy-img-lazy__image"
|
|
40
|
+
v-if="status === 2"
|
|
41
|
+
:show-menu-by-longpress="true"
|
|
42
|
+
:src="errorSrc"
|
|
43
|
+
:mode="mode"
|
|
44
|
+
:style="{
|
|
45
|
+
opacity: isShow ? '1' : '0',
|
|
46
|
+
borderRadius,
|
|
47
|
+
width,
|
|
48
|
+
height,
|
|
49
|
+
transition: `opacity ${duration / 1000}s ${effect}`
|
|
50
|
+
}"
|
|
51
|
+
/>
|
|
52
|
+
</view>
|
|
53
|
+
</template>
|
|
54
|
+
<script lang="ts">
|
|
55
|
+
export default {
|
|
56
|
+
name: 'oxy-img-lazy',
|
|
57
|
+
options: {
|
|
58
|
+
virtualHost: true,
|
|
59
|
+
addGlobalClass: true,
|
|
60
|
+
styleIsolation: 'shared'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<script lang="ts" setup>
|
|
66
|
+
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
67
|
+
import { addUnit, isDef, objToStyle } from '../common/util'
|
|
68
|
+
import { imgLazyProps } from './types'
|
|
69
|
+
|
|
70
|
+
const props = defineProps(imgLazyProps)
|
|
71
|
+
const emit = defineEmits(['show', 'load', 'error', 'destroyed'])
|
|
72
|
+
|
|
73
|
+
const rootStyle = computed(() => {
|
|
74
|
+
const style: Record<string, string | number> = {}
|
|
75
|
+
if (isDef(props.height)) {
|
|
76
|
+
style['height'] = addUnit(props.height)
|
|
77
|
+
}
|
|
78
|
+
if (isDef(props.width)) {
|
|
79
|
+
style['width'] = addUnit(props.width)
|
|
80
|
+
}
|
|
81
|
+
return `${objToStyle(style)}${props.customStyle}`
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// 响应式数据
|
|
85
|
+
const status = ref<number>(0) // 0加载中 1加载成功 2加载失败
|
|
86
|
+
const isShow = ref<boolean>(false)
|
|
87
|
+
const loadTimer = ref<number | null>(null)
|
|
88
|
+
const observer = ref<any>(null)
|
|
89
|
+
const renderCount = ref<number>(0) // 添加渲染计数器
|
|
90
|
+
const lastSrc = ref<string>('') // 记录上一次的src值
|
|
91
|
+
const scrollTimeout = ref<number | null>(null) // 滚动检测定时器
|
|
92
|
+
const retryCount = ref<number>(0) // 重试次数计数器
|
|
93
|
+
const maxRetries = ref<number>(3) // 最大重试次数
|
|
94
|
+
const imageSrc = ref<string>('')
|
|
95
|
+
// 获取当前组件实例
|
|
96
|
+
const instance = getCurrentInstance()
|
|
97
|
+
|
|
98
|
+
// 重置状态方法
|
|
99
|
+
const resetStatus = () => {
|
|
100
|
+
status.value = 0
|
|
101
|
+
isShow.value = false
|
|
102
|
+
loadTimer.value = null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 清理资源方法
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
if (observer.value) {
|
|
108
|
+
observer.value.disconnect()
|
|
109
|
+
observer.value = null
|
|
110
|
+
}
|
|
111
|
+
if (loadTimer.value) {
|
|
112
|
+
clearTimeout(loadTimer.value)
|
|
113
|
+
loadTimer.value = null
|
|
114
|
+
}
|
|
115
|
+
if (scrollTimeout.value) {
|
|
116
|
+
clearTimeout(scrollTimeout.value)
|
|
117
|
+
scrollTimeout.value = null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 开始加载方法
|
|
122
|
+
const startLoad = () => {
|
|
123
|
+
status.value = 1
|
|
124
|
+
loadTimer.value = new Date().getTime()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 图片加载成功
|
|
128
|
+
const load = (event: any) => {
|
|
129
|
+
const minTimeOut = props.minTimeOut
|
|
130
|
+
|
|
131
|
+
if (minTimeOut <= 0) {
|
|
132
|
+
isShow.value = true
|
|
133
|
+
} else {
|
|
134
|
+
// 确保loadTimer存在
|
|
135
|
+
const timerDiff = loadTimer.value ? new Date().getTime() - loadTimer.value : 0
|
|
136
|
+
|
|
137
|
+
if (timerDiff < minTimeOut) {
|
|
138
|
+
loadTimer.value = setTimeout(() => {
|
|
139
|
+
isShow.value = true
|
|
140
|
+
loadTimer.value = null
|
|
141
|
+
}, minTimeOut - timerDiff) as unknown as number
|
|
142
|
+
} else {
|
|
143
|
+
isShow.value = true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
emit('load', event)
|
|
149
|
+
}, props.duration)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 图片加载失败
|
|
153
|
+
const error = (event: any) => {
|
|
154
|
+
imageSrc.value = props.errorSrc
|
|
155
|
+
status.value = 2
|
|
156
|
+
isShow.value = true
|
|
157
|
+
cleanup() // 加载失败时清理资源
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
emit('error', event)
|
|
160
|
+
}, props.duration)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 初始化懒加载
|
|
164
|
+
const init = async () => {
|
|
165
|
+
// 确保清理之前的观察者
|
|
166
|
+
cleanup()
|
|
167
|
+
|
|
168
|
+
// 如果没有src,不进行懒加载
|
|
169
|
+
if (!props.src) return
|
|
170
|
+
|
|
171
|
+
// 如果组件已经在视口内,直接开始加载
|
|
172
|
+
const isInViewport = await isComponentInViewport()
|
|
173
|
+
if (isInViewport) {
|
|
174
|
+
startLoad()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// 使用组件实例创建IntersectionObserver
|
|
180
|
+
observer.value = uni.createIntersectionObserver(instance)
|
|
181
|
+
let load = false
|
|
182
|
+
|
|
183
|
+
observer.value.relativeToViewport(props.showDistance).observe('.oxy-img-lazy__load', (res: any) => {
|
|
184
|
+
if (!load && res.intersectionRatio > 0) {
|
|
185
|
+
emit('show')
|
|
186
|
+
load = true
|
|
187
|
+
startLoad()
|
|
188
|
+
cleanup()
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('懒加载初始化失败:', error)
|
|
193
|
+
// 降级处理:如果IntersectionObserver失败,直接加载
|
|
194
|
+
startLoad()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 检查组件是否在视口内
|
|
199
|
+
const isComponentInViewport = (): Promise<boolean> => {
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
try {
|
|
202
|
+
const query = uni.createSelectorQuery().in(instance)
|
|
203
|
+
query
|
|
204
|
+
.select('.oxy-img-lazy__load')
|
|
205
|
+
.boundingClientRect((rect: any) => {
|
|
206
|
+
if (rect) {
|
|
207
|
+
const windowHeight = uni.getSystemInfoSync().windowHeight
|
|
208
|
+
const windowWidth = uni.getSystemInfoSync().windowWidth
|
|
209
|
+
|
|
210
|
+
// 检查是否在可视区域内(考虑滚动容器的偏移)
|
|
211
|
+
const isInViewport = rect.top < windowHeight && rect.bottom > 0 && rect.left < windowWidth && rect.right > 0
|
|
212
|
+
|
|
213
|
+
resolve(isInViewport)
|
|
214
|
+
} else {
|
|
215
|
+
resolve(false)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
.exec()
|
|
219
|
+
} catch (error) {
|
|
220
|
+
resolve(false)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 监听src变化
|
|
226
|
+
watch(
|
|
227
|
+
() => props.src,
|
|
228
|
+
(newSrc, oldSrc) => {
|
|
229
|
+
// 无论isShow状态如何,都重置加载状态
|
|
230
|
+
if (newSrc) {
|
|
231
|
+
imageSrc.value = newSrc
|
|
232
|
+
}
|
|
233
|
+
if (newSrc !== oldSrc) {
|
|
234
|
+
resetStatus()
|
|
235
|
+
// 如果组件已经可见,直接开始加载
|
|
236
|
+
if (isShow.value) {
|
|
237
|
+
startLoad()
|
|
238
|
+
} else {
|
|
239
|
+
// 重新初始化懒加载观察者
|
|
240
|
+
nextTick(() => {
|
|
241
|
+
init()
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
immediate: true,
|
|
248
|
+
deep: true
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
// 添加一个强制刷新方法
|
|
253
|
+
const forceRefresh = () => {
|
|
254
|
+
resetStatus()
|
|
255
|
+
cleanup()
|
|
256
|
+
nextTick(() => {
|
|
257
|
+
init()
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 检查是否需要重新初始化
|
|
262
|
+
const checkAndReinit = () => {
|
|
263
|
+
// 如果src发生变化,或者这是第一次渲染,则重新初始化
|
|
264
|
+
if (props.src !== lastSrc.value || renderCount.value === 0) {
|
|
265
|
+
lastSrc.value = props.src || ''
|
|
266
|
+
resetStatus()
|
|
267
|
+
nextTick(() => {
|
|
268
|
+
init()
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
renderCount.value++
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 滚动检测和手动触发机制
|
|
275
|
+
const setupScrollDetection = () => {
|
|
276
|
+
// 监听页面滚动事件
|
|
277
|
+
uni.$on('pageScroll', () => {
|
|
278
|
+
if (scrollTimeout.value) {
|
|
279
|
+
clearTimeout(scrollTimeout.value)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 防抖处理,滚动停止后检测可见性
|
|
283
|
+
scrollTimeout.value = setTimeout(() => {
|
|
284
|
+
checkVisibilityOnScroll()
|
|
285
|
+
}, 300) as unknown as number
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 滚动时检查可见性
|
|
290
|
+
const checkVisibilityOnScroll = async () => {
|
|
291
|
+
if (status.value === 0) {
|
|
292
|
+
// 只有在未加载状态下检查
|
|
293
|
+
const isInViewport = await isComponentInViewport()
|
|
294
|
+
if (isInViewport) {
|
|
295
|
+
startLoad()
|
|
296
|
+
cleanup() // 清理观察器
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 手动触发加载(用于scroll-view和swiper等特殊情况)
|
|
302
|
+
const manualTriggerLoad = () => {
|
|
303
|
+
if (status.value === 0) {
|
|
304
|
+
startLoad()
|
|
305
|
+
cleanup()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 暴露方法给父组件
|
|
310
|
+
defineExpose({
|
|
311
|
+
forceRefresh,
|
|
312
|
+
manualTriggerLoad
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// 组件挂载后初始化
|
|
316
|
+
onMounted(() => {
|
|
317
|
+
// 增加渲染计数并检查是否需要重新初始化
|
|
318
|
+
checkAndReinit()
|
|
319
|
+
// 设置滚动检测
|
|
320
|
+
setupScrollDetection()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// 组件销毁前清理资源
|
|
324
|
+
onBeforeUnmount(() => {
|
|
325
|
+
cleanup()
|
|
326
|
+
emit('destroyed')
|
|
327
|
+
})
|
|
328
|
+
</script>
|
|
329
|
+
|
|
330
|
+
<style lang="scss" scoped>
|
|
331
|
+
@import './index.scss';
|
|
332
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { baseProps, makeNumberProp, makeStringProp, numericProp } from '../common/props'
|
|
2
|
+
export type ImageMode =
|
|
3
|
+
| 'scaleToFill'
|
|
4
|
+
| 'aspectFit'
|
|
5
|
+
| 'aspectFill'
|
|
6
|
+
| 'widthFix'
|
|
7
|
+
| 'heightFix'
|
|
8
|
+
| 'top'
|
|
9
|
+
| 'bottom'
|
|
10
|
+
| 'center'
|
|
11
|
+
| 'left'
|
|
12
|
+
| 'right'
|
|
13
|
+
| 'top left'
|
|
14
|
+
| 'top right'
|
|
15
|
+
| 'bottom left'
|
|
16
|
+
| 'bottom right'
|
|
17
|
+
|
|
18
|
+
export type ImageEffect = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'
|
|
19
|
+
|
|
20
|
+
export const imgLazyProps = {
|
|
21
|
+
...baseProps,
|
|
22
|
+
/**
|
|
23
|
+
* 图片链接
|
|
24
|
+
*/
|
|
25
|
+
src: String,
|
|
26
|
+
/**
|
|
27
|
+
* 填充模式:'top left' / 'top right' / 'bottom left' / 'bottom right' / 'right' / 'left' / 'center' / 'bottom' / 'top' / 'heightFix' / 'widthFix' / 'aspectFill' / 'aspectFit' / 'scaleToFill'
|
|
28
|
+
*/
|
|
29
|
+
mode: makeStringProp<ImageMode>('aspectFill'),
|
|
30
|
+
/**
|
|
31
|
+
* 宽度,默认单位为px
|
|
32
|
+
*/
|
|
33
|
+
width: numericProp,
|
|
34
|
+
/**
|
|
35
|
+
* 高度,默认单位为px
|
|
36
|
+
*/
|
|
37
|
+
height: numericProp,
|
|
38
|
+
/**
|
|
39
|
+
* 圆角大小,默认单位为px
|
|
40
|
+
*/
|
|
41
|
+
borderRadius: makeStringProp('0'),
|
|
42
|
+
/**
|
|
43
|
+
* 懒加载延迟时间
|
|
44
|
+
*/
|
|
45
|
+
minTimeOut: makeNumberProp(0),
|
|
46
|
+
/**
|
|
47
|
+
* 图片提前加载的距离阈值
|
|
48
|
+
*/
|
|
49
|
+
showDistance: {
|
|
50
|
+
type: Object,
|
|
51
|
+
default: () => ({ bottom: 20 })
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* 图片懒加载时的视觉过渡效果
|
|
55
|
+
*/
|
|
56
|
+
effect: makeStringProp<ImageEffect>('linear'),
|
|
57
|
+
/**
|
|
58
|
+
* 图片懒加载时的视觉过渡时间
|
|
59
|
+
*/
|
|
60
|
+
duration: makeNumberProp(0),
|
|
61
|
+
/**
|
|
62
|
+
* 加载中时显示的图片链接
|
|
63
|
+
*/
|
|
64
|
+
loadSrc: makeStringProp(''),
|
|
65
|
+
/**
|
|
66
|
+
* 加载失败时显示的图片链接
|
|
67
|
+
*/
|
|
68
|
+
errorSrc: makeStringProp('')
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
@import '../common/abstracts/variable';
|
|
2
|
+
@import '../common/abstracts/mixin';
|
|
3
|
+
|
|
4
|
+
@include b(link) {
|
|
5
|
+
display: inline-block;
|
|
6
|
+
@include e(medium) {
|
|
7
|
+
font-size: $-link-font-size-medium;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@include e(small) {
|
|
11
|
+
font-size: $-link-font-size-small;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@include e(large) {
|
|
15
|
+
font-size: $-link-font-size-large;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@include e(icon) {
|
|
19
|
+
display: inline-block;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@include when(underline) {
|
|
23
|
+
text-decoration: underline;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@include when(default) {
|
|
27
|
+
color: $-link-info-color;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@include when(primary) {
|
|
31
|
+
color: $-link-primary-color;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@include when(error) {
|
|
35
|
+
color: $-link-error-color;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@include when(warning) {
|
|
39
|
+
color: $-link-warning-color;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@include when(success) {
|
|
43
|
+
color: $-link-success-color;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@include when(disabled) {
|
|
47
|
+
opacity: .6;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&__prefix-icon:not(:empty) + &__content:not(:empty) {
|
|
51
|
+
padding-left: 4px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&__content:not(:empty) + &__suffix-icon:not(:empty) {
|
|
55
|
+
padding-left: 4px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<navigator
|
|
3
|
+
:class="rootClass"
|
|
4
|
+
:style="rootStyle"
|
|
5
|
+
:url="!disabled ? navigatorProps?.['url'] || url : ''"
|
|
6
|
+
:target="navigatorProps?.['target']"
|
|
7
|
+
:open-type="navigatorProps?.['openType'] || openType"
|
|
8
|
+
:delta="navigatorProps?.['delta']"
|
|
9
|
+
:app-id="navigatorProps?.['appId']"
|
|
10
|
+
:path="navigatorProps?.['path']"
|
|
11
|
+
:extra-data="navigatorProps?.['extraData']"
|
|
12
|
+
:version="navigatorProps?.['version']"
|
|
13
|
+
:short-link="navigatorProps?.['shortLink']"
|
|
14
|
+
:hover-class="hover && !disabled ? 'oxy-link__hover' : ''"
|
|
15
|
+
:hover-stop-propagation="navigatorProps?.['hoverStopPropagation']"
|
|
16
|
+
:hover-start-time="navigatorProps?.['hoverStartTime']"
|
|
17
|
+
:hover-stay-time="navigatorProps?.['hoverStayTime']"
|
|
18
|
+
@click="handleClick"
|
|
19
|
+
>
|
|
20
|
+
<slot name="prefix-icon">
|
|
21
|
+
<view class="oxy-link__icon oxy-link__prefix-icon" v-if="prefixIcon">
|
|
22
|
+
<oxy-icon :size="fontSize" :color="color" :name="prefixIcon"></oxy-icon>
|
|
23
|
+
</view>
|
|
24
|
+
</slot>
|
|
25
|
+
<text class="oxy-link__content" ref="textRef">
|
|
26
|
+
<slot>{{ content }}</slot>
|
|
27
|
+
</text>
|
|
28
|
+
<slot name="suffix-icon">
|
|
29
|
+
<view class="oxy-link__icon oxy-link__suffix-icon" v-if="suffixIcon">
|
|
30
|
+
<oxy-icon :size="fontSize" :color="color" :name="suffixIcon"></oxy-icon>
|
|
31
|
+
</view>
|
|
32
|
+
</slot>
|
|
33
|
+
</navigator>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script lang="ts">
|
|
37
|
+
export default {
|
|
38
|
+
name: 'oxy-link',
|
|
39
|
+
options: {
|
|
40
|
+
virtualHost: true,
|
|
41
|
+
addGlobalClass: true,
|
|
42
|
+
styleIsolation: 'shared'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<script lang="ts" setup>
|
|
48
|
+
import { computed, ref, watch } from 'vue'
|
|
49
|
+
import { objToStyle } from '../common/util'
|
|
50
|
+
import { linkProps } from './types'
|
|
51
|
+
|
|
52
|
+
// 获取组件的 props 和 emit 函数
|
|
53
|
+
const props = defineProps(linkProps)
|
|
54
|
+
const emit = defineEmits(['click', 'success', 'fail', 'complete'])
|
|
55
|
+
|
|
56
|
+
// 存储超链接类名的响应式变量
|
|
57
|
+
const linkClass = ref<string>('')
|
|
58
|
+
const fontSize = computed<string>(() => {
|
|
59
|
+
if (!['small', 'medium', 'large'].includes(props.size)) {
|
|
60
|
+
return props.size
|
|
61
|
+
}
|
|
62
|
+
return ''
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 监听 props 变化,合并 watch 逻辑
|
|
66
|
+
watch(
|
|
67
|
+
() => ({
|
|
68
|
+
size: props.size,
|
|
69
|
+
type: props.type,
|
|
70
|
+
disabled: props.disabled,
|
|
71
|
+
underline: props.underline
|
|
72
|
+
}),
|
|
73
|
+
({ type }) => {
|
|
74
|
+
// 验证 type 属性
|
|
75
|
+
const types = ['primary', 'danger', 'warning', 'success', 'default']
|
|
76
|
+
if (type && !types.includes(type)) {
|
|
77
|
+
console.error(`type must be one of ${types.toString()}`)
|
|
78
|
+
}
|
|
79
|
+
computeLinkClass()
|
|
80
|
+
},
|
|
81
|
+
{ deep: true, immediate: true }
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// 计算根元素的类名
|
|
85
|
+
const rootClass = computed(() => {
|
|
86
|
+
return `oxy-link ${props.customClass} ${linkClass.value}`
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// 计算根元素的样式
|
|
90
|
+
const rootStyle = computed(() => {
|
|
91
|
+
const rootStyle: Record<string, any> = {}
|
|
92
|
+
if (props.color) {
|
|
93
|
+
rootStyle['color'] = props.color
|
|
94
|
+
}
|
|
95
|
+
if (fontSize.value) {
|
|
96
|
+
rootStyle['font-size'] = fontSize.value
|
|
97
|
+
}
|
|
98
|
+
return `${objToStyle(rootStyle)}${props.customStyle}`
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// 计算超链接类名的函数
|
|
102
|
+
function computeLinkClass() {
|
|
103
|
+
const { size, type, disabled, underline } = props
|
|
104
|
+
const linkClassList: string[] = []
|
|
105
|
+
size && linkClassList.push(`oxy-link__${size}`)
|
|
106
|
+
type && linkClassList.push(`is-${type}`)
|
|
107
|
+
disabled && linkClassList.push('is-disabled')
|
|
108
|
+
underline && linkClassList.push('is-underline')
|
|
109
|
+
linkClass.value = linkClassList.join(' ')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleClick(event: Event) {
|
|
113
|
+
if (!props.url && props.href) {
|
|
114
|
+
// #ifdef APP-PLUS
|
|
115
|
+
plus.runtime.openURL(props.href)
|
|
116
|
+
// #endif
|
|
117
|
+
// #ifdef H5
|
|
118
|
+
window.open(props.href)
|
|
119
|
+
// #endif
|
|
120
|
+
// #ifdef MP
|
|
121
|
+
uni.setClipboardData({ data: props.href })
|
|
122
|
+
// #endif
|
|
123
|
+
}
|
|
124
|
+
emit('click', event)
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<style lang="scss" scoped>
|
|
129
|
+
@import './index.scss';
|
|
130
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ExtractPropTypes } from 'vue'
|
|
2
|
+
import { baseProps, makeBooleanProp, makeStringProp } from '../common/props'
|
|
3
|
+
|
|
4
|
+
export type OpenType = 'navigate' | 'redirect' | 'switchTab' | 'reLaunch' | 'navigateBack' | 'exit'
|
|
5
|
+
export type LinkType = 'default' | 'primary' | 'error' | 'warning' | 'success'
|
|
6
|
+
export type SizeEnum = 'small' | 'medium' | 'large'
|
|
7
|
+
|
|
8
|
+
export const linkProps = {
|
|
9
|
+
...baseProps,
|
|
10
|
+
/**
|
|
11
|
+
* 应用内跳转的链接
|
|
12
|
+
*/
|
|
13
|
+
url: makeStringProp(''),
|
|
14
|
+
/**
|
|
15
|
+
* 应用外跳转的链接
|
|
16
|
+
*/
|
|
17
|
+
href: makeStringProp(''),
|
|
18
|
+
/**
|
|
19
|
+
* 跳转方式
|
|
20
|
+
*/
|
|
21
|
+
openType: makeStringProp<OpenType>('navigate'),
|
|
22
|
+
/**
|
|
23
|
+
* 链接内容
|
|
24
|
+
* 类型:string
|
|
25
|
+
*/
|
|
26
|
+
content: makeStringProp(''),
|
|
27
|
+
/**
|
|
28
|
+
* 是否禁用
|
|
29
|
+
* 类型:boolean
|
|
30
|
+
* 默认值:false
|
|
31
|
+
*/
|
|
32
|
+
disabled: makeBooleanProp(false),
|
|
33
|
+
/**
|
|
34
|
+
* 是否开启点击反馈
|
|
35
|
+
* 类型:boolean
|
|
36
|
+
* 默认值:false
|
|
37
|
+
*/
|
|
38
|
+
hover: makeBooleanProp(false),
|
|
39
|
+
/**
|
|
40
|
+
* 是否显示链接下划线
|
|
41
|
+
* 类型:boolean
|
|
42
|
+
* 默认值:false
|
|
43
|
+
*/
|
|
44
|
+
underline: makeBooleanProp(false),
|
|
45
|
+
/**
|
|
46
|
+
* 主题类型
|
|
47
|
+
* 类型:string
|
|
48
|
+
* 可选值:'default' /'primary' / 'error' / 'warning' / 'success'
|
|
49
|
+
* 默认值:'default'
|
|
50
|
+
*/
|
|
51
|
+
type: makeStringProp<LinkType>('default'),
|
|
52
|
+
/**
|
|
53
|
+
* 字体大小
|
|
54
|
+
* 类型:string
|
|
55
|
+
* 默认值:'空字符串'
|
|
56
|
+
*/
|
|
57
|
+
size: makeStringProp<SizeEnum>('medium'),
|
|
58
|
+
/**
|
|
59
|
+
* 文本颜色
|
|
60
|
+
* 类型:string
|
|
61
|
+
* 默认值:''
|
|
62
|
+
*/
|
|
63
|
+
color: makeStringProp(''),
|
|
64
|
+
/**
|
|
65
|
+
* 前置图标
|
|
66
|
+
*/
|
|
67
|
+
prefixIcon: makeStringProp(''),
|
|
68
|
+
/**
|
|
69
|
+
* 后置图标
|
|
70
|
+
*/
|
|
71
|
+
suffixIcon: makeStringProp(''),
|
|
72
|
+
/**
|
|
73
|
+
* 与 navigator 原生组件属性保持一致,具体使用参考:[微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html)。使用时请将形如 `open-type` 风格的属性名改为 `openType` 风格
|
|
74
|
+
*/
|
|
75
|
+
navigatorProps: {
|
|
76
|
+
type: Object,
|
|
77
|
+
default: null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type LinkProps = ExtractPropTypes<typeof linkProps>
|