13 Commits

Author SHA1 Message Date
a3d2398f83 Merge branch 'dev' 2026-05-09 11:53:23 +08:00
80291c6a6a refactor: 优化图片迁移脚本
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-09 11:53:07 +08:00
1ff3d28ff7 Merge branch 'dev' 2026-05-09 10:16:50 +08:00
3d7ed2c16b 跳轉调整
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 5s
2026-05-08 09:30:38 +08:00
ade2c6bea8 添加懒加载
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-06 15:37:45 +08:00
2f4b0f8b76 mobile - topic ssd
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-06 14:39:04 +08:00
9fcdea0061 移动端闪存
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-06 13:37:29 +08:00
d7d8e2aa77 外边距设置
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 3s
2026-05-06 11:50:06 +08:00
cfe07c9df5 换背景色
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-06 11:48:51 +08:00
5eb49defe1 admin - system 内页
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 3s
2026-05-06 11:39:11 +08:00
00e8eed191 topic ssd data
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 5s
2026-05-06 11:34:57 +08:00
aeec3e4f0d 闪存
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 3s
2026-05-06 10:50:48 +08:00
251a13c368 feat: topic ssd
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
2026-05-06 10:28:46 +08:00
8 changed files with 379 additions and 68 deletions

View File

@@ -366,6 +366,18 @@ class System
'url' => (string)url('/index/topic/laptop/index')
],
]
],
[
'id' => 9,
'name' => '闪存SSD专题',
'url' => '',
'children' => [
[
'id' => 101,
'name' => '首页',
'url' => (string)url('/index/topic/ssd/index')
],
]
]
];
}

View File

@@ -0,0 +1,52 @@
<?php
declare (strict_types = 1);
namespace app\index\controller;
use app\index\model\SysBannerModel;
use think\facade\View;
/**
* 专题 - SSD
*/
class TopicSsd extends Common
{
/**
* 专题 - SSD首页
*/
public function index()
{
$banners = SysBannerModel::with([
'items' => function ($query) {
$query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at'])
->order(['sort' => 'asc', 'id' => 'desc'])
->enabled(true);
}
])
->atPlatform(request()->from)
->uniqueLabel([
'BANNER_69faaf8582967', // 专题 - 闪存SSD首页 - 焦点图
'BANNER_69fab1bed8f71', // 专题 - 闪存SSD首页 - 产品
])
->language($this->lang_id)
->enabled(true)
->order(['sort' => 'asc', 'id' => 'desc'])
->select();
$data = [];
if (!$banners->isEmpty()) {
$banners_map = [];
foreach ($banners as $banner) {
$banners_map[$banner->unique_label] = $banner;
}
// 焦点图轮播图
$data['top_focus_images'] = data_get($banners_map, 'BANNER_69faaf8582967')?->items->toArray();
// 产品
$data['products'] = data_get($banners_map, 'BANNER_69fab1bed8f71')?->items->toArray();
}
View::assign('data', $data);
return View::fetch('index');
}
}

View File

@@ -119,6 +119,12 @@ Route::group('topic', function () {
// 专题 - 笔记本电脑首页
Route::get('index', 'TopicLaptop/index');
});
// 专题 - SSD
Route::group('ssd', function() {
// 专题 - SSD首页
Route::get('index', 'TopicSsd/index');
});
});
// 数据迁移

View File

@@ -0,0 +1,43 @@
{extend name="public/base" /}
{block name="style"}
<link rel="stylesheet" href="__CSS__/topic_laptop/header.css">
<link rel="stylesheet" href="__CSS__/topic_laptop/footer.css">
<link rel="stylesheet" href="__CSS__/topic_ssd/index.css">
<!-- 将rem适配JS移到这里确保优先执行 -->
<script type="text/javascript">
(function (doc, win)
{
var docEl = doc.documentElement;
var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
function setRootFontSize ()
{
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
var fontSize = clientWidth / 7.5;
// 直接修改内联样式,优先级最高
docEl.setAttribute('style', 'font-size: ' + fontSize + 'px !important;');
}
setRootFontSize();
win.addEventListener(resizeEvt, setRootFontSize);
doc.addEventListener('DOMContentLoaded', setRootFontSize);
})(document, window);
</script>
{/block}
{block name="main"}
<div class="m-sc-main" style="margin-top:42px;">
{volist name="data.top_focus_images" id="item"}
<a href="{$item.link}" class="m-sc-mt20">
<img src="{$item.image}" alt="" class="m-sc-main-img" loading="lazy">
</a>
{/volist}
<div class="m-sc-main-imgs m-sc-mt20 m-sc-mb34">
{volist name="data.products" id="item"}
<a href="{$item.link}" class="">
<img src="{$item.image}" alt="" class="m-sc-main-img1" loading="lazy">
</a>
{/volist}
</div>
</div>
{/block}

View File

