This commit is contained in:
@@ -238,17 +238,16 @@ HAVLINE_VERSION=<旧版本> docker compose up -d --no-deps --force-recreate havl
|
|||||||
|
|
||||||
## 10. 在线升级验证
|
## 10. 在线升级验证
|
||||||
|
|
||||||
示例页面用于验证升级结果。v1.9.0 的验证流程:
|
「关于项目」页面可以用于确认升级后的版本信息。v1.9.0 的验证流程:
|
||||||
|
|
||||||
1. 确认当前线上实例为 v1.3.0,且 `havline-updater` sidecar 已运行。
|
1. 确认当前线上实例为 v1.3.0,且 `havline-updater` sidecar 已运行。
|
||||||
2. 确认远端 `main` 分支的 `VERSION` 为 `1.9.0`。
|
2. 确认远端 `main` 分支的 `VERSION` 为 `1.9.0`。
|
||||||
3. 在侧栏看到「一键升级」后点击并确认。
|
3. 在侧栏看到「一键升级」后点击并确认。
|
||||||
4. 等待升级弹窗显示成功,页面自动刷新。
|
4. 等待升级弹窗显示成功,页面自动刷新。
|
||||||
5. 打开侧栏「示例页面」,确认:
|
5. 打开侧栏「关于项目」,确认:
|
||||||
- 页面路由为 `/example`;
|
- 页面路由为 `/about`;
|
||||||
- 页面显示的版本为 `1.9.0`;
|
- 页面显示的版本为 `1.9.0`;
|
||||||
- 页面显示验证标识 `v1.9.0-example-page`;
|
- 「源项目」显示为 `O96u/Fonu`;
|
||||||
- 页面显示独立的「本次升级验证」区块;
|
- 开源协议显示为 `GPL-3.0`。
|
||||||
- 「升级验证」显示为「通过」。
|
|
||||||
|
|
||||||
如果示例页面可见且版本正确,说明镜像构建、容器重建、后端版本注入和前端静态资源更新均已生效。
|
如果「关于项目」页面可见且版本正确,说明镜像构建、容器重建、后端版本注入和前端静态资源更新均已生效。
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ import {
|
|||||||
CogOutline,
|
CogOutline,
|
||||||
DocumentTextOutline,
|
DocumentTextOutline,
|
||||||
Ellipse,
|
Ellipse,
|
||||||
FlashOutline,
|
|
||||||
GitNetworkOutline,
|
GitNetworkOutline,
|
||||||
GridOutline,
|
GridOutline,
|
||||||
|
InformationCircleOutline,
|
||||||
LockClosedOutline,
|
LockClosedOutline,
|
||||||
LogoGithub,
|
LogoGithub,
|
||||||
MoonOutline,
|
MoonOutline,
|
||||||
@@ -205,7 +205,7 @@ const menuDefs = [
|
|||||||
{ label: 'Cloudflare 隧道', key: 'cloudflare', icon: CloudOutline },
|
{ label: 'Cloudflare 隧道', key: 'cloudflare', icon: CloudOutline },
|
||||||
{ label: '日志中心', key: 'logs', icon: DocumentTextOutline },
|
{ label: '日志中心', key: 'logs', icon: DocumentTextOutline },
|
||||||
{ label: '系统设置', key: 'settings', icon: CogOutline },
|
{ label: '系统设置', key: 'settings', icon: CogOutline },
|
||||||
{ label: '示例页面', key: 'example', icon: FlashOutline },
|
{ label: '关于项目', key: 'about', icon: InformationCircleOutline },
|
||||||
]
|
]
|
||||||
|
|
||||||
const menuOptions = computed<MenuOption[]>(() =>
|
const menuOptions = computed<MenuOption[]>(() =>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import CloudflareTunnelConfigView from '../views/CloudflareTunnelConfigView.vue'
|
|||||||
import AgentView from '../views/AgentView.vue'
|
import AgentView from '../views/AgentView.vue'
|
||||||
import ProxyRemoteView from '../views/ProxyRemoteView.vue'
|
import ProxyRemoteView from '../views/ProxyRemoteView.vue'
|
||||||
import StatusPageView from '../views/StatusPageView.vue'
|
import StatusPageView from '../views/StatusPageView.vue'
|
||||||
import ExampleView from '../views/ExampleView.vue'
|
import AboutView from '../views/AboutView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
@@ -96,10 +96,10 @@ const router = createRouter({
|
|||||||
meta: { title: '系统设置', description: '配置系统参数与运行策略' },
|
meta: { title: '系统设置', description: '配置系统参数与运行策略' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'example',
|
path: 'about',
|
||||||
name: 'example',
|
name: 'about',
|
||||||
component: ExampleView,
|
component: AboutView,
|
||||||
meta: { title: '示例页面', description: '用于验证在线升级是否成功生效' },
|
meta: { title: '关于项目', description: '查看 Havline 版本、源开源项目和技术信息' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<PageHeader title="关于项目" description="查看 Havline 版本、源开源项目和技术信息">
|
||||||
|
<template #actions>
|
||||||
|
<n-button :loading="refreshing" @click="refreshVersion">
|
||||||
|
<template #icon><n-icon :component="RefreshOutline" /></template>
|
||||||
|
刷新版本信息
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<div class="stats-row">
|
||||||
|
<StatCard label="当前版本" :value="`v${version}`" sub="来自 /api/version" tone="brand">
|
||||||
|
<template #icon><n-icon :component="CloudOutline" /></template>
|
||||||
|
</StatCard>
|
||||||
|
<StatCard label="源项目" :value="GITHUB_REPO" sub="上游开源项目地址" tone="blue" value-small>
|
||||||
|
<template #icon><n-icon :component="LogoGithub" /></template>
|
||||||
|
</StatCard>
|
||||||
|
<StatCard label="开源协议" value="GPL-3.0" sub="GNU General Public License" tone="green">
|
||||||
|
<template #icon><n-icon :component="DocumentTextOutline" /></template>
|
||||||
|
</StatCard>
|
||||||
|
<StatCard label="运行状态" value="正常" sub="Havline 服务运行中" tone="teal">
|
||||||
|
<template #icon><n-icon :component="CheckmarkCircleOutline" /></template>
|
||||||
|
</StatCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HavlineCard title="项目信息" subtitle="当前版本、项目定位和最近检查时间">
|
||||||
|
<div class="about-grid">
|
||||||
|
<div class="about-item">
|
||||||
|
<label>项目名称</label>
|
||||||
|
<strong>Havline</strong>
|
||||||
|
</div>
|
||||||
|
<div class="about-item">
|
||||||
|
<label>项目定位</label>
|
||||||
|
<span>面向 NAS 的轻量公网访问管理工具</span>
|
||||||
|
</div>
|
||||||
|
<div class="about-item">
|
||||||
|
<label>当前版本</label>
|
||||||
|
<span class="mono">v{{ version }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="about-item">
|
||||||
|
<label>最近检查</label>
|
||||||
|
<span>{{ checkedAt || '刚刚' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="about-links">
|
||||||
|
<a :href="GITHUB_REPO_URL" target="_blank" rel="noopener noreferrer">
|
||||||
|
<n-icon :component="LogoGithub" />
|
||||||
|
源项目:{{ GITHUB_REPO }}
|
||||||
|
</a>
|
||||||
|
<a :href="GPL_URL" target="_blank" rel="noopener noreferrer">
|
||||||
|
<n-icon :component="DocumentTextOutline" />
|
||||||
|
查看 GPL-3.0 协议
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</HavlineCard>
|
||||||
|
|
||||||
|
<HavlineCard title="源项目与开源说明" subtitle="Havline 的源项目地址保持不变">
|
||||||
|
<div class="about-copy">
|
||||||
|
<p>
|
||||||
|
Havline 的源开源项目为
|
||||||
|
<a :href="GITHUB_REPO_URL" target="_blank" rel="noopener noreferrer">{{ GITHUB_REPO }}</a>。
|
||||||
|
该地址用于查阅上游代码、提交记录和 Release 信息。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
本项目继续采用
|
||||||
|
<a :href="GPL_URL" target="_blank" rel="noopener noreferrer">GPL-3.0</a>
|
||||||
|
开源。品牌名称调整不会改变原项目及其衍生版本的许可证义务。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</HavlineCard>
|
||||||
|
|
||||||
|
<HavlineCard title="技术栈" subtitle="Havline 当前使用的主要技术组件">
|
||||||
|
<div class="about-stack">
|
||||||
|
<span v-for="item in stack" :key="item">{{ item }}</span>
|
||||||
|
</div>
|
||||||
|
</HavlineCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { NButton, NIcon, useMessage } from 'naive-ui'
|
||||||
|
import {
|
||||||
|
CheckmarkCircleOutline,
|
||||||
|
CloudOutline,
|
||||||
|
DocumentTextOutline,
|
||||||
|
LogoGithub,
|
||||||
|
RefreshOutline,
|
||||||
|
} from '@vicons/ionicons5'
|
||||||
|
import { api } from '../api/client'
|
||||||
|
import HavlineCard from '../components/HavlineCard.vue'
|
||||||
|
import PageHeader from '../components/PageHeader.vue'
|
||||||
|
import StatCard from '../components/StatCard.vue'
|
||||||
|
import { GITHUB_REPO, GITHUB_REPO_URL } from '../constants/app'
|
||||||
|
import { formatDate } from '../utils/format'
|
||||||
|
|
||||||
|
const GPL_URL = 'https://www.gnu.org/licenses/gpl-3.0.html'
|
||||||
|
const message = useMessage()
|
||||||
|
const version = ref(__APP_VERSION__)
|
||||||
|
const checkedAt = ref('')
|
||||||
|
const refreshing = ref(false)
|
||||||
|
const stack = [
|
||||||
|
'Go 1.23',
|
||||||
|
'Vue 3',
|
||||||
|
'TypeScript',
|
||||||
|
'Vite',
|
||||||
|
'Naive UI',
|
||||||
|
'ECharts',
|
||||||
|
'SQLite',
|
||||||
|
'Nginx',
|
||||||
|
'FRP',
|
||||||
|
'Cloudflare Tunnel',
|
||||||
|
]
|
||||||
|
|
||||||
|
async function refreshVersion() {
|
||||||
|
refreshing.value = true
|
||||||
|
try {
|
||||||
|
const result = await api.getVersion()
|
||||||
|
version.value = result.version || version.value
|
||||||
|
checkedAt.value = formatDate(new Date().toISOString())
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error instanceof Error ? error.message : '读取版本失败')
|
||||||
|
} finally {
|
||||||
|
refreshing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void refreshVersion()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.about-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: var(--havline-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-item {
|
||||||
|
min-width: 0;
|
||||||
|
padding: var(--havline-space-4);
|
||||||
|
border: 1px solid var(--havline-border);
|
||||||
|
border-radius: var(--havline-radius-sm);
|
||||||
|
background: var(--havline-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-item label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--havline-text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-item strong,
|
||||||
|
.about-item span {
|
||||||
|
color: var(--havline-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-links,
|
||||||
|
.about-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--havline-space-3);
|
||||||
|
margin-top: var(--havline-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--havline-brand-text);
|
||||||
|
border: 1px solid var(--havline-border);
|
||||||
|
border-radius: var(--havline-radius-sm);
|
||||||
|
background: var(--havline-surface);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-copy {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--havline-space-3);
|
||||||
|
color: var(--havline-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-copy p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-copy a {
|
||||||
|
color: var(--havline-brand-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-stack span {
|
||||||
|
padding: 6px 10px;
|
||||||
|
color: var(--havline-text-secondary);
|
||||||
|
border: 1px solid var(--havline-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--havline-bg);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
<template>
|
|
||||||
<PageHeader title="示例页面" description="用于验证 Web 一键升级是否成功生效">
|
|
||||||
<template #actions>
|
|
||||||
<n-button :loading="refreshing" @click="refreshVersion">
|
|
||||||
<template #icon><n-icon :component="RefreshOutline" /></template>
|
|
||||||
刷新版本信息
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<n-alert type="success" :bordered="false" class="example-alert" title="v1.9.0 示例页面验证标识已生效">
|
|
||||||
当前页面由 v{{ version }} 提供,验证标识为 <span class="mono">{{ verificationMarker }}</span>。
|
|
||||||
如果你是在升级后首次看到这行内容,说明前端构建、镜像重建和服务重启均已成功。
|
|
||||||
</n-alert>
|
|
||||||
|
|
||||||
<div class="example-proof">
|
|
||||||
<div>
|
|
||||||
<span class="example-proof__label">本次升级验证</span>
|
|
||||||
<strong class="example-proof__value">{{ verificationRound }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="example-proof__hint">
|
|
||||||
看到这个新区块,说明前端资源已经从旧版本切换到 v1.9.0。
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stats-row">
|
|
||||||
<StatCard label="当前版本" :value="`v${version}`" sub="来自 /api/version" tone="brand">
|
|
||||||
<template #icon><n-icon :component="CloudOutline" /></template>
|
|
||||||
</StatCard>
|
|
||||||
<StatCard label="升级验证" value="通过" sub="示例路由与菜单已加载" tone="green">
|
|
||||||
<template #icon><n-icon :component="CheckmarkCircleOutline" /></template>
|
|
||||||
</StatCard>
|
|
||||||
<StatCard label="本次验证标识" :value="verificationMarker" sub="用于区分升级前后的前端资源" tone="blue" value-small>
|
|
||||||
<template #icon><n-icon :component="FlashOutline" /></template>
|
|
||||||
</StatCard>
|
|
||||||
<StatCard label="页面状态" value="已更新" sub="v1.9.0 前端资源已加载" tone="teal">
|
|
||||||
<template #icon><n-icon :component="CheckmarkCircleOutline" /></template>
|
|
||||||
</StatCard>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<HavlineCard title="本次示例页变化" subtitle="用于确认升级后的页面内容确实发生变化">
|
|
||||||
<ul class="example-changes">
|
|
||||||
<li>页面版本提示更新为 v1.9.0。</li>
|
|
||||||
<li>「本次升级验证」区块和页面状态同步更新为 v1.9.0。</li>
|
|
||||||
<li>验证标识更新为 <span class="mono">v1.9.0-example-page</span>。</li>
|
|
||||||
</ul>
|
|
||||||
</HavlineCard>
|
|
||||||
|
|
||||||
<HavlineCard title="验证步骤" subtitle="用这个页面确认线上实例已经更新到目标版本">
|
|
||||||
<ol class="example-steps">
|
|
||||||
<li>在旧版本页面点击侧栏的「一键升级」。</li>
|
|
||||||
<li>等待容器重建完成,页面自动刷新或恢复访问。</li>
|
|
||||||
<li>打开侧栏「示例页面」,确认本页可访问。</li>
|
|
||||||
<li>确认页面上的版本号为 <span class="mono">v{{ version }}</span>。</li>
|
|
||||||
</ol>
|
|
||||||
</HavlineCard>
|
|
||||||
|
|
||||||
<HavlineCard title="验证结果" subtitle="用于确认当前前端资源与后端版本一致">
|
|
||||||
<div class="example-result">
|
|
||||||
<div><label>前端路由</label><span class="mono">/example</span></div>
|
|
||||||
<div><label>侧栏菜单</label><span>已加载「示例页面」</span></div>
|
|
||||||
<div><label>后端版本</label><span class="mono">{{ version }}</span></div>
|
|
||||||
<div><label>验证标识</label><span class="mono">{{ verificationMarker }}</span></div>
|
|
||||||
<div><label>验证状态</label><StatusBadge kind="success" text="通过" /></div>
|
|
||||||
<div><label>最近检查</label><span>{{ checkedAt || '刚刚' }}</span></div>
|
|
||||||
</div>
|
|
||||||
</HavlineCard>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { NAlert, NButton, NIcon, useMessage } from 'naive-ui'
|
|
||||||
import {
|
|
||||||
CheckmarkCircleOutline,
|
|
||||||
CloudOutline,
|
|
||||||
FlashOutline,
|
|
||||||
RefreshOutline,
|
|
||||||
} from '@vicons/ionicons5'
|
|
||||||
import { api } from '../api/client'
|
|
||||||
import HavlineCard from '../components/HavlineCard.vue'
|
|
||||||
import PageHeader from '../components/PageHeader.vue'
|
|
||||||
import StatCard from '../components/StatCard.vue'
|
|
||||||
import StatusBadge from '../components/StatusBadge.vue'
|
|
||||||
import { formatDate } from '../utils/format'
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
const version = ref(__APP_VERSION__)
|
|
||||||
const checkedAt = ref('')
|
|
||||||
const refreshing = ref(false)
|
|
||||||
const verificationRound = 'v1.9.0'
|
|
||||||
const verificationMarker = 'v1.9.0-example-page'
|
|
||||||
|
|
||||||
async function refreshVersion() {
|
|
||||||
refreshing.value = true
|
|
||||||
try {
|
|
||||||
const result = await api.getVersion()
|
|
||||||
version.value = result.version || version.value
|
|
||||||
checkedAt.value = formatDate(new Date().toISOString())
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error instanceof Error ? error.message : '读取版本失败')
|
|
||||||
} finally {
|
|
||||||
refreshing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
void refreshVersion()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.example-alert {
|
|
||||||
margin-bottom: var(--havline-space-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-proof {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--havline-space-5);
|
|
||||||
margin-bottom: var(--havline-space-5);
|
|
||||||
padding: var(--havline-space-4) var(--havline-space-5);
|
|
||||||
border: 1px solid var(--havline-brand-soft);
|
|
||||||
border-radius: var(--havline-radius);
|
|
||||||
background: var(--havline-brand-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-proof__label {
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--havline-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-proof__value {
|
|
||||||
display: block;
|
|
||||||
margin-top: 2px;
|
|
||||||
font-size: 28px;
|
|
||||||
line-height: 1.2;
|
|
||||||
color: var(--havline-brand-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-proof__hint {
|
|
||||||
max-width: 360px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: var(--havline-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
||||||
gap: var(--havline-space-4);
|
|
||||||
margin-bottom: var(--havline-space-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-steps {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 22px;
|
|
||||||
color: var(--havline-text-secondary);
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-changes {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 22px;
|
|
||||||
color: var(--havline-text-secondary);
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-result {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: var(--havline-space-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-result > div {
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1px solid var(--havline-border);
|
|
||||||
border-radius: var(--havline-radius-sm);
|
|
||||||
background: var(--havline-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-result label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--havline-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.example-proof {
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-result {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user