Compare commits

..

9 Commits

Author SHA1 Message Date
becomer 6ec4118932 chore: trigger ci with new forge url and port fix
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
2026-07-01 00:30:25 +08:00
becomer 70fd865e39 fix: change frontend host port to 3080
ci/woodpecker/push/build Pipeline was successful
Port 3000 is occupied by Gitea on this server.
Map container port 3000 to host port 3080 instead.
2026-07-01 00:25:58 +08:00
becomer f77d71a96e fix: replace tags filter with branch filter in when clause
ci/woodpecker/push/build Pipeline is running
ci/woodpecker/tag/build Pipeline failed
Woodpecker v3 does not support `tags` in the when clause; use `branch`
with glob patterns instead for matching tag events.
2026-07-01 00:02:44 +08:00
becomer 561c756b26 ci: support tag-triggered pipelines with versioned Docker images
- Allow triggering on v* tags in addition to pushes to main/master
- Tag Docker images with CI_COMMIT_TAG when triggered by a tag
2026-07-01 00:01:56 +08:00
becomer 75c861629f ci: 添加 Woodpecker CI/CD 流水线
- 安装依赖、构建前端
- 构建 Docker 镜像并部署
- 环境变量和网络管理
2026-06-30 23:51:04 +08:00
sugarBecomer 18fcae8030 🎨 主题 v2:渐变按钮 + 4层语义色 + 降低饱和度
1. Primary 按钮改为渐变背景 linear-gradient(135deg, #6b8cff, #88a4ff)
2. 全局 primary 饱和度从 0.19 降至 0.12,更柔和
3. destructive 改为珊瑚色 oklch(0.70 0.14 25)
4. 新增 success/warning/info 三个语义色变量
5. Badge 新增 success/warning/info variant
6. 视图层硬编码 text-green/text-red/text-yellow 全部替换为语义 token
7. chart 颜色同步降低饱和度
2026-04-27 10:32:59 +08:00
sugarBecomer 564dbd436d 🎨 清新蓝色主题美化
将纯灰度无彩色主题替换为清新蓝色系:
- primary 从纯黑改为柔和蓝色
- accent/sidebar 加入淡蓝色调
- 圆角从 0.625rem 加大到 0.75rem
- chart 颜色换为蓝/薄荷/琥珀/薰衣草/珊瑚清新色板
- 暗色模式同步适配蓝色调
2026-04-27 09:12:48 +08:00
sugarBecomer 7ce6ca34ca 🐛 修复 JWT 过期只弹提示不跳转登录页
后端 JWT 过期返回 HTTP 200 + code 401/403 时,之前只走业务错误分支弹 toast,
现在增加对 code 401/403 的判断,清除 token 并跳转登录页。
2026-04-27 09:06:48 +08:00
sugarBecomer 94c5532b82 ✨ 首页统计条点击跳转任务列表筛选
1. Dashboard 近7日统计条形图和数字可点击
2. 点击成功/失败/待执行跳转任务列表并按对应任务ID筛选
3. 任务列表页显示筛选提示条,支持清除筛选
2026-04-24 12:32:23 +08:00
13 changed files with 248 additions and 108 deletions
+51
View File
@@ -0,0 +1,51 @@
when:
- event: push
branch: [main, master]
- event: tag
branch: [v*]
steps:
install-deps:
image: node:22-alpine
commands:
- npm ci
build-frontend:
image: node:22-alpine
commands:
- npm run build
depends_on:
- install-deps
build-image:
image: docker
commands:
- docker build -t likedream-web:latest .
- docker tag likedream-web:latest likedream-web:${CI_COMMIT_TAG:-${CI_COMMIT_SHA:0:8}}
depends_on:
- build-frontend
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
image: docker
commands: |
# 确保网络存在
docker network create likedream-net 2>/dev/null || true
# 停止并删除旧容器
docker stop likedream-web 2>/dev/null || true
docker rm likedream-web 2>/dev/null || true
# 启动新容器
docker run -d \
--name likedream-web \
--restart unless-stopped \
--network likedream-net \
-p 3080:3000 \
-e BACKEND_URL=http://likedream-go:8091 \
likedream-web:latest
depends_on:
- build-image
volumes:
- /var/run/docker.sock:/var/run/docker.sock
+15
View File
@@ -0,0 +1,15 @@
services:
likedream-web:
image: likedream-web:latest
container_name: likedream-web
restart: unless-stopped
ports:
- "3000:3000"
environment:
- BACKEND_URL=http://likedream-go:8091
networks:
- likedream-net
networks:
likedream-net:
external: true
+11
View File
@@ -70,6 +70,17 @@ request.interceptors.response.use(
// 业务错误:HTTP 200 但 code 不是 200
if (data && typeof data.code === 'number' && data.code !== 200) {
const msg = data.message || '请求失败'
// JWT 过期 / 未授权:清 token 跳登录
if (data.code === 401 || data.code === 403) {
localStorage.removeItem('token')
message.error(msg)
import('@/router').then(({ default: router }) => {
router.push('/login')
})
const err: any = new Error(msg)
err.code = data.code
return Promise.reject(err)
}
message.error(msg)
const err: any = new Error(msg)
err.code = data.code
+109 -87
View File
@@ -5,58 +5,58 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--background: 220 60% 99%;
--foreground: 220 30% 12%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--card-foreground: 220 30% 12%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--popover-foreground: 220 30% 12%;
--primary: 228 60% 62%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--secondary: 220 20% 96%;
--secondary-foreground: 220 30% 18%;
--muted: 220 15% 96%;
--muted-foreground: 220 10% 42%;
--accent: 220 30% 95%;
--accent-foreground: 220 30% 18%;
--destructive: 15 65% 55%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--border: 220 15% 92%;
--input: 220 15% 92%;
--ring: 228 60% 62%;
--radius: 0.75rem;
--chart-1: 228 60% 62%;
--chart-2: 155 40% 50%;
--chart-3: 40 50% 55%;
--chart-4: 280 40% 58%;
--chart-5: 15 50% 55%;
}
.dark {
--background: 240 10% 3.9%;
--background: 220 20% 8%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card: 220 20% 12%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover: 220 20% 12%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--primary: 228 55% 68%;
--primary-foreground: 220 30% 10%;
--secondary: 220 15% 18%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--muted: 220 15% 18%;
--muted-foreground: 220 10% 60%;
--accent: 220 18% 18%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive: 15 60% 55%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--border: 220 15% 18%;
--input: 220 15% 18%;
--ring: 228 55% 68%;
--chart-1: 228 55% 68%;
--chart-2: 155 40% 52%;
--chart-3: 40 55% 58%;
--chart-4: 280 45% 60%;
--chart-5: 15 55% 58%;
}
}
@@ -89,6 +89,12 @@
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -108,72 +114,88 @@
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--radius: 0.75rem;
--background: oklch(0.995 0.002 250);
--foreground: oklch(0.18 0.02 250);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--card-foreground: oklch(0.18 0.02 250);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--popover-foreground: oklch(0.18 0.02 250);
--primary: oklch(0.62 0.12 260);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--secondary: oklch(0.965 0.008 250);
--secondary-foreground: oklch(0.25 0.04 250);
--muted: oklch(0.965 0.006 250);
--muted-foreground: oklch(0.52 0.02 250);
--accent: oklch(0.955 0.018 250);
--accent-foreground: oklch(0.25 0.04 250);
--destructive: oklch(0.70 0.14 25);
--border: oklch(0.925 0.008 250);
--input: oklch(0.925 0.008 250);
--ring: oklch(0.62 0.12 260);
--success: oklch(0.65 0.12 155);
--success-foreground: oklch(0.985 0 0);
--warning: oklch(0.75 0.12 80);
--warning-foreground: oklch(0.25 0.04 60);
--info: oklch(0.68 0.10 240);
--info-foreground: oklch(0.985 0 0);
--gradient-primary: linear-gradient(135deg, #6b8cff, #88a4ff);
--gradient-primary-hover: linear-gradient(135deg, #5f80f5, #7d9af5);
--chart-1: oklch(0.62 0.12 260);
--chart-2: oklch(0.65 0.12 155);
--chart-3: oklch(0.75 0.12 80);
--chart-4: oklch(0.62 0.12 300);
--chart-5: oklch(0.70 0.12 25);
--sidebar: oklch(0.985 0.004 250);
--sidebar-foreground: oklch(0.18 0.02 250);
--sidebar-primary: oklch(0.62 0.12 260);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar-accent: oklch(0.955 0.018 250);
--sidebar-accent-foreground: oklch(0.25 0.04 250);
--sidebar-border: oklch(0.925 0.008 250);
--sidebar-ring: oklch(0.62 0.12 260);
}
.dark {
--background: oklch(0.145 0 0);
--background: oklch(0.145 0.01 250);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card: oklch(0.2 0.015 250);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover: oklch(0.2 0.015 250);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--primary: oklch(0.72 0.10 260);
--primary-foreground: oklch(0.15 0.02 250);
--secondary: oklch(0.26 0.02 250);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--muted: oklch(0.26 0.02 250);
--muted-foreground: oklch(0.7 0.02 250);
--accent: oklch(0.26 0.025 250);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--destructive: oklch(0.70 0.14 25);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--ring: oklch(0.72 0.10 260);
--success: oklch(0.70 0.12 155);
--success-foreground: oklch(0.985 0 0);
--warning: oklch(0.78 0.12 80);
--warning-foreground: oklch(0.18 0.04 60);
--info: oklch(0.72 0.10 240);
--info-foreground: oklch(0.985 0 0);
--gradient-primary: linear-gradient(135deg, #7b9aff, #99b2ff);
--gradient-primary-hover: linear-gradient(135deg, #6f8ef5, #8da6f5);
--chart-1: oklch(0.72 0.10 260);
--chart-2: oklch(0.70 0.12 155);
--chart-3: oklch(0.78 0.12 80);
--chart-4: oklch(0.68 0.12 300);
--chart-5: oklch(0.72 0.12 25);
--sidebar: oklch(0.2 0.015 250);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary: oklch(0.72 0.10 260);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent: oklch(0.26 0.025 250);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--sidebar-ring: oklch(0.72 0.10 260);
}
@layer base {
+6
View File
@@ -14,6 +14,12 @@ export const badgeVariants = cva(
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
success:
"border-transparent bg-success text-success-foreground [a&]:hover:bg-success/90",
warning:
"border-transparent bg-warning text-warning-foreground [a&]:hover:bg-warning/90",
info:
"border-transparent bg-info text-info-foreground [a&]:hover:bg-info/90",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
+1 -1
View File
@@ -9,7 +9,7 @@ export const buttonVariants = cva(
variants: {
variant: {
default:
"bg-primary text-primary-foreground hover:bg-primary/90",
"text-primary-foreground shadow-sm [background:var(--gradient-primary)] hover:[background:var(--gradient-primary-hover)]",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
+3
View File
@@ -173,6 +173,9 @@ export interface TaskStatisticsV2Item {
successTask: number
failTask: number
pendingTask: number
successTaskIds?: number[]
failTaskIds?: number[]
pendingTaskIds?: number[]
}
export interface DashboardSummary {
+2 -2
View File
@@ -26,12 +26,12 @@ const columns: ColumnDef<CpTaskLog, any>[] = [
if (typeof isSuccess === 'boolean') {
return h(
'span',
{ class: isSuccess ? 'text-green-600' : 'text-red-600' },
{ class: isSuccess ? 'text-success' : 'text-destructive' },
isSuccess ? '成功' : '失败',
)
}
if (errorMessage) {
return h('span', { class: 'text-red-600' }, '失败')
return h('span', { class: 'text-destructive' }, '失败')
}
return h('span', '-')
},
+22 -4
View File
@@ -27,6 +27,7 @@ import { extractPageData } from '@/types/api'
const router = useRouter()
const route = useRoute()
const tableRef = ref<{ refresh: () => void } | null>(null)
const filterIds = ref('')
// ---- 确认弹窗状态 ----
const confirmOpen = ref(false)
@@ -186,7 +187,11 @@ onMounted(() => {
const qUsername = route.query.addUsername as string
if (qUserId && qUsername) {
openAddDialogWithUser(Number(qUserId), qUsername)
// 清除 query 参数,避免刷新时重复弹窗
router.replace({ path: route.path })
}
const qIds = route.query.ids as string
if (qIds) {
filterIds.value = qIds
router.replace({ path: route.path })
}
})
@@ -274,9 +279,9 @@ const taskStatusMap: Record<string, { label: string; variant: 'default' | 'secon
}
const stepMap: Record<string, { label: string; color: string }> = {
form: { label: '表单待填', color: 'text-yellow-600' },
execute: { label: '执行参数待填', color: 'text-yellow-600' },
complete: { label: '已完成', color: 'text-green-600' },
form: { label: '表单待填', color: 'text-warning' },
execute: { label: '执行参数待填', color: 'text-warning' },
complete: { label: '已完成', color: 'text-success' },
}
const notificationTypeMap: Record<string, string> = {
@@ -408,9 +413,17 @@ const searchFields: SearchField[] = [
]
const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
if (filterIds.value) {
(params as any).ids = filterIds.value
}
const res = await cpTaskApi.getList(params)
return extractPageData(res)
}
function clearFilterIds() {
filterIds.value = ''
tableRef.value?.refresh()
}
</script>
<template>
@@ -423,6 +436,11 @@ const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
</Button>
</div>
<div v-if="filterIds" class="flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-sm">
<span class="text-primary">当前按统计数据筛选中,共 {{ filterIds.split(',').length }} 条任务</span>
<Button size="sm" variant="ghost" class="ml-auto h-7 px-2 text-xs" @click="clearFilterIds">清除筛选</Button>
</div>
<DataTable
ref="tableRef"
:columns="columns"
+25 -11
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { cpTaskApi } from '@/api/cpdaily'
import { authApi } from '@/api/auth'
@@ -10,6 +11,7 @@ import 'md-editor-v3/lib/preview.css'
import type { TaskStatisticsV2Item, DashboardSummary } from '@/types/cpdaily'
const userStore = useUserStore()
const router = useRouter()
const statistics = ref<TaskStatisticsV2Item[]>([])
const summary = ref<DashboardSummary | null>(null)
const loading = ref(false)
@@ -83,6 +85,11 @@ async function fetchAnnouncement() {
}
}
function goToTaskList(ids: number[] | undefined) {
if (!ids || ids.length === 0) return
router.push({ path: '/cpdaily/task/index', query: { ids: ids.join(',') } })
}
onMounted(() => {
fetchStatistics()
fetchSummary()
@@ -131,7 +138,7 @@ onMounted(() => {
<span>今日消费</span>
</div>
<div class="text-lg sm:text-2xl font-bold">{{ summary?.todayCost ?? '--' }}</div>
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="todayCostDiff > 0 ? 'text-red-500' : todayCostDiff < 0 ? 'text-green-500' : 'text-muted-foreground'">
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="todayCostDiff > 0 ? 'text-destructive' : todayCostDiff < 0 ? 'text-success' : 'text-muted-foreground'">
<TrendingUp v-if="todayCostDiff > 0" class="w-3 h-3" />
<TrendingDown v-else-if="todayCostDiff < 0" class="w-3 h-3" />
<Minus v-else class="w-3 h-3" />
@@ -148,7 +155,7 @@ onMounted(() => {
<span>近7日消费</span>
</div>
<div class="text-lg sm:text-2xl font-bold">{{ summary?.week1Cost ?? '--' }}</div>
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="weekCostDiff > 0 ? 'text-red-500' : weekCostDiff < 0 ? 'text-green-500' : 'text-muted-foreground'">
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="weekCostDiff > 0 ? 'text-destructive' : weekCostDiff < 0 ? 'text-success' : 'text-muted-foreground'">
<TrendingUp v-if="weekCostDiff > 0" class="w-3 h-3" />
<TrendingDown v-else-if="weekCostDiff < 0" class="w-3 h-3" />
<Minus v-else class="w-3 h-3" />
@@ -165,7 +172,7 @@ onMounted(() => {
<span>今日任务数</span>
</div>
<div class="text-lg sm:text-2xl font-bold">{{ summary?.todayTaskCount ?? '--' }}</div>
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="taskCountDiff > 0 ? 'text-red-500' : taskCountDiff < 0 ? 'text-green-500' : 'text-muted-foreground'">
<div v-if="summary" class="flex items-center gap-1 text-xs mt-1" :class="taskCountDiff > 0 ? 'text-destructive' : taskCountDiff < 0 ? 'text-success' : 'text-muted-foreground'">
<TrendingUp v-if="taskCountDiff > 0" class="w-3 h-3" />
<TrendingDown v-else-if="taskCountDiff < 0" class="w-3 h-3" />
<Minus v-else class="w-3 h-3" />
@@ -197,25 +204,32 @@ onMounted(() => {
<div class="flex-1 flex h-5 sm:h-6 rounded overflow-hidden bg-muted/30">
<div
v-if="item.successTask > 0"
class="bg-primary transition-all"
class="bg-primary transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.successTask / maxTaskCount * 100) + '%' }"
:title="`成功: ${item.successTask}`"
:title="`成功: ${item.successTask} — 点击查看`"
@click="goToTaskList(item.successTaskIds)"
></div>
<div
v-if="item.failTask > 0"
class="bg-destructive transition-all"
class="bg-destructive transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.failTask / maxTaskCount * 100) + '%' }"
:title="`失败: ${item.failTask}`"
:title="`失败: ${item.failTask} — 点击查看`"
@click="goToTaskList(item.failTaskIds)"
></div>
<div
v-if="item.pendingTask > 0"
class="bg-muted-foreground/30 transition-all"
class="bg-muted-foreground/30 transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.pendingTask / maxTaskCount * 100) + '%' }"
:title="`待执行: ${item.pendingTask}`"
:title="`待执行: ${item.pendingTask} — 点击查看`"
@click="goToTaskList(item.pendingTaskIds)"
></div>
</div>
<span class="text-xs text-muted-foreground w-16 sm:w-20 shrink-0">
{{ item.successTask }}/{{ item.failTask }}/{{ item.pendingTask }}
<span class="text-xs text-muted-foreground w-24 sm:w-28 shrink-0 flex gap-0.5">
<span class="cursor-pointer text-primary hover:underline" @click="goToTaskList(item.successTaskIds)">{{ item.successTask }}</span>
<span>/</span>
<span class="cursor-pointer text-destructive hover:underline" @click="goToTaskList(item.failTaskIds)">{{ item.failTask }}</span>
<span>/</span>
<span class="cursor-pointer hover:underline" @click="goToTaskList(item.pendingTaskIds)">{{ item.pendingTask }}</span>
</span>
</div>
</div>
+1 -1
View File
@@ -109,7 +109,7 @@ async function onConfirmCaptchaConfirm(result: { captchaKey: string; dots: Array
</div>
<div v-else-if="confirmStatus === 'success'" class="text-center space-y-4">
<CheckCircle class="h-12 w-12 mx-auto text-green-500" />
<CheckCircle class="h-12 w-12 mx-auto text-success" />
<h2 class="text-lg font-bold">转移成功</h2>
<p class="text-muted-foreground text-sm">{{ confirmMessage }}</p>
<router-link to="/login">
+1 -1
View File
@@ -51,7 +51,7 @@ onMounted(async () => {
</div>
<div v-else-if="status === 'success'" class="space-y-4">
<CheckCircle class="h-12 w-12 mx-auto text-green-500" />
<CheckCircle class="h-12 w-12 mx-auto text-success" />
<h1 class="text-xl font-bold">验证成功</h1>
<p class="text-muted-foreground text-sm">{{ msg }}</p>
<template v-if="autoLoginSuccess">
+1 -1
View File
@@ -33,7 +33,7 @@ const columns: ColumnDef<BalanceRecord, any>[] = [
cell: ({ row }) => {
const amount = row.getValue<string>('amount')
const num = parseFloat(amount)
const cls = num >= 0 ? 'text-green-600' : 'text-red-500'
const cls = num >= 0 ? 'text-success' : 'text-destructive'
return h('span', { class: `font-medium ${cls}` }, num >= 0 ? `+${amount}` : amount)
},
},