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.
Files changed (99) hide show
  1. package/index.js +10 -3
  2. package/package.json +1 -1
  3. package/secrets.js +10 -7
  4. package/templates/backend/Dockerfile +4 -0
  5. package/templates/backend/config/config.develop.yaml +10 -0
  6. package/templates/backend/config/config.prod.yaml +10 -0
  7. package/templates/backend/dist/config/configuration.js +1 -1
  8. package/templates/backend/dist/config/configuration.js.map +1 -1
  9. package/templates/backend/dist/modules/auth/auth.service.js +1 -1
  10. package/templates/backend/dist/modules/auth/auth.service.js.map +1 -1
  11. package/templates/backend/dist/modules/page-data/page-data.controller.js +1 -1
  12. package/templates/backend/dist/modules/page-data/page-data.controller.js.map +1 -1
  13. package/templates/backend/dist/modules/screen-config/screen-config.controller.js +1 -1
  14. package/templates/backend/dist/modules/screen-config/screen-config.controller.js.map +1 -1
  15. package/templates/backend/dist/tsconfig.build.tsbuildinfo +1 -1
  16. package/templates/backend/node_modules/.bin/js-yaml +2 -2
  17. package/templates/backend/node_modules/.bin/typeorm +2 -2
  18. package/templates/backend/node_modules/.bin/typeorm-ts-node-commonjs +2 -2
  19. package/templates/backend/node_modules/.bin/typeorm-ts-node-esm +2 -2
  20. package/templates/backend/ops/docker/.env.example +4 -0
  21. package/templates/backend/ops/docker/config.sh +2 -0
  22. package/templates/backend/ops/docker/install.sh +35 -0
  23. package/templates/backend/ops/install.sh +17 -3
  24. package/templates/backend/ops/pm2/config.sh +2 -0
  25. package/templates/backend/ops/systemd/config.sh +2 -0
  26. package/templates/backend/package.json +13 -6
  27. package/templates/backend/src/config/config.interface.ts +9 -0
  28. package/templates/backend/src/config/configuration.ts +11 -0
  29. package/templates/backend/src/modules/auth/auth.service.ts +57 -12
  30. package/templates/backend/src/modules/page-data/page-data.controller.ts +33 -10
  31. package/templates/backend/src/modules/screen-config/screen-config.controller.ts +33 -5
  32. package/templates/root/README.md +3 -0
  33. package/templates/root/package.json +9 -1
  34. package/templates/web/dist/assets/{404-63aa5037.js → 404-e6edada0.js} +1 -1
  35. package/templates/web/dist/assets/{404-legacy-494c4093.js → 404-legacy-f6ca5b91.js} +1 -1
  36. package/templates/web/dist/assets/AuthLayout-bcf98efb.js +1 -0
  37. package/templates/web/dist/assets/AuthLayout-legacy-6e7a8ae1.js +1 -0
  38. package/templates/web/dist/assets/{Background-13406ae6.js → Background-eb77ef54.js} +1 -1
  39. package/templates/web/dist/assets/{Background-legacy-35ca5eef.js → Background-legacy-50457007.js} +1 -1
  40. package/templates/web/dist/assets/{Screen-4310b270.js → Screen-318c3fc2.js} +1 -1
  41. package/templates/web/dist/assets/{Screen-legacy-d5f1a31f.js → Screen-legacy-af123b0f.js} +1 -1
  42. package/templates/web/dist/assets/{ScreenChartOption-a6927065.js → ScreenChartOption-953b9157.js} +1 -1
  43. package/templates/web/dist/assets/{ScreenChartOption-legacy-b4c04ce9.js → ScreenChartOption-legacy-2e8fcfd8.js} +1 -1
  44. package/templates/web/dist/assets/api-beff4a3e.js +1 -0
  45. package/templates/web/dist/assets/api-legacy-9fe41d4b.js +1 -0
  46. package/templates/web/dist/assets/{base-5bf689c3.js → base-a14309c8.js} +1 -1
  47. package/templates/web/dist/assets/{base-legacy-d3323277.js → base-legacy-2bc6e427.js} +1 -1
  48. package/templates/web/dist/assets/{child-87a05c88.js → child-2a48ca65.js} +1 -1
  49. package/templates/web/dist/assets/{child-legacy-d4d0277d.js → child-legacy-7665b5f8.js} +1 -1
  50. package/templates/web/dist/assets/{component-1fcd971d.js → component-89b5253e.js} +1 -1
  51. package/templates/web/dist/assets/{component-legacy-943364e8.js → component-legacy-8c5a28ef.js} +1 -1
  52. package/templates/web/dist/assets/{demo-14e7532b.js → demo-d7272cf4.js} +1 -1
  53. package/templates/web/dist/assets/{demo-legacy-da0d2bfa.js → demo-legacy-f7bbfe55.js} +1 -1
  54. package/templates/web/dist/assets/echarts-vendor-50090982.js +9 -0
  55. package/templates/web/dist/assets/echarts-vendor-legacy-50b58722.js +10 -0
  56. package/templates/web/dist/assets/{grandson-4f840b4d.js → grandson-05f8bb58.js} +1 -1
  57. package/templates/web/dist/assets/{grandson-legacy-527ae016.js → grandson-legacy-3e10990b.js} +1 -1
  58. package/templates/web/dist/assets/{index-268d1ac1.js → index-466a1ec1.js} +1 -1
  59. package/templates/web/dist/assets/{index-c1ee2fb3.js → index-5b5a8146.js} +1 -1
  60. package/templates/web/dist/assets/{index-3a2983ce.js → index-7277cbfb.js} +1 -1
  61. package/templates/web/dist/assets/{index-e03d0352.js → index-777ef781.js} +1 -1
  62. package/templates/web/dist/assets/{index-e4c9d42a.js → index-86214b19.js} +1 -1
  63. package/templates/web/dist/assets/{index-f57c9694.js → index-9cd5622a.js} +1 -1
  64. package/templates/web/dist/assets/{index-71f600d4.js → index-a79e448b.js} +1 -1
  65. package/templates/web/dist/assets/{index-af61b79b.js → index-b60badf7.js} +1 -1
  66. package/templates/web/dist/assets/{index-b75948c0.js → index-bd4ed808.js} +1 -1
  67. package/templates/web/dist/assets/{index-9120f091.js → index-cba19800.js} +1 -1
  68. package/templates/web/dist/assets/{index-3448ef35.js → index-d70eb540.js} +1 -1
  69. package/templates/web/dist/assets/{index-5e7dfdb5.js → index-e6071630.js} +1 -1
  70. package/templates/web/dist/assets/{index-legacy-081b881d.js → index-legacy-028ed5d5.js} +1 -1
  71. package/templates/web/dist/assets/{index-legacy-aebba3cb.js → index-legacy-06483318.js} +1 -1
  72. package/templates/web/dist/assets/{index-legacy-548904e6.js → index-legacy-0da8de82.js} +1 -1
  73. package/templates/web/dist/assets/{index-legacy-376a4633.js → index-legacy-3965dcaf.js} +1 -1
  74. package/templates/web/dist/assets/{index-legacy-5f22daba.js → index-legacy-4a13dddc.js} +1 -1
  75. package/templates/web/dist/assets/{index-legacy-0983aa0f.js → index-legacy-783129a7.js} +1 -1
  76. package/templates/web/dist/assets/{index-legacy-06643c96.js → index-legacy-8a2cfb9d.js} +1 -1
  77. package/templates/web/dist/assets/{index-legacy-8bba1715.js → index-legacy-993ef005.js} +1 -1
  78. package/templates/web/dist/assets/{index-legacy-19a8ac10.js → index-legacy-b075295d.js} +1 -1
  79. package/templates/web/dist/assets/{index-legacy-b03d819e.js → index-legacy-b3bd40c1.js} +1 -1
  80. package/templates/web/dist/assets/{index-legacy-35c1de97.js → index-legacy-d510e318.js} +1 -1
  81. package/templates/web/dist/assets/{index-legacy-3a393896.js → index-legacy-feb54578.js} +1 -1
  82. package/templates/web/dist/assets/{parent-ad02830b.js → parent-bba709aa.js} +1 -1
  83. package/templates/web/dist/assets/{parent-legacy-163f567f.js → parent-legacy-48111978.js} +1 -1
  84. package/templates/web/dist/assets/vendor-affac96c.js +124 -0
  85. package/templates/web/dist/assets/vendor-legacy-55fa76eb.js +122 -0
  86. package/templates/web/dist/index.html +3 -3
  87. package/templates/web/package.json +7 -7
  88. package/templates/web/src/js/xlsl.ts +110 -59
  89. package/templates/web/src/layouts/AuthLayout.tsx +16 -11
  90. package/templates/web/src/services/api/admin/user.ts +9 -2
  91. package/templates/web/dist/assets/AuthLayout-07e9f5d2.js +0 -1
  92. package/templates/web/dist/assets/AuthLayout-legacy-1b94749f.js +0 -1
  93. package/templates/web/dist/assets/api-83561db2.js +0 -1
  94. package/templates/web/dist/assets/api-legacy-8e15c853.js +0 -1
  95. package/templates/web/dist/assets/echarts-vendor-d6ce4b9c.js +0 -9
  96. package/templates/web/dist/assets/echarts-vendor-legacy-79597af6.js +0 -10
  97. package/templates/web/dist/assets/vendor-2663c204.js +0 -96
  98. package/templates/web/dist/assets/vendor-legacy-2df23db3.js +0 -87
  99. 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.1.0/node_modules/js-yaml/bin/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.1.0/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
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.1.0/node_modules/js-yaml/bin/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/js-yaml@4.1.0/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules:$NODE_PATH"
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.20_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.20_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
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.20_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.20_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"
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.20_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.20_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
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.20_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.20_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"
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.20_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.20_better-sqlite3@13.0.3_ts-node@10.9.2/node_modules:/Users/sunset/Desktop/YunZhiHuiProgram/fullstack-template/node_modules/.pnpm/node_modules"
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.20_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.20_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"
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" "$@"
@@ -32,3 +32,7 @@ LOG_LEVEL=warn
32
32
  ADMIN_PASSWORD=
