|
|
@@ -0,0 +1,171 @@
|
|
|
+<template>
|
|
|
+ <div class="arrow-container" :style="containerStyle" @click="handleClick" ref="containerRef">
|
|
|
+ <div class="arrow-wrapper" :style="wrapperStyle">
|
|
|
+ <!-- 长方形文本区域 -->
|
|
|
+ <div class="rectangle" :style="rectangleStyle" ref="rectangleRef">
|
|
|
+ <span class="arrowText" :style="arrowTextStyle" :class="{ 'vertical-text': isVerticalRotation }">{{
|
|
|
+ props.arrowText }}</span>
|
|
|
+ </div>
|
|
|
+ <!-- 三角形箭头 -->
|
|
|
+ <div class="triangle" :style="triangleStyle"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+import { computed, ref, watchEffect } from 'vue'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const rectangleRef = ref(null)
|
|
|
+const rectangleHeight = ref(0)
|
|
|
+const rectangleWidth = ref(0)
|
|
|
+
|
|
|
+// 动态获取长方形尺寸
|
|
|
+watchEffect(() => {
|
|
|
+ if (rectangleRef.value) {
|
|
|
+ // 使用requestAnimationFrame确保DOM已更新
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ rectangleHeight.value = rectangleRef.value.offsetHeight
|
|
|
+ rectangleWidth.value = rectangleRef.value.offsetWidth
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ arrowText: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ iconSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 16
|
|
|
+ },
|
|
|
+ bgColor: {
|
|
|
+ type: String,
|
|
|
+ default: '#ff6200'
|
|
|
+ },
|
|
|
+ linkUrl: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ specialCondition: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 使用 specialCondition 中的配置
|
|
|
+const realBgColor = computed(() => props.specialCondition.bgColor || props.bgColor)
|
|
|
+const realLinkUrl = computed(() => props.specialCondition.linkUrl || props.linkUrl)
|
|
|
+const arrowRotate = computed(() => props.specialCondition.arrowRotate || 0)
|
|
|
+
|
|
|
+// 判断是否为竖直旋转状态(90度或270度)
|
|
|
+const isVerticalRotation = computed(() => {
|
|
|
+ const normalizedRotate = ((arrowRotate.value % 360) + 360) % 360; // 标准化角度为0-360度
|
|
|
+ return normalizedRotate === 90 || normalizedRotate === 270;
|
|
|
+})
|
|
|
+
|
|
|
+// 容器样式
|
|
|
+const containerStyle = computed(() => ({
|
|
|
+ display: 'inline-block',
|
|
|
+ cursor: realLinkUrl.value ? 'pointer' : 'default',
|
|
|
+ userSelect: 'none'
|
|
|
+}))
|
|
|
+
|
|
|
+// 整个箭头的旋转(不包含文字)
|
|
|
+const wrapperStyle = computed(() => ({
|
|
|
+ display: 'inline-flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ transform: `rotate(${arrowRotate.value}deg)`,
|
|
|
+ transformOrigin: 'center center'
|
|
|
+}))
|
|
|
+
|
|
|
+// 长方形样式
|
|
|
+const rectangleStyle = computed(() => ({
|
|
|
+ backgroundColor: realBgColor.value,
|
|
|
+ padding: isVerticalRotation.value ? '8px 6px' : '6px 12px',
|
|
|
+ borderRadius: '4px',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ // 竖直旋转时允许文字换行
|
|
|
+ whiteSpace: isVerticalRotation.value ? 'normal' : 'nowrap'
|
|
|
+}))
|
|
|
+
|
|
|
+// 文字样式 - 反向旋转抵消容器的旋转效果
|
|
|
+const arrowTextStyle = computed(() => ({
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: `${props.iconSize * 0.25}px`,
|
|
|
+ lineHeight: '1.2', // 增加行高便于多行显示
|
|
|
+ // 反向旋转使文字保持正常方向
|
|
|
+ transform: `rotate(${(arrowRotate.value > 90 && arrowRotate.value < 270 ? 180 : 0)}deg)`,
|
|
|
+ transformOrigin: 'center center',
|
|
|
+ // 竖直状态下限制最大宽度以便换行
|
|
|
+ maxWidth: isVerticalRotation.value ? `${props.iconSize * 2}px` : 'auto'
|
|
|
+}))
|
|
|
+
|
|
|
+// 三角形箭头样式 - 与长方形高度一致且随旋转改变指向
|
|
|
+const triangleStyle = computed(() => {
|
|
|
+ const color = realBgColor.value;
|
|
|
+
|
|
|
+ // 三角形高度与长方形高度完全一致
|
|
|
+ // 三角形宽度为高度的40%,保持合适比例
|
|
|
+ const height = rectangleHeight.value;
|
|
|
+ const width = height * 0.4;
|
|
|
+
|
|
|
+ const baseArrowStyle = {
|
|
|
+ width: 0,
|
|
|
+ height: 0,
|
|
|
+ borderTop: `${height / 2}px solid transparent`,
|
|
|
+ borderBottom: `${height / 2}px solid transparent`,
|
|
|
+ borderLeft: `${width}px solid ${color}`,
|
|
|
+ flexShrink: 0,
|
|
|
+ marginLeft: '-1px'
|
|
|
+ };
|
|
|
+ return { ...baseArrowStyle }
|
|
|
+})
|
|
|
+
|
|
|
+// 点击跳转
|
|
|
+const handleClick = () => {
|
|
|
+ if (realLinkUrl.value) {
|
|
|
+ router.push(realLinkUrl.value)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.arrow-container {
|
|
|
+
|
|
|
+ .rectangle {
|
|
|
+ min-width: 100px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rectangle,
|
|
|
+ .triangle {
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+
|
|
|
+ .rectangle,
|
|
|
+ .triangle {
|
|
|
+ filter: brightness(1.1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+
|
|
|
+ .rectangle,
|
|
|
+ .triangle {
|
|
|
+ filter: brightness(0.9);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 竖直排布文字的特殊样式
|
|
|
+.vertical-text {
|
|
|
+ text-align: center;
|
|
|
+ word-break: break-word; // 允许在单词内换行
|
|
|
+}
|
|
|
+</style>
|