create-ssnode-cli 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +10 -3
- package/package.json +1 -1
- package/secrets.js +10 -7
- package/templates/backend/Dockerfile +4 -0
- package/templates/backend/config/config.develop.yaml +10 -0
- package/templates/backend/config/config.prod.yaml +10 -0
- package/templates/backend/dist/config/configuration.js +1 -1
- package/templates/backend/dist/config/configuration.js.map +1 -1
- package/templates/backend/dist/modules/auth/auth.service.js +1 -1
- package/templates/backend/dist/modules/auth/auth.service.js.map +1 -1
- package/templates/backend/dist/modules/page-data/page-data.controller.js +1 -1
- package/templates/backend/dist/modules/page-data/page-data.controller.js.map +1 -1
- package/templates/backend/dist/modules/screen-config/screen-config.controller.js +1 -1
- package/templates/backend/dist/modules/screen-config/screen-config.controller.js.map +1 -1
- package/templates/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/templates/backend/node_modules/.bin/js-yaml +2 -2
- package/templates/backend/node_modules/.bin/typeorm +2 -2
- package/templates/backend/node_modules/.bin/typeorm-ts-node-commonjs +2 -2
- package/templates/backend/node_modules/.bin/typeorm-ts-node-esm +2 -2
- package/templates/backend/ops/docker/.env.example +4 -0
- package/templates/backend/ops/docker/config.sh +2 -0
- package/templates/backend/ops/docker/install.sh +35 -0
- package/templates/backend/ops/install.sh +17 -3
- package/templates/backend/ops/pm2/config.sh +2 -0
- package/templates/backend/ops/systemd/config.sh +2 -0
- package/templates/backend/package.json +13 -6
- package/templates/backend/src/config/config.interface.ts +9 -0
- package/templates/backend/src/config/configuration.ts +11 -0
- package/templates/backend/src/modules/auth/auth.service.ts +57 -12
- package/templates/backend/src/modules/page-data/page-data.controller.ts +33 -10
- package/templates/backend/src/modules/screen-config/screen-config.controller.ts +33 -5
- package/templates/root/README.md +3 -0
- package/templates/root/package.json +9 -1
- package/templates/web/dist/assets/{404-63aa5037.js → 404-e6edada0.js} +1 -1
- package/templates/web/dist/assets/{404-legacy-494c4093.js → 404-legacy-f6ca5b91.js} +1 -1
- package/templates/web/dist/assets/AuthLayout-bcf98efb.js +1 -0
- package/templates/web/dist/assets/AuthLayout-legacy-6e7a8ae1.js +1 -0
- package/templates/web/dist/assets/{Background-13406ae6.js → Background-eb77ef54.js} +1 -1
- package/templates/web/dist/assets/{Background-legacy-35ca5eef.js → Background-legacy-50457007.js} +1 -1
- package/templates/web/dist/assets/{Screen-4310b270.js → Screen-318c3fc2.js} +1 -1
- package/templates/web/dist/assets/{Screen-legacy-d5f1a31f.js → Screen-legacy-af123b0f.js} +1 -1
- package/templates/web/dist/assets/{ScreenChartOption-a6927065.js → ScreenChartOption-953b9157.js} +1 -1
- package/templates/web/dist/assets/{ScreenChartOption-legacy-b4c04ce9.js → ScreenChartOption-legacy-2e8fcfd8.js} +1 -1
- package/templates/web/dist/assets/api-beff4a3e.js +1 -0
- package/templates/web/dist/assets/api-legacy-9fe41d4b.js +1 -0
- package/templates/web/dist/assets/{base-5bf689c3.js → base-a14309c8.js} +1 -1
- package/templates/web/dist/assets/{base-legacy-d3323277.js → base-legacy-2bc6e427.js} +1 -1
- package/templates/web/dist/assets/{child-87a05c88.js → child-2a48ca65.js} +1 -1
- package/templates/web/dist/assets/{child-legacy-d4d0277d.js → child-legacy-7665b5f8.js} +1 -1
- package/templates/web/dist/assets/{component-1fcd971d.js → component-89b5253e.js} +1 -1
- package/templates/web/dist/assets/{component-legacy-943364e8.js → component-legacy-8c5a28ef.js} +1 -1
- package/templates/web/dist/assets/{demo-14e7532b.js → demo-d7272cf4.js} +1 -1
- package/templates/web/dist/assets/{demo-legacy-da0d2bfa.js → demo-legacy-f7bbfe55.js} +1 -1
- package/templates/web/dist/assets/echarts-vendor-50090982.js +9 -0
- package/templates/web/dist/assets/echarts-vendor-legacy-50b58722.js +10 -0
- package/templates/web/dist/assets/{grandson-4f840b4d.js → grandson-05f8bb58.js} +1 -1
- package/templates/web/dist/assets/{grandson-legacy-527ae016.js → grandson-legacy-3e10990b.js} +1 -1
- package/templates/web/dist/assets/{index-268d1ac1.js → index-466a1ec1.js} +1 -1
- package/templates/web/dist/assets/{index-c1ee2fb3.js → index-5b5a8146.js} +1 -1
- package/templates/web/dist/assets/{index-3a2983ce.js → index-7277cbfb.js} +1 -1
- package/templates/web/dist/assets/{index-e03d0352.js → index-777ef781.js} +1 -1
- package/templates/web/dist/assets/{index-e4c9d42a.js → index-86214b19.js} +1 -1
- package/templates/web/dist/assets/{index-f57c9694.js → index-9cd5622a.js} +1 -1
- package/templates/web/dist/assets/{index-71f600d4.js → index-a79e448b.js} +1 -1
- package/templates/web/dist/assets/{index-af61b79b.js → index-b60badf7.js} +1 -1
- package/templates/web/dist/assets/{index-b75948c0.js → index-bd4ed808.js} +1 -1
- package/templates/web/dist/assets/{index-9120f091.js → index-cba19800.js} +1 -1
- package/templates/web/dist/assets/{index-3448ef35.js → index-d70eb540.js} +1 -1
- package/templates/web/dist/assets/{index-5e7dfdb5.js → index-e6071630.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-081b881d.js → index-legacy-028ed5d5.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-aebba3cb.js → index-legacy-06483318.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-548904e6.js → index-legacy-0da8de82.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-376a4633.js → index-legacy-3965dcaf.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-5f22daba.js → index-legacy-4a13dddc.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-0983aa0f.js → index-legacy-783129a7.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-06643c96.js → index-legacy-8a2cfb9d.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-8bba1715.js → index-legacy-993ef005.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-19a8ac10.js → index-legacy-b075295d.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-b03d819e.js → index-legacy-b3bd40c1.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-35c1de97.js → index-legacy-d510e318.js} +1 -1
- package/templates/web/dist/assets/{index-legacy-3a393896.js → index-legacy-feb54578.js} +1 -1
- package/templates/web/dist/assets/{parent-ad02830b.js → parent-bba709aa.js} +1 -1
- package/templates/web/dist/assets/{parent-legacy-163f567f.js → parent-legacy-48111978.js} +1 -1
- package/templates/web/dist/assets/vendor-affac96c.js +124 -0
- package/templates/web/dist/assets/vendor-legacy-55fa76eb.js +122 -0
- package/templates/web/dist/index.html +3 -3
- package/templates/web/package.json +7 -7
- package/templates/web/src/js/xlsl.ts +110 -59
- package/templates/web/src/layouts/AuthLayout.tsx +16 -11
- package/templates/web/src/services/api/admin/user.ts +9 -2
- package/templates/web/dist/assets/AuthLayout-07e9f5d2.js +0 -1
- package/templates/web/dist/assets/AuthLayout-legacy-1b94749f.js +0 -1
- package/templates/web/dist/assets/api-83561db2.js +0 -1
- package/templates/web/dist/assets/api-legacy-8e15c853.js +0 -1
- package/templates/web/dist/assets/echarts-vendor-d6ce4b9c.js +0 -9
- package/templates/web/dist/assets/echarts-vendor-legacy-79597af6.js +0 -10
- package/templates/web/dist/assets/vendor-2663c204.js +0 -96
- package/templates/web/dist/assets/vendor-legacy-2df23db3.js +0 -87
- package/templates/web/node_modules/.bin/xlsx +0 -17
|
@@ -6,9 +6,9 @@ case `uname` in
|
|
|
6
6
|
esac
|
|
7
7
|
|
|
8
8
|
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.
|
|
9
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules/js-yaml/bin/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules/js-yaml/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
|
|
10
10
|
else
|
|
11
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.
|
|
11
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules/js-yaml/bin/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules/js-yaml/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.3.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
12
|
fi
|
|
13
13
|
if [ -x "$basedir/node" ]; then
|
|
14
14
|
exec "$basedir/node" "$basedir/../js-yaml/bin/js-yaml.js" "$@"
|
|
@@ -6,9 +6,9 @@ case `uname` in
|
|
|
6
6
|
esac
|
|
7
7
|
|
|
8
8
|
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
9
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
|
|
10
10
|
else
|
|
11
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
11
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
12
|
fi
|
|
13
13
|
if [ -x "$basedir/node" ]; then
|
|
14
14
|
exec "$basedir/node" "$basedir/../typeorm/cli.js" "$@"
|
|
@@ -6,9 +6,9 @@ case `uname` in
|
|
|
6
6
|
esac
|
|
7
7
|
|
|
8
8
|
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
9
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
|
|
10
10
|
else
|
|
11
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
11
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
12
|
fi
|
|
13
13
|
if [ -x "$basedir/node" ]; then
|
|
14
14
|
exec "$basedir/node" "$basedir/../typeorm/cli-ts-node-commonjs.js" "$@"
|
|
@@ -6,9 +6,9 @@ case `uname` in
|
|
|
6
6
|
esac
|
|
7
7
|
|
|
8
8
|
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
9
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
|
|
10
10
|
else
|
|
11
|
-
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.
|
|
11
|
+
export NODE_PATH="/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules/typeorm/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/typeorm@0.3.31_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
12
|
fi
|
|
13
13
|
if [ -x "$basedir/node" ]; then
|
|
14
14
|
exec "$basedir/node" "$basedir/../typeorm/cli-ts-node-esm.js" "$@"
|
|
@@ -33,6 +33,8 @@ CONTAINER_PORT="$PORT"
|
|
|
33
33
|
CONTAINER_DATA_DIR="${CONTAINER_DATA_DIR:-/app/data}"
|
|
34
34
|
# 重启策略
|
|
35
35
|
RESTART_POLICY="${RESTART_POLICY:-unless-stopped}"
|
|
36
|
+
# 版本轮转保留数(docker = 本镜像仓库历史 tag 数,即回滚窗口;顶层 install.sh 轮转时读)
|
|
37
|
+
KEEP_VERSIONS="${KEEP_VERSIONS:-3}"
|
|
36
38
|
|
|
37
39
|
# 读 current 的 version + .build-ts 作镜像 tag(<version>-<14位ts>,与 systemd/pm2 releases/<ver>-<ts> 同口径)。
|
|
38
40
|
# .build-ts 由 buildops 写入;缺则回退纯 version(兼容旧 release,但同 version 重发会覆盖,留意回滚)。
|
|
@@ -67,4 +67,39 @@ docker run -d \
|
|
|
67
67
|
"$IMAGE_TAG"
|
|
68
68
|
|
|
69
69
|
echo "✓ 容器已起: $CONTAINER_NAME (宿主 $HOST_PORT → $CONTAINER_PORT, tag $TAG)"
|
|
70
|
+
|
|
71
|
+
# ── 镜像轮转:对齐 systemd/pm2 的 rotate_releases 口径 ──
|
|
72
|
+
# docker 模式版本靠镜像 tag、不进 releases/,故轮转对象 = 本镜像仓库的历史 tag:
|
|
73
|
+
# 按末段 14 位时间戳降序保留 KEEP_VERSIONS 个(缺省见 config.sh),其余 rmi(回滚窗口=保留数)。
|
|
74
|
+
# KEEP_VERSIONS 优先级:顶层 --keep/env 显式 export > config.sh 持久值(运维改这处)。
|
|
75
|
+
# 不删运行中容器在用的 tag(防御);versionswitch.sh 切版本不经过本脚本,不受影响。
|
|
76
|
+
rotate_images() {
|
|
77
|
+
# 半成品兜底:经典 builder 多阶段构建的 deps 阶段成品(711MB 级 <none>)无 tag、无子层,轮转清不到;
|
|
78
|
+
# 按 Dockerfile 烙进各层的 app label 定向 prune(仅本应用,不碰宿主其他镜像)。
|
|
79
|
+
# 但该 <none> 同时是经典 builder 的构建缓存(删掉 = 下次全量重跑 apt+npm ci),故加 until=72h:
|
|
80
|
+
# 只清 72h 前的陈年残留,当天/周末内刚生成的缓存保住(发版即刷新存活期);放在 tag 轮转前,
|
|
81
|
+
# 不受 ≤KEEP_VERSIONS 提前 return 影响。
|
|
82
|
+
docker image prune -f --filter "label=app=$IMAGE" --filter "until=72h" >/dev/null 2>&1 || true
|
|
83
|
+
local running_ref
|
|
84
|
+
running_ref="$(docker inspect "$CONTAINER_NAME" --format '{{.Config.Image}}' 2>/dev/null || true)"
|
|
85
|
+
local list=() t
|
|
86
|
+
# tag 形如 <version>-<14位ts>;awk 提取末段时间戳做 sort 键降序(与顶层 sort_releases 同构)
|
|
87
|
+
while IFS= read -r t; do
|
|
88
|
+
[[ -n "$t" ]] || continue
|
|
89
|
+
list+=("$t")
|
|
90
|
+
done < <(docker images "$IMAGE" --format '{{.Tag}}' | grep -E -- '-[0-9]{14}$' \
|
|
91
|
+
| awk '{ts=$0; sub(/.*-/,"",ts); print ts" "$0}' | sort -r | awk '{print $2}')
|
|
92
|
+
[[ ${#list[@]} -le $KEEP_VERSIONS ]] && return 0
|
|
93
|
+
local del=("${list[@]:$KEEP_VERSIONS}")
|
|
94
|
+
for t in "${del[@]}"; do
|
|
95
|
+
[[ -n "$t" ]] || continue
|
|
96
|
+
[[ "$IMAGE:$t" = "$running_ref" ]] && continue
|
|
97
|
+
docker rmi "$IMAGE:$t" >/dev/null 2>&1 \
|
|
98
|
+
&& echo " ↳ 清理旧镜像: $IMAGE:$t" \
|
|
99
|
+
|| echo " ⚠ rmi 失败(可能被退出容器引用,清理残留容器后重试): $IMAGE:$t"
|
|
100
|
+
done
|
|
101
|
+
echo "✓ 镜像轮转:保留 $KEEP_VERSIONS 个 tag"
|
|
102
|
+
}
|
|
103
|
+
rotate_images
|
|
104
|
+
|
|
70
105
|
echo " 状态: ./status.sh | 日志: ./logs.sh"
|
|
@@ -31,7 +31,9 @@ set -euo pipefail
|
|
|
31
31
|
# ── baked 字段(首次为空;bootstrap 拷自身到 APP_ROOT 时 sed 改写为字面量) ──
|
|
32
32
|
APP_ROOT="${APP_ROOT:-}"
|
|
33
33
|
MODE="${MODE:-}"
|
|
34
|
-
|
|
34
|
+
# 轮转保留数:此处只存显式值(--keep / env),不预设缺省 —— 缺省在轮转前由
|
|
35
|
+
# resolve_keep_versions 从 ops/<mode>/config.sh 取(运维改那处,跨版本保留)。
|
|
36
|
+
KEEP_VERSIONS="${KEEP_VERSIONS:-}"
|
|
35
37
|
|
|
36
38
|
# 脚本所在目录(首次=release 包根,即 install.sh 所在的包根;二次=APP_ROOT)
|
|
37
39
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -417,12 +419,24 @@ dispatch_install() {
|
|
|
417
419
|
local mode_install="$APP_ROOT/ops/$MODE/install.sh" # 共享 ops(releases 不放 ops)
|
|
418
420
|
[[ -f "$mode_install" ]] || die "缺 mode 安装脚本: $mode_install(先跑 lay_out_ops 或检查后台服务目录/ops)"
|
|
419
421
|
say "派发 $MODE 安装: $mode_install"
|
|
420
|
-
export APP_ROOT MODE
|
|
422
|
+
export APP_ROOT MODE KEEP_VERSIONS
|
|
421
423
|
bash "$mode_install"
|
|
422
424
|
}
|
|
423
425
|
|
|
426
|
+
# ── 轮转保留数解析:--keep/env 显式 > ops/<mode>/config.sh 持久值 > 3 ──
|
|
427
|
+
# KEEP_VERSIONS 不 bake(区别 APP_ROOT/MODE):运维的保留数放 mode config.sh,
|
|
428
|
+
# lay_out_ops 跨版本保留(不 overlay);--keep 仅当次部署生效。
|
|
429
|
+
# source 无名冲突(config.sh 只设 mode 专属变量 + APP_ROOT 校验,顶层变量不被覆盖)。
|
|
430
|
+
resolve_keep_versions() {
|
|
431
|
+
[[ -n "$KEEP_VERSIONS" ]] && return 0
|
|
432
|
+
local cfg="$APP_ROOT/ops/$MODE/config.sh"
|
|
433
|
+
if [[ -f "$cfg" ]]; then source "$cfg"; fi # bootstrap 时 lay_out_ops 已先于轮转就位
|
|
434
|
+
KEEP_VERSIONS="${KEEP_VERSIONS:-3}" # 旧部署 config.sh 无此项 → 回落 3(与历史行为一致)
|
|
435
|
+
}
|
|
436
|
+
|
|
424
437
|
rotate_releases() {
|
|
425
|
-
|
|
438
|
+
resolve_keep_versions
|
|
439
|
+
[[ "$IS_SYMLINK" = 0 ]] && return 0 # docker 不用 releases(镜像轮转在 ops/docker/install.sh::rotate_images)
|
|
426
440
|
local list=()
|
|
427
441
|
while IFS= read -r d; do list+=("$d"); done < <(sort_releases "$APP_ROOT/releases")
|
|
428
442
|
[[ ${#list[@]} -le $KEEP_VERSIONS ]] && return 0
|
|
@@ -16,6 +16,8 @@ APP_ROOT="${APP_ROOT:-}"
|
|
|
16
16
|
PM2_BIN="${PM2_BIN:-pm2}"
|
|
17
17
|
# 实例数(单实例 fork;SQLite 单文件写串行,不建议 cluster 多实例)
|
|
18
18
|
INSTANCES="${INSTANCES:-1}"
|
|
19
|
+
# 版本轮转保留数(releases 目录数,即回滚窗口;顶层 install.sh::rotate_releases 读)
|
|
20
|
+
KEEP_VERSIONS="${KEEP_VERSIONS:-3}"
|
|
19
21
|
|
|
20
22
|
# ── 派生路径(APP_ROOT 定下来后才能算;由 resolve_app_root 调用) ──
|
|
21
23
|
derive_paths() {
|
|
@@ -16,6 +16,8 @@ APP_ROOT="${APP_ROOT:-}"
|
|
|
16
16
|
NODE_BIN="${NODE_BIN:-/usr/bin/node}"
|
|
17
17
|
# 运行用户
|
|
18
18
|
RUN_USER="${RUN_USER:-root}"
|
|
19
|
+
# 版本轮转保留数(releases 目录数,即回滚窗口;顶层 install.sh::rotate_releases 读)
|
|
20
|
+
KEEP_VERSIONS="${KEEP_VERSIONS:-3}"
|
|
19
21
|
|
|
20
22
|
# ── 派生路径(APP_ROOT 定下来后才能算;由 resolve_app_root 调用) ──
|
|
21
23
|
derive_paths() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "全栈模板 - 后台服务(NestJS + SQLite + TypeORM)",
|
|
5
5
|
"private": true,
|
|
6
6
|
"engines": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"migration:generate": "TS_NODE_PROJECT=tsconfig.migration.json typeorm-ts-node-commonjs migration:generate --pretty -d scripts/data-source.ts"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@nestjs/axios": "4.0.1",
|
|
24
23
|
"@nestjs/common": "11.1.28",
|
|
25
24
|
"@nestjs/config": "4.0.4",
|
|
26
25
|
"@nestjs/core": "11.1.28",
|
|
@@ -37,16 +36,16 @@
|
|
|
37
36
|
"better-sqlite3": "13.0.3",
|
|
38
37
|
"class-transformer": "0.5.1",
|
|
39
38
|
"class-validator": "0.15.1",
|
|
40
|
-
"js-yaml": "4.
|
|
41
|
-
"multer": "
|
|
39
|
+
"js-yaml": "4.3.2",
|
|
40
|
+
"multer": "2.4.0",
|
|
42
41
|
"passport": "0.7.0",
|
|
43
42
|
"passport-jwt": "4.0.1",
|
|
44
43
|
"passport-local": "1.0.0",
|
|
45
44
|
"reflect-metadata": "0.2.2",
|
|
46
45
|
"rxjs": "7.8.2",
|
|
47
|
-
"socket.io": "4.8.
|
|
46
|
+
"socket.io": "4.8.4",
|
|
48
47
|
"svg-captcha": "1.4.0",
|
|
49
|
-
"typeorm": "0.3.
|
|
48
|
+
"typeorm": "0.3.31"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"@nestjs/cli": "11.0.24",
|
|
@@ -69,5 +68,13 @@
|
|
|
69
68
|
"ts-node": "10.9.2",
|
|
70
69
|
"tsconfig-paths": "4.2.0",
|
|
71
70
|
"typescript": "5.4.3"
|
|
71
|
+
},
|
|
72
|
+
"overrides": {
|
|
73
|
+
"proxy-addr": "^2.0.8",
|
|
74
|
+
"multer": "$multer",
|
|
75
|
+
"engine.io": "^6.6.10",
|
|
76
|
+
"@nestjs/swagger": {
|
|
77
|
+
"js-yaml": "^5.4.3"
|
|
78
|
+
}
|
|
72
79
|
}
|
|
73
80
|
}
|
|
@@ -48,6 +48,15 @@ export interface AppConfig {
|
|
|
48
48
|
username: string;
|
|
49
49
|
password: string;
|
|
50
50
|
};
|
|
51
|
+
/** 单点登录 /api/ssologin(演示接入范式,默认关闭) */
|
|
52
|
+
sso: {
|
|
53
|
+
/** 是否开启;false 时接口返回 404(对外不暴露该能力) */
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
/** 票据校验密钥(HMAC-SHA256),与接入方(门户/统一认证)共享 */
|
|
56
|
+
ticketKey: string;
|
|
57
|
+
/** 时间戳允许偏差(秒),默认 300 */
|
|
58
|
+
tsWindow: number;
|
|
59
|
+
};
|
|
51
60
|
/** 大屏 API 签名加密配置(无 token、无过期) */
|
|
52
61
|
appSign: {
|
|
53
62
|
/** HMAC-SHA256 签名密钥,永不过期 */
|
|
@@ -27,6 +27,11 @@ const envInt = (k: string, fallback?: number): number | undefined => {
|
|
|
27
27
|
const n = Number(v);
|
|
28
28
|
return Number.isFinite(n) ? n : fallback;
|
|
29
29
|
};
|
|
30
|
+
const envBool = (k: string, fallback?: boolean): boolean | undefined => {
|
|
31
|
+
const v = process.env[k];
|
|
32
|
+
if (v === undefined || v === '') return fallback;
|
|
33
|
+
return v === 'true' || v === '1';
|
|
34
|
+
};
|
|
30
35
|
const envList = (k: string, fallback?: string[]): string[] | undefined => {
|
|
31
36
|
const v = process.env[k];
|
|
32
37
|
if (v === undefined || v === '') return fallback;
|
|
@@ -102,6 +107,12 @@ export default (): AppConfig => {
|
|
|
102
107
|
...config.appSign,
|
|
103
108
|
signKey: envStr('APP_SIGN_KEY', config.appSign.signKey) as string,
|
|
104
109
|
tsWindow: envInt('APP_SIGN_TS_WINDOW', config.appSign.tsWindow) as number
|
|
110
|
+
},
|
|
111
|
+
sso: {
|
|
112
|
+
...config.sso,
|
|
113
|
+
enabled: envBool('SSO_ENABLED', config.sso.enabled) as boolean,
|
|
114
|
+
ticketKey: envStr('SSO_TICKET_KEY', config.sso.ticketKey) as string,
|
|
115
|
+
tsWindow: envInt('SSO_TS_WINDOW', config.sso.tsWindow) as number
|
|
105
116
|
}
|
|
106
117
|
};
|
|
107
118
|
};
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
1
|
+
import { Injectable, UnauthorizedException, NotFoundException, Logger } from '@nestjs/common';
|
|
2
2
|
import { JwtService } from '@nestjs/jwt';
|
|
3
3
|
import { ConfigService } from '@nestjs/config';
|
|
4
4
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
5
5
|
import { Repository } from 'typeorm';
|
|
6
6
|
import * as bcrypt from 'bcryptjs';
|
|
7
|
+
import * as crypto from 'crypto';
|
|
7
8
|
import { UserEntity } from '../../entities/user.entity';
|
|
8
9
|
import { CaptchaService } from './captcha.service';
|
|
9
10
|
import { AuditService } from '../audit/audit.service';
|
|
11
|
+
import { safeEqualHex } from '../../common/app-crypto';
|
|
10
12
|
import type { AppConfig } from '../../config/config.interface';
|
|
11
13
|
|
|
12
14
|
@Injectable()
|
|
13
15
|
export class AuthService {
|
|
16
|
+
private readonly logger = new Logger(AuthService.name);
|
|
17
|
+
|
|
14
18
|
constructor(
|
|
15
19
|
@InjectRepository(UserEntity)
|
|
16
20
|
private readonly userRepo: Repository<UserEntity>,
|
|
@@ -56,10 +60,14 @@ export class AuthService {
|
|
|
56
60
|
});
|
|
57
61
|
throw new UnauthorizedException('用户名或密码错误');
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
// 非 bcrypt 哈希(历史明文/手工改库)一律拒绝,不做明文比对回退;
|
|
64
|
+
// 存量明文用户走 /api/user/resetPassword 恢复,超管走「改配置+重启」reconcile。
|
|
65
|
+
const hashed = user.password.startsWith('$2');
|
|
66
|
+
const ok = hashed && (await bcrypt.compare(params.password, user.password));
|
|
62
67
|
if (!ok) {
|
|
68
|
+
if (!hashed) {
|
|
69
|
+
this.logger.warn(`用户 ${user.username} 密码非 bcrypt 哈希,请管理员重置`);
|
|
70
|
+
}
|
|
63
71
|
await this.auditService.record({
|
|
64
72
|
actorName: params.username,
|
|
65
73
|
action: 'login_fail',
|
|
@@ -88,23 +96,60 @@ export class AuthService {
|
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
/**
|
|
91
|
-
* 单点登录 /api/ssologin
|
|
99
|
+
* 单点登录 /api/ssologin(演示接入范式,默认关闭)
|
|
100
|
+
*
|
|
101
|
+
* 未开启(yaml sso.enabled=false)时返回 404,对外不暴露该能力。
|
|
102
|
+
* 开启后校验接入方票据:
|
|
103
|
+
* GET /api/ssologin?username=xxx&ts=<unix秒>&sign=<hex>
|
|
104
|
+
* sign = HMAC-SHA256(`${username}\n${ts}`, sso.ticketKey) hex
|
|
105
|
+
* 接入方(门户/统一认证)持 ticketKey 生成跳转 URL,票据时效由 tsWindow 约束。
|
|
106
|
+
* role 固定 'web'(最小权限),勿给该角色挂敏感路由。
|
|
92
107
|
*/
|
|
93
|
-
async ssoLogin(
|
|
94
|
-
|
|
108
|
+
async ssoLogin(
|
|
109
|
+
params: { username?: string; ts?: string; sign?: string },
|
|
110
|
+
ip?: string
|
|
111
|
+
) {
|
|
112
|
+
const ssoCfg = this.config.get<AppConfig['sso']>('sso');
|
|
113
|
+
const username = params?.username || '';
|
|
114
|
+
|
|
115
|
+
if (!ssoCfg.enabled) {
|
|
116
|
+
throw new NotFoundException('接口不存在');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const fail = async (reason: string) => {
|
|
95
120
|
await this.auditService.record({
|
|
96
|
-
actorName:
|
|
121
|
+
actorName: username || 'sso',
|
|
97
122
|
action: 'login_fail',
|
|
98
|
-
detail: { reason
|
|
123
|
+
detail: { reason },
|
|
99
124
|
ip
|
|
100
125
|
});
|
|
101
|
-
throw new UnauthorizedException(
|
|
126
|
+
throw new UnauthorizedException(reason);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const tsNum = Number(params.ts);
|
|
130
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
131
|
+
if (!username || !params.ts || !params.sign) {
|
|
132
|
+
await fail('缺少单点登录票据参数');
|
|
133
|
+
}
|
|
134
|
+
if (!Number.isFinite(tsNum) || tsNum <= 0) {
|
|
135
|
+
await fail('票据时间戳非法');
|
|
136
|
+
}
|
|
137
|
+
if (Math.abs(nowSec - tsNum) > ssoCfg.tsWindow) {
|
|
138
|
+
await fail('票据已过期');
|
|
102
139
|
}
|
|
103
|
-
|
|
140
|
+
|
|
141
|
+
const expected = crypto
|
|
142
|
+
.createHmac('sha256', ssoCfg.ticketKey)
|
|
143
|
+
.update(`${username}\n${params.ts}`, 'utf8')
|
|
144
|
+
.digest('hex');
|
|
145
|
+
if (!safeEqualHex(expected, String(params.sign).toLowerCase())) {
|
|
146
|
+
await fail('票据校验失败');
|
|
147
|
+
}
|
|
148
|
+
|
|
104
149
|
await this.auditService.record({
|
|
105
150
|
actorName: username,
|
|
106
151
|
action: 'login_success',
|
|
107
|
-
detail: { sso: true
|
|
152
|
+
detail: { sso: true },
|
|
108
153
|
ip
|
|
109
154
|
});
|
|
110
155
|
const payload = { sub: 0, username, role: 'web' };
|
|
@@ -8,49 +8,72 @@ import {
|
|
|
8
8
|
Query,
|
|
9
9
|
Request,
|
|
10
10
|
UploadedFile,
|
|
11
|
-
|
|
11
|
+
UseGuards,
|
|
12
|
+
UseInterceptors,
|
|
13
|
+
ForbiddenException
|
|
12
14
|
} from '@nestjs/common';
|
|
13
15
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
14
|
-
import { ApiTags, ApiConsumes, ApiBody } from '@nestjs/swagger';
|
|
16
|
+
import { ApiTags, ApiConsumes, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
|
|
15
17
|
import { PageDataService } from './page-data.service';
|
|
18
|
+
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
16
19
|
|
|
17
20
|
@ApiTags('page-data 大屏页面数据')
|
|
18
21
|
@Controller()
|
|
19
22
|
export class PageDataController {
|
|
20
23
|
constructor(private readonly service: PageDataService) {}
|
|
21
24
|
|
|
22
|
-
/**
|
|
25
|
+
/** 仅管理员可写(与 user.controller 的 ensureAdmin 同模式) */
|
|
26
|
+
private ensureAdmin(req: any) {
|
|
27
|
+
if (req.user?.role !== 'admin' && req.user?.role !== 'superadmin') {
|
|
28
|
+
throw new ForbiddenException('仅管理员可操作大屏页面数据');
|
|
29
|
+
}
|
|
30
|
+
return { id: req.user.id, username: req.user.username };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 列表 /api/getList
|
|
35
|
+
* 保持公开:docs/api-security.md §1.1/§6 已拍板「暂不纳入签名保护」(大屏消费接口)。
|
|
36
|
+
*/
|
|
23
37
|
@Get('getList')
|
|
24
38
|
getList(@Query() query: any) {
|
|
25
39
|
return this.service.getList(query);
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
/** 新增 /api/add */
|
|
42
|
+
/** 新增 /api/add(需登录,admin-only) */
|
|
43
|
+
@UseGuards(JwtAuthGuard)
|
|
29
44
|
@Post('add')
|
|
30
45
|
add(@Body() body: any, @Request() req: any) {
|
|
46
|
+
this.ensureAdmin(req);
|
|
31
47
|
return this.service.add(body, req.user);
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
/** 修改 /api/edit */
|
|
50
|
+
/** 修改 /api/edit(需登录,admin-only) */
|
|
51
|
+
@UseGuards(JwtAuthGuard)
|
|
35
52
|
@Put('edit')
|
|
36
|
-
edit(@Body() body: any) {
|
|
53
|
+
edit(@Body() body: any, @Request() req: any) {
|
|
54
|
+
this.ensureAdmin(req);
|
|
37
55
|
return this.service.update(body);
|
|
38
56
|
}
|
|
39
57
|
|
|
40
|
-
/** 删除 /api/deleteById */
|
|
58
|
+
/** 删除 /api/deleteById(需登录,admin-only) */
|
|
59
|
+
@UseGuards(JwtAuthGuard)
|
|
41
60
|
@Delete('deleteById')
|
|
42
|
-
deleteById(@Query('id') id: string) {
|
|
61
|
+
deleteById(@Query('id') id: string, @Request() req: any) {
|
|
62
|
+
this.ensureAdmin(req);
|
|
43
63
|
return this.service.deleteById(Number(id));
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
/** 导入文件 /api/v1/import */
|
|
66
|
+
/** 导入文件 /api/v1/import(需登录,admin-only) */
|
|
67
|
+
@UseGuards(JwtAuthGuard)
|
|
47
68
|
@Post('v1/import')
|
|
48
69
|
@UseInterceptors(FileInterceptor('file'))
|
|
49
70
|
@ApiConsumes('multipart/form-data')
|
|
71
|
+
@ApiBearerAuth()
|
|
50
72
|
@ApiBody({
|
|
51
73
|
schema: { type: 'object', properties: { file: { type: 'string', format: 'binary' } } }
|
|
52
74
|
})
|
|
53
|
-
import(@UploadedFile() file: any) {
|
|
75
|
+
import(@UploadedFile() file: any, @Request() req: any) {
|
|
76
|
+
this.ensureAdmin(req);
|
|
54
77
|
if (!file) {
|
|
55
78
|
throw new Error('缺少 file');
|
|
56
79
|
}
|
|
@@ -1,22 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Get,
|
|
4
|
+
Param,
|
|
5
|
+
Body,
|
|
6
|
+
Put,
|
|
7
|
+
Request,
|
|
8
|
+
UseGuards,
|
|
9
|
+
ForbiddenException
|
|
10
|
+
} from '@nestjs/common';
|
|
11
|
+
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
|
3
12
|
import { ScreenConfigService } from './screen-config.service';
|
|
4
13
|
import { ScreenConfigEntity } from '../../entities/screen-config.entity';
|
|
14
|
+
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
5
15
|
|
|
6
16
|
@ApiTags('screen 大屏配置')
|
|
7
17
|
@Controller('screen')
|
|
8
18
|
export class ScreenConfigController {
|
|
9
19
|
constructor(private readonly service: ScreenConfigService) {}
|
|
10
20
|
|
|
11
|
-
/**
|
|
21
|
+
/** 仅管理员可写(与 user.controller 的 ensureAdmin 同模式) */
|
|
22
|
+
private ensureAdmin(req: any) {
|
|
23
|
+
if (req.user?.role !== 'admin' && req.user?.role !== 'superadmin') {
|
|
24
|
+
throw new ForbiddenException('仅管理员可操作大屏配置');
|
|
25
|
+
}
|
|
26
|
+
return { id: req.user.id, username: req.user.username };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 大屏清单 /api/screen/list
|
|
31
|
+
* 保持公开:docs/api-security.md §1.1/§6 已拍板「暂不纳入签名保护」(大屏消费接口)。
|
|
32
|
+
*/
|
|
12
33
|
@Get('list')
|
|
13
34
|
list() {
|
|
14
35
|
return this.service.list();
|
|
15
36
|
}
|
|
16
37
|
|
|
17
|
-
/** 更新大屏配置 /api/screen/:key */
|
|
38
|
+
/** 更新大屏配置 /api/screen/:key(需登录,admin-only) */
|
|
39
|
+
@UseGuards(JwtAuthGuard)
|
|
40
|
+
@ApiBearerAuth()
|
|
18
41
|
@Put(':key')
|
|
19
|
-
update(
|
|
42
|
+
update(
|
|
43
|
+
@Param('key') key: string,
|
|
44
|
+
@Body() dto: Partial<ScreenConfigEntity>,
|
|
45
|
+
@Request() req: any
|
|
46
|
+
) {
|
|
47
|
+
this.ensureAdmin(req);
|
|
20
48
|
return this.service.update(key, dto);
|
|
21
49
|
}
|
|
22
50
|
}
|
package/templates/root/README.md
CHANGED
|
@@ -34,8 +34,11 @@ dev/prod 两套密钥已生成并写入下列位置(超管密码见生成时控
|
|
|
34
34
|
- `backend/config/config.develop.yaml` + `config.prod.yaml` 的 `appSign.signKey`
|
|
35
35
|
- **JWT secret**:backend 两份 yaml 的 `jwt.secret`
|
|
36
36
|
- **超管密码**:backend 两份 yaml 的 `admin.password`(启动 reconcile 到 DB)
|
|
37
|
+
- **SSO 票据密钥** `sso.ticketKey`:backend 两份 yaml(默认关闭,见下「单点登录」)
|
|
37
38
|
- **docker 部署**:`backend/ops/docker/.env.example` 的 `JWT_SECRET`/`ADMIN_PASSWORD`/`APP_SIGN_KEY`(拷为服务器 `.env` 后改真值)
|
|
38
39
|
|
|
40
|
+
**单点登录(/api/ssologin,默认关闭)**:接入第三方门户时,改 backend yaml `sso.enabled: true` 并把 `sso.ticketKey` 交给接入方;接入方生成跳转 URL `?user=<用户名>&ts=<unix秒>&sign=<hex>`,其中 `sign = HMAC-SHA256('<username>\n<ts>', ticketKey)`。未开启时接口返回 404,签发的 token 角色为 `web`(最小权限)。
|
|
41
|
+
|
|
39
42
|
### 3. 部署凭证(本地、不入库)
|
|
40
43
|
- `web/scripts/server.config.json`:从 `server.config.example.json` 拷,填 SSH host/账号/密码/部署路径(已 `.gitignore`)
|
|
41
44
|
- `backend/scripts/server.config.cjs`:从 `server.config.example.cjs` 拷,填 SSH + `backendServiceDir`(已 `.gitignore`)
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
"shelljs": "0.8.5"
|
|
27
27
|
},
|
|
28
28
|
"pnpm": {
|
|
29
|
-
"requiredScripts": []
|
|
29
|
+
"requiredScripts": [],
|
|
30
|
+
"overrides": {
|
|
31
|
+
"proxy-addr": "^2.0.8",
|
|
32
|
+
"brace-expansion@1": "^1.1.21",
|
|
33
|
+
"brace-expansion@2": "^2.1.7",
|
|
34
|
+
"multer": "^2.4.0",
|
|
35
|
+
"engine.io": "^6.6.10",
|
|
36
|
+
"@nestjs/swagger>js-yaml": "^5.4.3"
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{u as n,j as t}from"./vendor-
|
|
1
|
+
import{u as n,j as t}from"./vendor-affac96c.js";function i(){const r=n();return t.jsx("div",{style:{width:"100%",height:"100vh",boxSizing:"border-box",margin:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:"#fff",color:"#1a1a1a",overflow:"hidden",position:"relative"},children:t.jsxs("div",{style:{position:"relative",textAlign:"center"},children:[t.jsx("div",{style:{fontSize:"200px",fontWeight:800,lineHeight:1,letterSpacing:"4px",color:"#1a1a1a",textShadow:"0 6px 20px rgba(0, 0, 0, 0.08)"},children:"404"}),t.jsx("div",{style:{marginTop:20,fontSize:24,fontWeight:600,color:"#1a1a1a",letterSpacing:"2px"},children:"页面走丢了"}),t.jsx("div",{style:{marginTop:8,fontSize:14,color:"rgba(0, 0, 0, 0.45)"},children:"抱歉,您访问的页面不存在或已被移除"}),t.jsxs("div",{style:{marginTop:36,display:"flex",gap:16,justifyContent:"center"},children:[t.jsx("span",{onClick:()=>r("/"),style:{padding:"11px 32px",fontSize:15,fontWeight:500,borderRadius:24,cursor:"pointer",color:"#fff",background:"#000",transition:"all 0.2s ease"},onMouseEnter:e=>e.currentTarget.style.opacity="0.8",onMouseLeave:e=>e.currentTarget.style.opacity="1",children:"返回首页"}),t.jsx("span",{onClick:()=>window.history.back(),style:{padding:"11px 32px",fontSize:15,fontWeight:500,borderRadius:24,cursor:"pointer",color:"#000",border:"1px solid #000",background:"transparent",boxSizing:"border-box",transition:"all 0.2s ease"},onMouseEnter:e=>{e.currentTarget.style.background="#000",e.currentTarget.style.color="#fff"},onMouseLeave:e=>{e.currentTarget.style.background="transparent",e.currentTarget.style.color="#000"},children:"返回上一页"})]})]})})}export{i as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
System.register(["./vendor-legacy-
|
|
1
|
+
System.register(["./vendor-legacy-55fa76eb.js"],(function(e,t){"use strict";var r,n;return{setters:[e=>{r=e.u,n=e.j}],execute:function(){e("default",(function(){const e=r();return n.jsx("div",{style:{width:"100%",height:"100vh",boxSizing:"border-box",margin:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:"#fff",color:"#1a1a1a",overflow:"hidden",position:"relative"},children:n.jsxs("div",{style:{position:"relative",textAlign:"center"},children:[n.jsx("div",{style:{fontSize:"200px",fontWeight:800,lineHeight:1,letterSpacing:"4px",color:"#1a1a1a",textShadow:"0 6px 20px rgba(0, 0, 0, 0.08)"},children:"404"}),n.jsx("div",{style:{marginTop:20,fontSize:24,fontWeight:600,color:"#1a1a1a",letterSpacing:"2px"},children:"页面走丢了"}),n.jsx("div",{style:{marginTop:8,fontSize:14,color:"rgba(0, 0, 0, 0.45)"},children:"抱歉,您访问的页面不存在或已被移除"}),n.jsxs("div",{style:{marginTop:36,display:"flex",gap:16,justifyContent:"center"},children:[n.jsx("span",{onClick:()=>e("/"),style:{padding:"11px 32px",fontSize:15,fontWeight:500,borderRadius:24,cursor:"pointer",color:"#fff",background:"#000",transition:"all 0.2s ease"},onMouseEnter:e=>e.currentTarget.style.opacity="0.8",onMouseLeave:e=>e.currentTarget.style.opacity="1",children:"返回首页"}),n.jsx("span",{onClick:()=>window.history.back(),style:{padding:"11px 32px",fontSize:15,fontWeight:500,borderRadius:24,cursor:"pointer",color:"#000",border:"1px solid #000",background:"transparent",boxSizing:"border-box",transition:"all 0.2s ease"},onMouseEnter:e=>{e.currentTarget.style.background="#000",e.currentTarget.style.color="#fff"},onMouseLeave:e=>{e.currentTarget.style.background="transparent",e.currentTarget.style.color="#000"},children:"返回上一页"})]})]})})}))}}}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as m,e as p,r as n,Q as g,j as f,S as d,O as h}from"./vendor-affac96c.js";import{a as o}from"./api-beff4a3e.js";import"./index-86214b19.js";function E(){const c=m(),i=p(),[l,t]=n.useState(!0),[u,r]=n.useState(!1);return n.useEffect(()=>{if(!window.ENV.checkToken){t(!1);return}const e=()=>{c("/login?redirect=".concat(encodeURIComponent(i.pathname+i.search)))};t(!0);const s=g.parse(i.search.replace("?",""));if(s.user){o.admin.user.ssoLogin({username:s.user,ts:s.ts,sign:s.sign}).then(a=>{t(!1),a?r(!0):(r(!1),e())});return}o.admin.user.isLogin().then(a=>{a.login?(t(!1),r(a.permission)):(t(!1),e())})},[]),n.useEffect(()=>{u&&o.admin.user.getInfo().then(e=>{e.status==="SUCCESS"&&(e.data.menu?e.data.menuArray=JSON.parse(e.data.menu):e.data.menuArray=[])})},[u]),l?f.jsx(d,{spinning:!0,tip:"登录中...",size:"large",style:{height:"100vh",width:"100vw",display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"center"}}):f.jsx(h,{})}export{E as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
System.register(["./vendor-legacy-55fa76eb.js","./api-legacy-9fe41d4b.js","./index-legacy-06483318.js"],(function(e,n){"use strict";var t,s,r,a,i,u,c,o;return{setters:[e=>{t=e.u,s=e.e,r=e.r,a=e.Q,i=e.j,u=e.S,c=e.O},e=>{o=e.a},null],execute:function(){e("default",(function(){const e=t(),n=s(),[d,g]=r.useState(!0),[l,m]=r.useState(!1);return r.useEffect((()=>{if(!window.ENV.checkToken)return void g(!1);const t=()=>{e(`/login?redirect=${encodeURIComponent(n.pathname+n.search)}`)};g(!0);const s=a.parse(n.search.replace("?",""));s.user?o.admin.user.ssoLogin({username:s.user,ts:s.ts,sign:s.sign}).then((e=>{g(!1),e?m(!0):(m(!1),t())})):o.admin.user.isLogin().then((e=>{e.login?(g(!1),m(e.permission)):(g(!1),t())}))}),[]),r.useEffect((()=>{l&&o.admin.user.getInfo().then((e=>{"SUCCESS"===e.status&&(e.data.menu?e.data.menuArray=JSON.parse(e.data.menu):e.data.menuArray=[])}))}),[l]),d?i.jsx(u,{spinning:!0,tip:"登录中...",size:"large",style:{height:"100vh",width:"100vw",display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"center"}}):i.jsx(c,{})}))}}}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as C,u as I,r as c,j as e,C as w,Z as M,D as P,f as K,A as E,U as O,g as R,M as S,h as T,i as D,k as F,O as W,l as B,z as U,m as z,L}from"./vendor-
|
|
1
|
+
import{e as C,u as I,r as c,j as e,C as w,Z as M,D as P,f as K,A as E,U as O,g as R,M as S,h as T,i as D,k as F,O as W,l as B,z as U,m as z,L}from"./vendor-affac96c.js";import{g as $,R as A,P as V}from"./index-86214b19.js";import{a as Z}from"./api-beff4a3e.js";import{a as G}from"./theme-3a4c7290.js";const H="_background_1qfg6_10",J="_header_1qfg6_19",Q="_logo_1qfg6_28",X="_logotext_1qfg6_31",Y="_menus_1qfg6_36",ee="_menublock_1qfg6_45",se="_menublockimg_1qfg6_56",ne="_menublockselect_1qfg6_64",te="_userview_1qfg6_68",oe="_version_1qfg6_75",le="_screenEntry_1qfg6_85",ae="_bottom_1qfg6_101",ce="_slider_1qfg6_108",re="_menuWrap_1qfg6_115",ie="_menuIcon_1qfg6_127",de="_sliderFooter_1qfg6_139",ue="_collapseText_1qfg6_157",me="_right_1qfg6_160",he="_route_1qfg6_169",fe="_breadcrumb_1qfg6_175",ge="_contentview_1qfg6_178",t={background:H,header:J,logo:Q,logotext:X,menus:Y,menublock:ee,menublockimg:se,menublockselect:ne,userview:te,version:oe,screenEntry:le,bottom:ae,slider:ce,menuWrap:re,menuIcon:ie,sliderFooter:de,collapseText:ue,right:me,route:he,breadcrumb:fe,contentview:ge};B.locale(U);z.object;function je(){const i=C(),h=I(),j=c.useRef(),[f,v]=c.useState([]),[d,k]=c.useState(!1),[m,g]=c.useState({selectKeys:[],openKeys:[]}),y=c.useCallback(s=>e.jsx("a",{href:s.path,style:{color:"unset",fontSize:16},children:s.title},s.path),[]),_=c.useCallback((s,l)=>{const n=l&&s.selectIcon||s.icon;if(n)return typeof n!="string"?n:e.jsx("i",{className:t.menuIcon,style:{maskImage:"url(".concat(n,")"),WebkitMaskImage:"url(".concat(n,")")}})},[]);c.useEffect(()=>{const{routes:s}=$(i.pathname),l=[...s];l.pop(),g({openKeys:l.map(n=>n.fullPath),selectKeys:s.map(n=>n.fullPath)})},[i.pathname]),c.useEffect(()=>{var r,a;const s=A.find(o=>o.name==="系统"),l=(r=s==null?void 0:s.children)==null?void 0:r.find(o=>o.name==="后台管理系统"),n=(a=l==null?void 0:l.children)==null?void 0:a.find(o=>o.name==="后台管理系统");v((n==null?void 0:n.children)||[])},[]);const q=c.useMemo(()=>{const s=(l,n,r=[])=>l.filter(a=>a.name&&!a.hideInMenu).reduce((a,o)=>{var x;if(a)return a;const u=[...r,o];if(o.fullPath===n)return u;if((x=o.children)!=null&&x.length){const b=s(o.children,n,u);if(b)return b}return null},null);return s(f,i.pathname)||[]},[f,i.pathname]),p=(s,l=[])=>s.filter(n=>n.name&&!n.hideInMenu).map(n=>{var o;const r=m.selectKeys.includes(n.fullPath);return((o=n.children)==null?void 0:o.filter(u=>u.name&&!u.hideInMenu).length)>0?{key:n.fullPath,icon:_(n,r),label:n.name,children:p(n.children,[...l,n])}:{key:n.fullPath,icon:_(n,r),label:n.name}}),N=[{key:"logout",label:"退出系统",icon:e.jsx(L,{}),onClick:()=>{Z.admin.user.logout().then(s=>{s&&h("/login?redirect=".concat(encodeURIComponent(i.pathname+i.search)))})}}];return e.jsx(w,{locale:M,theme:G,children:e.jsx("div",{style:{width:"100vw",height:"100vh"},children:e.jsxs("div",{className:t.background,ref:j,children:[e.jsxs("div",{className:t.header,children:[e.jsx("img",{className:t.logo,alt:"logo",src:"/logo.svg"}),e.jsx("span",{className:t.logotext,children:V.title}),e.jsx("div",{style:{flex:1}}),e.jsxs("div",{className:t.userview,children:[e.jsx("span",{className:t.version,children:"v1.0.0"}),e.jsx("button",{type:"button",className:t.screenEntry,title:"进入大屏",onClick:()=>h("/screen"),children:e.jsx(P,{})}),e.jsx(K,{menu:{items:N},children:e.jsxs("div",{className:t.userInfo,children:[e.jsx(E,{style:{backgroundColor:"transparent",border:"2px solid #d1d1d1"},icon:e.jsx(O,{})}),e.jsx("span",{style:{margin:"0 10px"},children:"admin"}),e.jsx(R,{})]})})]})]}),e.jsxs("div",{className:t.bottom,children:[e.jsxs("div",{className:t.slider,style:{width:d?64:240},children:[e.jsx("div",{className:t.menuWrap,children:e.jsx(S,{mode:"inline",inlineCollapsed:d,items:p(f),onSelect:s=>{h(s.key)},selectedKeys:m.selectKeys,openKeys:d?[]:m.openKeys,onOpenChange:s=>{g({...m,openKeys:s})}})}),e.jsxs("div",{className:t.sliderFooter,onClick:()=>k(s=>!s),role:"button",tabIndex:0,children:[d?e.jsx(T,{}):e.jsx(D,{}),!d&&e.jsx("span",{className:t.collapseText,children:"收起菜单"})]})]}),e.jsxs("div",{className:t.right,children:[e.jsx("div",{className:t.route,children:e.jsx(F,{items:q.map(s=>({key:s.name,title:s.name,path:s.fullPath})),className:t.breadcrumb,itemRender:y})}),e.jsx("div",{className:t.contentview,children:e.jsx(W,{})})]})]})]})})})}export{je as default};
|