diff --git a/dashboard/DEPLOY.md b/dashboard/DEPLOY.md index 2525fc7..1598ad0 100644 --- a/dashboard/DEPLOY.md +++ b/dashboard/DEPLOY.md @@ -64,15 +64,15 @@ pnpm build ```bash # 上传前端 dist、nginx.conf、后端代码、docker-compose.yml -./scripts/upload.sh all +../deploy/upload.sh all # 或分别上传 -./scripts/upload.sh frontend -./scripts/upload.sh backend -./scripts/upload.sh compose +../deploy/upload.sh frontend +../deploy/upload.sh backend +../deploy/upload.sh compose ``` -脚本默认上传到 `ubuntu@<服务器>:/data/project/dashboard/`。可在 `scripts/upload.sh` 中修改 `SERVER`、`REMOTE_ROOT`、`SSH_KEY`。 +脚本默认上传到 `ubuntu@<服务器>:/data/project/dashboard/`。可在仓库根目录 `deploy/upload.sh` 中修改 `SERVER`、`REMOTE_ROOT`、`SSH_KEY`(www 产物用同一脚本的 `www` 目标上传)。 **方式 B:手动 rsync** @@ -126,7 +126,7 @@ curl http://localhost:8083/health ```bash # 本地 cd frontend && pnpm build -./scripts/upload.sh frontend +../deploy/upload.sh frontend # 服务器 docker compose restart frontend @@ -136,7 +136,7 @@ docker compose restart frontend ```bash # 本地 -./scripts/upload.sh backend +../deploy/upload.sh backend # 服务器 docker compose build --no-cache backend @@ -146,7 +146,7 @@ docker compose up -d backend ### 一键:构建前端 + 上传 + 服务器部署 ```bash -pnpm --dir frontend build && ./scripts/upload.sh all +pnpm --dir frontend build && ../deploy/upload.sh all # 服务器上执行 cd /data/project/dashboard && docker compose build --no-cache backend && docker compose up -d @@ -165,7 +165,7 @@ cd /data/project/dashboard && docker compose build --no-cache backend && docker ```bash # 修改 compose 后 -./scripts/upload.sh compose +../deploy/upload.sh compose docker compose up -d ``` diff --git a/dashboard/backend/src/routes/www/sections.ts b/dashboard/backend/src/routes/www/sections.ts index fb708cc..c51331c 100644 --- a/dashboard/backend/src/routes/www/sections.ts +++ b/dashboard/backend/src/routes/www/sections.ts @@ -96,8 +96,6 @@ router.post('/api/www/sections', async (req: Request, res: Response) => { bodyColor: b.body_color || '', bgType: b.bg_type || 'color', bgValue: b.bg_value || '', - overlayEnabled: b.overlay_enabled ? 1 : 0, - overlayOpacity: b.overlay_opacity ?? '0.00', overlineZh: b.overline_zh || '', overlineEn: b.overline_en || '', titleZh: b.title_zh || '', @@ -148,8 +146,6 @@ router.put('/api/www/sections/:id', async (req: Request, res: Response) => { bodyColor: b.body_color ?? existing.bodyColor, bgType: b.bg_type ?? existing.bgType, bgValue: b.bg_value ?? existing.bgValue, - overlayEnabled: b.overlay_enabled !== undefined ? (b.overlay_enabled ? 1 : 0) : existing.overlayEnabled, - overlayOpacity: b.overlay_opacity ?? existing.overlayOpacity, overlineZh: b.overline_zh ?? existing.overlineZh, overlineEn: b.overline_en ?? existing.overlineEn, titleZh: b.title_zh ?? existing.titleZh, diff --git a/dashboard/backend/src/schemas/www/sections.ts b/dashboard/backend/src/schemas/www/sections.ts index 9ca35c6..a78e87c 100644 --- a/dashboard/backend/src/schemas/www/sections.ts +++ b/dashboard/backend/src/schemas/www/sections.ts @@ -1,4 +1,4 @@ -import { mysqlTable, bigint, varchar, tinyint, int, text, decimal, json, datetime } from 'drizzle-orm/mysql-core'; +import { mysqlTable, bigint, varchar, tinyint, int, text, json, datetime } from 'drizzle-orm/mysql-core'; import { sql } from 'drizzle-orm'; export const wwwSectionBlocks = mysqlTable('www_section_blocks', { @@ -13,8 +13,6 @@ export const wwwSectionBlocks = mysqlTable('www_section_blocks', { bodyColor: varchar('body_color', { length: 20 }).notNull().default(''), bgType: varchar('bg_type', { length: 10 }).notNull().default('color'), bgValue: varchar('bg_value', { length: 500 }).notNull().default(''), - overlayEnabled: tinyint('overlay_enabled').notNull().default(0), - overlayOpacity: decimal('overlay_opacity', { precision: 3, scale: 2 }).notNull().default('0.00'), overlineZh: varchar('overline_zh', { length: 100 }).notNull().default(''), overlineEn: varchar('overline_en', { length: 100 }).notNull().default(''), titleZh: varchar('title_zh', { length: 200 }).notNull().default(''), diff --git a/dashboard/frontend/src/typings/api/www.d.ts b/dashboard/frontend/src/typings/api/www.d.ts index 5cd5935..233968b 100644 --- a/dashboard/frontend/src/typings/api/www.d.ts +++ b/dashboard/frontend/src/typings/api/www.d.ts @@ -83,8 +83,6 @@ declare namespace Api { body_color: string; bg_type: string; bg_value: string; - overlay_enabled: boolean; - overlay_opacity: number; overline_zh: string; overline_en: string; title_zh: string; @@ -119,8 +117,6 @@ declare namespace Api { body_color?: string; bg_type?: string; bg_value?: string; - overlay_enabled?: boolean; - overlay_opacity?: number; overline_zh?: string; overline_en?: string; title_zh?: string; diff --git a/dashboard/frontend/src/views/www/components/GradientInput.vue b/dashboard/frontend/src/views/www/components/GradientInput.vue new file mode 100644 index 0000000..74011ad --- /dev/null +++ b/dashboard/frontend/src/views/www/components/GradientInput.vue @@ -0,0 +1,175 @@ + + + diff --git a/dashboard/frontend/src/views/www/components/SectionManager.vue b/dashboard/frontend/src/views/www/components/SectionManager.vue index b0a288e..3232186 100644 --- a/dashboard/frontend/src/views/www/components/SectionManager.vue +++ b/dashboard/frontend/src/views/www/components/SectionManager.vue @@ -2,6 +2,7 @@ import { computed, h, onMounted, reactive, ref } from 'vue'; import { NButton, NSpace, NSwitch, NTag, type DataTableColumns, type FormInst, type SelectOption, type SelectRenderOption } from 'naive-ui'; import ColorInput from '../page/home/components/ColorInput.vue'; +import GradientInput from './GradientInput.vue'; import MediaSelectInput from './MediaSelectInput.vue'; import RichTextEditor from './RichTextEditor.vue'; import SpecManager from './SpecManager.vue'; @@ -89,8 +90,6 @@ const formData = reactive({ body_color: '', bg_type: 'color', bg_value: '', - overlay_enabled: false, - overlay_opacity: 0, overline_zh: '', overline_en: '', title_zh: '', @@ -349,8 +348,6 @@ function handleAdd() { formData.body_color = ''; formData.bg_type = 'color'; formData.bg_value = ''; - formData.overlay_enabled = false; - formData.overlay_opacity = 0; formData.overline_zh = ''; formData.overline_en = ''; formData.title_zh = ''; @@ -382,8 +379,6 @@ function fillFormFromRow(row: Api.Www.SectionBlock) { formData.body_color = row.body_color || ''; formData.bg_type = row.bg_type; formData.bg_value = row.bg_value; - formData.overlay_enabled = Boolean(row.overlay_enabled); - formData.overlay_opacity = Number(row.overlay_opacity); formData.overline_zh = row.overline_zh; formData.overline_en = row.overline_en; formData.title_zh = row.title_zh; @@ -519,146 +514,170 @@ onMounted(loadData); > 基础配置 - - - - - - {{ opt.label }} - - - -
- - - - 占视口宽度百分比,居中显示;100 = 全宽。移动端(小屏)始终全宽 -
-
- -
- - 区块内容容器的最大宽度,居中于区块内;全宽 = 内容占满整个区块 -
-
- -
- - - - 区块四角圆角大小;0 = 直角(默认)。建议与「区块宽度」小于 100 搭配使用,全宽时圆角不可见 -
-
- -
- - - - 内容区与区块上下边缘的间距。留空 = 布局默认(多数 96px / 全幅横幅 120px / 主视觉分屏 64px);0 = 完全贴边 -
-
- -
- - - - 区块与上下相邻区块之间的间隙,露出页面背景色;适合「区块宽度 < 100 + 圆角」的卡片式区块。留空/0 = 紧贴(默认) -
-
- -
- - - - 桌面端标题字号;移动端自动按约 62.5% 缩小(如填 48 → 移动端 30px)。留空 = 兜底 36px;引言布局无标题字段不受影响 -
-
- -
- - - - +
+ + + + + + {{ opt.label }} + + +
+
+ + + + 占视口宽度百分比,居中显示;100 = 全宽。移动端(小屏)始终全宽 + + + + + + 区块内容容器的最大宽度,居中于区块内;全宽 = 内容占满整个区块 + + + + + + 区块四角圆角大小;0 = 直角(默认)。建议与「区块宽度」小于 100 搭配使用,全宽时圆角不可见 + + +
+
+ + + + 内容区与区块上下边缘的间距。留空 = 布局默认(多数 96px / 全幅横幅 120px / 主视觉分屏 64px);0 = 完全贴边 + + + + + + 区块与上下相邻区块之间的间隙,露出页面背景色;适合「区块宽度 < 100 + 圆角」的卡片式区块。留空/0 = 紧贴(默认) + + + + + - -
- - 文案颜色(留空=跟随主题默认色) - - - - - - - - - - - - 纯色 - 图片 - 渐变 - 视频 - - - - - + + +
+
+ + + 纯色 + 渐变 + 图片 + 视频 + + + + + + + +
+ 文案样式 +
+ + + + 桌面端标题字号;移动端自动按约 62.5% 缩小(如填 48 → 移动端 30px)。留空 = 兜底 36px;引言布局无标题字段不受影响 + + + + + + + + + + + +
{{ opt.label }} @@ -704,14 +723,6 @@ onMounted(loadData); 固定版式;切换版式会同步下方槽位数量(已填素材按顺序保留) - - - - 透明度 - - {{ formData.overlay_opacity.toFixed(2) }} - - 文案内容 diff --git a/dashboard/frontend/src/views/www/page/home/components/ColorInput.vue b/dashboard/frontend/src/views/www/page/home/components/ColorInput.vue index 810a7d0..905baec 100644 --- a/dashboard/frontend/src/views/www/page/home/components/ColorInput.vue +++ b/dashboard/frontend/src/views/www/page/home/components/ColorInput.vue @@ -4,7 +4,7 @@ import { NColorPicker, NInput } from 'naive-ui'; defineOptions({ name: 'ColorInput' }); -const props = defineProps<{ value: string }>(); +const props = withDefaults(defineProps<{ value: string; placeholder?: string }>(), { placeholder: '留空则跟随主题默认色' }); const emit = defineEmits<{ (e: 'update:value', value: string): void }>(); // 空值 = 跟随主题默认色 @@ -19,23 +19,24 @@ const showPicker = ref(false); diff --git a/dashboard/scripts/upload.sh b/dashboard/scripts/upload.sh deleted file mode 100755 index 3e44503..0000000 --- a/dashboard/scripts/upload.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SERVER="ubuntu@ec2-3-71-153-45.eu-central-1.compute.amazonaws.com" -REMOTE_ROOT="/data/project/dashboard" -PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" -SSH_KEY="$HOME/.ssh/aws_eafon.pem" -SSH_OPTS=(-i "$SSH_KEY" -o StrictHostKeyChecking=no) - -FRONTEND_PATHS=( - ./frontend/dist/ -) - -BACKEND_PATHS=( - ./backend/dist/ - ./backend/package.json - ./backend/pnpm-lock.yaml - ./backend/pnpm-workspace.yaml -) - -COMPOSE_PATHS=( - ./docker-compose.yml -) - -DEFAULT_PATHS=( - "${FRONTEND_PATHS[@]}" - "${BACKEND_PATHS[@]}" - "${COMPOSE_PATHS[@]}" -) - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - -info() { echo -e "${CYAN}[INFO]${NC} $1"; } -ok() { echo -e "${GREEN}[OK]${NC} $1"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } - -show_help() { - cat < ${REMOTE_ROOT}/frontend/dist/assets - -示例: - $(basename "$0") 同步默认内容到服务器 - $(basename "$0") frontend 仅同步前端 - $(basename "$0") backend compose 同步后端与 docker-compose - $(basename "$0") ./frontend/nginx.conf 单独同步 nginx.conf - $(basename "$0") ./backend/Dockerfile 单独同步 Dockerfile - $(basename "$0") ./frontend/dist/assets 同步指定目录 - $(basename "$0") -n 虚拟执行,预览全部同步 - $(basename "$0") -n frontend 虚拟执行,预览前端同步 -EOF -} - -resolve_preset_paths() { - local preset="$1" - case "$preset" in - frontend|f) printf '%s\n' "${FRONTEND_PATHS[@]}" ;; - backend|b) printf '%s\n' "${BACKEND_PATHS[@]}" ;; - compose|c) printf '%s\n' "${COMPOSE_PATHS[@]}" ;; - all|a) printf '%s\n' "${DEFAULT_PATHS[@]}" ;; - *) - echo "$preset" - ;; - esac -} - -rsync_item() { - local item="$1" - local rel remote_path local_full - - if [[ "$item" == /* ]]; then - if [[ "$item" == "$PROJECT_ROOT"/* ]]; then - rel="${item#"$PROJECT_ROOT"/}" - local_full="$item" - else - warn "路径不在项目内,跳过: $item" - return 1 - fi - else - rel="${item#./}" - rel="${rel%/}" - local_full="${PROJECT_ROOT}/${rel}" - fi - - remote_path="${REMOTE_ROOT}/${rel}" - - if [[ ! -e "$local_full" ]]; then - warn "路径不存在: ${local_full}" - return 1 - fi - - local rsync_opts=(-avz --progress) - if [[ "$DRY_RUN" = true ]]; then - rsync_opts+=(-n) - fi - - if [[ -d "$local_full" ]]; then - if [[ "$DRY_RUN" = true ]]; then - info "预览目录: ${rel}/ -> ${SERVER}:${remote_path}/" - else - info "同步目录: ${rel}/ -> ${SERVER}:${remote_path}/" - fi - rsync "${rsync_opts[@]}" --delete \ - -e "ssh ${SSH_OPTS[*]}" \ - "${local_full}/" "${SERVER}:${remote_path}/" - else - if [[ "$DRY_RUN" = true ]]; then - info "预览文件: ${rel} -> ${SERVER}:${remote_path}" - else - info "同步文件: ${rel} -> ${SERVER}:${remote_path}" - ssh "${SSH_OPTS[@]}" "$SERVER" "mkdir -p $(dirname "$remote_path")" - fi - rsync "${rsync_opts[@]}" \ - -e "ssh ${SSH_OPTS[*]}" \ - "$local_full" "${SERVER}:${remote_path}" - fi -} - -sync_paths() { - local paths=("$@") - local item - - cd "$PROJECT_ROOT" - - for item in "${paths[@]}"; do - rsync_item "$item" - done -} - -DRY_RUN=false -ARGS=() -for arg in "$@"; do - case "$arg" in - -n) - DRY_RUN=true - ;; - -h|--help) - show_help - exit 0 - ;; - *) - ARGS+=("$arg") - ;; - esac -done - -PATHS=() -if [[ ${#ARGS[@]} -eq 0 ]]; then - PATHS=("${DEFAULT_PATHS[@]}") -else - for arg in "${ARGS[@]}"; do - while IFS= read -r path; do - PATHS+=("$path") - done < <(resolve_preset_paths "$arg") - done -fi - -if [[ "$DRY_RUN" = true ]]; then - info "虚拟同步至 ${SERVER}:${REMOTE_ROOT}/" -else - info "同步至 ${SERVER}:${REMOTE_ROOT}/" -fi - -sync_paths "${PATHS[@]}" - -echo "" -ok "完成!" diff --git a/dashboard/scripts/user_active.sql b/dashboard/scripts/user_active.sql deleted file mode 100644 index 673e7a3..0000000 --- a/dashboard/scripts/user_active.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE `user_active` ( - `id` int NOT NULL AUTO_INCREMENT, - `mac_addr` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `active_date` date NOT NULL, - `ip_addr` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE KEY `mac_active_date` (`mac_addr`,`active_date`) USING BTREE, - KEY `idx_active_date` (`active_date`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=127564 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; diff --git a/dashboard/scripts/user_device.sql b/dashboard/scripts/user_device.sql deleted file mode 100644 index 3b1eac8..0000000 --- a/dashboard/scripts/user_device.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE `user_device` ( - `id` int NOT NULL AUTO_INCREMENT, - `mac_addr` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, - `add_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `ver` varchar(10) DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE KEY `mac` (`mac_addr`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2497 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; diff --git a/dashboard/writings/Luxsin CMS 操作手册.docx b/dashboard/writings/Luxsin CMS 操作手册.docx deleted file mode 100644 index 6130408..0000000 Binary files a/dashboard/writings/Luxsin CMS 操作手册.docx and /dev/null differ diff --git a/dashboard/writings/generate_manual.py b/dashboard/writings/generate_manual.py deleted file mode 100644 index 6a92d60..0000000 --- a/dashboard/writings/generate_manual.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python3 -"""生成 Luxsin CMS 软著操作手册 Word 文档""" - -import os -from docx import Document -from docx.shared import Inches, Pt, Cm, RGBColor -from docx.enum.text import WD_ALIGN_PARAGRAPH -from docx.enum.table import WD_TABLE_ALIGNMENT -from docx.oxml.ns import qn, nsdecls -from docx.oxml import parse_xml - -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -SCREENSHOT_DIR = os.path.join(SCRIPT_DIR, "screenshots") -OUTPUT_PATH = os.path.join(SCRIPT_DIR, "Luxsin CMS 操作手册.docx") - -doc = Document() - -# ── 全局样式设置 ────────────────────────────────────── -style = doc.styles["Normal"] -style.font.name = "微软雅黑" -style.font.size = Pt(11) -style.paragraph_format.line_spacing = 1.5 -style.element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") - -# 页面设置 -section = doc.sections[0] -section.page_width = Cm(21) -section.page_height = Cm(29.7) -section.top_margin = Cm(2.54) -section.bottom_margin = Cm(2.54) -section.left_margin = Cm(3.18) -section.right_margin = Cm(3.18) - - -def add_heading(text, level=1): - """添加标题""" - h = doc.add_heading(text, level=level) - for run in h.runs: - run.font.name = "微软雅黑" - run.element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") - return h - - -def add_para(text, bold=False, align=None, font_size=None, space_after=Pt(6)): - """添加段落""" - p = doc.add_paragraph() - if align: - p.alignment = align - run = p.add_run(text) - run.font.name = "微软雅黑" - run.element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") - if bold: - run.bold = True - if font_size: - run.font.size = font_size - p.paragraph_format.space_after = space_after - return p - - -def add_screenshot(filename, width=Inches(5.8)): - """添加截图""" - filepath = os.path.join(SCREENSHOT_DIR, filename) - if os.path.exists(filepath): - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - run = p.add_run() - run.add_picture(filepath, width=width) - return p - else: - add_para(f"[截图缺失: {filename}]", align=WD_ALIGN_PARAGRAPH.CENTER) - return None - - -def add_table(headers, rows): - """添加表格""" - table = doc.add_table(rows=1 + len(rows), cols=len(headers)) - table.style = "Table Grid" - table.alignment = WD_TABLE_ALIGNMENT.CENTER - # Header - for i, h in enumerate(headers): - cell = table.rows[0].cells[i] - cell.text = h - for run in cell.paragraphs[0].runs: - run.bold = True - run.font.name = "微软雅黑" - run.font.size = Pt(10) - shading = parse_xml(f'') - cell._tc.get_or_add_tcPr().append(shading) - # Data - for r_idx, row in enumerate(rows): - for c_idx, val in enumerate(row): - cell = table.rows[r_idx + 1].cells[c_idx] - cell.text = str(val) - for run in cell.paragraphs[0].runs: - run.font.name = "微软雅黑" - run.font.size = Pt(10) - return table - - -# ════════════════════════════════════════════════════════ -# 封面 -# ════════════════════════════════════════════════════════ -for _ in range(6): - doc.add_paragraph() - -add_para("Luxsin CMS", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(32), space_after=Pt(12)) -add_para("音频设备内容管理系统", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(18), space_after=Pt(36)) -add_para("操 作 手 册", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(26), space_after=Pt(48)) - -for _ in range(4): - doc.add_paragraph() - -add_para("软件名称:Luxsin CMS 音频设备内容管理系统", align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(12)) -add_para("版 本 号:V1.0", align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(12)) -add_para("编制日期:2026 年 7 月", align=WD_ALIGN_PARAGRAPH.CENTER, font_size=Pt(12)) - -doc.add_page_break() - -# ════════════════════════════════════════════════════════ -# 目录页 -# ════════════════════════════════════════════════════════ -add_heading("目 录", level=1) -toc_items = [ - "1. 系统概述", - "2. 系统运行环境", - "3. 系统登录", - "4. 首页(仪表盘)", - "5. 耳机管理", - " 5.1 品牌管理", - " 5.2 型号管理", - "6. 升级管理", - " 6.1 OTA 管理", - " 6.2 定向升级", - " 6.3 黑名单", - "7. 分享码", - " 7.1 分享日志", - "8. 工具箱", - " 8.1 Luxsin 控制器", - "9. 系统管理", - " 9.1 账号管理", -] -for item in toc_items: - add_para(item, font_size=Pt(12), space_after=Pt(4)) - -doc.add_page_break() - -# ════════════════════════════════════════════════════════ -# 1. 系统概述 -# ════════════════════════════════════════════════════════ -add_heading("1. 系统概述", level=1) -add_para( - "Luxsin CMS 是一套面向音频设备(耳机)行业的内容管理系统," - "为运营人员提供耳机品牌/型号管理、OTA 固件升级管理、分享码使用日志查询、" - "局域网设备数据同步等功能。系统采用 B/S 架构,用户通过浏览器即可访问全部功能," - "无需安装客户端软件。" -) -add_para( - "系统主要功能模块包括:" -) -modules = [ - ("首页", "展示系统快捷入口、今日新增型号及 OTA 统计信息"), - ("耳机管理", "维护耳机品牌信息与耳机型号数据,支持型号搜索、批量推送至搜索引擎"), - ("升级管理", "管理 OTA 固件升级包版本,支持定向升级(按 MAC 地址推送)及黑名单管控"), - ("分享码", "查询分享码使用日志,可按设备 MAC 和分享码进行检索"), - ("工具箱", "提供 Luxsin 控制器工具,用于局域网设备数据同步与解码"), - ("系统管理", "账号管理功能(仅超级管理员可见),支持用户增删改查"), -] -add_table(["模块名称", "功能说明"], modules) -doc.add_paragraph() - -# ════════════════════════════════════════════════════════ -# 2. 系统运行环境 -# ════════════════════════════════════════════════════════ -add_heading("2. 系统运行环境", level=1) - -add_heading("2.1 服务端环境", level=2) -add_table( - ["项目", "要求"], - [ - ("操作系统", "Linux (Ubuntu 20.04+) / Docker"), - ("运行时", "Node.js 22+"), - ("数据库", "MySQL 5.7+"), - ("缓存", "Redis 6+"), - ("Web 服务器", "Nginx (前端静态资源托管)"), - ("容器编排", "Docker Compose V2"), - ], -) -doc.add_paragraph() - -add_heading("2.2 客户端环境", level=2) -add_table( - ["项目", "要求"], - [ - ("操作系统", "Windows 10+、macOS 12+、Linux"), - ("浏览器", "Chrome 90+、Edge 90+、Firefox 90+、Safari 15+"), - ("分辨率", "建议 1920×1080 及以上"), - ("网络", "可访问系统部署地址即可"), - ], -) -doc.add_paragraph() - -# ════════════════════════════════════════════════════════ -# 3. 系统登录 -# ════════════════════════════════════════════════════════ -add_heading("3. 系统登录", level=1) -add_para( - "打开浏览器,在地址栏输入系统访问地址,系统将自动跳转至登录页面。" - "在登录页面中输入用户名和密码,点击「确认」按钮即可登录。" -) -add_para("操作步骤:", bold=True) -add_para("1. 在用户名输入框中输入您的账号。") -add_para("2. 在密码输入框中输入对应的密码。") -add_para("3. 点击「确认」按钮,系统验证通过后自动跳转至首页。") -add_para("") -add_para("登录页面截图:", bold=True) -add_screenshot("01_login_page.png") -add_para("") -add_para("注意事项:", bold=True) -add_para("• 登录状态有效期以服务端 Token 为准,过期后需重新登录。") -add_para("• 如忘记密码,请联系系统管理员重置。") - -# ════════════════════════════════════════════════════════ -# 4. 首页 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("4. 首页(仪表盘)", level=1) -add_para( - "登录系统后默认进入首页。首页为用户提供系统概览和快捷操作入口," - "方便快速访问各功能模块。" -) -add_para("页面功能:", bold=True) -add_para("• 快捷入口卡片:提供耳机品牌管理、型号管理、OTA 管理、分享日志、Luxsin 控制器等常用功能的快捷入口,点击即可直接跳转。") -add_para("• 今日新增型号:展示当天新增的耳机型号列表。") -add_para("• 今日新增 OTA:展示当天新增的 OTA 固件版本列表。") -add_para("") -add_para("首页截图:", bold=True) -add_screenshot("02_home.png") - -# ════════════════════════════════════════════════════════ -# 5. 耳机管理 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("5. 耳机管理", level=1) -add_para( - "耳机管理模块包含品牌管理和型号管理两个子功能," - "用于维护系统中的耳机品牌与型号数据。" -) - -# 5.1 品牌管理 -add_heading("5.1 品牌管理", level=2) -add_para( - "品牌管理页面用于维护系统中所有耳机品牌信息。" - "支持品牌的搜索、新增、编辑和删除操作。" -) -add_para("功能说明:", bold=True) -add_para("• 搜索:在搜索框中输入品牌名称,点击「搜索」按钮可按名称筛选品牌。点击「重置」可清除筛选条件。") -add_para("• 新增品牌:点击「新增品牌」按钮,在弹出的对话框中输入品牌名称,确认后即可创建新品牌。") -add_para("• 编辑:点击品牌列表中的「编辑」按钮,可修改品牌名称。") -add_para("• 删除:点击品牌列表中的「删除」按钮,确认后删除该品牌。请注意,已关联型号的品牌删除前需先处理关联数据。") -add_para("• 分页:列表底部提供分页控件,可切换页码和调整每页显示条数。") -add_para("") -add_para("品牌管理页面截图:", bold=True) -add_screenshot("03_headphone_brand.png") - -# 5.2 型号管理 -doc.add_page_break() -add_heading("5.2 型号管理", level=2) -add_para( - "型号管理页面用于维护系统中所有耳机型号数据。" - "每个型号包含品牌、型号名称、佩戴方式、来源、创建时间等信息。" -) -add_para("功能说明:", bold=True) -add_para("• 搜索:支持按品牌名称和型号名称进行组合筛选。输入条件后点击「搜索」按钮执行查询,点击「重置」清除条件。") -add_para("• 新型号:点击「新型号」按钮,在弹出的表单中填写品牌、型号名称、佩戴方式等信息。支持频响数据文件上传。") -add_para("• 编辑:点击「编辑」按钮可修改型号的详细信息。") -add_para("• 删除:点击「删除」按钮可删除单个型号记录。") -add_para("• 批量操作:通过勾选复选框可选择多个型号,执行批量复制、批量推送到搜索引擎等操作。") -add_para("• 推送搜索:选中型号后点击「推送搜索」按钮,可将型号数据批量推送至 Meilisearch 搜索引擎。") -add_para("• 更多操作:点击「更多操作」按钮可访问 EQ 缓存查看、频响 CSV 查看、搜索引擎文档查看等扩展功能。") -add_para("") -add_para("型号管理页面截图:", bold=True) -add_screenshot("04_headphone_model.png") - -# ════════════════════════════════════════════════════════ -# 6. 升级管理 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("6. 升级管理", level=1) -add_para( - "升级管理模块用于管理耳机设备的 OTA 固件升级," - "包含 OTA 版本管理、定向升级和黑名单三个子功能。" -) - -# 6.1 OTA 管理 -add_heading("6.1 OTA 管理", level=2) -add_para( - "OTA 管理页面用于维护固件升级包的版本信息。" - "每条记录包含版本号、版本名称、适配设备型号、硬件版本、升级包地址、" - "是否强制升级、灰度发布状态等信息。" -) -add_para("功能说明:", bold=True) -add_para("• 搜索筛选:支持按版本名称(模糊查询)、设备型号、状态、灰度标记、版本号(精确匹配)等多维度筛选。") -add_para("• 新增 OTA:点击「新增 OTA」按钮,填写版本号、版本名称、设备型号、硬件版本等信息,并上传升级包文件。系统将自动上传升级包至云存储并生成下载链接。") -add_para("• 编辑:点击「编辑」按钮可修改 OTA 版本的各项参数。") -add_para("• 复制:点击「复制」按钮可基于当前版本快速创建新的 OTA 记录。") -add_para("• 更多操作:包括删除等操作。") -add_para("• 复制包地址:点击「复制包地址」按钮可将升级包的下载链接复制到剪贴板。") -add_para("") -add_para("OTA 管理页面截图:", bold=True) -add_screenshot("05_upgrade_ota.png") - -# 6.2 定向升级 -doc.add_page_break() -add_heading("6.2 定向升级", level=2) -add_para( - "定向升级功能允许管理员将特定 OTA 版本推送给指定 MAC 地址的设备。" - "适用于测试验证或小范围灰度发布场景。" -) -add_para("功能说明:", bold=True) -add_para("• 搜索筛选:支持按 OTA 版本、设备型号、MAC 地址进行筛选。") -add_para("• 新增定向设备:点击「新增定向设备」按钮,选择目标 OTA 版本并输入设备 MAC 地址,确认后即可创建定向升级记录。") -add_para("• 编辑:点击「编辑」按钮可修改已绑定的 OTA 版本或 MAC 地址。") -add_para("• 删除:点击「删除」按钮可移除定向升级记录。") -add_para("") -add_para("定向升级页面截图:", bold=True) -add_screenshot("06_upgrade_target_device.png") - -# 6.3 黑名单 -add_heading("6.3 黑名单", level=2) -add_para( - "黑名单功能用于禁止特定 MAC 地址的设备接收 OTA 升级," - "防止问题设备反复触发固件更新。" -) -add_para("功能说明:", bold=True) -add_para("• 新增黑名单:点击「新增黑名单」按钮,输入需要封禁的设备 MAC 地址。") -add_para("• 编辑:点击「编辑」按钮可修改黑名单中的 MAC 地址。") -add_para("• 删除:点击「删除」按钮可将设备从黑名单中移除,恢复其 OTA 升级资格。") -add_para("") -add_para("黑名单页面截图:", bold=True) -add_screenshot("07_upgrade_blacklist.png") - -# ════════════════════════════════════════════════════════ -# 7. 分享码 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("7. 分享码", level=1) -add_para( - "分享码模块用于查看和检索设备的分享码使用记录。" -) - -# 7.1 分享日志 -add_heading("7.1 分享日志", level=2) -add_para( - "分享日志页面展示所有分享码的使用记录," - "包含分享码、设备 MAC 地址、使用时间等信息。" -) -add_para("功能说明:", bold=True) -add_para("• 按 MAC 搜索:在 MAC 地址输入框中输入设备 MAC,可按设备筛选分享记录。") -add_para("• 按分享码搜索:在分享码输入框中输入分享码,可精确查找特定分享码的使用记录。") -add_para("• 搜索与重置:点击「搜索」执行查询,点击「重置」清除筛选条件。") -add_para("• 分页浏览:列表支持分页浏览,可调整每页显示条数。") -add_para("") -add_para("分享日志页面截图:", bold=True) -add_screenshot("08_share_code_log.png") - -# ════════════════════════════════════════════════════════ -# 8. 工具箱 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("8. 工具箱", level=1) -add_para( - "工具箱模块提供辅助工具,帮助运营人员进行设备调试与数据同步。" -) - -# 8.1 Luxsin 控制器 -add_heading("8.1 Luxsin 控制器", level=2) -add_para( - "Luxsin 控制器是一个局域网设备数据同步工具," - "用于从局域网内的 Luxsin 设备获取数据并进行解码展示。" -) -add_para("功能说明:", bold=True) -add_para("• 同步数据:点击「同步数据」按钮,系统将从局域网内的 Luxsin 设备获取设备配置数据。") -add_para("• 同步 PEQ:点击「同步 PEQ」按钮,系统将获取设备的 PEQ(参数均衡器)配置数据。") -add_para("• 原始响应展示:系统获取到的原始 Base64 编码数据将展示在「原始响应」区域。") -add_para("• 解码结果:系统内置自定义 Base64 解码算法,将解码后的 JSON 数据格式化展示在「解码结果」区域,便于技术人员查看设备参数。") -add_para("") -add_para("Luxsin 控制器页面截图:", bold=True) -add_screenshot("09_toolbox_luxsin_controller.png") - -# ════════════════════════════════════════════════════════ -# 9. 系统管理 -# ════════════════════════════════════════════════════════ -doc.add_page_break() -add_heading("9. 系统管理", level=1) -add_para( - "系统管理模块提供后台管理功能,仅对超级管理员角色开放。" - "普通用户登录后无法看到此菜单入口。" -) - -# 9.1 账号管理 -add_heading("9.1 账号管理", level=2) -add_para( - "账号管理页面用于管理系统中的所有用户账号。" - "超级管理员可在此创建、编辑和删除用户。" -) -add_para("功能说明:", bold=True) -add_para("• 新增用户:点击「新增用户」按钮,填写用户名、密码、角色等信息创建新账号。") -add_para("• 编辑:点击「编辑」按钮可修改用户信息,包括重置密码、更改角色等。") -add_para("• 删除:点击「删除」按钮可删除用户账号。删除操作不可撤销,请谨慎操作。") -add_para("• 角色说明:系统支持超级管理员(R_SUPER)和普通用户(R_USER)两种角色。超级管理员拥有全部权限,普通用户无法访问系统管理等敏感功能。") -add_para("") -add_para("账号管理页面截图:", bold=True) -add_screenshot("10_system_users.png") - -# ════════════════════════════════════════════════════════ -# 保存文档 -# ════════════════════════════════════════════════════════ -doc.save(OUTPUT_PATH) -print(f"文档已生成: {OUTPUT_PATH}") diff --git a/dashboard/writings/screenshots/01-login.png b/dashboard/writings/screenshots/01-login.png deleted file mode 100644 index 964bbe7..0000000 Binary files a/dashboard/writings/screenshots/01-login.png and /dev/null differ diff --git a/dashboard/writings/screenshots/01_login_page.png b/dashboard/writings/screenshots/01_login_page.png deleted file mode 100644 index 371b079..0000000 Binary files a/dashboard/writings/screenshots/01_login_page.png and /dev/null differ diff --git a/dashboard/writings/screenshots/02_home.png b/dashboard/writings/screenshots/02_home.png deleted file mode 100644 index cb4b7d8..0000000 Binary files a/dashboard/writings/screenshots/02_home.png and /dev/null differ diff --git a/dashboard/writings/screenshots/03_headphone_brand.png b/dashboard/writings/screenshots/03_headphone_brand.png deleted file mode 100644 index 113ea55..0000000 Binary files a/dashboard/writings/screenshots/03_headphone_brand.png and /dev/null differ diff --git a/dashboard/writings/screenshots/04_headphone_model.png b/dashboard/writings/screenshots/04_headphone_model.png deleted file mode 100644 index f5f35bc..0000000 Binary files a/dashboard/writings/screenshots/04_headphone_model.png and /dev/null differ diff --git a/dashboard/writings/screenshots/05_upgrade_ota.png b/dashboard/writings/screenshots/05_upgrade_ota.png deleted file mode 100644 index 6bdfe1b..0000000 Binary files a/dashboard/writings/screenshots/05_upgrade_ota.png and /dev/null differ diff --git a/dashboard/writings/screenshots/06_upgrade_target_device.png b/dashboard/writings/screenshots/06_upgrade_target_device.png deleted file mode 100644 index 30dc01e..0000000 Binary files a/dashboard/writings/screenshots/06_upgrade_target_device.png and /dev/null differ diff --git a/dashboard/writings/screenshots/07_upgrade_blacklist.png b/dashboard/writings/screenshots/07_upgrade_blacklist.png deleted file mode 100644 index 5fbb7ae..0000000 Binary files a/dashboard/writings/screenshots/07_upgrade_blacklist.png and /dev/null differ diff --git a/dashboard/writings/screenshots/08_share_code_log.png b/dashboard/writings/screenshots/08_share_code_log.png deleted file mode 100644 index 764dc43..0000000 Binary files a/dashboard/writings/screenshots/08_share_code_log.png and /dev/null differ diff --git a/dashboard/writings/screenshots/09_toolbox_luxsin_controller.png b/dashboard/writings/screenshots/09_toolbox_luxsin_controller.png deleted file mode 100644 index b5ab8fb..0000000 Binary files a/dashboard/writings/screenshots/09_toolbox_luxsin_controller.png and /dev/null differ diff --git a/dashboard/writings/screenshots/10_system_users.png b/dashboard/writings/screenshots/10_system_users.png deleted file mode 100644 index 0dee526..0000000 Binary files a/dashboard/writings/screenshots/10_system_users.png and /dev/null differ diff --git a/db/02_sections.sql b/db/02_sections.sql index 88e7496..8f216d5 100644 --- a/db/02_sections.sql +++ b/db/02_sections.sql @@ -20,8 +20,6 @@ CREATE TABLE IF NOT EXISTS `www_section_blocks` ( `body_color` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '副标题/正文自定义颜色(空=跟随主题默认色)', `bg_type` VARCHAR(10) NOT NULL DEFAULT 'color' COMMENT '背景类型: color / image / video', `bg_value` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '背景值(颜色值 或 素材路径)', - `overlay_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用遮罩层', - `overlay_opacity` DECIMAL(3,2) NOT NULL DEFAULT 0.00 COMMENT '遮罩透明度 0~1', `overline_zh` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Overline 文本(中文)', `overline_en` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Overline 文本(英文)', `title_zh` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '标题(中文)', diff --git a/deploy/upload.sh b/deploy/upload.sh new file mode 100755 index 0000000..7465665 --- /dev/null +++ b/deploy/upload.sh @@ -0,0 +1,252 @@ +#!/usr/bin/env bash +# ============================================================ +# 统一部署脚本:将本地产物同步到正式服务器(rsync over ssh) +# +# 覆盖两类目标(统一 ssh 别名 luxsin-api2,按顶层目录自动路由远程路径): +# dashboard/ -> /data/project/dashboard/ (frontend / backend / compose) +# www/ -> /data/project/dashboard/www/ (默认仅 .output/;nginx.conf / docker-compose.yml 需显式指定) +# +# 用法:./deploy/upload.sh [选项] [目标|路径...] +# ============================================================ +set -euo pipefail + +# ===== 连接(统一使用 ssh 别名) ===== +SERVER="luxsin-api2" +REMOTE_ROOT="/data/project/dashboard" +# www 静态产物位于 ${REMOTE_ROOT}/www +WWW_REMOTE_ROOT="${REMOTE_ROOT}/www" + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +FRONTEND_PATHS=( + ./dashboard/frontend/dist/ +) + +BACKEND_PATHS=( + ./dashboard/backend/dist/ + ./dashboard/backend/package.json + ./dashboard/backend/pnpm-lock.yaml + ./dashboard/backend/pnpm-workspace.yaml +) + +COMPOSE_PATHS=( + ./dashboard/docker-compose.yml +) + +WWW_PATHS=( + ./www/.output/ +) + +DEFAULT_PATHS=( + "${FRONTEND_PATHS[@]}" + "${BACKEND_PATHS[@]}" + "${COMPOSE_PATHS[@]}" +) + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${CYAN}[INFO]${NC} $1"; } +ok() { echo -e "${GREEN}[OK]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } + +show_help() { + cat < ${SERVER}:${REMOTE_ROOT}/ + www/ -> ${SERVER}:${WWW_REMOTE_ROOT}/ + +选项: + -n 虚拟执行,不实际传输或删除远程文件 + -h, --help 显示此帮助信息 + +目标: + frontend, f 同步 dashboard 前端 dist/(默认跳过 uploads/) + backend, b 同步 dashboard 后端 dist/、package.json、pnpm-lock.yaml + compose, c 同步 dashboard docker-compose.yml + www, w 同步 www 构建产物 .output/(nginx.conf / docker-compose.yml 需显式指定路径) + all, a 同步 frontend + backend + compose(不含 www) + (路径) 同步指定文件或目录,可传多个 + + 默认不同步 nginx.conf 与 Dockerfile(dashboard),需要时请显式指定,例如: + ./dashboard/frontend/nginx.conf + ./dashboard/backend/Dockerfile + + www 同步前需先本地构建: cd www && pnpm generate + +示例: + $(basename "$0") 同步 dashboard 默认内容 + $(basename "$0") frontend 仅同步 dashboard 前端 + $(basename "$0") www 仅同步 www .output/ + $(basename "$0") www ./www/nginx.conf ./www/docker-compose.yml 同步 www 配置 + $(basename "$0") backend www 同步后端与 www + $(basename "$0") all www 同步全部(dashboard + www) + $(basename "$0") -n www 虚拟执行,预览 www 同步 +EOF +} + +resolve_preset_paths() { + local preset="$1" + case "$preset" in + frontend|f) printf '%s\n' "${FRONTEND_PATHS[@]}" ;; + backend|b) printf '%s\n' "${BACKEND_PATHS[@]}" ;; + compose|c) printf '%s\n' "${COMPOSE_PATHS[@]}" ;; + www|w) printf '%s\n' "${WWW_PATHS[@]}" ;; + all|a) printf '%s\n' "${DEFAULT_PATHS[@]}" ;; + *) + echo "$preset" + ;; + esac +} + +# www 静态产物同步前校验本地已构建(nuxt generate) +ensure_www_built() { + local idx="${PROJECT_ROOT}/www/.output/public/index.html" + if [[ ! -f "$idx" ]]; then + echo "❌ 本地缺少 ${idx},请先执行:" + echo " cd ${PROJECT_ROOT}/www && pnpm generate" + exit 1 + fi +} + +rsync_item() { + local item="$1" + local rel local_full + + if [[ "$item" == /* ]]; then + if [[ "$item" == "$PROJECT_ROOT"/* ]]; then + rel="${item#"$PROJECT_ROOT"/}" + local_full="$item" + else + warn "路径不在项目内,跳过: $item" + return 1 + fi + else + rel="${item#./}" + rel="${rel%/}" + local_full="${PROJECT_ROOT}/${rel}" + fi + + # 依据顶层目录映射远程路径(dashboard/ 内容在远程根,www/ 内容在远程 www 子目录) + local sub + if [[ "$rel" == dashboard/* ]]; then + sub="${rel#dashboard/}" + elif [[ "$rel" == www/* ]]; then + sub="$rel" + else + warn "路径不在 dashboard/ 或 www/ 下,跳过: $item" + return 1 + fi + local remote_path="${REMOTE_ROOT}/${sub}" + + if [[ ! -e "$local_full" ]]; then + warn "路径不存在: ${local_full}" + return 1 + fi + + local rsync_opts=(-avz --progress) + if [[ "$DRY_RUN" = true ]]; then + rsync_opts+=(-n) + fi + # dashboard 前端 dist 同步默认跳过 uploads/(服务器挂载的上传目录,避免 --delete 误删) + if [[ "$sub" == "frontend/dist" ]]; then + rsync_opts+=(--exclude '/uploads/') + fi + + if [[ -d "$local_full" ]]; then + if [[ "$DRY_RUN" = true ]]; then + info "预览目录: ${rel}/ -> ${SERVER}:${remote_path}/" + else + info "同步目录: ${rel}/ -> ${SERVER}:${remote_path}/" + fi + rsync "${rsync_opts[@]}" --delete \ + -e ssh \ + "${local_full}/" "${SERVER}:${remote_path}/" + else + if [[ "$DRY_RUN" = true ]]; then + info "预览文件: ${rel} -> ${SERVER}:${remote_path}" + else + info "同步文件: ${rel} -> ${SERVER}:${remote_path}" + ssh "$SERVER" "mkdir -p $(dirname "$remote_path")" + fi + rsync "${rsync_opts[@]}" \ + -e ssh \ + "$local_full" "${SERVER}:${remote_path}" + fi +} + +sync_paths() { + local paths=("$@") + local item + + cd "$PROJECT_ROOT" + + for item in "${paths[@]}"; do + rsync_item "$item" + done +} + +DRY_RUN=false +ARGS=() +for arg in "$@"; do + case "$arg" in + -n) + DRY_RUN=true + ;; + -h|--help) + show_help + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac +done + +PATHS=() +if [[ ${#ARGS[@]} -eq 0 ]]; then + PATHS=("${DEFAULT_PATHS[@]}") +else + for arg in "${ARGS[@]}"; do + while IFS= read -r path; do + PATHS+=("$path") + done < <(resolve_preset_paths "$arg") + done +fi + +# 若包含 www .output 产物,先校验本地已构建 +for p in "${PATHS[@]}"; do + rel="${p#./}" + if [[ "$rel" == www/.output || "$rel" == www/.output/* ]]; then + ensure_www_built + break + fi +done + +if [[ "$DRY_RUN" = true ]]; then + info "虚拟同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ ;www -> ${SERVER}:${WWW_REMOTE_ROOT}/)" +else + info "同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ ;www -> ${SERVER}:${WWW_REMOTE_ROOT}/)" +fi + +sync_paths "${PATHS[@]}" + +# www 部署后续提示 +for p in "${PATHS[@]}"; do + rel="${p#./}" + if [[ "$rel" == www/* ]]; then + echo "" + ok "www 产物已上传,下一步(在服务器上执行):" + echo " cd ${WWW_REMOTE_ROOT} && docker compose up -d" + break + fi +done + +echo "" +ok "完成!" diff --git a/docs/dashboard/www-cms-api.md b/docs/dashboard/www-cms-api.md index 781850b..375fe0f 100644 --- a/docs/dashboard/www-cms-api.md +++ b/docs/dashboard/www-cms-api.md @@ -243,8 +243,6 @@ "theme": "dark", "bg_type": "image", "bg_value": "/uploads/banners/hero-bg.jpg", - "overlay_enabled": true, - "overlay_opacity": 0.3, "overline_zh": "旗舰系列", "overline_en": "FLAGSHIP SERIES", "title_zh": "聆听,本该如此", diff --git a/www/app/components/section/SectionRenderer.vue b/www/app/components/section/SectionRenderer.vue index b017a47..5b8147d 100644 --- a/www/app/components/section/SectionRenderer.vue +++ b/www/app/components/section/SectionRenderer.vue @@ -33,16 +33,16 @@ const layoutComponent = computed(() => layoutComponents[props.section.layout] ?? const isDark = computed(() => props.section.theme === 'dark'); -// 遮罩透明度(decimal 字符串 -> 数值) -const overlayOpacity = computed(() => Number(props.section.overlay_opacity) || 0); -const showOverlay = computed(() => !!props.section.overlay_enabled && overlayOpacity.value > 0); - // 背景样式 const bgStyle = computed(() => { const { bg_type, bg_value } = props.section; if (bg_type === 'color' && bg_value) { return { backgroundColor: bg_value }; } + // 渐变:bg_value 为完整 CSS 渐变函数(如 linear-gradient(...));仅在形如渐变函数时应用,避免把普通字符串当背景 + if (bg_type === 'gradient' && bg_value && bg_value.includes('gradient(')) { + return { background: bg_value }; + } return {}; }); @@ -144,13 +144,6 @@ const bgVideoClass = computed(() => playsinline /> - -
-
diff --git a/www/app/types/site.ts b/www/app/types/site.ts index 3e5d027..509dfc1 100644 --- a/www/app/types/site.ts +++ b/www/app/types/site.ts @@ -176,10 +176,8 @@ export interface SectionBlock { overline_color: string; title_color: string; body_color: string; - bg_type: 'color' | 'image' | 'video'; + bg_type: 'color' | 'image' | 'gradient' | 'video'; bg_value: string; - overlay_enabled: number; - overlay_opacity: string; overline_zh: string; overline_en: string; title_zh: string; diff --git a/www/scripts/deploy-www.sh b/www/scripts/deploy-www.sh deleted file mode 100755 index 82c68cb..0000000 --- a/www/scripts/deploy-www.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# ============================================================ -# www 部署脚本:上传构建产物与部署配置到正式服务器 -# 上传内容:.output/public/、nginx.conf、docker-compose.yml -# 目标:luxsin-api2:/data/project/dashboard/www -# 用法:./scripts/deploy-www.sh -# ============================================================ -set -euo pipefail - -REMOTE_HOST="luxsin-api2" -REMOTE_DIR="/data/project/dashboard/www" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -WWW_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" - -# 本地需先完成静态构建(nuxt generate,ssr:false 产出 .output/public/index.html) -if [ ! -f "${WWW_DIR}/.output/public/index.html" ]; then - echo "❌ 本地缺少 ${WWW_DIR}/.output/public/index.html,请先执行:" - echo " cd ${WWW_DIR} && pnpm generate" - exit 1 -fi - -echo "==> 确保远程目录存在" -ssh "${REMOTE_HOST}" "mkdir -p ${REMOTE_DIR}/.output/public" - -echo "==> 同步 .output/public/(--delete:删除远端已失效的旧产物)" -rsync -avz --delete -e ssh \ - "${WWW_DIR}/.output/public/" "${REMOTE_HOST}:${REMOTE_DIR}/.output/public/" - -echo "==> 上传 nginx.conf / docker-compose.yml" -rsync -avz -e ssh \ - "${WWW_DIR}/nginx.conf" \ - "${WWW_DIR}/docker-compose.yml" \ - "${REMOTE_HOST}:${REMOTE_DIR}/" - -echo "✅ 上传完成:${REMOTE_HOST}:${REMOTE_DIR}" -echo "下一步(在服务器上执行):" -echo " cd ${REMOTE_DIR} && docker compose up -d"