33
33
  APP_SIGN_KEY=
34
34
  APP_SIGN_TS_WINDOW=300
35
+ # 单点登录(默认关闭;接入时改 true 并配 ticketKey,见 README「单点登录」)
36
+ SSO_ENABLED=false
37
+ SSO_TICKET_KEY=
38
+ SSO_TS_WINDOW=300
@@ -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
- KEEP_VERSIONS="${KEEP_VERSIONS:-3}"
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
- [[ "$IS_SYMLINK" = 0 ]] && return 0 # docker 不用 releases
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.1",
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.1.0",
41
- "multer": "1.4.5-lts.1",
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.3",
46
+ "socket.io": "4.8.4",
48
47
  "svg-captcha": "1.4.0",
49
- "typeorm": "0.3.20"
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
- const ok = user.password.startsWith('$2')
60
- ? await bcrypt.compare(params.password, user.password)
61
- : params.password === user.password;
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(params: { [key: string]: any }, ip?: string) {
94
- if (!params || Object.keys(params).length === 0) {
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: params?.username || 'sso',
121
+ actorName: username || 'sso',
97
122
  action: 'login_fail',
98
- detail: { reason: '缺少 sso 参数' },
123
+ detail: { reason },
99
124
  ip
100
125
  });
101
- throw new UnauthorizedException('缺少 sso 参数');
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
- const username = params.username || 'sso';
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, params: Object.keys(params) },
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
- UseInterceptors
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
- /** 列表 /api/getList */
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 { Controller, Get, Param, Body, Put } from '@nestjs/common';
2
- import { ApiTags } from '@nestjs/swagger';
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
- /** 大屏清单 /api/screen/list */
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(@Param('key') key: string, @Body() dto: Partial<ScreenConfigEntity>) {
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
  }
@@ -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-2663c204.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
+ 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-2df23db3.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:"返回上一页"})]})]})})}))}}}));
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-2663c204.js";import{g as $,R as A,P as V}from"./index-e4c9d42a.js";import{a as Z}from"./api-83561db2.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};
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};