Compare commits
15 Commits
v0.0.2.beta5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ec4118932 | |||
| 70fd865e39 | |||
| f77d71a96e | |||
| 561c756b26 | |||
| 75c861629f | |||
| 18fcae8030 | |||
| 564dbd436d | |||
| 7ce6ca34ca | |||
| 94c5532b82 | |||
| dd6c308996 | |||
| ebf02eb58f | |||
| 5baa91e3e7 | |||
| 08c47cfa03 | |||
| cb5440c8d9 | |||
| 05eaa4741a |
@@ -6,7 +6,7 @@ on: [release]
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
REPO: sugarbecomer/tianshen-web
|
REPO: sugarbecomer/tianshen-web
|
||||||
REGISTRY: git.likedream.xyz
|
REGISTRY: mycoder.cc.cd
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-And-Upload:
|
Build-And-Upload:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -19,9 +19,17 @@ http:// {
|
|||||||
reverse_proxy ${BACKEND_URL}
|
reverse_proxy ${BACKEND_URL}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@assets path /assets/* /favicon.svg
|
||||||
|
handle @assets {
|
||||||
|
root * /www
|
||||||
|
header Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
handle {
|
handle {
|
||||||
root * /www
|
root * /www
|
||||||
try_files {path} /index.html
|
try_files {path} /index.html
|
||||||
|
header Cache-Control "no-store, no-cache, must-revalidate"
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ export const cpTaskApi = {
|
|||||||
request.post<any, ApiResponse>(`/v1/cpdaily/task/execute/${id}`, data),
|
request.post<any, ApiResponse>(`/v1/cpdaily/task/execute/${id}`, data),
|
||||||
executeNow: (id: number) =>
|
executeNow: (id: number) =>
|
||||||
request.post<any, ApiResponse>(`/v1/cpdaily/task/executeNow/${id}`),
|
request.post<any, ApiResponse>(`/v1/cpdaily/task/executeNow/${id}`),
|
||||||
|
updateTitle: (id: number, title: string) =>
|
||||||
|
request.put<any, ApiResponse>(`/v1/cpdaily/task/title/${id}`, { title }),
|
||||||
pause: (id: number) =>
|
pause: (id: number) =>
|
||||||
request.post<any, ApiResponse>(`/v1/cpdaily/task/pause/${id}`),
|
request.post<any, ApiResponse>(`/v1/cpdaily/task/pause/${id}`),
|
||||||
findLocation: (address: string) =>
|
findLocation: (address: string) =>
|
||||||
|
|||||||
@@ -70,6 +70,17 @@ request.interceptors.response.use(
|
|||||||
// 业务错误:HTTP 200 但 code 不是 200
|
// 业务错误:HTTP 200 但 code 不是 200
|
||||||
if (data && typeof data.code === 'number' && data.code !== 200) {
|
if (data && typeof data.code === 'number' && data.code !== 200) {
|
||||||
const msg = data.message || '请求失败'
|
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)
|
message.error(msg)
|
||||||
const err: any = new Error(msg)
|
const err: any = new Error(msg)
|
||||||
err.code = data.code
|
err.code = data.code
|
||||||
|
|||||||
+109
-87
@@ -5,58 +5,58 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 220 60% 99%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 220 30% 12%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 220 30% 12%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 220 30% 12%;
|
||||||
--primary: 240 5.9% 10%;
|
--primary: 228 60% 62%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 220 20% 96%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 220 30% 18%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 220 15% 96%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 220 10% 42%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 220 30% 95%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 220 30% 18%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 15 65% 55%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 220 15% 92%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 220 15% 92%;
|
||||||
--ring: 240 5.9% 10%;
|
--ring: 228 60% 62%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.75rem;
|
||||||
--chart-1: 12 76% 61%;
|
--chart-1: 228 60% 62%;
|
||||||
--chart-2: 173 58% 39%;
|
--chart-2: 155 40% 50%;
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 40 50% 55%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 280 40% 58%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 15 50% 55%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 240 10% 3.9%;
|
--background: 220 20% 8%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 220 20% 12%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 0 0% 98%;
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 220 20% 12%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 0 0% 98%;
|
||||||
--primary: 0 0% 98%;
|
--primary: 228 55% 68%;
|
||||||
--primary-foreground: 240 5.9% 10%;
|
--primary-foreground: 220 30% 10%;
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 220 15% 18%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 0 0% 98%;
|
||||||
--muted: 240 3.7% 15.9%;
|
--muted: 220 15% 18%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 220 10% 60%;
|
||||||
--accent: 240 3.7% 15.9%;
|
--accent: 220 18% 18%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 15 60% 55%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 220 15% 18%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 220 15% 18%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 228 55% 68%;
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 228 55% 68%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 155 40% 52%;
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 40 55% 58%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 45% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 15 55% 58%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +89,12 @@
|
|||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--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-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
@@ -108,72 +114,88 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.75rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(0.995 0.002 250);
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.18 0.02 250);
|
||||||
--card: oklch(1 0 0);
|
--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: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.18 0.02 250);
|
||||||
--primary: oklch(0.205 0 0);
|
--primary: oklch(0.62 0.12 260);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--secondary: oklch(0.97 0 0);
|
--secondary: oklch(0.965 0.008 250);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.25 0.04 250);
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: oklch(0.965 0.006 250);
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.52 0.02 250);
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.955 0.018 250);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.25 0.04 250);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.70 0.14 25);
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.925 0.008 250);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.925 0.008 250);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.62 0.12 260);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--success: oklch(0.65 0.12 155);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--success-foreground: oklch(0.985 0 0);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--warning: oklch(0.75 0.12 80);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--warning-foreground: oklch(0.25 0.04 60);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--info: oklch(0.68 0.10 240);
|
||||||
--sidebar: oklch(0.985 0 0);
|
--info-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--gradient-primary: linear-gradient(135deg, #6b8cff, #88a4ff);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--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-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--sidebar-accent: oklch(0.955 0.018 250);
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.25 0.04 250);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.925 0.008 250);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.62 0.12 260);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: oklch(0.145 0.01 250);
|
||||||
--foreground: oklch(0.985 0 0);
|
--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);
|
--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);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.922 0 0);
|
--primary: oklch(0.72 0.10 260);
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
--primary-foreground: oklch(0.15 0.02 250);
|
||||||
--secondary: oklch(0.269 0 0);
|
--secondary: oklch(0.26 0.02 250);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.269 0 0);
|
--muted: oklch(0.26 0.02 250);
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--muted-foreground: oklch(0.7 0.02 250);
|
||||||
--accent: oklch(0.269 0 0);
|
--accent: oklch(0.26 0.025 250);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--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%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--ring: oklch(0.556 0 0);
|
--ring: oklch(0.72 0.10 260);
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
--success: oklch(0.70 0.12 155);
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
--success-foreground: oklch(0.985 0 0);
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--warning: oklch(0.78 0.12 80);
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
--warning-foreground: oklch(0.18 0.04 60);
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
--info: oklch(0.72 0.10 240);
|
||||||
--sidebar: oklch(0.205 0 0);
|
--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-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-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-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.72 0.10 260);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ export const badgeVariants = cva(
|
|||||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||||
destructive:
|
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",
|
"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:
|
outline:
|
||||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const buttonVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
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:
|
destructive:
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
outline:
|
||||||
|
|||||||
+13
-3
@@ -88,9 +88,16 @@ export interface CpLeave {
|
|||||||
|
|
||||||
export interface CpTaskLog {
|
export interface CpTaskLog {
|
||||||
id: number
|
id: number
|
||||||
taskId: number
|
cp_task_id?: number
|
||||||
result: string
|
system_user_id?: number
|
||||||
created_at: string
|
cp_user_id?: number
|
||||||
|
is_success?: boolean
|
||||||
|
real_name?: string
|
||||||
|
real_title?: string
|
||||||
|
error_message?: string
|
||||||
|
createTime?: string
|
||||||
|
created_at?: string
|
||||||
|
updateTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CpSchool {
|
export interface CpSchool {
|
||||||
@@ -166,6 +173,9 @@ export interface TaskStatisticsV2Item {
|
|||||||
successTask: number
|
successTask: number
|
||||||
failTask: number
|
failTask: number
|
||||||
pendingTask: number
|
pendingTask: number
|
||||||
|
successTaskIds?: number[]
|
||||||
|
failTaskIds?: number[]
|
||||||
|
pendingTaskIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardSummary {
|
export interface DashboardSummary {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { message as toast } from '@/composables/useMessage'
|
import { message as toast } from '@/composables/useMessage'
|
||||||
import { ArrowLeft, Save, Upload, X, Eye, HelpCircle } from 'lucide-vue-next'
|
import { ArrowLeft, Save, Upload, X, Eye, HelpCircle, Play } from 'lucide-vue-next'
|
||||||
import PageLoading from '@/components/common/PageLoading.vue'
|
import PageLoading from '@/components/common/PageLoading.vue'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -32,6 +32,10 @@ const formItems = ref<CpTaskFormItem[]>([])
|
|||||||
const savingForm = ref(false)
|
const savingForm = ref(false)
|
||||||
const logicShowMap = ref<Record<string, boolean>>({})
|
const logicShowMap = ref<Record<string, boolean>>({})
|
||||||
|
|
||||||
|
// ---- 任务标题 ----
|
||||||
|
const taskTitle = ref('')
|
||||||
|
const savingTitle = ref(false)
|
||||||
|
const executingNow = ref(false)
|
||||||
// ---- 执行参数 ----
|
// ---- 执行参数 ----
|
||||||
const executeParams = ref<CpTaskExecute>({
|
const executeParams = ref<CpTaskExecute>({
|
||||||
id: 0, frequency: 1, notification_type: 'none',
|
id: 0, frequency: 1, notification_type: 'none',
|
||||||
@@ -275,6 +279,7 @@ async function loadExecute() {
|
|||||||
// getExecute 返回整个 cpTask 对象
|
// getExecute 返回整个 cpTask 对象
|
||||||
cpUserId.value = data.edges?.cp_user?.id || data.cp_user_id || 0
|
cpUserId.value = data.edges?.cp_user?.id || data.cp_user_id || 0
|
||||||
taskType.value = data.task_type || ''
|
taskType.value = data.task_type || ''
|
||||||
|
taskTitle.value = data.title || ''
|
||||||
executeParams.value = {
|
executeParams.value = {
|
||||||
id: data.id || 0, frequency: data.frequency ?? 1,
|
id: data.id || 0, frequency: data.frequency ?? 1,
|
||||||
notification_type: data.notification_type || 'none',
|
notification_type: data.notification_type || 'none',
|
||||||
@@ -319,6 +324,27 @@ async function saveExecute() {
|
|||||||
} finally { savingExecute.value = false }
|
} finally { savingExecute.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveTitle() {
|
||||||
|
if (!taskTitle.value.trim()) { toast.error('标题不能为空'); return }
|
||||||
|
savingTitle.value = true
|
||||||
|
try {
|
||||||
|
await cpTaskApi.updateTitle(taskId.value, taskTitle.value.trim())
|
||||||
|
toast.success('标题修改成功')
|
||||||
|
} catch {
|
||||||
|
// 全局拦截器已处理错误提示
|
||||||
|
} finally { savingTitle.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleExecuteNow() {
|
||||||
|
executingNow.value = true
|
||||||
|
try {
|
||||||
|
await cpTaskApi.executeNow(taskId.value)
|
||||||
|
toast.success('任务已开始执行')
|
||||||
|
} catch {
|
||||||
|
// 全局拦截器已处理错误提示
|
||||||
|
} finally { executingNow.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadForm(), loadExecute()])
|
await Promise.all([loadForm(), loadExecute()])
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -332,6 +358,12 @@ onMounted(async () => {
|
|||||||
<ArrowLeft class="h-4 w-4" />
|
<ArrowLeft class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<h2 class="text-2xl font-bold tracking-tight">任务配置</h2>
|
<h2 class="text-2xl font-bold tracking-tight">任务配置</h2>
|
||||||
|
<div class="ml-auto flex items-center gap-2">
|
||||||
|
<Button variant="default" size="sm" :disabled="executingNow" @click="handleExecuteNow">
|
||||||
|
<Play class="mr-1 h-4 w-4" />
|
||||||
|
{{ executingNow ? '执行中...' : '立即执行' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PageLoading v-if="loading" text="加载中..." />
|
<PageLoading v-if="loading" text="加载中..." />
|
||||||
@@ -447,6 +479,18 @@ onMounted(async () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div v-if="executeLoading" class="text-sm text-muted-foreground py-4">加载中...</div>
|
<div v-if="executeLoading" class="text-sm text-muted-foreground py-4">加载中...</div>
|
||||||
<div v-else class="space-y-5">
|
<div v-else class="space-y-5">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="taskTitle">任务标题</Label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Input id="taskTitle" v-model="taskTitle" placeholder="请输入任务标题" class="flex-1" />
|
||||||
|
<Button size="sm" variant="outline" :disabled="savingTitle" @click="saveTitle">
|
||||||
|
{{ savingTitle ? '保存中' : '保存' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="frequency">执行次数</Label>
|
<Label for="frequency">执行次数</Label>
|
||||||
<Input id="frequency" type="number" v-model.number="executeParams.frequency" placeholder="请输入执行次数" :min="1" />
|
<Input id="frequency" type="number" v-model.number="executeParams.frequency" placeholder="请输入执行次数" :min="1" />
|
||||||
|
|||||||
@@ -18,18 +18,44 @@ const taskId = computed(() => Number(route.params.id))
|
|||||||
const columns: ColumnDef<CpTaskLog, any>[] = [
|
const columns: ColumnDef<CpTaskLog, any>[] = [
|
||||||
{ accessorKey: 'id', header: 'ID' },
|
{ accessorKey: 'id', header: 'ID' },
|
||||||
{
|
{
|
||||||
accessorKey: 'result',
|
accessorKey: 'is_success',
|
||||||
header: '执行结果',
|
header: '执行结果',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const result = row.getValue<string>('result')
|
const isSuccess = row.original.is_success
|
||||||
return h('span', { class: 'whitespace-pre-wrap break-all' }, result || '-')
|
const errorMessage = row.original.error_message
|
||||||
|
if (typeof isSuccess === 'boolean') {
|
||||||
|
return h(
|
||||||
|
'span',
|
||||||
|
{ class: isSuccess ? 'text-success' : 'text-destructive' },
|
||||||
|
isSuccess ? '成功' : '失败',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (errorMessage) {
|
||||||
|
return h('span', { class: 'text-destructive' }, '失败')
|
||||||
|
}
|
||||||
|
return h('span', '-')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'created_at',
|
accessorKey: 'real_name',
|
||||||
|
header: '成功标题',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const realTitle = row.original.real_name || row.original.real_title
|
||||||
|
return h('span', { class: 'whitespace-pre-wrap break-all' }, realTitle || '-')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'error_message',
|
||||||
|
header: '失败原因',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h('span', { class: 'whitespace-pre-wrap break-all' }, row.original.error_message || '-')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'createTime',
|
||||||
header: '执行时间',
|
header: '执行时间',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const val = row.getValue<string>('created_at')
|
const val = row.original.createTime || row.original.created_at
|
||||||
return val ? val.replace('T', ' ').slice(0, 19) : '-'
|
return val ? val.replace('T', ' ').slice(0, 19) : '-'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { extractPageData } from '@/types/api'
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const tableRef = ref<{ refresh: () => void } | null>(null)
|
const tableRef = ref<{ refresh: () => void } | null>(null)
|
||||||
|
const filterIds = ref('')
|
||||||
|
|
||||||
// ---- 确认弹窗状态 ----
|
// ---- 确认弹窗状态 ----
|
||||||
const confirmOpen = ref(false)
|
const confirmOpen = ref(false)
|
||||||
@@ -186,7 +187,11 @@ onMounted(() => {
|
|||||||
const qUsername = route.query.addUsername as string
|
const qUsername = route.query.addUsername as string
|
||||||
if (qUserId && qUsername) {
|
if (qUserId && qUsername) {
|
||||||
openAddDialogWithUser(Number(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 })
|
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 }> = {
|
const stepMap: Record<string, { label: string; color: string }> = {
|
||||||
form: { label: '表单待填', color: 'text-yellow-600' },
|
form: { label: '表单待填', color: 'text-warning' },
|
||||||
execute: { label: '执行参数待填', color: 'text-yellow-600' },
|
execute: { label: '执行参数待填', color: 'text-warning' },
|
||||||
complete: { label: '已完成', color: 'text-green-600' },
|
complete: { label: '已完成', color: 'text-success' },
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationTypeMap: Record<string, string> = {
|
const notificationTypeMap: Record<string, string> = {
|
||||||
@@ -408,9 +413,17 @@ const searchFields: SearchField[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
|
const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
|
||||||
|
if (filterIds.value) {
|
||||||
|
(params as any).ids = filterIds.value
|
||||||
|
}
|
||||||
const res = await cpTaskApi.getList(params)
|
const res = await cpTaskApi.getList(params)
|
||||||
return extractPageData(res)
|
return extractPageData(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearFilterIds() {
|
||||||
|
filterIds.value = ''
|
||||||
|
tableRef.value?.refresh()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -423,6 +436,11 @@ const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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
|
<DataTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { cpTaskApi } from '@/api/cpdaily'
|
import { cpTaskApi } from '@/api/cpdaily'
|
||||||
import { authApi } from '@/api/auth'
|
import { authApi } from '@/api/auth'
|
||||||
@@ -10,6 +11,7 @@ import 'md-editor-v3/lib/preview.css'
|
|||||||
import type { TaskStatisticsV2Item, DashboardSummary } from '@/types/cpdaily'
|
import type { TaskStatisticsV2Item, DashboardSummary } from '@/types/cpdaily'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const router = useRouter()
|
||||||
const statistics = ref<TaskStatisticsV2Item[]>([])
|
const statistics = ref<TaskStatisticsV2Item[]>([])
|
||||||
const summary = ref<DashboardSummary | null>(null)
|
const summary = ref<DashboardSummary | null>(null)
|
||||||
const loading = ref(false)
|
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(() => {
|
onMounted(() => {
|
||||||
fetchStatistics()
|
fetchStatistics()
|
||||||
fetchSummary()
|
fetchSummary()
|
||||||
@@ -131,7 +138,7 @@ onMounted(() => {
|
|||||||
<span>今日消费</span>
|
<span>今日消费</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-lg sm:text-2xl font-bold">{{ summary?.todayCost ?? '--' }}</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" />
|
<TrendingUp v-if="todayCostDiff > 0" class="w-3 h-3" />
|
||||||
<TrendingDown v-else-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" />
|
<Minus v-else class="w-3 h-3" />
|
||||||
@@ -148,7 +155,7 @@ onMounted(() => {
|
|||||||
<span>近7日消费</span>
|
<span>近7日消费</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-lg sm:text-2xl font-bold">{{ summary?.week1Cost ?? '--' }}</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" />
|
<TrendingUp v-if="weekCostDiff > 0" class="w-3 h-3" />
|
||||||
<TrendingDown v-else-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" />
|
<Minus v-else class="w-3 h-3" />
|
||||||
@@ -165,7 +172,7 @@ onMounted(() => {
|
|||||||
<span>今日任务数</span>
|
<span>今日任务数</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-lg sm:text-2xl font-bold">{{ summary?.todayTaskCount ?? '--' }}</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" />
|
<TrendingUp v-if="taskCountDiff > 0" class="w-3 h-3" />
|
||||||
<TrendingDown v-else-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" />
|
<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 class="flex-1 flex h-5 sm:h-6 rounded overflow-hidden bg-muted/30">
|
||||||
<div
|
<div
|
||||||
v-if="item.successTask > 0"
|
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) + '%' }"
|
:style="{ width: (item.successTask / maxTaskCount * 100) + '%' }"
|
||||||
:title="`成功: ${item.successTask}`"
|
:title="`成功: ${item.successTask} — 点击查看`"
|
||||||
|
@click="goToTaskList(item.successTaskIds)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="item.failTask > 0"
|
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) + '%' }"
|
:style="{ width: (item.failTask / maxTaskCount * 100) + '%' }"
|
||||||
:title="`失败: ${item.failTask}`"
|
:title="`失败: ${item.failTask} — 点击查看`"
|
||||||
|
@click="goToTaskList(item.failTaskIds)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="item.pendingTask > 0"
|
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) + '%' }"
|
:style="{ width: (item.pendingTask / maxTaskCount * 100) + '%' }"
|
||||||
:title="`待执行: ${item.pendingTask}`"
|
:title="`待执行: ${item.pendingTask} — 点击查看`"
|
||||||
|
@click="goToTaskList(item.pendingTaskIds)"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-muted-foreground w-16 sm:w-20 shrink-0">
|
<span class="text-xs text-muted-foreground w-24 sm:w-28 shrink-0 flex gap-0.5">
|
||||||
{{ item.successTask }}/{{ item.failTask }}/{{ item.pendingTask }}
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ async function onConfirmCaptchaConfirm(result: { captchaKey: string; dots: Array
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="confirmStatus === 'success'" class="text-center space-y-4">
|
<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>
|
<h2 class="text-lg font-bold">转移成功</h2>
|
||||||
<p class="text-muted-foreground text-sm">{{ confirmMessage }}</p>
|
<p class="text-muted-foreground text-sm">{{ confirmMessage }}</p>
|
||||||
<router-link to="/login">
|
<router-link to="/login">
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="status === 'success'" class="space-y-4">
|
<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>
|
<h1 class="text-xl font-bold">验证成功</h1>
|
||||||
<p class="text-muted-foreground text-sm">{{ msg }}</p>
|
<p class="text-muted-foreground text-sm">{{ msg }}</p>
|
||||||
<template v-if="autoLoginSuccess">
|
<template v-if="autoLoginSuccess">
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const columns: ColumnDef<BalanceRecord, any>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const amount = row.getValue<string>('amount')
|
const amount = row.getValue<string>('amount')
|
||||||
const num = parseFloat(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)
|
return h('span', { class: `font-medium ${cls}` }, num >= 0 ? `+${amount}` : amount)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user