Skip to content

Commit 3758247

Browse files
committed
前端界面重构,+后端优化
1 parent a3a563a commit 3758247

File tree

12 files changed

+407
-424
lines changed

12 files changed

+407
-424
lines changed

src/main/java/com/layor/tinyflow/config/RabbitMQConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
import org.springframework.amqp.rabbit.core.RabbitTemplate;
66
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
77
import org.springframework.amqp.support.converter.MessageConverter;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
89
import org.springframework.context.annotation.Bean;
910
import org.springframework.context.annotation.Configuration;
1011

1112
/**
1213
* RabbitMQ 配置类
1314
* 配置点击事件队列、死信队列、消息转换器等
15+
*
16+
* 只有在配置了 spring.rabbitmq.host 时才启用
1417
*/
1518
@Configuration
19+
@ConditionalOnProperty(name = "spring.rabbitmq.host")
1620
public class RabbitMQConfig {
1721

1822
// ========== 队列名称常量 ==========

src/main/java/com/layor/tinyflow/repository/ShortUrlRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,19 @@ public interface ShortUrlRepository extends JpaRepository<ShortUrl, Long> {
2121

2222
boolean existsByLongUrl(String longUrl);
2323

24+
// 根据 longUrl 查询(可能多个结果,已废弃)
25+
@Deprecated
2426
ShortUrl findByLongUrl(String longUrl);
27+
28+
/**
29+
* 根据用户ID和长链接查询短链(唯一)
30+
*/
31+
ShortUrl findByUserIdAndLongUrl(Long userId, String longUrl);
32+
33+
/**
34+
* 查询匿名短链(userId 为 null)
35+
*/
36+
ShortUrl findByUserIdIsNullAndLongUrl(String longUrl);
2537

2638
@Transactional
2739
@Modifying(clearAutomatically = false, flushAutomatically = false)

src/main/java/com/layor/tinyflow/service/ClickMessageConsumer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.amqp.core.Message;
1010
import org.springframework.amqp.rabbit.annotation.RabbitListener;
1111
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1213
import org.springframework.stereotype.Service;
1314
import org.springframework.transaction.annotation.Transactional;
1415

@@ -21,9 +22,12 @@
2122
/**
2223
* RabbitMQ 消息消费者
2324
* 批量消费点击事件消息,批量更新数据库
25+
*
26+
* 只有在配置了 RabbitMQ 时才启用
2427
*/
2528
@Service
2629
@Slf4j
30+
@ConditionalOnProperty(name = "spring.rabbitmq.host")
2731
public class ClickMessageConsumer {
2832

2933
@Autowired

src/main/java/com/layor/tinyflow/service/DeadLetterConsumer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@
66
import lombok.extern.slf4j.Slf4j;
77
import org.springframework.amqp.core.Message;
88
import org.springframework.amqp.rabbit.annotation.RabbitListener;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
910
import org.springframework.stereotype.Service;
1011

1112
import java.io.IOException;
1213

1314
/**
1415
* 死信队列消费者
1516
* 处理消费失败的消息,记录日志并告警
17+
*
18+
* 只有在配置了 RabbitMQ 时才启用
1619
*/
1720
@Service
1821
@Slf4j
22+
@ConditionalOnProperty(name = "spring.rabbitmq.host")
1923
public class DeadLetterConsumer {
2024

2125
/**

src/main/java/com/layor/tinyflow/service/ShortUrlService.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,22 @@ public ShortUrlDTO createShortUrl(String longUrl, String customAlias) throws Exc
7474
Long userId = authService.getCurrentUserId();
7575

7676
//1.1如果长链接已经存在且属于当前用户,直接返回对应的短链
77-
if (shortUrlRepository.existsByLongUrl(longUrl)) {
78-
ShortUrl existingUrl = shortUrlRepository.findByLongUrl(longUrl);
79-
// 检查是否属于当前用户
80-
if (existingUrl.getUserId() != null && existingUrl.getUserId().equals(userId)) {
81-
return ShortUrlDTO.builder()
82-
.shortCode(existingUrl.getShortCode())
83-
.shortUrl(baseUrl + "/" + existingUrl.getShortCode())
84-
.longUrl(existingUrl.getLongUrl())
85-
.createdAt(existingUrl.getCreatedAt())
86-
.build();
87-
}
77+
ShortUrl existingUrl = null;
78+
if (userId != null) {
79+
// 已登录用户:查询当前用户的短链
80+
existingUrl = shortUrlRepository.findByUserIdAndLongUrl(userId, longUrl);
81+
} else {
82+
// 匿名用户:查询匿名短链
83+
existingUrl = shortUrlRepository.findByUserIdIsNullAndLongUrl(longUrl);
84+
}
85+
86+
if (existingUrl != null) {
87+
return ShortUrlDTO.builder()
88+
.shortCode(existingUrl.getShortCode())
89+
.shortUrl(baseUrl + "/" + existingUrl.getShortCode())
90+
.longUrl(existingUrl.getLongUrl())
91+
.createdAt(existingUrl.getCreatedAt())
92+
.build();
8893
}
8994

9095
// 2. 处理别名

web/App.vue

Lines changed: 145 additions & 246 deletions
Large diffs are not rendered by default.

web/src/components/DataStreamMetaphor.vue

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export default {
9797
x, y,
9898
vx: Math.cos(a) * speed,
9999
vy: Math.sin(a) * speed,
100-
size: 0.8 + Math.random() * 1.5,
101-
hue: 230 + Math.random() * 40, // 蓝紫色系
100+
size: 1.2 + Math.random() * 2.0,
101+
hue: 200 + Math.random() * 30, // 水滴蓝色系
102102
life: 0,
103103
arrived: false,
104104
})
@@ -110,8 +110,8 @@ export default {
110110
const ctx = this.ctx
111111
if (!ctx) return
112112
const W = this.canvas.width, H = this.canvas.height
113-
// 背景淡淡拖影,暗科技感
114-
ctx.fillStyle = 'rgba(4,6,10,0.08)'
113+
// 背景:柔和流动的水面感
114+
ctx.fillStyle = 'rgba(239, 246, 255, 0.9)'
115115
ctx.fillRect(0, 0, W, H)
116116
ctx.globalCompositeOperation = 'lighter'
117117
@@ -142,14 +142,14 @@ export default {
142142
if (p.y < 0 || p.y > H) p.vy *= -0.9
143143
// 绘制发光粒子与轨迹
144144
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, 6 * this.dpr)
145-
grad.addColorStop(0, `hsla(${p.hue}, 90%, 70%, 0.85)`)
146-
grad.addColorStop(1, `hsla(${p.hue}, 90%, 50%, 0.0)`)
145+
grad.addColorStop(0, `hsla(${p.hue}, 95%, 72%, 0.9)`)
146+
grad.addColorStop(1, `hsla(${p.hue}, 95%, 55%, 0.0)`)
147147
ctx.fillStyle = grad
148148
ctx.beginPath()
149149
ctx.arc(p.x, p.y, (p.size + (this.phase === 'collapse' ? 0.6 : 0)) * this.dpr, 0, Math.PI * 2)
150150
ctx.fill()
151151
// 速度线(数据流感)
152-
ctx.strokeStyle = `hsla(${p.hue}, 100%, 70%, 0.18)`
152+
ctx.strokeStyle = `hsla(${p.hue}, 100%, 70%, 0.28)`
153153
ctx.lineWidth = 1 * this.dpr
154154
ctx.beginPath()
155155
ctx.moveTo(p.x, p.y)
@@ -159,11 +159,11 @@ export default {
159159
160160
// 闪点效果
161161
if (this.flash.active) {
162-
this.flash.alpha *= 0.92
163-
const r = Math.max(W, H) * 0.25
162+
this.flash.alpha *= 0.9
163+
const r = Math.max(W, H) * 0.32
164164
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r)
165-
g.addColorStop(0, `rgba(180,200,255,${0.25 * this.flash.alpha})`)
166-
g.addColorStop(1, `rgba(180,200,255,0)`)
165+
g.addColorStop(0, `rgba(191,219,254,${0.35 * this.flash.alpha})`)
166+
g.addColorStop(1, `rgba(191,219,254,0)`)
167167
ctx.fillStyle = g
168168
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill()
169169
if (this.flash.alpha < 0.02) this.flash.active = false
@@ -176,6 +176,6 @@ export default {
176176
</script>
177177

178178
<style scoped>
179-
.tf-metaphor { background: radial-gradient(1200px 600px at 50% 50%, #0b0f18 0%, #070a12 40%, #05070d 100%); border-radius: 16px; overflow: hidden; }
180-
.tf-reveal-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: clamp(20px, 4vw, 28px); letter-spacing: 0.6px; color: #cfe3ff; text-shadow: 0 0 18px rgba(120,140,255,0.65), 0 0 6px rgba(120,140,255,0.45); filter: drop-shadow(0 4px 8px rgba(0,0,0,0.35)); }
179+
.tf-metaphor { background: radial-gradient(1200px 600px at 50% 0%, #DBEAFE 0%, #EFF6FF 40%, #FFFFFF 100%); border-radius: 16px; overflow: hidden; }
180+
.tf-reveal-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: clamp(20px, 4vw, 28px); letter-spacing: 0.6px; color: #0F172A; text-shadow: 0 0 16px rgba(59,130,246,0.35), 0 0 4px rgba(59,130,246,0.25); }
181181
</style>

web/src/composables/useStats.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ref } from 'vue'
22
import axios from 'axios'
33
import { API_BASE } from './shortBase'
4-
try { if (API_BASE) { axios.defaults.baseURL = API_BASE } } catch {}
4+
// 只有在 API_BASE 有值时才设置 baseURL
5+
try { if (API_BASE && API_BASE.trim()) { axios.defaults.baseURL = API_BASE } } catch {}
56

67
function createApiState() {
78
return { data: ref(null), loading: ref(false), error: ref(null) }

web/src/pages/DashboardPage.vue

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
<template>
2-
<div class="min-h-screen q-page font-[Inter,system-ui,-apple-system,sans-serif]">
2+
<div class="min-h-screen font-[Inter,system-ui,-apple-system,sans-serif]" style="background-color:var(--tf-bg-page)">
33
<div class="max-w-6xl mx-auto p-6">
44
<!-- Header -->
55
<div class="flex items-center justify-between mb-8">
6-
<h1 class="md-text text-[24px] font-medium">{{ $t('dashboard.title') }}</h1>
6+
<h1 class="text-[24px] font-semibold" style="color:var(--tf-text-title)">{{ $t('dashboard.title') }}</h1>
77
<div class="flex items-center gap-4">
8-
<input v-model="query" type="text" :placeholder="$t('dashboard.searchPlaceholder')" class="h-9 px-3 rounded-lg border" style="border-color:#E5E7EB; background:#FFFFFF; color:#333333" />
9-
<button @click="refresh" class="md-btn-text">{{ $t('dashboard.refresh') }}</button>
8+
<input v-model="query" type="text" :placeholder="$t('dashboard.searchPlaceholder')" class="fs-input h-9 px-3" />
9+
<button @click="refresh" class="fs-btn-secondary px-3 py-1 text-[13px]">{{ $t('dashboard.refresh') }}</button>
1010
</div>
1111
</div>
1212

1313
<!-- 今日与总点击分布饼图 / 近七天趋势切换 -->
1414
<div class="flex items-center justify-between mb-3">
15-
<div class="md-label">{{ $t('dashboard.overview') }}</div>
16-
<button class="md-btn-text" @click="toggleShowTrend7">{{ showTrend7 ? $t('dashboard.toggleShowPie') : $t('dashboard.toggleShowTrend') }}</button>
15+
<div class="text-[14px] font-medium" style="color:var(--tf-text-body)">{{ $t('dashboard.overview') }}</div>
16+
<button class="fs-btn-secondary px-3 py-1 text-[13px]" @click="toggleShowTrend7">{{ showTrend7 ? $t('dashboard.toggleShowPie') : $t('dashboard.toggleShowTrend') }}</button>
1717
</div>
18-
<div v-if="!showTrend7" class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
19-
<div class="q-card p-6" style="box-shadow: 0 2px 12px rgba(0,0,0,0.04)">
20-
<div class="q-card-title mb-4">{{ $t('dashboard.todayDist') }}</div>
21-
<div class="relative" style="height:280px;background:#fafafa;border:1px dashed #ddd;border-radius:8px">
18+
<div v-if="!showTrend7" class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
19+
<div class="fs-card p-6">
20+
<div class="text-[16px] font-medium mb-4" style="color:var(--tf-text-title)">{{ $t('dashboard.todayDist') }}</div>
21+
<div class="relative" style="height:280px;background:var(--tf-bg-page);border:1px dashed var(--tf-border);border-radius:8px">
2222
<DistributionChart :type="'pie'" :data="pieTodayData" :colors="pieColors" :engine="'echarts'" />
2323
</div>
24-
<div v-if="!hasPieTodayData" class="q-muted text-center mt-2">{{ $t('common.noData') }}</div>
25-
<div class="q-help mt-1">数据条数:{{ pieTodayData.length }},总和:{{ pieTodaySum }}</div>
26-
<div class="q-help mt-3">悬停查看名称、百分比与数值</div>
24+
<div v-if="!hasPieTodayData" class="text-center mt-2 text-[13px]" style="color:var(--tf-text-muted)">{{ $t('common.noData') }}</div>
25+
<div class="text-[12px] mt-1" style="color:var(--tf-text-muted)">数据条数:{{ pieTodayData.length }},总和:{{ pieTodaySum }}</div>
26+
<div class="text-[12px] mt-3" style="color:var(--tf-text-muted)">悬停查看名称、百分比与数值</div>
2727
</div>
28-
<div class="q-card p-6" style="box-shadow: 0 2px 12px rgba(0,0,0,0.04)">
29-
<div class="q-card-title mb-4">{{ $t('dashboard.totalDist') }}</div>
30-
<div class="relative" style="height:280px;background:#fafafa;border:1px dashed #ddd;border-radius:8px">
28+
<div class="fs-card p-6">
29+
<div class="text-[16px] font-medium mb-4" style="color:var(--tf-text-title)">{{ $t('dashboard.totalDist') }}</div>
30+
<div class="relative" style="height:280px;background:var(--tf-bg-page);border:1px dashed var(--tf-border);border-radius:8px">
3131
<DistributionChart :type="'pie'" :data="pieTotalData" :colors="pieColors" :engine="'echarts'" />
3232
</div>
33-
<div v-if="!hasPieTotalData" class="q-muted text-center mt-2">{{ $t('common.noData') }}</div>
34-
<div class="q-help mt-1">数据条数:{{ pieTotalData.length }},总和:{{ pieTotalSum }}</div>
35-
<div class="q-help mt-3">悬停查看名称、百分比与数值</div>
33+
<div v-if="!hasPieTotalData" class="text-center mt-2 text-[13px]" style="color:var(--tf-text-muted)">{{ $t('common.noData') }}</div>
34+
<div class="text-[12px] mt-1" style="color:var(--tf-text-muted)">数据条数:{{ pieTotalData.length }},总和:{{ pieTotalSum }}</div>
35+
<div class="text-[12px] mt-3" style="color:var(--tf-text-muted)">悬停查看名称、百分比与数值</div>
3636
</div>
3737
</div>
3838
<div v-else class="q-card p-6 mb-8" style="box-shadow: 0 2px 12px rgba(0,0,0,0.04)">
@@ -82,7 +82,7 @@
8282
<td class="md-td">
8383
<div class="flex items-center gap-2">
8484
<div class="w-[160px] h-2 rounded" style="background: var(--divider)">
85-
<div class="h-2 rounded" :style="{ width: (percentMap[row.shortCode]||0).toFixed(2) + '%', background: '#8A7CFD' }"></div>
85+
<div class="h-2 rounded" :style="{ width: (percentMap[row.shortCode]||0).toFixed(2) + '%', background: '#2563EB' }"></div>
8686
</div>
8787
<span class="md-muted">{{ (percentMap[row.shortCode]||0).toFixed(2) }}%</span>
8888
</div>
@@ -142,7 +142,7 @@ const { data: clickStatsRef, loading: clickLoading, error: clickError, refresh:
142142
const list = listRef
143143
144144
// Pie data and colors
145-
const pieColors = ['#6E44FF','#8A7CFD','#7A5BFF','#9D8BFF','#B0A4FF','#C7BEFF','#D6D1FF','#E0DCFF','#EAE8FF']
145+
const pieColors = ['#3370FF','#2B5FE6','#1F4ED8','#00C875','#FFAB00','#6366F1','#38BDF8','#14B8A6','#22C55E']
146146
// 使用 click-stats 数据源生成饼图数据
147147
const pieTotalData = computed(() => buildPieData(clickStatsRef.value || [], 'totalVisits'))
148148
const pieTodayData = computed(() => buildPieData(clickStatsRef.value || [], 'todayVisits'))

0 commit comments

Comments
 (0)