@@ -0,0 +1,44 @@
{extend name="public/base" /}
{block name="style"}
<link rel="stylesheet" href="__CSS__/topic_laptop/header.css">
<link rel="stylesheet" href="__CSS__/topic_laptop/footer.css">
<link rel="stylesheet" href="__CSS__/topic_ssd/index.css">
<script type="text/javascript">
(function (doc, win)
{
var docEl = doc.documentElement;
var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
var designWidth = 2560;
var designRemPx = 100;
function setRootFontSize ()
{
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
var fontSize = (clientWidth / designWidth) * designRemPx;
fontSize = Math.max(fontSize, 20);
fontSize = Math.min(fontSize, designRemPx);
docEl.style.fontSize = fontSize + 'px';
}
setRootFontSize();
win.addEventListener(resizeEvt, setRootFontSize);
doc.addEventListener('DOMContentLoaded', setRootFontSize);
})(document, window);
</script>
{/block}
{block name="main"}
<div class="sc-main">
{volist name="data.top_focus_images" id="item"}
<a href="{$item.link}" class="sc-mt20">
<img src="{$item.image}" alt="" class="sc-main-img" loading="lazy">
</a>
{/volist}
<div class="sc-main-imgs sc-mt20 sc-mb-78">
{volist name="data.products" id="item"}
<a href="{$item.link}" class="">
<img src="{$item.image}" alt="" class="sc-main-img1" loading="lazy">
</a>
{/volist}
</div>
</div>
{/block}

View File

@@ -0,0 +1,65 @@
* {
margin: 0;
padding: 0;
}
html {
width: 100% !important;
overflow-x: hidden;
margin: 0 !important;
padding: 0 !important;
max-width: 100vw !important;
}
body {
width: 100%;
background: #fff;
overflow-x: hidden;
margin: 0 !important;
padding: 0 !important;
max-width: 100vw !important;
}
.m-sc-main {
width: 100%;
height: 100%;
}
a {
text-decoration: none;
display: block;
}
img {
display: block;
}
.m-sc-main-img {
width: 100%;
/* max-width: 2560px; */
}
.m-sc-mt20 {
margin-top: 0.1rem;
}
.m-sc-mt20:first-child {
margin-top: 0 !important;
}
.m-sc-mb34 {
margin-bottom: 0.34rem;
}
.m-sc-main-imgs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.1rem;
list-style: none;
/* 去除列表样式(如果有) */
}
.m-sc-main-imgs a {
display: block;
}
.m-sc-main-img1 {
width: 100%;
height: auto;
/* 保持比例 */
display: block;
}

View File

@@ -0,0 +1,68 @@
html {
width: 100%;
}
body {
/* width: 100vw;
height: 100vh; */
background: #fff;
overflow-x: hidden;
scroll-behavior: smooth !important;
-webkit-overflow-scrolling: touch !important;
}
/* 当视口宽度大于1920px时生效 */
@media screen and (min-width: 1920px) {
/* 这里写你的样式 */
body {
max-width:100% !important;
width: 100vw !important;
}
}
.sc-main {
width: 100%;
height: 100%;
max-width: 2560px;
margin: 0 auto;
}
a {
text-decoration: none;
display: block;
}
img {
display: block;
}
.sc-main-img {
width: 100%;
max-width: 2560px;
}
.sc-mt20:first-child {
margin-top: 0 !important;
}
.sc-mt20 {
margin-top: 0.2rem;
}
.sc-mb-78 {
margin-bottom: 0.78rem;
}
.sc-main-imgs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.2rem;
list-style: none;
/* 去除列表样式(如果有) */
}
.sc-main-imgs a {
display: block;
}
.sc-main-img1 {
width: 100%;
height: auto;
/* 保持比例 */
display: block;
}

View File

