修改项目整体风格
This commit is contained in:
12
app.go
12
app.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"go-site-clone/services"
|
"go-site-clone/services"
|
||||||
"go-site-clone/utils"
|
"go-site-clone/utils"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -124,3 +125,14 @@ func (a *App) OpenSiteFileDir(pathDir string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteSiteFileDir(pathDir string) bool {
|
||||||
|
appConfig, _ := config.LoadConfig()
|
||||||
|
newPath := filepath.Join(appConfig.SiteFileDir, pathDir)
|
||||||
|
err := os.RemoveAll(newPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("删除文件夹失败:", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
14
config.yaml
14
config.yaml
@@ -1,16 +1,10 @@
|
|||||||
appName: "Go Desk"
|
appName: "Go Site Clone"
|
||||||
|
|
||||||
# 当前监听的端口
|
# 当前监听的端口
|
||||||
port: 6997
|
port: 6997
|
||||||
|
|
||||||
# ws 连接地址
|
|
||||||
wsUrl: "106.12.33.188:6996"
|
|
||||||
|
|
||||||
# STUN 服务器地址
|
|
||||||
stunUrl: "stun:106.12.33.188:3478"
|
|
||||||
|
|
||||||
# api地址
|
|
||||||
apiUrl: "http://106.12.33.188:6996"
|
|
||||||
|
|
||||||
# 网页文件保存目录
|
# 网页文件保存目录
|
||||||
siteFileDir: "www"
|
siteFileDir: "www"
|
||||||
|
|
||||||
|
# 打包的软件保存目录
|
||||||
|
packSiteFileDir: "site-dist"
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ import (
|
|||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
WsUrl string `yaml:"wsUrl"`
|
|
||||||
StunUrl string `yaml:"stunUrl"`
|
|
||||||
ApiUrl string `yaml:"apiUrl"`
|
|
||||||
AppName string `yaml:"appName"`
|
AppName string `yaml:"appName"`
|
||||||
SiteFileDir string `yaml:"siteFileDir"`
|
SiteFileDir string `yaml:"siteFileDir"`
|
||||||
|
PackSiteFileDir string `yaml:"packSiteFileDir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() (*AppConfig, error) {
|
func LoadConfig() (*AppConfig, error) {
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<!-- Active Users Card -->
|
|
||||||
<a-card :bordered="false" class="dashboard-bar-chart" style="margin-bottom: 20px;">
|
|
||||||
<chart-bar :height="220" :data="barChartData"></chart-bar>
|
|
||||||
<div class="card-title">
|
|
||||||
<h2>Active Users</h2>
|
|
||||||
<p>than last week <span class="text-success">+23%</span></p>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>We have created multiple options for you to put together and customise into pixel perfect pages.</p>
|
|
||||||
</div>
|
|
||||||
<a-row class="card-footer" type="flex" justify="center" align="top">
|
|
||||||
<a-col :span="6">
|
|
||||||
<h4>3,6K</h4>
|
|
||||||
<span>Users</span>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="6">
|
|
||||||
<h4>2m</h4>
|
|
||||||
<span>Clicks</span>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="6">
|
|
||||||
<h4>$772</h4>
|
|
||||||
<span>Sales</span>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="6">
|
|
||||||
<h4>82</h4>
|
|
||||||
<span>Items</span>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
<!-- Active Users Card -->
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
const barChartData = {
|
|
||||||
labels: ["01", "02", "03", "04", "05", "06", "07", "08", "09"],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Sales",
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
borderWidth: 0,
|
|
||||||
borderSkipped: false,
|
|
||||||
borderRadius: 6,
|
|
||||||
data: [850, 600, 500, 620, 900, 500, 900, 630, 900],
|
|
||||||
maxBarThickness: 20
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card :bordered="false" class="dashboard-bar-line header-solid">
|
|
||||||
<template #title>
|
|
||||||
<h2>Sales Overview</h2>
|
|
||||||
<p>than last year <span class="text-success">+20%</span></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #extra>
|
|
||||||
<a-badge color="primary" class="badge-dot-primary" text="Traffic" />
|
|
||||||
<a-badge color="primary" class="badge-dot-secondary" text="Sales" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<chart-line :height="310" :data="lineChartData" />
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 定义折线图数据
|
|
||||||
const lineChartData = {
|
|
||||||
labels: ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Mobile apps",
|
|
||||||
tension: 0.4,
|
|
||||||
borderWidth: 0,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: "#1890FF",
|
|
||||||
borderWidth: 3,
|
|
||||||
data: [50, 40, 300, 220, 500, 250, 400, 230, 500],
|
|
||||||
maxBarThickness: 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Websites",
|
|
||||||
tension: 0.4,
|
|
||||||
borderWidth: 0,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: "#B37FEB",
|
|
||||||
borderWidth: 3,
|
|
||||||
data: [30, 90, 40, 140, 290, 290, 340, 230, 400],
|
|
||||||
maxBarThickness: 6
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<canvas ref="chartRef" :style="{ height: height + 'px' }"></canvas>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Chart, registerables } from 'chart.js'
|
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
||||||
|
|
||||||
// 注册 Chart.js 所有组件
|
|
||||||
Chart.register(...registerables)
|
|
||||||
|
|
||||||
// 定义 props
|
|
||||||
const props = defineProps<{
|
|
||||||
data: any
|
|
||||||
height: number
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// chart 实例引用
|
|
||||||
const chartRef = ref<HTMLCanvasElement | null>(null)
|
|
||||||
let chartInstance: Chart | null = null
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (chartRef.value) {
|
|
||||||
const ctx = chartRef.value.getContext('2d')
|
|
||||||
if (!ctx) return
|
|
||||||
|
|
||||||
chartInstance = new Chart(ctx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: props.data,
|
|
||||||
options: {
|
|
||||||
layout: {
|
|
||||||
padding: { top: 30, right: 15, left: 10, bottom: 5 }
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: { display: false }
|
|
||||||
},
|
|
||||||
// Chart.js v3+ 的 tooltip 配置在 plugins.tooltip
|
|
||||||
// 如果你用的是 v4,tooltips 配置需要改为 plugins.tooltip
|
|
||||||
// 这里保留原写法
|
|
||||||
tooltips: {
|
|
||||||
enabled: true,
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
grid: {
|
|
||||||
display: true,
|
|
||||||
color: 'rgba(255, 255, 255, .2)',
|
|
||||||
borderDash: [6],
|
|
||||||
borderDashOffset: 6
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
suggestedMin: 0,
|
|
||||||
suggestedMax: 1000,
|
|
||||||
display: true,
|
|
||||||
color: '#fff',
|
|
||||||
font: {
|
|
||||||
size: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
weight: '600',
|
|
||||||
family: 'Open Sans'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x: {
|
|
||||||
grid: { display: false },
|
|
||||||
ticks: {
|
|
||||||
display: true,
|
|
||||||
color: '#fff',
|
|
||||||
font: {
|
|
||||||
size: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
weight: '600',
|
|
||||||
family: 'Open Sans'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.destroy()
|
|
||||||
chartInstance = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
canvas {
|
|
||||||
background-image: linear-gradient(to right, #00369E, #005CFD, #A18DFF);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<canvas ref="chartRef" :style="{ height: height + 'px' }"></canvas>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Chart, registerables } from 'chart.js'
|
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
||||||
|
|
||||||
// 注册 Chart.js 所有组件
|
|
||||||
Chart.register(...registerables)
|
|
||||||
|
|
||||||
// 定义 props
|
|
||||||
const props = defineProps<{
|
|
||||||
data: any
|
|
||||||
height: number
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// DOM 引用
|
|
||||||
const chartRef = ref<HTMLCanvasElement | null>(null)
|
|
||||||
// Chart 实例
|
|
||||||
let chartInstance: Chart | null = null
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!chartRef.value) return
|
|
||||||
const ctx = chartRef.value.getContext('2d')
|
|
||||||
if (!ctx) return
|
|
||||||
|
|
||||||
chartInstance = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: props.data,
|
|
||||||
options: {
|
|
||||||
layout: {
|
|
||||||
padding: { top: 30, right: 15, left: 10, bottom: 5 }
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: { display: false }
|
|
||||||
},
|
|
||||||
// Chart.js v3+ 的 tooltip 配置在 plugins.tooltip
|
|
||||||
// 如果你用的是 v4,需要改成 plugins.tooltip
|
|
||||||
tooltips: {
|
|
||||||
enabled: true,
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
grid: {
|
|
||||||
display: true,
|
|
||||||
color: 'rgba(0, 0, 0, .2)',
|
|
||||||
borderDash: [6],
|
|
||||||
borderDashOffset: 6
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
suggestedMin: 0,
|
|
||||||
suggestedMax: 1000,
|
|
||||||
display: true,
|
|
||||||
color: '#8C8C8C',
|
|
||||||
font: {
|
|
||||||
size: 14,
|
|
||||||
lineHeight: 1.8,
|
|
||||||
weight: '600',
|
|
||||||
family: 'Open Sans'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x: {
|
|
||||||
grid: { display: false },
|
|
||||||
ticks: {
|
|
||||||
display: true,
|
|
||||||
color: '#8C8C8C',
|
|
||||||
font: {
|
|
||||||
size: 14,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
weight: '600',
|
|
||||||
family: 'Open Sans'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.destroy()
|
|
||||||
chartInstance = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
/* 你可以在这里加样式 */
|
|
||||||
</style>
|
|
||||||
@@ -1,34 +1,313 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header-outside">
|
<div class="header-outside">
|
||||||
<a-layout-header class="header">
|
<a-layout-header class="modern-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<!-- 左侧面包屑导航 -->
|
||||||
|
<div class="header-left">
|
||||||
|
<a-breadcrumb class="breadcrumb">
|
||||||
|
<a-breadcrumb-item>
|
||||||
|
<home-outlined />
|
||||||
|
<span>{{ breadcrumbTitle }}</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="header-menu">
|
<!-- 右侧操作区 -->
|
||||||
<div></div>
|
<div class="header-right">
|
||||||
<div>
|
<!-- 搜索框 -->
|
||||||
|
<div class="search-box">
|
||||||
|
<a-input
|
||||||
|
v-model:value="searchValue"
|
||||||
|
placeholder="搜索功能..."
|
||||||
|
class="search-input"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<search-outlined class="search-icon" />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快捷操作 -->
|
||||||
|
<div class="header-actions">
|
||||||
|
<a-tooltip title="通知">
|
||||||
|
<a-badge :count="3" :offset="[-4, 4]">
|
||||||
|
<div class="action-btn">
|
||||||
|
<bell-outlined class="action-icon" />
|
||||||
|
</div>
|
||||||
|
</a-badge>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<a-tooltip title="设置">
|
||||||
|
<div class="action-btn">
|
||||||
|
<setting-outlined class="action-icon" />
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<a-divider type="vertical" class="action-divider" />
|
||||||
|
|
||||||
|
<!-- 用户信息 -->
|
||||||
|
<a-dropdown placement="bottomRight">
|
||||||
|
<div class="user-info">
|
||||||
|
<a-avatar
|
||||||
|
class="user-avatar"
|
||||||
|
:style="{ backgroundColor: '#667eea' }"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<user-outlined />
|
||||||
|
</template>
|
||||||
|
</a-avatar>
|
||||||
|
<span class="user-name">管理员</span>
|
||||||
|
<down-outlined class="dropdown-icon" />
|
||||||
|
</div>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu class="user-menu">
|
||||||
|
<a-menu-item key="profile">
|
||||||
|
<user-outlined />
|
||||||
|
<span>个人中心</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="settings">
|
||||||
|
<setting-outlined />
|
||||||
|
<span>系统设置</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout">
|
||||||
|
<logout-outlined />
|
||||||
|
<span>退出登录</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
|
||||||
import { DownOutlined } from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
HomeOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
BellOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
LogoutOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const searchValue = ref('');
|
||||||
|
|
||||||
|
// 根据路由动态生成面包屑标题
|
||||||
|
const breadcrumbTitle = computed(() => {
|
||||||
|
const path = route.path;
|
||||||
|
if (path === '/' || path === '') return '控制台';
|
||||||
|
if (path.includes('download')) return '整站下载';
|
||||||
|
if (path.includes('record')) return '下载记录';
|
||||||
|
if (path.includes('webpage')) return 'Nginx网站';
|
||||||
|
if (path.includes('pack')) return '打包应用';
|
||||||
|
return '控制台';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.header-menu{
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.header-outside {
|
.header-outside {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
/* z-index: 9999; */
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modern-header {
|
||||||
|
background: white;
|
||||||
|
padding: 0 24px;
|
||||||
|
height: 64px;
|
||||||
|
line-height: 64px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧区域 */
|
||||||
|
.header-left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
line-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.breadcrumb .ant-breadcrumb-link) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.breadcrumb .anticon) {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧区域 */
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框 */
|
||||||
|
.search-box {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 240px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:hover,
|
||||||
|
.search-input:focus {
|
||||||
|
background: white;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-input .ant-input) {
|
||||||
|
background: transparent;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮 */
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
background: #e6f7ff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover .action-icon {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-divider {
|
||||||
|
height: 24px;
|
||||||
|
margin: 0 4px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户信息 */
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info:hover {
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info:hover .dropdown-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户菜单 */
|
||||||
|
:deep(.user-menu) {
|
||||||
|
min-width: 160px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.user-menu .ant-dropdown-menu-item) {
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.user-menu .ant-dropdown-menu-item:hover) {
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.modern-header {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import {GreetService} from "../../bindings/go-site-clone";
|
|
||||||
import {Events} from "@wailsio/runtime";
|
|
||||||
|
|
||||||
const name = ref('')
|
|
||||||
const result = ref('Please enter your name below 👇')
|
|
||||||
const time = ref('Listening for Time event...')
|
|
||||||
|
|
||||||
const doGreet = () => {
|
|
||||||
let localName = name.value;
|
|
||||||
if (!localName) {
|
|
||||||
localName = 'anonymous';
|
|
||||||
}
|
|
||||||
GreetService.Greet(localName).then((resultValue) => {
|
|
||||||
result.value = resultValue;
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
Events.On('time', (timeValue) => {
|
|
||||||
time.value = timeValue.data;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
msg: String,
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
|
|
||||||
<div class="result">{{ result }}</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="input-box">
|
|
||||||
<input class="input" v-model="name" type="text" autocomplete="off"/>
|
|
||||||
<button class="btn" @click="doGreet">Greet</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div><p>Click on the Wails logo to learn more</p></div>
|
|
||||||
<div><p>{{ time }}</p></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,84 +1,385 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="menu-out">
|
<div class="menu-out">
|
||||||
<a-layout-sider v-model:collapsed="collapsed" collapsible style="height: 100%;">
|
<!-- 折叠按钮 - 移到外层以避免被遮挡 -->
|
||||||
<div class="logo" />
|
<div class="collapse-trigger" @click="collapsed = !collapsed">
|
||||||
<a-menu v-model:selectedKeys="selectedKeys" theme="dark" mode="inline" @click="menuClick">
|
<menu-unfold-outlined v-if="collapsed" class="trigger-icon" />
|
||||||
<a-menu-item key="">
|
<menu-fold-outlined v-else class="trigger-icon" />
|
||||||
<pie-chart-outlined />
|
</div>
|
||||||
|
|
||||||
|
<a-layout-sider
|
||||||
|
v-model:collapsed="collapsed"
|
||||||
|
:trigger="null"
|
||||||
|
:width="240"
|
||||||
|
class="modern-sider"
|
||||||
|
>
|
||||||
|
<!-- Logo区域 -->
|
||||||
|
<div class="logo-container">
|
||||||
|
<div class="logo-icon">
|
||||||
|
<cloud-server-outlined class="icon" />
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="!collapsed" class="logo-text">
|
||||||
|
<div class="app-title">整站克隆</div>
|
||||||
|
<div class="app-subtitle">Site Clone</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 菜单 -->
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
mode="inline"
|
||||||
|
class="modern-menu"
|
||||||
|
@click="menuClick"
|
||||||
|
>
|
||||||
|
<a-menu-item key="" class="menu-item">
|
||||||
|
<template #icon>
|
||||||
|
<home-outlined class="menu-icon" />
|
||||||
|
</template>
|
||||||
<span>控制台</span>
|
<span>控制台</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-sub-menu key="site">
|
|
||||||
<template #title>
|
<a-menu-divider class="menu-divider" />
|
||||||
<span>
|
|
||||||
<user-outlined />
|
<a-menu-item-group title="站点管理" class="menu-group">
|
||||||
<span>仿站</span>
|
<a-menu-item key="site/download" class="menu-item">
|
||||||
</span>
|
<template #icon>
|
||||||
|
<cloud-download-outlined class="menu-icon" />
|
||||||
</template>
|
</template>
|
||||||
<a-menu-item key="download">整站下载</a-menu-item>
|
<span>整站下载</span>
|
||||||
<a-menu-item key="record">仿站记录</a-menu-item>
|
</a-menu-item>
|
||||||
</a-sub-menu>
|
<a-menu-item key="site/record" class="menu-item">
|
||||||
<a-sub-menu key="run">
|
<template #icon>
|
||||||
<template #title>
|
<history-outlined class="menu-icon" />
|
||||||
<span>
|
|
||||||
<IeOutlined />
|
|
||||||
<span>运行</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<a-menu-item key="webpage">Nginx网站</a-menu-item>
|
<span>下载记录</span>
|
||||||
<a-menu-item key="pack">打包应用</a-menu-item>
|
</a-menu-item>
|
||||||
</a-sub-menu>
|
</a-menu-item-group>
|
||||||
|
|
||||||
|
<a-menu-divider class="menu-divider" />
|
||||||
|
|
||||||
|
<a-menu-item-group title="部署运行" class="menu-group">
|
||||||
|
<a-menu-item key="run/webpage" class="menu-item">
|
||||||
|
<template #icon>
|
||||||
|
<global-outlined class="menu-icon" />
|
||||||
|
</template>
|
||||||
|
<span>Nginx网站</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="run/pack" class="menu-item">
|
||||||
|
<template #icon>
|
||||||
|
<appstore-outlined class="menu-icon" />
|
||||||
|
</template>
|
||||||
|
<span>打包应用</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu-item-group>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|
||||||
|
<!-- 底部信息 -->
|
||||||
|
<div class="sider-footer">
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="!collapsed" class="footer-content">
|
||||||
|
<div class="version-info">
|
||||||
|
<api-outlined class="footer-icon" />
|
||||||
|
<span>v1.0.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a-layout-sider v-model:collapsed="collapsed" collapsible style="height: 100%;"></a-layout-sider>
|
<a-layout-sider
|
||||||
|
v-model:collapsed="collapsed"
|
||||||
|
:trigger="null"
|
||||||
|
:width="240"
|
||||||
|
style="height: 100%; visibility: hidden;"
|
||||||
|
></a-layout-sider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
PieChartOutlined,
|
HomeOutlined,
|
||||||
UserOutlined,
|
CloudDownloadOutlined,
|
||||||
IeOutlined,
|
HistoryOutlined,
|
||||||
|
GlobalOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
CloudServerOutlined,
|
||||||
|
MenuFoldOutlined,
|
||||||
|
MenuUnfoldOutlined,
|
||||||
|
ApiOutlined
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { ref } from 'vue';
|
import { ref, watch, onMounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const collapsed = ref<boolean>(false);
|
const collapsed = ref<boolean>(false);
|
||||||
const selectedKeys = ref<string[]>(['']);
|
const selectedKeys = ref<string[]>(['']);
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
// 监听路由变化更新选中状态
|
||||||
|
watch(() => route.path, (newPath) => {
|
||||||
|
updateSelectedKeys(newPath);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateSelectedKeys(route.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateSelectedKeys(path: string) {
|
||||||
|
// 移除开头的斜杠
|
||||||
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
||||||
|
selectedKeys.value = cleanPath ? [cleanPath] : [''];
|
||||||
|
}
|
||||||
|
|
||||||
function menuClick(e: any) {
|
function menuClick(e: any) {
|
||||||
console.log(e);
|
const key = e.key;
|
||||||
const keyList = e.keyPath
|
if (key) {
|
||||||
let path = ''
|
navigateTo('/' + key);
|
||||||
for (let i = 0; i < keyList.length; i++) {
|
} else {
|
||||||
const element = keyList[i];
|
navigateTo('/');
|
||||||
path += "/"+element
|
|
||||||
}
|
}
|
||||||
navigateTo(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.logo {
|
|
||||||
height: 32px;
|
|
||||||
margin: 16px;
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-layout .site-layout-background {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
[data-theme='dark'] .site-layout .site-layout-background {
|
|
||||||
background: #141414;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-out {
|
.menu-out {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 9999;
|
z-index: 1000;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-sider {
|
||||||
|
background: linear-gradient(180deg, #1a1f35 0%, #0f1419 100%) !important;
|
||||||
|
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow: visible;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-layout-sider-children) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo区域 */
|
||||||
|
.logo-container {
|
||||||
|
padding: 24px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon .icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-subtitle {
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠按钮 */
|
||||||
|
.collapse-trigger {
|
||||||
|
position: absolute;
|
||||||
|
top: 32px;
|
||||||
|
right: -14px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1001;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-trigger:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-icon {
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单样式 */
|
||||||
|
.modern-menu {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏滚动条但保持功能 */
|
||||||
|
.modern-menu::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-menu::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-menu::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-menu::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-menu-item-group-title) {
|
||||||
|
color: rgba(255, 255, 255, 0.45) !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
padding: 16px 16px 8px !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-item) {
|
||||||
|
margin: 4px 0 !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
height: 44px !important;
|
||||||
|
line-height: 44px !important;
|
||||||
|
color: rgba(255, 255, 255, 0.75) !important;
|
||||||
|
transition: all 0.3s ease !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-item:hover) {
|
||||||
|
background: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-item.ant-menu-item-selected) {
|
||||||
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%) !important;
|
||||||
|
color: white !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-item.ant-menu-item-selected::before) {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 60%;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-icon) {
|
||||||
|
font-size: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.menu-divider) {
|
||||||
|
background: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
margin: 16px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部信息 */
|
||||||
|
.sider-footer {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠状态调整 */
|
||||||
|
:deep(.ant-layout-sider-collapsed) .logo-container {
|
||||||
|
padding: 24px 16px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-layout-sider-collapsed) .modern-menu {
|
||||||
|
padding: 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-layout-sider-collapsed .ant-menu-item-group-title) {
|
||||||
|
padding: 8px 0 !important;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-layout-sider-collapsed .menu-item) {
|
||||||
|
padding: 0 calc(50% - 18px / 2) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-card :bordered="false" class="widget-1" style="margin-bottom: 20px;">
|
|
||||||
<a-statistic
|
|
||||||
:title="title"
|
|
||||||
:value="value"
|
|
||||||
:prefix="prefix"
|
|
||||||
:suffix="suffix"
|
|
||||||
:precision="0"
|
|
||||||
class="text-success"
|
|
||||||
:class="'text-' + status"
|
|
||||||
/>
|
|
||||||
<div class="icon" v-html="icon"></div>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const props = defineProps<{
|
|
||||||
title?: string
|
|
||||||
value?: number
|
|
||||||
prefix?: string
|
|
||||||
suffix?: string
|
|
||||||
icon?: string
|
|
||||||
status?: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// 默认值处理(可选)
|
|
||||||
const {
|
|
||||||
title = '',
|
|
||||||
value = 0,
|
|
||||||
prefix = '',
|
|
||||||
suffix = '',
|
|
||||||
icon = '',
|
|
||||||
status = 'success'
|
|
||||||
} = props
|
|
||||||
</script>
|
|
||||||
@@ -1,18 +1,31 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="app-layout">
|
||||||
<a-layout style="min-height: 100vh">
|
<a-layout class="main-layout">
|
||||||
<SiderMenu />
|
<SiderMenu />
|
||||||
<a-layout>
|
<a-layout class="content-layout">
|
||||||
<Header></Header>
|
<Header />
|
||||||
<a-layout-content style="margin: 0 16px">
|
<a-layout-content class="page-content">
|
||||||
<div style="min-height: calc(100vh - 155px)">
|
<div class="content-wrapper">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
<a-layout-footer style="text-align: center">
|
<a-layout-footer class="app-footer">
|
||||||
<!-- Ant Design ©2018 Created by Ant UED -->
|
<div class="footer-content">
|
||||||
|
<div class="footer-text">
|
||||||
|
<span class="copyright">© 2024 整站克隆工具</span>
|
||||||
|
<span class="separator">•</span>
|
||||||
|
<span class="version">Version 1.0.0</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="#" class="footer-link">使用文档</a>
|
||||||
|
<span class="separator">•</span>
|
||||||
|
<a href="#" class="footer-link">意见反馈</a>
|
||||||
|
<span class="separator">•</span>
|
||||||
|
<a href="#" class="footer-link">关于我们</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</a-layout-footer>
|
</a-layout-footer>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@@ -20,7 +33,107 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
onMounted(() => {
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化逻辑
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-layout {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-layout {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-layout {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: calc(100vh - 128px);
|
||||||
|
background: transparent;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
min-height: calc(100vh - 128px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面底部 */
|
||||||
|
.app-footer {
|
||||||
|
background: white;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text,
|
||||||
|
.footer-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
color: #d9d9d9;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-footer {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text,
|
||||||
|
.footer-links {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,79 +1,775 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="dashboard-page">
|
||||||
<a-breadcrumb style="margin: 10px 0">
|
<!-- 面包屑导航 -->
|
||||||
<a-breadcrumb-item>控制台</a-breadcrumb-item>
|
<a-breadcrumb class="page-breadcrumb">
|
||||||
|
<a-breadcrumb-item>
|
||||||
|
<home-outlined />
|
||||||
|
<span>控制台</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
</a-breadcrumb>
|
</a-breadcrumb>
|
||||||
<div>
|
|
||||||
<a-row :gutter="24">
|
<!-- 背景装饰 -->
|
||||||
<a-col :span="24" :lg="12" :xl="6" class="mb-24" v-for="(stat, index) in stats" :key="index">
|
<div class="background-decoration">
|
||||||
<WidgetCounter :title="stat.title" :value="stat.value" :prefix="stat.prefix" :suffix="stat.suffix"
|
<div class="gradient-blob blob-1"></div>
|
||||||
:icon="stat.icon" :status="stat.status"></WidgetCounter>
|
<div class="gradient-blob blob-2"></div>
|
||||||
|
<div class="gradient-blob blob-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 欢迎区域 -->
|
||||||
|
<div class="welcome-section">
|
||||||
|
<div class="welcome-content">
|
||||||
|
<div class="welcome-text">
|
||||||
|
<h1 class="welcome-title">
|
||||||
|
<rocket-outlined class="welcome-icon" />
|
||||||
|
欢迎使用整站下载工具
|
||||||
|
</h1>
|
||||||
|
<p class="welcome-subtitle">快速克隆任何网站,一键下载所有资源文件</p>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-actions">
|
||||||
|
<a-button type="primary" size="large" @click="goToDownload">
|
||||||
|
<template #icon><cloud-download-outlined /></template>
|
||||||
|
开始下载
|
||||||
|
</a-button>
|
||||||
|
<a-button size="large" @click="goToRecords">
|
||||||
|
<template #icon><history-outlined /></template>
|
||||||
|
查看记录
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-illustration">
|
||||||
|
<cloud-server-outlined class="illustration-icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<div class="stats-grid">
|
||||||
|
<a-card class="stat-card" :bordered="false" hoverable>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon sites-icon">
|
||||||
|
<global-outlined />
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">已下载站点</div>
|
||||||
|
<div class="stat-value">{{ stats.totalSites }}</div>
|
||||||
|
<div class="stat-trend positive">
|
||||||
|
<arrow-up-outlined />
|
||||||
|
<span>较上周 +{{ stats.sitesTrend }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card class="stat-card" :bordered="false" hoverable>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon files-icon">
|
||||||
|
<file-outlined />
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">总文件数</div>
|
||||||
|
<div class="stat-value">{{ formatNumber(stats.totalFiles) }}</div>
|
||||||
|
<div class="stat-trend positive">
|
||||||
|
<arrow-up-outlined />
|
||||||
|
<span>较上周 +{{ stats.filesTrend }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card class="stat-card" :bordered="false" hoverable>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon storage-icon">
|
||||||
|
<database-outlined />
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">占用空间</div>
|
||||||
|
<div class="stat-value">{{ stats.totalStorage }}</div>
|
||||||
|
<div class="stat-trend positive">
|
||||||
|
<arrow-up-outlined />
|
||||||
|
<span>较上周 +{{ stats.storageTrend }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card class="stat-card" :bordered="false" hoverable>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-icon success-icon">
|
||||||
|
<check-circle-outlined />
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">成功率</div>
|
||||||
|
<div class="stat-value">{{ stats.successRate }}%</div>
|
||||||
|
<div class="stat-trend positive">
|
||||||
|
<arrow-up-outlined />
|
||||||
|
<span>较上周 +{{ stats.rateTrend }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 功能卡片 -->
|
||||||
|
<a-row :gutter="24" class="feature-cards">
|
||||||
|
<a-col :xs="24" :sm="12" :lg="8">
|
||||||
|
<a-card class="feature-card" :bordered="false" hoverable @click="goToDownload">
|
||||||
|
<div class="feature-icon-wrapper download-feature">
|
||||||
|
<cloud-download-outlined class="feature-icon" />
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">整站下载</h3>
|
||||||
|
<p class="feature-desc">输入网址,自动分析并下载网站的所有资源文件,包括HTML、CSS、JS、图片等</p>
|
||||||
|
<a-button type="link" class="feature-link">
|
||||||
|
开始使用
|
||||||
|
<right-outlined />
|
||||||
|
</a-button>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="12" :lg="8">
|
||||||
|
<a-card class="feature-card" :bordered="false" hoverable @click="goToRecords">
|
||||||
|
<div class="feature-icon-wrapper records-feature">
|
||||||
|
<history-outlined class="feature-icon" />
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">下载记录</h3>
|
||||||
|
<p class="feature-desc">查看所有已下载的网站记录,管理文件,支持快速打开本地文件夹查看内容</p>
|
||||||
|
<a-button type="link" class="feature-link">
|
||||||
|
查看记录
|
||||||
|
<right-outlined />
|
||||||
|
</a-button>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="12" :lg="8">
|
||||||
|
<a-card class="feature-card" :bordered="false" hoverable>
|
||||||
|
<div class="feature-icon-wrapper settings-feature">
|
||||||
|
<setting-outlined class="feature-icon" />
|
||||||
|
</div>
|
||||||
|
<h3 class="feature-title">高级设置</h3>
|
||||||
|
<p class="feature-desc">自定义下载配置,设置保存路径、并发数、超时时间等参数,优化下载体验</p>
|
||||||
|
<a-button type="link" class="feature-link">
|
||||||
|
配置选项
|
||||||
|
<right-outlined />
|
||||||
|
</a-button>
|
||||||
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :gutter="24" type="flex" align="stretch">
|
|
||||||
<a-col :span="24" :lg="10" class="mb-24">
|
<!-- 使用指南 -->
|
||||||
<CardBarChart></CardBarChart>
|
<a-card class="guide-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title-wrapper">
|
||||||
|
<book-outlined class="title-icon" />
|
||||||
|
<span>快速开始</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-row :gutter="[24, 24]">
|
||||||
|
<a-col :xs="24" :md="8">
|
||||||
|
<div class="guide-step">
|
||||||
|
<div class="step-number">1</div>
|
||||||
|
<h4 class="step-title">输入网址</h4>
|
||||||
|
<p class="step-desc">在整站下载页面输入你想要下载的网站完整地址</p>
|
||||||
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="24" :lg="14" class="mb-24">
|
<a-col :xs="24" :md="8">
|
||||||
<CardLineChart></CardLineChart>
|
<div class="guide-step">
|
||||||
|
<div class="step-number">2</div>
|
||||||
|
<h4 class="step-title">分析资源</h4>
|
||||||
|
<p class="step-desc">系统自动分析网站结构,统计所有可下载的资源文件</p>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :md="8">
|
||||||
|
<div class="guide-step">
|
||||||
|
<div class="step-number">3</div>
|
||||||
|
<h4 class="step-title">开始下载</h4>
|
||||||
|
<p class="step-desc">点击下载按钮,等待下载完成,即可在本地查看文件</p>
|
||||||
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
HomeOutlined,
|
||||||
|
RocketOutlined,
|
||||||
|
CloudDownloadOutlined,
|
||||||
|
CloudServerOutlined,
|
||||||
|
HistoryOutlined,
|
||||||
|
GlobalOutlined,
|
||||||
|
FileOutlined,
|
||||||
|
DatabaseOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
ArrowUpOutlined,
|
||||||
|
RightOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
BookOutlined
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
// Counter Widgets stats
|
const router = useRouter()
|
||||||
const stats = [
|
|
||||||
{
|
|
||||||
title: "Today’s Sales",
|
|
||||||
value: 53000,
|
|
||||||
prefix: "$",
|
|
||||||
suffix: "+30%",
|
|
||||||
icon: `
|
|
||||||
<svg width="22" height="22" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8.43338 7.41784C8.58818 7.31464 8.77939 7.2224 9 7.15101L9.00001 8.84899C8.77939 8.7776 8.58818 8.68536 8.43338 8.58216C8.06927 8.33942 8 8.1139 8 8C8 7.8861 8.06927 7.66058 8.43338 7.41784Z" fill="#111827"/>
|
|
||||||
<path d="M11 12.849L11 11.151C11.2206 11.2224 11.4118 11.3146 11.5666 11.4178C11.9308 11.6606 12 11.8861 12 12C12 12.1139 11.9308 12.3394 11.5666 12.5822C11.4118 12.6854 11.2206 12.7776 11 12.849Z" fill="#111827"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18ZM11 5C11 4.44772 10.5523 4 10 4C9.44772 4 9 4.44772 9 5V5.09199C8.3784 5.20873 7.80348 5.43407 7.32398 5.75374C6.6023 6.23485 6 7.00933 6 8C6 8.99067 6.6023 9.76515 7.32398 10.2463C7.80348 10.5659 8.37841 10.7913 9.00001 10.908L9.00002 12.8492C8.60902 12.7223 8.31917 12.5319 8.15667 12.3446C7.79471 11.9275 7.16313 11.8827 6.74599 12.2447C6.32885 12.6067 6.28411 13.2382 6.64607 13.6554C7.20855 14.3036 8.05956 14.7308 9 14.9076L9 15C8.99999 15.5523 9.44769 16 9.99998 16C10.5523 16 11 15.5523 11 15L11 14.908C11.6216 14.7913 12.1965 14.5659 12.676 14.2463C13.3977 13.7651 14 12.9907 14 12C14 11.0093 13.3977 10.2348 12.676 9.75373C12.1965 9.43407 11.6216 9.20873 11 9.09199L11 7.15075C11.391 7.27771 11.6808 7.4681 11.8434 7.65538C12.2053 8.07252 12.8369 8.11726 13.254 7.7553C13.6712 7.39335 13.7159 6.76176 13.354 6.34462C12.7915 5.69637 11.9405 5.26915 11 5.09236V5Z" fill="#111827"/>
|
|
||||||
</svg>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Today’s Users",
|
|
||||||
value: 3200,
|
|
||||||
suffix: "+20%",
|
|
||||||
icon: `
|
|
||||||
<svg width="22" height="22" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M9 6C9 7.65685 7.65685 9 6 9C4.34315 9 3 7.65685 3 6C3 4.34315 4.34315 3 6 3C7.65685 3 9 4.34315 9 6Z" fill="#111827"/>
|
|
||||||
<path d="M17 6C17 7.65685 15.6569 9 14 9C12.3431 9 11 7.65685 11 6C11 4.34315 12.3431 3 14 3C15.6569 3 17 4.34315 17 6Z" fill="#111827"/>
|
|
||||||
<path d="M12.9291 17C12.9758 16.6734 13 16.3395 13 16C13 14.3648 12.4393 12.8606 11.4998 11.6691C12.2352 11.2435 13.0892 11 14 11C16.7614 11 19 13.2386 19 16V17H12.9291Z" fill="#111827"/>
|
|
||||||
<path d="M6 11C8.76142 11 11 13.2386 11 16V17H1V16C1 13.2386 3.23858 11 6 11Z" fill="#111827"/>
|
|
||||||
</svg>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New Clients",
|
|
||||||
value: 1200,
|
|
||||||
prefix: "+",
|
|
||||||
status: "danger",
|
|
||||||
suffix: "-20%",
|
|
||||||
icon: `
|
|
||||||
<svg width="22" height="22" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.17157 5.17157C4.73367 3.60948 7.26633 3.60948 8.82843 5.17157L10 6.34315L11.1716 5.17157C12.7337 3.60948 15.2663 3.60948 16.8284 5.17157C18.3905 6.73367 18.3905 9.26633 16.8284 10.8284L10 17.6569L3.17157 10.8284C1.60948 9.26633 1.60948 6.73367 3.17157 5.17157Z" fill="#111827"/>
|
|
||||||
</svg>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New Orders",
|
|
||||||
value: 13200,
|
|
||||||
prefix: "$",
|
|
||||||
suffix: "+10%",
|
|
||||||
icon: `
|
|
||||||
<svg width="22" height="22" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V7H5C4.49046 7 4.06239 7.38314 4.00612 7.88957L3.00612 16.8896C2.97471 17.1723 3.06518 17.455 3.25488 17.6669C3.44458 17.8789 3.71556 18 4 18H16C16.2844 18 16.5554 17.8789 16.7451 17.6669C16.9348 17.455 17.0253 17.1723 16.9939 16.8896L15.9939 7.88957C15.9376 7.38314 15.5096 7 15 7H14V6C14 3.79086 12.2091 2 10 2ZM12 7V6C12 4.89543 11.1046 4 10 4C8.89543 4 8 4.89543 8 6V7H12ZM6 10C6 9.44772 6.44772 9 7 9C7.55228 9 8 9.44772 8 10C8 10.5523 7.55228 11 7 11C6.44772 11 6 10.5523 6 10ZM13 9C12.4477 9 12 9.44772 12 10C12 10.5523 12.4477 11 13 11C13.5523 11 14 10.5523 14 10C14 9.44772 13.5523 9 13 9Z" fill="#111827"/>
|
|
||||||
</svg>`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const stats = reactive({
|
||||||
|
totalSites: 24,
|
||||||
|
sitesTrend: 15,
|
||||||
|
totalFiles: 15680,
|
||||||
|
filesTrend: 23,
|
||||||
|
totalStorage: '2.8 GB',
|
||||||
|
storageTrend: 18,
|
||||||
|
successRate: 98.5,
|
||||||
|
rateTrend: 2.5
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化数字
|
||||||
|
const formatNumber = (num: number) => {
|
||||||
|
return num.toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到下载页面
|
||||||
|
const goToDownload = () => {
|
||||||
|
router.push('/site/download')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到记录页面
|
||||||
|
const goToRecords = () => {
|
||||||
|
router.push('/site/record')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
/* 页面布局 */
|
||||||
|
.dashboard-page {
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-breadcrumb {
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-breadcrumb span {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰 */
|
||||||
|
.background-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(100px);
|
||||||
|
opacity: 0.25;
|
||||||
|
animation: float 25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
top: -15%;
|
||||||
|
right: -15%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-2 {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
bottom: -15%;
|
||||||
|
left: -15%;
|
||||||
|
animation-delay: 12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-3 {
|
||||||
|
width: 450px;
|
||||||
|
height: 450px;
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
animation-delay: 8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主容器 */
|
||||||
|
.main-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 欢迎区域 */
|
||||||
|
.welcome-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 48px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 48px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
animation: fadeInDown 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-content {
|
||||||
|
flex: 1;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
font-size: 42px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0 0 32px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions .ant-btn {
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 32px;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 4px 16px rgba(24, 144, 255, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions .ant-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-illustration {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration-icon {
|
||||||
|
font-size: 100px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计卡片 */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.stat-card .ant-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sites-icon {
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-icon {
|
||||||
|
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-icon {
|
||||||
|
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-trend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-trend.positive {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 功能卡片 */
|
||||||
|
.feature-cards {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.feature-card .ant-card-body) {
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon-wrapper {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-feature {
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-feature {
|
||||||
|
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-feature {
|
||||||
|
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover .feature-icon-wrapper {
|
||||||
|
transform: scale(1.1) rotate(5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-feature .feature-icon {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-feature .feature-icon {
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-feature .feature-icon {
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
min-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-link {
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 使用指南 */
|
||||||
|
.guide-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.guide-card .ant-card-head) {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-step:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(0, 0) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translate(40px, -40px) rotate(120deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(-30px, 30px) rotate(240deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.main-container {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.welcome-section {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 32px 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-size: 28px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-illustration {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.page-breadcrumb {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-section {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-size: 24px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-actions .ant-btn {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.feature-card .ant-card-body) {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-desc {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,876 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>1</div>
|
<div class="pack-page">
|
||||||
|
<!-- 背景装饰 -->
|
||||||
|
<div class="background-decoration">
|
||||||
|
<div class="gradient-blob blob-1"></div>
|
||||||
|
<div class="gradient-blob blob-2"></div>
|
||||||
|
<div class="gradient-blob blob-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-section">
|
||||||
|
<appstore-outlined class="page-icon" />
|
||||||
|
<div>
|
||||||
|
<h1 class="page-title">应用打包</h1>
|
||||||
|
<p class="page-subtitle">将下载的网站打包成独立应用程序</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 打包向导 -->
|
||||||
|
<a-card class="wizard-card" :bordered="false">
|
||||||
|
<a-steps :current="currentStep" class="pack-steps">
|
||||||
|
<a-step title="选择网站" />
|
||||||
|
<a-step title="配置应用" />
|
||||||
|
<a-step title="打包设置" />
|
||||||
|
<a-step title="开始打包" />
|
||||||
|
</a-steps>
|
||||||
|
|
||||||
|
<!-- 步骤内容 -->
|
||||||
|
<div class="step-content">
|
||||||
|
<!-- 步骤1: 选择网站 -->
|
||||||
|
<div v-if="currentStep === 0" class="step-panel">
|
||||||
|
<div class="step-header">
|
||||||
|
<h3 class="step-title">选择要打包的网站</h3>
|
||||||
|
<a-button @click="getDownloadList" :loading="loading" size="small">
|
||||||
|
<template #icon><reload-outlined /></template>
|
||||||
|
刷新列表
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="loading-state">
|
||||||
|
<a-spin size="large" tip="加载中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="availableSites.length === 0" class="empty-state">
|
||||||
|
<inbox-outlined class="empty-icon" />
|
||||||
|
<p class="empty-text">暂无已下载的网站</p>
|
||||||
|
<a-button type="primary" @click="goToDownload">
|
||||||
|
<template #icon><cloud-download-outlined /></template>
|
||||||
|
去下载网站
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="sites-list">
|
||||||
|
<a-radio-group v-model:value="selectedSite" class="site-radio-group">
|
||||||
|
<a-card
|
||||||
|
v-for="site in availableSites"
|
||||||
|
:key="site.id"
|
||||||
|
class="site-card"
|
||||||
|
:class="{ 'selected': selectedSite === site.id }"
|
||||||
|
:bordered="false"
|
||||||
|
@click="selectedSite = site.id"
|
||||||
|
>
|
||||||
|
<a-radio :value="site.id" class="site-radio">
|
||||||
|
<div class="site-content">
|
||||||
|
<a-avatar :style="{ backgroundColor: getAvatarColor(site.name) }">
|
||||||
|
{{ site.name.charAt(0).toUpperCase() }}
|
||||||
|
</a-avatar>
|
||||||
|
<div class="site-details">
|
||||||
|
<div class="site-name">{{ site.name }}</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span>{{ site.size }}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{{ formatTime(site.modTime) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-radio>
|
||||||
|
</a-card>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤2: 配置应用 -->
|
||||||
|
<div v-if="currentStep === 1" class="step-panel">
|
||||||
|
<h3 class="step-title">配置应用信息</h3>
|
||||||
|
<a-form layout="vertical" class="config-form">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="应用名称">
|
||||||
|
<a-input v-model:value="appConfig.name" placeholder="请输入应用名称" size="large" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12">
|
||||||
|
<a-form-item label="应用版本">
|
||||||
|
<a-input v-model:value="appConfig.version" placeholder="1.0.0" size="large" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12">
|
||||||
|
<a-form-item label="作者">
|
||||||
|
<a-input v-model:value="appConfig.author" placeholder="请输入作者名称" size="large" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="应用描述">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="appConfig.description"
|
||||||
|
placeholder="请输入应用描述"
|
||||||
|
:rows="4"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤3: 打包设置 -->
|
||||||
|
<div v-if="currentStep === 2" class="step-panel">
|
||||||
|
<h3 class="step-title">打包设置</h3>
|
||||||
|
<a-form layout="vertical" class="config-form">
|
||||||
|
<a-form-item label="目标平台">
|
||||||
|
<a-checkbox-group v-model:value="packConfig.platforms" class="platform-group">
|
||||||
|
<a-card class="platform-card" :bordered="false">
|
||||||
|
<a-checkbox value="windows">
|
||||||
|
<windows-outlined class="platform-icon" />
|
||||||
|
<span>Windows</span>
|
||||||
|
</a-checkbox>
|
||||||
|
</a-card>
|
||||||
|
<a-card class="platform-card" :bordered="false">
|
||||||
|
<a-checkbox value="macos">
|
||||||
|
<apple-outlined class="platform-icon" />
|
||||||
|
<span>macOS</span>
|
||||||
|
</a-checkbox>
|
||||||
|
</a-card>
|
||||||
|
<a-card class="platform-card" :bordered="false">
|
||||||
|
<a-checkbox value="linux">
|
||||||
|
<linux-outlined class="platform-icon" />
|
||||||
|
<span>Linux</span>
|
||||||
|
</a-checkbox>
|
||||||
|
</a-card>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :xs="24" :sm="12">
|
||||||
|
<a-form-item label="窗口宽度">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="packConfig.width"
|
||||||
|
:min="800"
|
||||||
|
class="full-width"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12">
|
||||||
|
<a-form-item label="窗口高度">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="packConfig.height"
|
||||||
|
:min="600"
|
||||||
|
class="full-width"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item label="输出目录">
|
||||||
|
<a-input
|
||||||
|
v-model:value="packConfig.outputDir"
|
||||||
|
placeholder="选择输出目录"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<folder-outlined class="input-icon" />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤4: 开始打包 -->
|
||||||
|
<div v-if="currentStep === 3" class="step-panel">
|
||||||
|
<h3 class="step-title">准备打包</h3>
|
||||||
|
<div class="pack-summary">
|
||||||
|
<a-descriptions :column="1" bordered>
|
||||||
|
<a-descriptions-item label="网站名称">{{ selectedSiteName }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="应用名称">{{ appConfig.name }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="版本号">{{ appConfig.version }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="目标平台">
|
||||||
|
<a-tag v-for="platform in packConfig.platforms" :key="platform" color="blue">
|
||||||
|
{{ platform }}
|
||||||
|
</a-tag>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="窗口尺寸">
|
||||||
|
{{ packConfig.width }} x {{ packConfig.height }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<div class="pack-action">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
:loading="packing"
|
||||||
|
@click="startPacking"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
<template #icon><rocket-outlined /></template>
|
||||||
|
{{ packing ? '打包中...' : '开始打包' }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 打包进度 -->
|
||||||
|
<div v-if="packing" class="pack-progress">
|
||||||
|
<a-progress :percent="packProgress" status="active" />
|
||||||
|
<p class="progress-text">{{ packProgressText }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤操作 -->
|
||||||
|
<div class="step-actions">
|
||||||
|
<a-button v-if="currentStep > 0" @click="prevStep" size="large">
|
||||||
|
<template #icon><left-outlined /></template>
|
||||||
|
上一步
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="currentStep < 3"
|
||||||
|
type="primary"
|
||||||
|
@click="nextStep"
|
||||||
|
:disabled="!canProceed"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
<template #icon><right-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 帮助提示 -->
|
||||||
|
<a-card class="help-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title-wrapper">
|
||||||
|
<question-circle-outlined class="title-icon" />
|
||||||
|
<span>帮助说明</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="help-content">
|
||||||
|
<p><strong>打包流程:</strong></p>
|
||||||
|
<ol>
|
||||||
|
<li>选择要打包的已下载网站</li>
|
||||||
|
<li>填写应用的基本信息(名称、版本、作者等)</li>
|
||||||
|
<li>配置打包参数(目标平台、窗口大小等)</li>
|
||||||
|
<li>确认信息无误后开始打包</li>
|
||||||
|
</ol>
|
||||||
|
<p><strong>注意事项:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>打包过程可能需要几分钟,请耐心等待</li>
|
||||||
|
<li>确保有足够的磁盘空间用于存储打包文件</li>
|
||||||
|
<li>不同平台的打包文件格式不同(Windows: .exe, macOS: .app, Linux: .AppImage)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import {
|
||||||
|
AppstoreOutlined,
|
||||||
|
WindowsOutlined,
|
||||||
|
AppleOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
RocketOutlined,
|
||||||
|
LeftOutlined,
|
||||||
|
RightOutlined,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
InboxOutlined,
|
||||||
|
CloudDownloadOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { App } from "../../../bindings/go-site-clone";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 步骤控制
|
||||||
|
const currentStep = ref(0);
|
||||||
|
const selectedSite = ref<string>('');
|
||||||
|
const packing = ref(false);
|
||||||
|
const packProgress = ref(0);
|
||||||
|
const packProgressText = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 从后端获取已下载的网站列表
|
||||||
|
const availableSites = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 获取网站列表
|
||||||
|
const getDownloadList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await App.GetDownloadList();
|
||||||
|
availableSites.value = res.map((site: any, index: number) => ({
|
||||||
|
id: index.toString(),
|
||||||
|
name: site.name,
|
||||||
|
size: formatSize(site.size),
|
||||||
|
files: '未知', // 如果后端提供文件数量可以使用
|
||||||
|
modTime: site.modTime,
|
||||||
|
rawData: site
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error);
|
||||||
|
message.error('获取站点列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatSize = (bytes: number) => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面挂载时加载数据
|
||||||
|
onMounted(() => {
|
||||||
|
getDownloadList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用配置
|
||||||
|
const appConfig = ref({
|
||||||
|
name: '',
|
||||||
|
version: '1.0.0',
|
||||||
|
author: '',
|
||||||
|
description: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 打包配置
|
||||||
|
const packConfig = ref({
|
||||||
|
platforms: ['windows'],
|
||||||
|
width: 1280,
|
||||||
|
height: 800,
|
||||||
|
outputDir: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const selectedSiteName = computed(() => {
|
||||||
|
const site = availableSites.value.find(s => s.id === selectedSite.value);
|
||||||
|
return site?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const canProceed = computed(() => {
|
||||||
|
if (currentStep.value === 0) return selectedSite.value !== '';
|
||||||
|
if (currentStep.value === 1) return appConfig.value.name !== '';
|
||||||
|
if (currentStep.value === 2) return packConfig.value.platforms.length > 0;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 步骤操作
|
||||||
|
const nextStep = () => {
|
||||||
|
if (currentStep.value < 3) {
|
||||||
|
currentStep.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevStep = () => {
|
||||||
|
if (currentStep.value > 0) {
|
||||||
|
currentStep.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始打包
|
||||||
|
const startPacking = () => {
|
||||||
|
packing.value = true;
|
||||||
|
packProgress.value = 0;
|
||||||
|
|
||||||
|
// 模拟打包进度
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
packProgress.value += 10;
|
||||||
|
|
||||||
|
if (packProgress.value <= 30) {
|
||||||
|
packProgressText.value = '正在准备打包环境...';
|
||||||
|
} else if (packProgress.value <= 60) {
|
||||||
|
packProgressText.value = '正在编译应用...';
|
||||||
|
} else if (packProgress.value <= 90) {
|
||||||
|
packProgressText.value = '正在生成安装包...';
|
||||||
|
} else {
|
||||||
|
packProgressText.value = '打包完成!';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packProgress.value >= 100) {
|
||||||
|
clearInterval(interval);
|
||||||
|
packing.value = false;
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取头像颜色
|
||||||
|
const getAvatarColor = (name: string) => {
|
||||||
|
const colors = ['#1890ff', '#52c41a', '#722ed1', '#fa8c16', '#eb2f96'];
|
||||||
|
const index = name.charCodeAt(0) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (dateStr: string) => {
|
||||||
|
if (!dateStr) return '--';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
|
||||||
|
const minute = 60 * 1000;
|
||||||
|
const hour = 60 * minute;
|
||||||
|
const day = 24 * hour;
|
||||||
|
|
||||||
|
if (diff < hour) {
|
||||||
|
return Math.floor(diff / minute) + ' 分钟前';
|
||||||
|
} else if (diff < day) {
|
||||||
|
return Math.floor(diff / hour) + ' 小时前';
|
||||||
|
} else if (diff < 7 * day) {
|
||||||
|
return Math.floor(diff / day) + ' 天前';
|
||||||
|
} else {
|
||||||
|
return date.toLocaleDateString('zh-CN');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到下载页面
|
||||||
|
const goToDownload = () => {
|
||||||
|
router.push('/site/download');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 页面布局 */
|
||||||
|
.pack-page {
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰 - 复用之前的样式 */
|
||||||
|
.background-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(100px);
|
||||||
|
opacity: 0.25;
|
||||||
|
animation: float 25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
top: -15%;
|
||||||
|
right: -15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-2 {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
bottom: -15%;
|
||||||
|
left: -15%;
|
||||||
|
animation-delay: 12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-3 {
|
||||||
|
width: 450px;
|
||||||
|
height: 450px;
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
animation-delay: 8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主容器 */
|
||||||
|
.main-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面标题 */
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
animation: fadeInDown 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #722ed1;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 向导卡片 */
|
||||||
|
.wizard-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wizard-card .ant-card-body) {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-steps {
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 步骤内容 */
|
||||||
|
.step-content {
|
||||||
|
min-height: 400px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-panel {
|
||||||
|
animation: fadeIn 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-header .step-title {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载和空状态 */
|
||||||
|
.loading-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网站列表 */
|
||||||
|
.sites-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-radio-group {
|
||||||
|
width: 100%;
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card {
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card.selected {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-radio {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.site-radio .ant-radio) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-details {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #999;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置表单 */
|
||||||
|
.config-form {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平台选择 */
|
||||||
|
.platform-group {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-card {
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.platform-card .ant-card-body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-card:has(:checked) {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 打包摘要 */
|
||||||
|
.pack-summary {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-action {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-progress {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
text-align: center;
|
||||||
|
margin: 12px 0 0 0;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 步骤操作 */
|
||||||
|
.step-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 帮助卡片 */
|
||||||
|
.help-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content strong {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content ol,
|
||||||
|
.help-content ul {
|
||||||
|
margin: 12px 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content li {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用样式 */
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(0, 0) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translate(40px, -40px) rotate(120deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(-30px, 30px) rotate(240deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-container {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wizard-card .ant-card-body) {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sites-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-group {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,936 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>1</div>
|
<div class="webpage-page">
|
||||||
|
<!-- 背景装饰 -->
|
||||||
|
<div class="background-decoration">
|
||||||
|
<div class="gradient-blob blob-1"></div>
|
||||||
|
<div class="gradient-blob blob-2"></div>
|
||||||
|
<div class="gradient-blob blob-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-section">
|
||||||
|
<global-outlined class="page-icon" />
|
||||||
|
<div>
|
||||||
|
<h1 class="page-title">网站托管</h1>
|
||||||
|
<p class="page-subtitle">使用 Nginx 托管和预览已下载的网站</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-space>
|
||||||
|
<a-button size="large" @click="handleRefresh" :loading="loading">
|
||||||
|
<template #icon><reload-outlined /></template>
|
||||||
|
刷新
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
:danger="nginxRunning"
|
||||||
|
@click="toggleNginx"
|
||||||
|
:loading="nginxLoading"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<poweroff-outlined v-if="nginxRunning" />
|
||||||
|
<play-circle-outlined v-else />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
{{ nginxRunning ? '停止 Nginx' : '启动 Nginx' }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nginx 状态卡片 -->
|
||||||
|
<a-card class="status-card" :bordered="false">
|
||||||
|
<div class="status-header">
|
||||||
|
<div class="status-title">
|
||||||
|
<api-outlined class="title-icon" />
|
||||||
|
<span>Nginx 服务状态</span>
|
||||||
|
</div>
|
||||||
|
<a-badge
|
||||||
|
:status="nginxRunning ? 'processing' : 'default'"
|
||||||
|
:text="nginxRunning ? '运行中' : '未启动'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-row :gutter="24" class="status-info">
|
||||||
|
<a-col :xs="24" :sm="12" :lg="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">
|
||||||
|
<thunderbolt-outlined />
|
||||||
|
服务状态
|
||||||
|
</div>
|
||||||
|
<div class="info-value" :style="{ color: nginxRunning ? '#52c41a' : '#999' }">
|
||||||
|
{{ nginxRunning ? '正常运行' : '已停止' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12" :lg="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">
|
||||||
|
<cluster-outlined />
|
||||||
|
托管站点
|
||||||
|
</div>
|
||||||
|
<div class="info-value">{{ hostingSites.length }} 个</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12" :lg="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">
|
||||||
|
<clock-circle-outlined />
|
||||||
|
运行时长
|
||||||
|
</div>
|
||||||
|
<div class="info-value">{{ uptime }}</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="12" :lg="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">
|
||||||
|
<fund-outlined />
|
||||||
|
总访问量
|
||||||
|
</div>
|
||||||
|
<div class="info-value">{{ totalVisits }}</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 网站托管列表 -->
|
||||||
|
<a-card class="sites-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title-wrapper">
|
||||||
|
<folder-open-outlined class="title-icon" />
|
||||||
|
<span>网站托管管理</span>
|
||||||
|
<a-badge :count="hostingSites.length" :number-style="{ backgroundColor: '#1890ff' }" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
添加站点
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="loading" class="loading-state">
|
||||||
|
<a-spin size="large" tip="加载中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="hostingSites.length === 0" class="empty-state">
|
||||||
|
<inbox-outlined class="empty-icon" />
|
||||||
|
<p class="empty-text">暂无托管的网站</p>
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
添加第一个站点
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
v-else
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="hostingSites"
|
||||||
|
:pagination="{ pageSize: 10, showSizeChanger: true, showTotal: (total) => `共 ${total} 个站点` }"
|
||||||
|
:scroll="{ x: 800 }"
|
||||||
|
class="hosting-table"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'site'">
|
||||||
|
<div class="site-cell">
|
||||||
|
<a-avatar :style="{ backgroundColor: getAvatarColor(record.name) }" size="small">
|
||||||
|
{{ record.name.charAt(0).toUpperCase() }}
|
||||||
|
</a-avatar>
|
||||||
|
<div class="site-details">
|
||||||
|
<div class="site-name">{{ record.name }}</div>
|
||||||
|
<div class="site-path">{{ record.path }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'domain'">
|
||||||
|
<div class="domain-cell">
|
||||||
|
<a-tag color="blue" v-for="domain in record.domains" :key="domain">
|
||||||
|
{{ domain }}
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'port'">
|
||||||
|
<a-tag color="cyan">{{ record.port }}</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<a-badge
|
||||||
|
:status="record.enabled ? 'success' : 'default'"
|
||||||
|
:text="record.enabled ? '已启用' : '已禁用'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<a-space size="small">
|
||||||
|
<a-button type="link" size="small" @click="openSite(record)" :disabled="!nginxRunning || !record.enabled">
|
||||||
|
<template #icon><eye-outlined /></template>
|
||||||
|
访问
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" size="small" @click="openFolder(record)">
|
||||||
|
<template #icon><folder-outlined /></template>
|
||||||
|
文件夹
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" size="small" @click="editSite(record)">
|
||||||
|
<template #icon><edit-outlined /></template>
|
||||||
|
编辑
|
||||||
|
</a-button>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="record.enabled"
|
||||||
|
size="small"
|
||||||
|
@change="toggleSiteStatus(record)"
|
||||||
|
/>
|
||||||
|
<a-popconfirm
|
||||||
|
title="确定要删除此站点吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="deleteSite(record)"
|
||||||
|
>
|
||||||
|
<a-button type="link" size="small" danger>
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加/编辑站点对话框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="editingRecord ? '编辑站点' : '添加站点'"
|
||||||
|
width="700px"
|
||||||
|
@ok="handleModalOk"
|
||||||
|
@cancel="handleModalCancel"
|
||||||
|
>
|
||||||
|
<a-form :model="formData" layout="vertical" ref="formRef">
|
||||||
|
<a-form-item label="选择网站" name="name" :rules="[{ required: true, message: '请选择网站' }]">
|
||||||
|
<a-select v-model:value="formData.name" placeholder="请选择已下载的网站" @change="onSiteSelect">
|
||||||
|
<a-select-option v-for="site in availableSites" :key="site.name" :value="site.name">
|
||||||
|
{{ site.name }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="域名配置" name="domains" :rules="[{ required: true, message: '请至少添加一个域名' }]">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.domains"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入域名后按回车添加"
|
||||||
|
:token-separators="[',', ' ']"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
<div class="form-tip">
|
||||||
|
<info-circle-outlined />
|
||||||
|
<span>可以添加多个域名,如 example.com 或 127.0.0.1</span>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="监听端口" name="port" :rules="[{ required: true, message: '请输入端口' }]">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.port"
|
||||||
|
:min="80"
|
||||||
|
:max="65535"
|
||||||
|
placeholder="80"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="默认首页" name="index">
|
||||||
|
<a-input v-model:value="formData.index" placeholder="index.html" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item label="网站路径" name="path">
|
||||||
|
<a-input v-model:value="formData.path" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="启用状态">
|
||||||
|
<a-switch v-model:checked="formData.enabled" />
|
||||||
|
<span style="margin-left: 12px; color: #666;">{{ formData.enabled ? '启用后立即生效' : '暂不启用' }}</span>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import {
|
||||||
|
GlobalOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
|
PoweroffOutlined,
|
||||||
|
ApiOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
InboxOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
ThunderboltOutlined,
|
||||||
|
ClusterOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
FundOutlined,
|
||||||
|
InfoCircleOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { App } from "../../../bindings/go-site-clone";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const loading = ref(false);
|
||||||
|
const nginxRunning = ref(false);
|
||||||
|
const nginxLoading = ref(false);
|
||||||
|
const uptime = ref('--');
|
||||||
|
const totalVisits = ref(0);
|
||||||
|
|
||||||
|
// 托管站点列表
|
||||||
|
const hostingSites = ref<any[]>([]);
|
||||||
|
const availableSites = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 对话框
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const editingRecord = ref<any>(null);
|
||||||
|
const formRef = ref();
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
domains: ['127.0.0.1'],
|
||||||
|
port: 80,
|
||||||
|
path: '',
|
||||||
|
index: 'index.html',
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '网站',
|
||||||
|
key: 'site',
|
||||||
|
width: '25%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '域名',
|
||||||
|
key: 'domain',
|
||||||
|
width: '25%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '端口',
|
||||||
|
key: 'port',
|
||||||
|
width: '10%',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
width: '10%',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: '30%',
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = async () => {
|
||||||
|
await Promise.all([
|
||||||
|
getAvailableSites(),
|
||||||
|
getHostingSites()
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取可用网站列表(已下载的)
|
||||||
|
const getAvailableSites = async () => {
|
||||||
|
try {
|
||||||
|
const res = await App.GetDownloadList();
|
||||||
|
availableSites.value = res || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取网站列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取托管列表
|
||||||
|
const getHostingSites = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// TODO: 调用后端接口获取托管列表
|
||||||
|
// 暂时使用模拟数据
|
||||||
|
hostingSites.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'example.com',
|
||||||
|
path: '/www/example.com',
|
||||||
|
domains: ['example.com', '127.0.0.1'],
|
||||||
|
port: 80,
|
||||||
|
index: 'index.html',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取托管列表失败:', error);
|
||||||
|
message.error('获取托管列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadData();
|
||||||
|
message.success('刷新成功');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启停 Nginx
|
||||||
|
const toggleNginx = async () => {
|
||||||
|
nginxLoading.value = true;
|
||||||
|
try {
|
||||||
|
if (nginxRunning.value) {
|
||||||
|
// TODO: 调用后端接口停止 Nginx
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
nginxRunning.value = false;
|
||||||
|
uptime.value = '--';
|
||||||
|
message.success('Nginx 已停止');
|
||||||
|
} else {
|
||||||
|
// TODO: 调用后端接口启动 Nginx
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
nginxRunning.value = true;
|
||||||
|
uptime.value = '00:00:00';
|
||||||
|
message.success('Nginx 已启动');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(nginxRunning.value ? '停止失败' : '启动失败');
|
||||||
|
} finally {
|
||||||
|
nginxLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示添加对话框
|
||||||
|
const showAddModal = () => {
|
||||||
|
editingRecord.value = null;
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
domains: ['127.0.0.1'],
|
||||||
|
port: 80,
|
||||||
|
path: '',
|
||||||
|
index: 'index.html',
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
modalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑站点
|
||||||
|
const editSite = (record: any) => {
|
||||||
|
editingRecord.value = record;
|
||||||
|
formData.value = { ...record };
|
||||||
|
modalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 网站选择改变
|
||||||
|
const onSiteSelect = (value: string) => {
|
||||||
|
const site = availableSites.value.find(s => s.name === value);
|
||||||
|
if (site) {
|
||||||
|
formData.value.path = `/www/${value}`;
|
||||||
|
formData.value.domains = [value];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确定对话框
|
||||||
|
const handleModalOk = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
|
||||||
|
if (editingRecord.value) {
|
||||||
|
// 更新
|
||||||
|
const index = hostingSites.value.findIndex(s => s.id === editingRecord.value.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
hostingSites.value[index] = { ...formData.value, id: editingRecord.value.id };
|
||||||
|
}
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
// 添加
|
||||||
|
hostingSites.value.push({
|
||||||
|
...formData.value,
|
||||||
|
id: Date.now().toString()
|
||||||
|
});
|
||||||
|
message.success('添加成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
modalVisible.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消对话框
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
modalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换站点状态
|
||||||
|
const toggleSiteStatus = (record: any) => {
|
||||||
|
message.success(record.enabled ? '已启用' : '已禁用');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除站点
|
||||||
|
const deleteSite = (record: any) => {
|
||||||
|
const index = hostingSites.value.findIndex(s => s.id === record.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
hostingSites.value.splice(index, 1);
|
||||||
|
message.success('删除成功');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 访问站点
|
||||||
|
const openSite = (record: any) => {
|
||||||
|
const url = `http://${record.domains[0]}:${record.port}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开文件夹
|
||||||
|
const openFolder = async (record: any) => {
|
||||||
|
try {
|
||||||
|
await App.OpenSiteFileDir(record.name);
|
||||||
|
message.success('已打开文件夹');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开文件夹失败:', error);
|
||||||
|
message.error('打开文件夹失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取头像颜色
|
||||||
|
const getAvatarColor = (name: string) => {
|
||||||
|
const colors = ['#1890ff', '#52c41a', '#722ed1', '#fa8c16', '#eb2f96'];
|
||||||
|
const index = name.charCodeAt(0) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 页面布局 */
|
||||||
|
.webpage-page {
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰 */
|
||||||
|
.background-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(100px);
|
||||||
|
opacity: 0.25;
|
||||||
|
animation: float 25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
top: -15%;
|
||||||
|
right: -15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-2 {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
bottom: -15%;
|
||||||
|
left: -15%;
|
||||||
|
animation-delay: 12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-3 {
|
||||||
|
width: 450px;
|
||||||
|
height: 450px;
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
animation-delay: 8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主容器 */
|
||||||
|
.main-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面标题 */
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
animation: fadeInDown 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #1890ff;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片样式 */
|
||||||
|
.status-card,
|
||||||
|
.sites-card,
|
||||||
|
.config-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态卡片 */
|
||||||
|
.status-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 托管表格 */
|
||||||
|
.hosting-table {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.hosting-table .ant-table) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.hosting-table .ant-table-thead > tr > th) {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.hosting-table .ant-table-tbody > tr) {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.hosting-table .ant-table-tbody > tr:hover) {
|
||||||
|
background: #e6f7ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-details {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-path {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载和空状态 */
|
||||||
|
.loading-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 100px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单提示 */
|
||||||
|
.form-tip {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #e6f7ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #1890ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框样式优化 */
|
||||||
|
:deep(.ant-modal-header) {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-bottom: 2px solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-modal-title) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式优化 */
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-btn-primary:hover) {
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网站网格 */
|
||||||
|
.sites-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-item {
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-item:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-url {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单 */
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(0, 0) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translate(40px, -40px) rotate(120deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(-30px, 30px) rotate(240deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-container {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sites-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,683 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-breadcrumb style="margin: 10px 0">
|
<div class="record-page">
|
||||||
<a-breadcrumb-item>仿站</a-breadcrumb-item>
|
<!-- 面包屑导航 -->
|
||||||
<a-breadcrumb-item>仿站记录</a-breadcrumb-item>
|
<a-breadcrumb class="page-breadcrumb">
|
||||||
|
<a-breadcrumb-item>
|
||||||
|
<home-outlined />
|
||||||
|
<span>仿站</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item>
|
||||||
|
<history-outlined />
|
||||||
|
<span>仿站记录</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
</a-breadcrumb>
|
</a-breadcrumb>
|
||||||
<div>
|
|
||||||
<a-card :bordered="false" class="table-list">
|
<!-- 背景装饰 -->
|
||||||
<a-table :dataSource="dataSource" :columns="columns" >
|
<div class="background-decoration">
|
||||||
<template #bodyCell="{ column, record }">
|
<div class="gradient-blob blob-1"></div>
|
||||||
<template v-if="column.key === 'action'">
|
<div class="gradient-blob blob-2"></div>
|
||||||
<span>
|
</div>
|
||||||
<a @click="openFileDir(record.name)">查看文件</a>
|
|
||||||
</span>
|
<!-- 主内容区 -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 页面头部 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="header-left">
|
||||||
|
<folder-open-outlined class="header-icon" />
|
||||||
|
<div class="header-info">
|
||||||
|
<h1 class="page-title">仿站记录</h1>
|
||||||
|
<p class="page-subtitle">查看和管理已下载的网站资源</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<a-space size="middle">
|
||||||
|
<a-button @click="handleRefresh" :loading="loading">
|
||||||
|
<template #icon><reload-outlined /></template>
|
||||||
|
刷新列表
|
||||||
|
</a-button>
|
||||||
|
<a-badge :count="dataSource.length" :number-style="{ backgroundColor: '#52c41a' }">
|
||||||
|
<a-button type="primary">
|
||||||
|
<template #icon><database-outlined /></template>
|
||||||
|
总计站点
|
||||||
|
</a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据表格卡片 -->
|
||||||
|
<a-card :bordered="false" class="table-card">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title-wrapper">
|
||||||
|
<appstore-outlined class="title-icon" />
|
||||||
|
<span>站点列表</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:dataSource="dataSource"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="paginationConfig"
|
||||||
|
:scroll="{ x: 800 }"
|
||||||
|
class="custom-table"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<!-- 名称列 -->
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<div class="name-cell">
|
||||||
|
<a-avatar
|
||||||
|
:style="{ backgroundColor: getAvatarColor(index) }"
|
||||||
|
:size="40"
|
||||||
|
shape="square"
|
||||||
|
>
|
||||||
|
<template #icon><global-outlined /></template>
|
||||||
|
</a-avatar>
|
||||||
|
<div class="name-info">
|
||||||
|
<div class="site-name">{{ record.name }}</div>
|
||||||
|
<div class="site-path">{{ record.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 大小列 -->
|
||||||
|
<template v-if="column.key === 'size'">
|
||||||
|
<a-tag color="blue" class="size-tag">
|
||||||
|
<file-outlined />
|
||||||
|
{{ formatSize(record.size) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 权限列 -->
|
||||||
|
<template v-if="column.key === 'mode'">
|
||||||
|
<a-tag color="purple">
|
||||||
|
<lock-outlined />
|
||||||
|
{{ record.mode }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 修改时间列 -->
|
||||||
|
<template v-if="column.key === 'modTime'">
|
||||||
|
<div class="time-cell">
|
||||||
|
<clock-circle-outlined class="time-icon" />
|
||||||
|
<span>{{ formatTime(record.modTime) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 操作列 -->
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click="openFileDir(record.name)"
|
||||||
|
class="action-btn"
|
||||||
|
>
|
||||||
|
<template #icon><folder-open-outlined /></template>
|
||||||
|
打开文件夹
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
class="action-btn"
|
||||||
|
>
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<template #emptyText>
|
||||||
|
<a-empty description="暂无仿站记录">
|
||||||
|
<template #image>
|
||||||
|
<inbox-outlined style="font-size: 64px; color: #d9d9d9;" />
|
||||||
|
</template>
|
||||||
|
</a-empty>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { App } from "../../../bindings/go-site-clone";
|
import {
|
||||||
|
HomeOutlined,
|
||||||
|
HistoryOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
DatabaseOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
GlobalOutlined,
|
||||||
|
FileOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
InboxOutlined
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import { App } from "../../../bindings/go-site-clone"
|
||||||
|
import { Modal, message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '站点名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
width: '35%',
|
||||||
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '大小',
|
title: '文件大小',
|
||||||
dataIndex: 'size',
|
dataIndex: 'size',
|
||||||
key: 'size',
|
key: 'size',
|
||||||
|
width: '15%',
|
||||||
|
sorter: (a, b) => a.size - b.size,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '权限',
|
title: '权限',
|
||||||
dataIndex: 'mode',
|
dataIndex: 'mode',
|
||||||
key: 'mode',
|
key: 'mode',
|
||||||
|
width: '15%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '修改时间',
|
title: '最后修改时间',
|
||||||
key: 'modTime',
|
key: 'modTime',
|
||||||
dataIndex: 'modTime',
|
dataIndex: 'modTime',
|
||||||
|
width: '20%',
|
||||||
|
sorter: (a, b) => new Date(a.modTime) - new Date(b.modTime),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Action',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
width: '15%',
|
||||||
|
fixed: 'right',
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const paginationConfig = {
|
||||||
|
pageSize: 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条记录`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataSource = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const dataSource = ref([]);
|
|
||||||
const siteList = ref([])
|
|
||||||
// 获取网站列表
|
// 获取网站列表
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
App.GetDownloadList().then((res)=>{
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await App.GetDownloadList()
|
||||||
dataSource.value = JSON.parse(JSON.stringify(res))
|
dataSource.value = JSON.parse(JSON.stringify(res))
|
||||||
})
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error)
|
||||||
|
message.error('获取站点列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
const handleRefresh = () => {
|
||||||
|
getList()
|
||||||
|
message.success('刷新成功')
|
||||||
|
}
|
||||||
|
|
||||||
// 打开文件夹
|
// 打开文件夹
|
||||||
const openFileDir = async (name) => {
|
const openFileDir = async (name) => {
|
||||||
App.OpenSiteFileDir(name)
|
try {
|
||||||
|
await App.OpenSiteFileDir(name)
|
||||||
|
message.success('已打开文件夹')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开文件夹失败:', error)
|
||||||
|
message.error('打开文件夹失败')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除记录
|
||||||
|
const handleDelete = (record) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除站点 "${record.name}" 吗?此操作不可恢复。`,
|
||||||
|
okText: '确定',
|
||||||
|
okType: 'danger',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
await App.DeleteSiteFileDir(record.name)
|
||||||
|
message.success('删除成功')
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error)
|
||||||
|
message.error('删除失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatSize = (bytes) => {
|
||||||
|
if (!bytes || bytes === 0) return '0 B'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (time) => {
|
||||||
|
if (!time) return '-'
|
||||||
|
const date = new Date(time)
|
||||||
|
const now = new Date()
|
||||||
|
const diff = now - date
|
||||||
|
|
||||||
|
// 小于1分钟
|
||||||
|
if (diff < 60000) {
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
// 小于1小时
|
||||||
|
if (diff < 3600000) {
|
||||||
|
return Math.floor(diff / 60000) + ' 分钟前'
|
||||||
|
}
|
||||||
|
// 小于24小时
|
||||||
|
if (diff < 86400000) {
|
||||||
|
return Math.floor(diff / 3600000) + ' 小时前'
|
||||||
|
}
|
||||||
|
// 小于7天
|
||||||
|
if (diff < 604800000) {
|
||||||
|
return Math.floor(diff / 86400000) + ' 天前'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化为完整日期
|
||||||
|
return date.toLocaleDateString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取头像颜色
|
||||||
|
const getAvatarColor = (index) => {
|
||||||
|
const colors = [
|
||||||
|
'#1890ff', '#52c41a', '#722ed1', '#fa8c16',
|
||||||
|
'#eb2f96', '#13c2c2', '#faad14', '#f5222d'
|
||||||
|
]
|
||||||
|
return colors[index % colors.length]
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 页面布局 */
|
||||||
|
.record-page {
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-breadcrumb {
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-breadcrumb span {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景装饰 */
|
||||||
|
.background-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(100px);
|
||||||
|
opacity: 0.25;
|
||||||
|
animation: float 25s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
top: -15%;
|
||||||
|
right: -15%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-2 {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
bottom: -15%;
|
||||||
|
left: -15%;
|
||||||
|
animation-delay: 12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主容器 */
|
||||||
|
.main-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px 32px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
animation: fadeInDown 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #1890ff;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格卡片 */
|
||||||
|
.table-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.table-card .ant-card-head) {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义表格样式 */
|
||||||
|
.custom-table {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table-thead > tr > th) {
|
||||||
|
background: #fafafa;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #e8e8e8;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table-tbody > tr) {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table-tbody > tr:hover) {
|
||||||
|
background: rgba(24, 144, 255, 0.04);
|
||||||
|
transform: translateX(4px);
|
||||||
|
box-shadow: -4px 0 0 0 #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table-tbody > tr > td) {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 名称单元格 */
|
||||||
|
.name-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-path {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 大小标签 */
|
||||||
|
.size-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 时间单元格 */
|
||||||
|
.time-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-icon {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮 */
|
||||||
|
.action-btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(0, 0) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translate(40px, -40px) rotate(120deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(-30px, 30px) rotate(240deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.main-container {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right :deep(.ant-space) {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.table-card .ant-card-body) {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table-thead > tr > th),
|
||||||
|
:deep(.custom-table .ant-table-tbody > tr > td) {
|
||||||
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-cell {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.page-breadcrumb {
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-table .ant-table) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,6 +13,14 @@ import * as services$0 from "./services/models.js";
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as utils$0 from "./utils/models.js";
|
import * as utils$0 from "./utils/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pathDir
|
||||||
|
* @returns {$CancellablePromise<boolean>}
|
||||||
|
*/
|
||||||
|
export function DeleteSiteFileDir(pathDir) {
|
||||||
|
return $Call.ByID(2647643782, pathDir);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} uri
|
* @param {string} uri
|
||||||
* @param {services$0.ResourcesList} obj
|
* @param {services$0.ResourcesList} obj
|
||||||
|
|||||||
Reference in New Issue
Block a user