@@ -147,46 +147,6 @@ class ImageMigrator:
# 确保paramiko已导入
ensure_paramiko()
# 连接目标服务器必须为SSH
if self.target_config.is_local():
print("错误: 目标服务器必须为SSH服务器不能是本地目录")
return False
self.target_client = paramiko.SSHClient()
self.target_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if self.verbose:
print(
f"连接到目标服务器: {self.target_config.host}:{self.target_config.port}"
)
if self.target_config.key_file:
key = paramiko.RSAKey.from_private_key_file(self.target_config.key_file)
self.target_client.connect(
hostname=self.target_config.host,
port=self.target_config.port,
username=self.target_config.username,
pkey=key,
)
else:
self.target_client.connect(
hostname=self.target_config.host,
port=self.target_config.port,
username=self.target_config.username,
password=self.target_config.password,
)
self.target_sftp = self.target_client.open_sftp()
# 检查SFTP服务器的工作目录
if self.verbose:
try:
cwd = self.target_sftp.getcwd()
print(f"DEBUG: 目标SFTP服务器当前工作目录: {cwd}")
except Exception as e:
print(f"DEBUG: 无法获取目标SFTP服务器工作目录: {e}")
# 连接源服务器如果是SSH类型
if not self.source_config.is_local():
self.source_client = paramiko.SSHClient()
@@ -218,11 +178,61 @@ class ImageMigrator:
self.source_sftp = self.source_client.open_sftp()
# 检查SFTP服务器的工作目录
if self.verbose:
try:
cwd = self.source_sftp.getcwd()
print(f"DEBUG: 源SFTP服务器当前工作目录: {cwd}")
except Exception as e:
print(f"DEBUG: 无法获取源SFTP服务器工作目录: {e}")
# 连接目标服务器如果为SSH
if not self.target_config.is_local():
self.target_client = paramiko.SSHClient()
self.target_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if self.verbose:
print(
f"连接到目标服务器: {self.target_config.host}:{self.target_config.port}"
)
if self.target_config.key_file:
key = paramiko.RSAKey.from_private_key_file(
self.target_config.key_file
)
self.target_client.connect(
hostname=self.target_config.host,
port=self.target_config.port,
username=self.target_config.username,
pkey=key,
)
else:
self.target_client.connect(
hostname=self.target_config.host,
port=self.target_config.port,
username=self.target_config.username,
password=self.target_config.password,
)
self.target_sftp = self.target_client.open_sftp()
# 检查SFTP服务器的工作目录
if self.verbose:
try:
cwd = self.target_sftp.getcwd()
print(f"DEBUG: 目标SFTP服务器当前工作目录: {cwd}")
except Exception as e:
print(f"DEBUG: 无法获取目标SFTP服务器工作目录: {e}")
if self.verbose:
source_type = (
"本地目录" if self.source_config.is_local() else "SSH服务器"
)
print(f"连接成功! 源: {source_type}, 目标: SSH服务器")
target_type = (
"本地目录" if self.target_config.is_local() else "SSH服务器"
)
print(f"连接成功! 源: {source_type}, 目标: {target_type}")
return True
except Exception as e:
@@ -718,7 +728,7 @@ def read_image_paths(image_list_file: str) -> List[str]:
def main():
parser = argparse.ArgumentParser(
description="本地目录或SSH服务器迁移图片到远程SSH服务器保持相对路径结构",
description="本地目录之间迁移图片,本地目录或SSH服务器迁移图片到远程SSH服务器保持相对路径结构",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
@@ -756,8 +766,7 @@ def main():
parser.add_argument(
"--source-type",
choices=["local", "ssh"],
default="local",
help="源服务器类型: local(本地目录) 或 ssh(SSH服务器),默认: local",
help="源服务器类型: local(本地目录) 或 ssh(SSH服务器)",
)
parser.add_argument("--source-host", help="源服务器地址仅SSH类型需要")
parser.add_argument(
@@ -772,6 +781,11 @@ def main():
parser.add_argument("--source-dir", help="源服务器图片基础目录")
# 目标服务器选项
parser.add_argument(
"--target-type",
choices=["local", "ssh"],
help="目标服务器类型: local(本地目录) 或 ssh(SSH服务器)",
)
parser.add_argument("--target-host", help="目标服务器地址")
parser.add_argument(
"--target-port", type=int, default=22, help="目标服务器端口(默认: 22"
@@ -859,23 +873,38 @@ def main():
# 构建目标服务器配置
target_config_data = config.get("target", {})
target_config_data["type"] = "ssh" # 目标必须是SSH
ssh_config = target_config_data.get("ssh", {})
# 命令行参数覆盖配置文件
if args.target_host:
ssh_config["host"] = args.target_host
if args.target_port:
ssh_config["port"] = args.target_port
if args.target_user:
ssh_config["username"] = args.target_user
if args.target_password:
ssh_config["password"] = args.target_password
if args.target_key:
ssh_config["key_file"] = args.target_key
if args.target_type:
target_config_data["type"] = args.target_type
target_config_data["ssh"] = ssh_config
# 命令行参数覆盖配置文件
if args.target_type == "ssh" or target_config_data.get("type") == "ssh":
ssh_config = target_config_data.get("ssh", {})
if args.target_host:
ssh_config["host"] = args.target_host
if args.target_port:
ssh_config["port"] = args.target_port
if args.target_user:
ssh_config["username"] = args.target_user
if args.target_password:
ssh_config["password"] = args.target_password
if args.target_key:
ssh_config["key_file"] = args.target_key
target_config_data["ssh"] = ssh_config
# 检查必要的目标SSH参数
if not ssh_config.get("host"):
print("错误: 必须指定目标服务器地址 (--target-host)")
sys.exit(1)
if not ssh_config.get("username"):
print("错误: 必须指定目标服务器用户名 (--target-user)")
sys.exit(1)
else:
# 本地类型不需要SSH配置
target_config_data.pop("ssh", None)
# 设置基础目录
if args.target_dir:
@@ -884,14 +913,6 @@ def main():
print("错误: 必须指定目标服务器图片基础目录 (--target-dir)")
sys.exit(1)
# 检查必要的目标SSH参数
if not ssh_config.get("host"):
print("错误: 必须指定目标服务器地址 (--target-host)")
sys.exit(1)
if not ssh_config.get("username"):
print("错误: 必须指定目标服务器用户名 (--target-user)")
sys.exit(1)
# 创建服务器配置对象
try:
source_config = ServerConfig.from_dict(source_config_data)