This commit is contained in:
zlwu committed 2026-08-08 22:19:09 +08:00
1 parent 683f14828f
commit fdf7d391a6
40 files changed
+6242 -19470

No files matched your search

+1
View File
@@ -54,6 +54,7 @@ backend/.ocr-venv/
*.key
*.p12
secrets/
生产机器账号密码
# 日志与临时文件
*.log
+1 -1
View File
@@ -1 +1 @@
49544
19962
+10 -12
View File
@@ -209,19 +209,17 @@ def refresh_token(body: dict, db: Session = Depends(get_db)):
@router.post("/web/auth/logout", response_model=dict)
def logout(request: Request):
"""登出:吊销 Redis 会话(若已配置 REDIS_HOST)。"""
"""登出:吊销 Redis 会话(若已配置 REDIS_HOST)。无 Token 或已失效时仍返回成功(幂等)。"""
authorization = request.headers.get("Authorization", "")
if not authorization.startswith("Bearer "):
return error_response(401, "未授权")
token = authorization.replace("Bearer ", "")
try:
payload = decode_token(token)
session_id = payload.get("sid")
if session_id:
revoke_session(session_id=session_id)
except HTTPException:
return error_response(401, "Token无效或已过期")
if authorization.startswith("Bearer "):
token = authorization.replace("Bearer ", "")
try:
payload = decode_token(token)
session_id = payload.get("sid")
if session_id:
revoke_session(session_id=session_id)
except HTTPException:
pass
return success_response({"message": "登出成功"})
@@ -0,0 +1,25 @@
# 生产一键发版脚本设计
日期:2026-08-08
脚本:`生产环境部署/deploy-release.sh`
## 目标
在 macOS 本机一键完成生产环境前后端更新:先前端后后端,发版前备份,保留远程 `web/dist/src`。
## 流程
1. 解析仓库根目录 `生产机器账号密码`(IP + root 密码)
2. 本地 `web-admin` 执行 `pnpm build`(可用 `--skip-build` 跳过)
3. 备份远程 `/iflytek/shanxia2.0/web` → `bak/web-YYYYMMDD-HHMMSS`
4. `rsync --delete --exclude 'src/'` 同步到 `web/dist`
5. 备份远程 `backend` → `bak/backend-YYYYMMDD-HHMMSS`
6. 删除远程 `backend/app`,同步本地 `backend/app`
7. `PORT=9111 WORKERS=1 ENV_FILE=./.env.production ./start-prod.sh restart`
8. `curl http://127.0.0.1:9111/health`
## 约束
- 凭据不写进脚本;`生产机器账号密码` 已加入 `.gitignore`
- 仅替换 `app/`,不覆盖远程 `venv` / `.env.production` / `logs` / `uploads`
- 依赖本机:`ssh`、`rsync`、`sshpass`、`pnpm`
+42 -42
View File
@@ -20,8 +20,8 @@
"semantic_hash": ""
},
"package.json": {
"mtime": 1785657009.9324844,
"ast_hash": "f84943886ed35640c67e7d9d11ad9857",
"mtime": 1786194935.77331,
"ast_hash": "c9645afa983ec0773f38202f0417fd4d",
"semantic_hash": ""
},
"public/tinymce/icons/default/icons.js": {
@@ -900,8 +900,8 @@
"semantic_hash": ""
},
"src/api/auth.ts": {
"mtime": 1783859000.0,
"ast_hash": "e613f5fd485fb55d09f96c609722655e",
"mtime": 1786197470.7104685,
"ast_hash": "00f91a528856880868cb7298323f5c4b",
"semantic_hash": ""
},
"src/api/bx/index.ts": {
@@ -1500,8 +1500,8 @@
"semantic_hash": ""
},
"src/components/business/product-form-dialog/index.vue": {
"mtime": 1786095022.967315,
"ast_hash": "2e3740ddea4872e7011ddced83ac9aba",
"mtime": 1786196563.5496323,
"ast_hash": "1a9f47895063a29d94d0083c220db835",
"semantic_hash": ""
},
"src/components/business/product-selector/index.vue": {
@@ -1685,8 +1685,8 @@
"semantic_hash": ""
},
"src/components/core/base/art-svg-icon/index.vue": {
"mtime": 1782858534.0,
"ast_hash": "2ff45e19c9d506f4120a786d4f38efb1",
"mtime": 1786194949.5663605,
"ast_hash": "272235c611dc0d356237d216cf7fd74b",
"semantic_hash": ""
},
"src/components/core/base/list-index-column/index.vue": {
@@ -1935,8 +1935,8 @@
"semantic_hash": ""
},
"src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts": {
"mtime": 1783256189.0,
"ast_hash": "ee54130b67d8f6b68f43b41ca8dd9537",
"mtime": 1786194954.6344905,
"ast_hash": "4912965c8644b354f58a653e990ed6d0",
"semantic_hash": ""
},
"src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts": {
@@ -2025,8 +2025,8 @@
"semantic_hash": ""
},
"src/components/core/list-toolbar-date-shortcuts/index.vue": {
"mtime": 1786181835.4696286,
"ast_hash": "26c69c9f5ab284a3f8cd73f824f0d22d",
"mtime": 1786197038.1514368,
"ast_hash": "746febcf60690e8e3178832b65bbe8f7",
"semantic_hash": ""
},
"src/components/core/media/art-cutter-img/index.vue": {
@@ -2365,8 +2365,8 @@
"semantic_hash": ""
},
"src/main.ts": {
"mtime": 1785690210.1346507,
"ast_hash": "3316a9eb4b350993c9ca3f1ed2b71ed5",
"mtime": 1786194951.3744397,
"ast_hash": "8307c8801d4f5f907496f2b876a71a10",
"semantic_hash": ""
},
"src/mock/json/chinaMap.json": {
@@ -2400,8 +2400,8 @@
"semantic_hash": ""
},
"src/router/core/MenuProcessor.ts": {
"mtime": 1786161771.6129146,
"ast_hash": "68bcc06072dbf23a066ac4c296160925",
"mtime": 1786195456.961378,
"ast_hash": "06ad320f46422b2664f9ddf0c01e9dd2",
"semantic_hash": ""
},
"src/router/core/RoutePermissionValidator.ts": {
@@ -2465,8 +2465,8 @@
"semantic_hash": ""
},
"src/router/modules/sqxp.ts": {
"mtime": 1786166018.5711956,
"ast_hash": "19c098bc6627247d0d22eb3f2277f194",
"mtime": 1786195447.3096144,
"ast_hash": "e97b601c3d1da7ff31f42613e5e05103",
"semantic_hash": ""
},
"src/router/routes/asyncRoutes.ts": {
@@ -2525,8 +2525,8 @@
"semantic_hash": ""
},
"src/store/modules/user.ts": {
"mtime": 1786164213.99407,
"ast_hash": "b4994b9206c5460048d403b6003e45ef",
"mtime": 1786197472.6410632,
"ast_hash": "f5743d88eca390d4eb987aaece9858fd",
"semantic_hash": ""
},
"src/store/modules/worktab.ts": {
@@ -2760,8 +2760,8 @@
"semantic_hash": ""
},
"src/utils/http/index.ts": {
"mtime": 1782858530.0,
"ast_hash": "f772026ad1b690269b612b38a8dfc2d8",
"mtime": 1786197473.6926188,
"ast_hash": "2ba44539229220f84e5ce589e60c8de0",
"semantic_hash": ""
},
"src/utils/http/portal-auth.ts": {
@@ -3055,8 +3055,8 @@
"semantic_hash": ""
},
"src/utils/ui/iconify-loader.ts": {
"mtime": 1782858528.0,
"ast_hash": "6742ca3f093bc765a1e8b4523daa96d6",
"mtime": 1786194970.003212,
"ast_hash": "b3ebab6f113784b8c5a9b585573f7866",
"semantic_hash": ""
},
"src/utils/ui/index.ts": {
@@ -3250,8 +3250,8 @@
"semantic_hash": ""
},
"src/views/crm/contract/components/ContractDetailDialog.vue": {
"mtime": 1786188825.3967483,
"ast_hash": "75282c90bb8e615a09df89beebf63b15",
"mtime": 1786196745.0045779,
"ast_hash": "ca3ce20832c2c30984038b9447d7dde9",
"semantic_hash": ""
},
"src/views/crm/contract/components/ContractFormDialog.vue": {
@@ -3265,8 +3265,8 @@
"semantic_hash": ""
},
"src/views/crm/contract/components/InvalidDialog.vue": {
"mtime": 1783167841.0,
"ast_hash": "b89c8be12419753a8467606ee771543e",
"mtime": 1786196151.567379,
"ast_hash": "f9cef0e753fa01ebc59cea7a47ac131e",
"semantic_hash": ""
},
"src/views/crm/contract/components/RenewDialog.vue": {
@@ -3570,18 +3570,18 @@
"semantic_hash": ""
},
"src/views/db/inventory/index.vue": {
"mtime": 1786186548.239029,
"ast_hash": "1b3249c7ddfda59e59470640ff079869",
"mtime": 1786197034.6730628,
"ast_hash": "8f43e8f844a0ad71aea848271e282c07",
"semantic_hash": ""
},
"src/views/db/plans/index.vue": {
"mtime": 1786180978.8021648,
"ast_hash": "3398d4465cc663dad313d3d7e789c226",
"mtime": 1786197034.6860974,
"ast_hash": "708627416dd8db8deb20afb37f49e681",
"semantic_hash": ""
},
"src/views/db/weighing/index.vue": {
"mtime": 1786186548.2393966,
"ast_hash": "b3518d3c8836ba9f70504677abfe720b",
"mtime": 1786197034.6689532,
"ast_hash": "3443fb83b0ded1c711fea82793e76ad8",
"semantic_hash": ""
},
"src/views/demo/category/index.vue": {
@@ -3635,8 +3635,8 @@
"semantic_hash": ""
},
"src/views/pl/_shared/IoOrderListPage.vue": {
"mtime": 1786180978.7263196,
"ast_hash": "1f7a434f5f01bbd8ac8a71b8465632cc",
"mtime": 1786197035.291659,
"ast_hash": "d6ba66f36fa4e2fec738d8233c6f3814",
"semantic_hash": ""
},
"src/views/pl/carrier/index.vue": {
@@ -3725,13 +3725,13 @@
"semantic_hash": ""
},
"src/views/pl/plan-archive/index.vue": {
"mtime": 1786180978.724036,
"ast_hash": "6a6d95b3b02297d54ec71269ee204e4a",
"mtime": 1786197035.7200282,
"ast_hash": "e753264e0e5f4cd0d2ac7ad4b306afb9",
"semantic_hash": ""
},
"src/views/pl/plan-cert/index.vue": {
"mtime": 1786180978.7093124,
"ast_hash": "69829b160d1890dce3bd8dfef5d492f8",
"mtime": 1786197037.217237,
"ast_hash": "8fef9e3eda810d83d6d37d086398e437",
"semantic_hash": ""
},
"src/views/pl/plan/arrange.vue": {
@@ -3750,8 +3750,8 @@
"semantic_hash": ""
},
"src/views/pl/plan/index.vue": {
"mtime": 1786180978.7139883,
"ast_hash": "06d7f9efec9c69889b5f9dd6d8effa67",
"mtime": 1786197036.7124588,
"ast_hash": "b21a0b880d17f4d39606e2e10e5568b0",
"semantic_hash": ""
},
"src/views/pl/self-vehicle/index.vue": {
+10 -6
View File
@@ -162,9 +162,9 @@
"160": "zh_CN.js",
"161": "Community 161",
"162": "Community 162",
"163": "pl-batch-print.ts",
"163": "saveNodes",
"164": "Community 164",
"165": "colors.ts",
"165": "handleDisburse",
"166": "Community 166",
"167": "Community 167",
"168": "loadDefaultCustomerOptions",
@@ -185,16 +185,22 @@
"183": "index.vue",
"184": "v2.4.2.1 - 2025-06-16",
"185": "zh_CN.js",
"186": "fetchData",
"186": "highlight.ts",
"187": "Community 187",
"188": "Community 188",
"189": "Community 189",
"190": "index.vue",
"191": "index.vue",
"191": "canDeleteByCreator",
"192": "loadUserOptions",
"193": "loadUserOptions",
"194": "detail.vue",
"195": "Community 195",
"196": "Community 196",
"197": "handleSearch",
"198": "loadUserOptions",
"199": "v2.4.2.3 - 2025-06-18",
"200": "index.js",
"201": "loading.ts",
"202": "Community 202",
"207": "Community 207",
"208": "Community 208",
@@ -504,7 +510,6 @@
"1224": "emojis.js",
"1225": "emojis.min.js",
"1226": "index.js",
"1227": "index.js",
"1228": "ar.js",
"1229": "bg_BG.js",
"1230": "ca.js",
@@ -594,7 +599,6 @@
"1318": "content.js",
"1319": "skin.js",
"1320": "skin.shadowdom.js",
"1322": "iconify-loader.ts",
"1323": "index.vue",
"1324": "index.vue",
"1325": "index.vue",
@@ -162,9 +162,9 @@
"160": "zh_CN.js",
"161": "Community 161",
"162": "Community 162",
"163": "pl-batch-print.ts",
"163": "saveNodes",
"164": "Community 164",
"165": "colors.ts",
"165": "handleDisburse",
"166": "Community 166",
"167": "Community 167",
"168": "loadDefaultCustomerOptions",
@@ -185,12 +185,24 @@
"183": "index.vue",
"184": "v2.4.2.1 - 2025-06-16",
"185": "zh_CN.js",
"186": "highlight.ts",
"187": "Community 187",
"188": "Community 188",
"189": "Community 189",
"190": "index.vue",
"191": "canDeleteByCreator",
"192": "loadUserOptions",
"193": "loadUserOptions",
"194": "detail.vue",
"195": "Community 195",
"196": "Community 196",
"197": "handleSearch",
"198": "loadUserOptions",
"199": "v2.4.2.3 - 2025-06-18",
"200": "index.js",
"201": "loading.ts",
"202": "Community 202",
"203": "tabs.ts",
"207": "Community 207",
"208": "Community 208",
"209": "Community 209",
@@ -436,7 +448,6 @@
"732": "Community 732",
"733": "Community 733",
"734": "修复",
"764": "新增",
"766": "新增",
"770": "移除",
"772": "重构",
@@ -500,7 +511,6 @@
"1224": "emojis.js",
"1225": "emojis.min.js",
"1226": "index.js",
"1227": "index.js",
"1228": "ar.js",
"1229": "bg_BG.js",
"1230": "ca.js",
@@ -590,7 +600,6 @@
"1318": "content.js",
"1319": "skin.js",
"1320": "skin.shadowdom.js",
"1322": "iconify-loader.ts",
"1323": "index.vue",
"1324": "index.vue",
"1325": "index.vue",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_json", "label": "package.json", "file_type": "code", "source_file": "package.json", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_name", "label": "name", "file_type": "code", "source_file": "package.json", "source_location": "L2"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_version", "label": "version", "file_type": "code", "source_file": "package.json", "source_location": "L3"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_description", "label": "description", "file_type": "code", "source_file": "package.json", "source_location": "L4"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_type", "label": "type", "file_type": "code", "source_file": "package.json", "source_location": "L5"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_engines", "label": "engines", "file_type": "code", "source_file": "package.json", "source_location": "L6"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_engines_node", "label": "node", "file_type": "code", "source_file": "package.json", "source_location": "L7"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_engines_pnpm", "label": "pnpm", "file_type": "code", "source_file": "package.json", "source_location": "L8"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts", "label": "scripts", "file_type": "code", "source_file": "package.json", "source_location": "L10"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_dev", "label": "dev", "file_type": "code", "source_file": "package.json", "source_location": "L11"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_build", "label": "build", "file_type": "code", "source_file": "package.json", "source_location": "L12"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_build_typecheck", "label": "build:typecheck", "file_type": "code", "source_file": "package.json", "source_location": "L13"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_serve", "label": "serve", "file_type": "code", "source_file": "package.json", "source_location": "L14"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_lint", "label": "lint", "file_type": "code", "source_file": "package.json", "source_location": "L15"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_fix", "label": "fix", "file_type": "code", "source_file": "package.json", "source_location": "L16"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_lint_prettier", "label": "lint:prettier", "file_type": "code", "source_file": "package.json", "source_location": "L17"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_lint_stylelint", "label": "lint:stylelint", "file_type": "code", "source_file": "package.json", "source_location": "L18"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_lint_lint_staged", "label": "lint:lint-staged", "file_type": "code", "source_file": "package.json", "source_location": "L19"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_prepare", "label": "prepare", "file_type": "code", "source_file": "package.json", "source_location": "L20"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_commit", "label": "commit", "file_type": "code", "source_file": "package.json", "source_location": "L21"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_scripts_clean_dev", "label": "clean:dev", "file_type": "code", "source_file": "package.json", "source_location": "L22"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_config", "label": "config", "file_type": "code", "source_file": "package.json", "source_location": "L24"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_config_commitizen", "label": "commitizen", "file_type": "code", "source_file": "package.json", "source_location": "L25"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_commitizen_path", "label": "path", "file_type": "code", "source_file": "package.json", "source_location": "L26"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_lint_staged", "label": "lint-staged", "file_type": "code", "source_file": "package.json", "source_location": "L29"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_lint_staged_js_ts_mjs_mts_tsx", "label": "*.{js,ts,mjs,mts,tsx}", "file_type": "code", "source_file": "package.json", "source_location": "L30"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_lint_staged_cjs_json_jsonc", "label": "*.{cjs,json,jsonc}", "file_type": "code", "source_file": "package.json", "source_location": "L34"}, {"id": "users_wuzilong_work_00code_shanxia2_0_web_admin_package_lint_staged_vue", "label": "*.vue", "fileLine truncated
+1 -1
View File
@@ -1 +1 @@
{"/Users/zlwu/work/00code/shanxia2.0/web-admin/package.json":{"size":3957,"mtime_ns":1781960299953649209,"hash":"ca01a16c94014e791391dfd8896f93c1ba7c451db1afa2f49f8a2a68b87dcc56"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/tsconfig.json":{"size":921,"mtime_ns":1780108931391506392,"hash":"8cec43256bc87216f729ae2c9588979558b20f33831419aa58502b54f5127c98"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/.vscode/settings.json":{"size":425,"mtime_ns":1780108931156918274,"hash":"0f2b6369eaf54ccac995e091d7ef5e292fa1396eadd32b64cbddd5bba0018d14"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/.vscode/extensions.json":{"size":200,"mtime_ns":1780108931156854566,"hash":"520c42e097d801a599814f12fe6a97bf5d7100e5395fd33217a365baa2cfe3c8"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/locales/langs/zh.json":{"size":10170,"mtime_ns":1783851637746348929,"hash":"015bc477ca647b32f2ef55c87db91ead797ec9db036de6dbf3578cd1be78de89"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/locales/langs/en.json":{"size":10312,"mtime_ns":1783851637976877140,"hash":"c46cb6e3ab9d55364fa79ce2ee42e46fb307f54350ee9abff7a71a74387c5982"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/mock/json/chinaMap.json":{"size":956443,"mtime_ns":1780108931366831877,"hash":"2ce3f532aed5b51fa9b64bf134bf1d77c7886e695fd55a3368333584cbef4113"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/assets/data/china-region-flat.json":{"size":341606,"mtime_ns":1780108931184708348,"hash":"120c09f381ee3ac8fc6282e0be885a72ff256093c5aebf7a0861e38185d5f8ae"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/assets/data/china-region-cascader.json":{"size":217969,"mtime_ns":1780108931184402180,"hash":"63789a66f6bd55e8adeda487f6e4f361f1a916c4fdb3e2d8961ee412de57d307"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/CHANGELOG.md":{"size":19526,"mtime_ns":1780108931156993275,"hash":"3e1cf79e220067ef1e99102d5cee89646591a03e292f7ca957e209ee264d416a"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/README.md":{"size":4196,"mtime_ns":1780108931157199359,"hash":"114c94f9bf11e2083f1c985b2a5f58c5fbc50334dfbd25438415f21f8f577150"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/CHANGELOG.zh-CN.md":{"size":17172,"mtime_ns":1780108931157071900,"hash":"f4ec5751d75c6d3ec0767fd9c1f5787cc96c0a1ab059ed199ae4de1f1959f9fc"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/README.zh-CN.md":{"size":3775,"mtime_ns":1780108931157266568,"hash":"75a5522b08799b17dfe4416d20153a9cd247f5cbf280fe04ed0858a8337dc2d7"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/README.md":{"size":7884,"mtime_ns":1780211534938564937,"hash":"43ee077b77166be37ebf44e0f13c8f01801f08a8274ebfa976ae21cbe8c27ca7"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-quote-vue.py":{"size":4450,"mtime_ns":1780242127112333277,"hash":"97378f52743089231fbc83430106cad7701aa59b045126af45a238d7d2878bac"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-index-shell.py":{"size":11102,"mtime_ns":1780228518737746875,"hash":"0557768a404e2a0d9f2d3b233599bd741bdeb61a8e2d3cadf8d8dc4f1d6be25d"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-vue.py":{"size":4673,"mtime_ns":1780228615418009691,"hash":"790b4297df996f03b45d2efda686e219d60cd8e0f796fd860c070ed2e13939d6"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-attachments.py":{"size":1708,"mtime_ns":1780229286334758010,"hash":"eb364a061d7f7a6659311604353cd64a53edc81c1f1413b626ddf0388d709c46"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-panel.py":{"size":5523,"mtime_ns":1780229250658746500,"hash":"c873cdb8f5e8d8339aec3f47dc840106e1a176e3c9ac210ddd6d0abe547d928a"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-attachments.py":{"size":14077,"mtime_ns":1780229316215427400,"hash":"470af77a8b9508291fab61aeee4c64bd495f78c77d09574e6b49962ce165eeaf"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-layout.py":{"size":5157,"mtime_ns":1780229581646441465,"hash":"d5d97158dd892bd942f61721bcae770de518de368f487c2a30129502b3125787"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-detail-fresh-mint.py":{"size":24615,"mtime_ns":1780229887010846627,"hash":"daf372506c69aa3a92a4ddf3d508252ae63b68718d00bb49fb03914cc8bcfee3"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/apply-list-toolbar-refresh.py":{"size":5777,"mtime_ns":1782834192989973152,"hash":"a2329df76d2aca29b6a7c751e43e61ec47b569718de910f3f10a2391d7b3bedb"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/add-list-index-columns.py":{"size":4644,"mtime_ns":1782865925817727372,"hash":"7e769c9dcb928cb8c4a5ce3eecebd488b94c4a2467959c99d8ec4cbed76bdb44"},"/Users/wuzilong/work/00code/shanxia2.0/web-admin/.vscode/extensions.json":{"size":200,"mtime_ns":1780108931000000000,"hash":"520c42e097d801a599814f12fe6a97bf5d7100e5395fd33217a365baa2cfe3c8"},"/Users/wuzilong/work/00code/shanxia2.0/web-admin/.vscode/settings.json":{"size":425,"mtime_ns":1780108931Line truncated
{"/Users/zlwu/work/00code/shanxia2.0/web-admin/package.json":{"size":3957,"mtime_ns":1781960299953649209,"hash":"ca01a16c94014e791391dfd8896f93c1ba7c451db1afa2f49f8a2a68b87dcc56"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/tsconfig.json":{"size":921,"mtime_ns":1780108931391506392,"hash":"8cec43256bc87216f729ae2c9588979558b20f33831419aa58502b54f5127c98"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/.vscode/settings.json":{"size":425,"mtime_ns":1780108931156918274,"hash":"0f2b6369eaf54ccac995e091d7ef5e292fa1396eadd32b64cbddd5bba0018d14"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/.vscode/extensions.json":{"size":200,"mtime_ns":1780108931156854566,"hash":"520c42e097d801a599814f12fe6a97bf5d7100e5395fd33217a365baa2cfe3c8"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/locales/langs/zh.json":{"size":10170,"mtime_ns":1783851637746348929,"hash":"015bc477ca647b32f2ef55c87db91ead797ec9db036de6dbf3578cd1be78de89"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/locales/langs/en.json":{"size":10312,"mtime_ns":1783851637976877140,"hash":"c46cb6e3ab9d55364fa79ce2ee42e46fb307f54350ee9abff7a71a74387c5982"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/mock/json/chinaMap.json":{"size":956443,"mtime_ns":1780108931366831877,"hash":"2ce3f532aed5b51fa9b64bf134bf1d77c7886e695fd55a3368333584cbef4113"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/assets/data/china-region-flat.json":{"size":341606,"mtime_ns":1780108931184708348,"hash":"120c09f381ee3ac8fc6282e0be885a72ff256093c5aebf7a0861e38185d5f8ae"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/assets/data/china-region-cascader.json":{"size":217969,"mtime_ns":1780108931184402180,"hash":"63789a66f6bd55e8adeda487f6e4f361f1a916c4fdb3e2d8961ee412de57d307"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/CHANGELOG.md":{"size":19526,"mtime_ns":1780108931156993275,"hash":"3e1cf79e220067ef1e99102d5cee89646591a03e292f7ca957e209ee264d416a"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/README.md":{"size":4196,"mtime_ns":1780108931157199359,"hash":"114c94f9bf11e2083f1c985b2a5f58c5fbc50334dfbd25438415f21f8f577150"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/CHANGELOG.zh-CN.md":{"size":17172,"mtime_ns":1780108931157071900,"hash":"f4ec5751d75c6d3ec0767fd9c1f5787cc96c0a1ab059ed199ae4de1f1959f9fc"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/README.zh-CN.md":{"size":3775,"mtime_ns":1780108931157266568,"hash":"75a5522b08799b17dfe4416d20153a9cd247f5cbf280fe04ed0858a8337dc2d7"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/README.md":{"size":7884,"mtime_ns":1780211534938564937,"hash":"43ee077b77166be37ebf44e0f13c8f01801f08a8274ebfa976ae21cbe8c27ca7"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-quote-vue.py":{"size":4450,"mtime_ns":1780242127112333277,"hash":"97378f52743089231fbc83430106cad7701aa59b045126af45a238d7d2878bac"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-index-shell.py":{"size":11102,"mtime_ns":1780228518737746875,"hash":"0557768a404e2a0d9f2d3b233599bd741bdeb61a8e2d3cadf8d8dc4f1d6be25d"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-vue.py":{"size":4673,"mtime_ns":1780228615418009691,"hash":"790b4297df996f03b45d2efda686e219d60cd8e0f796fd860c070ed2e13939d6"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-attachments.py":{"size":1708,"mtime_ns":1780229286334758010,"hash":"eb364a061d7f7a6659311604353cd64a53edc81c1f1413b626ddf0388d709c46"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-panel.py":{"size":5523,"mtime_ns":1780229250658746500,"hash":"c873cdb8f5e8d8339aec3f47dc840106e1a176e3c9ac210ddd6d0abe547d928a"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-attachments.py":{"size":14077,"mtime_ns":1780229316215427400,"hash":"470af77a8b9508291fab61aeee4c64bd495f78c77d09574e6b49962ce165eeaf"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-form-layout.py":{"size":5157,"mtime_ns":1780229581646441465,"hash":"d5d97158dd892bd942f61721bcae770de518de368f487c2a30129502b3125787"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/migrate-contract-detail-fresh-mint.py":{"size":24615,"mtime_ns":1780229887010846627,"hash":"daf372506c69aa3a92a4ddf3d508252ae63b68718d00bb49fb03914cc8bcfee3"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/apply-list-toolbar-refresh.py":{"size":5777,"mtime_ns":1782834192989973152,"hash":"a2329df76d2aca29b6a7c751e43e61ec47b569718de910f3f10a2391d7b3bedb"},"/Users/zlwu/work/00code/shanxia2.0/web-admin/scripts/add-list-index-columns.py":{"size":4644,"mtime_ns":1782865925817727372,"hash":"7e769c9dcb928cb8c4a5ce3eecebd488b94c4a2467959c99d8ec4cbed76bdb44"},"/Users/wuzilong/work/00code/shanxia2.0/web-admin/.vscode/extensions.json":{"size":200,"mtime_ns":1780108931000000000,"hash":"520c42e097d801a599814f12fe6a97bf5d7100e5395fd33217a365baa2cfe3c8"},"/Users/wuzilong/work/00code/shanxia2.0/web-admin/.vscode/settings.json":{"size":425,"mtime_ns":1780108931Line truncated
File diff suppressed because it is too large. Load diff
-13732
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -56,6 +56,7 @@
"dependencies": {
"@antv/x6": "^3.1.7",
"@element-plus/icons-vue": "^2.3.2",
"@iconify-json/ri": "^1.2.10",
"@iconify/vue": "^5.0.0",
"@tailwindcss/vite": "^4.1.14",
"@tinymce/tinymce-vue": "^6.3.0",
+16 -26
View File
@@ -14,6 +14,9 @@ importers:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.22(typescript@5.6.3))
'@iconify-json/ri':
specifier: ^1.2.10
version: 1.2.10
'@iconify/vue':
specifier: ^5.0.0
version: 5.0.0(vue@3.5.22(typescript@5.6.3))
@@ -761,6 +764,9 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@iconify-json/ri@1.2.10':
resolution: {integrity: sha512-WWMhoncVVM+Xmu9T5fgu2lhYRrKTEWhKk3Com0KiM111EeEsRLiASjpsFKnC/SrB6covhUp95r2mH8tGxhgd5Q==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -852,42 +858,36 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
@@ -964,67 +964,56 @@ packages:
resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.52.3':
resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.52.3':
resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.52.3':
resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.52.3':
resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.52.3':
resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.52.3':
resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.52.3':
resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.52.3':
resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.52.3':
resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.52.3':
resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openharmony-arm64@4.52.3':
resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==}
@@ -1099,28 +1088,24 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.1.14':
resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.1.14':
resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.1.14':
resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.1.14':
resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==}
@@ -1900,6 +1885,7 @@ packages:
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained.
css-functions-list@3.2.3:
resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
@@ -2492,6 +2478,7 @@ packages:
git-raw-commits@4.0.0:
resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==}
engines: {node: '>=16'}
deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
hasBin: true
glob-parent@5.1.2:
@@ -2504,7 +2491,7 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
@@ -2909,28 +2896,24 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.30.1:
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.30.1:
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.30.1:
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.30.1:
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
@@ -3889,6 +3872,7 @@ packages:
tar@7.5.1:
resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==}
engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
terser@5.44.0:
resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}
@@ -4068,6 +4052,7 @@ packages:
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
vant@4.9.24:
@@ -4178,6 +4163,7 @@ packages:
vue-i18n@9.14.5:
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
engines: {node: '>= 16'}
deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
peerDependencies:
vue: ^3.0.0
@@ -4873,6 +4859,10 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@iconify-json/ri@1.2.10':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
'@iconify/vue@5.0.0(vue@3.5.22(typescript@5.6.3))':
+5 -2
View File
@@ -83,10 +83,13 @@ export function fetchRefreshToken(refreshToken: string) {
/**
* 登出
* @param accessToken 可选:主动退出时传入当前 token,避免清空 store 后拦截器拿不到 Authorization
*/
export function fetchLogout() {
export function fetchLogout(accessToken?: string) {
return request.post({
url: '/web/auth/logout'
url: '/web/auth/logout',
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
showErrorMessage: false
})
}
@@ -31,7 +31,7 @@
<el-form-item label="产品名称" prop="productName">
<el-input v-model="formData.productName" placeholder="请输入产品名称" />
</el-form-item>
<el-form-item label="拼音首字母" class="form-col-span-2">
<el-form-item label="拼音首字母">
<div class="pinyin-abbr-row">
<el-input
v-model="formData.namePinyinAbbr"
@@ -7,7 +7,8 @@
</template>
<script setup lang="ts">
import { Icon } from '@iconify/vue'
// 使用 offline 构建,避免缺失图标时回退请求 api.unisvg.com
import { Icon } from '@iconify/vue/offline'
import { resolveEpIcon } from '@/utils/ui/ep-icon-map'
defineOptions({ name: 'ArtSvgIcon', inheritAttrs: false })
@@ -66,12 +66,12 @@ export function useSettingsConfig() {
{
value: ContainerWidthEnum.FULL,
label: t('setting.container.list[0]'),
icon: 'icon-park-outline:auto-width'
icon: 'ri:fullscreen-line'
},
{
value: ContainerWidthEnum.BOXED,
label: t('setting.container.list[1]'),
icon: 'ix:width'
icon: 'ri:aspect-ratio-line'
}
])
@@ -468,7 +468,10 @@
const wrap = scrollRef.value
const ul = tabsRef.value
if (!wrap || !ul) return false
return ul.scrollWidth > wrap.clientWidth + 1
// 布局未完成时 clientWidth 可能为 0,勿当作溢出(否则会误淘汰全部可关闭标签)
const wrapWidth = wrap.clientWidth
if (wrapWidth <= 0) return false
return ul.scrollWidth > wrapWidth + 1
}
// 生命周期
@@ -66,7 +66,7 @@
}>(),
{
ariaLabel: '日期快捷筛选',
variant: 'select'
variant: 'pills'
}
)
+1
View File
@@ -1,4 +1,5 @@
import './utils/patch-el-select-filterable'
import './utils/ui/iconify-loader' // 离线注册 Remix Icon,避免请求 Iconify CDN
import App from './App.vue'
import { createApp } from 'vue'
import { ElTable } from 'element-plus'
+9 -30
View File
@@ -60,39 +60,23 @@ export class MenuProcessor {
/** 确保 /home 启动器路由存在于菜单树(供动态注册) */
private ensurePortalHomeRoute(menuList: AppRouteRecord[]): AppRouteRecord[] {
const hasHome = menuList.some(
(m) =>
m.path === '/home-root' ||
m.name === 'PortalHomeRoot' ||
m.children?.some((c) => c.path === '/home' || c.name === 'PortalHome')
(m) => m.path === '/home' || m.name === 'PortalHome'
)
if (hasHome) {
return menuList
}
const homeRoute: AppRouteRecord = {
id: 'HOME00000000000000000000000000001',
name: 'PortalHomeRoot',
path: '/home-root',
component: '/index/index',
id: 'HOME00000000000000000000000000002',
name: 'PortalHome',
path: '/home',
component: '/portal/home/index',
meta: {
title: '首页',
keepAlive: false,
keepAlive: true,
fixedTab: true,
isHide: true
},
children: [
{
id: 'HOME00000000000000000000000000002',
name: 'PortalHome',
path: '/home',
component: '/portal/home/index',
meta: {
title: '首页',
keepAlive: true,
fixedTab: true,
isHide: true
}
}
]
}
}
return [homeRoute, ...menuList]
}
@@ -217,12 +201,7 @@ export class MenuProcessor {
): AppRouteRecord[] {
return menu.reduce((acc: AppRouteRecord[], item) => {
// 门户启动器首页不参与 menuCodes 过滤
if (
item.path === '/home-root' ||
item.path === '/home' ||
item.name === 'PortalHomeRoot' ||
item.name === 'PortalHome'
) {
if (item.path === '/home' || item.name === 'PortalHome') {
acc.push({ ...item })
return acc
}
+8 -22
View File
@@ -6,34 +6,20 @@ import type { AppRouteRecord } from '@/types/router'
*/
export const sqxpAdminRoutes: AppRouteRecord[] = [
// ──────────────────────────────────────────────
// 门户菜单启动器首页(不在业务菜单树中展示)
// 门户菜单启动器首页(一级叶子路由,由 Layout 包裹;不在业务菜单树中展示)
// ──────────────────────────────────────────────
{
id: 'HOME00000000000000000000000000001',
name: 'PortalHomeRoot',
path: '/home-root',
component: '/index/index',
id: 'HOME00000000000000000000000000002',
name: 'PortalHome',
path: '/home',
component: '/portal/home/index',
meta: {
title: '首页',
keepAlive: false,
keepAlive: true,
fixedTab: true,
isHide: true,
roles: ['admin', 'super_admin']
},
children: [
{
id: 'HOME00000000000000000000000000002',
name: 'PortalHome',
path: '/home',
component: '/portal/home/index',
meta: {
title: '首页',
keepAlive: true,
fixedTab: true,
isHide: true,
roles: ['admin', 'super_admin']
}
}
]
}
},
// ──────────────────────────────────────────────
+5 -3
View File
@@ -123,9 +123,11 @@ export const useUserStore = defineStore(
* 通知后端撤销 Redis 会话,然后清空所有用户相关状态并跳转到登录页
*/
const logOut = async () => {
// 先通知后端撤销 Redis 会话(不等待结果,失败也不阻塞退出)
if (accessToken.value) {
fetchLogout().catch(() => {})
// 先记下 token,再通知后端撤销会话(fire-and-forget);须显式带 Authorization,
// 否则清空 store 后请求拦截器可能发无 Token 请求,后端返回 401 并触发未授权提示
const tokenSnapshot = accessToken.value
if (tokenSnapshot) {
fetchLogout(tokenSnapshot).catch(() => {})
}
// 保存当前用户 ID,用于下次登录时判断是否为同一用户
+9 -5
View File
@@ -153,14 +153,18 @@ export const useWorktabStore = defineStore(
overflowCheckHandler.value = fn
}
/**
* 门户多标签:若 DOM 判定溢出,仅淘汰 1 个最早的可关闭标签。
*
* 不能在同步 while 中反复 checker():关闭后 Vue 尚未重绘,scrollWidth 仍是旧值,
* 会误判「持续溢出」并把所有可关闭标签一次清光(只剩固定首页)。
* ArtWorkTab 在 opened.length 变更后 nextTick 再调用,逐步收到刚好不溢出为止。
*/
const evictUntilNoOverflow = (): void => {
if (!isPortalMultiTabMode()) return
const checker = overflowCheckHandler.value
if (!checker) return
let guard = 0
while (checker() && guard++ < 50) {
if (!evictOldestClosableTab()) break
}
if (!checker || !checker()) return
if (!evictOldestClosableTab()) return
if (current.value.path && findTabIndex(String(current.value.path)) === -1) {
const fallback = opened.value[opened.value.length - 1]
if (fallback?.path) {
+2 -2
View File
@@ -163,10 +163,10 @@ axiosInstance.interceptors.request.use(
}
)
/** 登录/公钥等未持 Token 的认证接口:业务 401 不得走 refresh/登出 */
/** 登录/公钥/登出等认证接口:业务 401 不得走 refresh/再登出(主动退出时尤其勿弹「未授权」) */
function isPublicAuthApiUrl(url?: string): boolean {
if (!url) return false
return /\/web\/auth\/(login|login-rsa-public-key)(?:\?|$)/.test(url)
return /\/web\/auth\/(login|login-rsa-public-key|logout)(?:\?|$)/.test(url)
}
function isBlobResponseType(config?: AxiosRequestConfig): boolean {
+6 -18
View File
@@ -1,11 +1,11 @@
/**
* 离线图标加载器
*
* 用于在内网环境下支持 Iconify 图标的离线加载。
* 通过预加载图标集数据,避免运行时从 CDN 获取图标。
* 用于在内网/无法访问 Iconify CDN 的环境下支持 Iconify 图标。
* 通过预加载图标集数据,避免运行时请求 api.unisvg.com / api.iconify.design。
*
* 使用方式:
* 1. 安装所需图标集:pnpm add -D @iconify-json/[icon-set-name]
* 1. 安装所需图标集:pnpm add @iconify-json/[icon-set-name]
* 2. 在此文件中导入并注册图标集
* 3. 在组件中使用:<ArtSvgIcon icon="ri:home-line" />
*
@@ -13,19 +13,7 @@
* @author Art Design Pro Team
*/
// import { addCollection } from '@iconify/vue'
import { addCollection } from '@iconify/vue/offline'
import { icons as riIcons } from '@iconify-json/ri'
// // 导入离线图标数据
// // 系统必要图标库
// import riIcons from '@iconify-json/ri/icons.json'
// // 演示图标库(可选,生产环境可移除)
// import svgSpinners from '@iconify-json/svg-spinners/icons.json'
// import lineMd from '@iconify-json/line-md/icons.json'
// // 注册离线图标集
// addCollection(riIcons)
// addCollection(svgSpinners)
// addCollection(lineMd)
addCollection(riIcons)
@@ -22,12 +22,6 @@
size="large"
class="ml-2"
>{{ dictStore.getLabel('crm_contract_status', detailContract?.status) }}</el-tag>
<el-tag
v-if="detailContract?.approvalStatus"
:type="dictStore.getTagType('crm_approval_status', detailContract?.approvalStatus)"
size="large"
class="ml-2"
>{{ dictStore.getLabel('crm_approval_status', detailContract?.approvalStatus) }}</el-tag>
</div>
<div class="detail-actions">
<el-button
@@ -103,8 +103,7 @@ const submitLoading = ref(false)
const formRef = ref<ElFormInstance>()
const rules = ref({
invalidReason: [{ required: true, message: '请选择作废原因', trigger: 'change' }],
invalidRemark: [{ required: true, message: '请输入详细说明', trigger: 'blur' }]
invalidReason: [{ required: true, message: '请选择作废原因', trigger: 'change' }]
})
const hasPaymentOrInvoice = computed(() => paymentAmount.value > 0 || invoiceAmount.value > 0)
+1 -1
View File
@@ -53,7 +53,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row db-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="日期快捷筛选"
@change="handleDateShortcut"
+1 -1
View File
@@ -67,7 +67,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row db-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="日期快捷筛选"
@change="handleDateShortcut"
+1 -1
View File
@@ -51,7 +51,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row db-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="日期快捷筛选"
@change="handleDateShortcut"
@@ -43,7 +43,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row io-order-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="计划日期快捷筛选"
@change="handleDateShortcut"
@@ -75,7 +75,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row pl-plan-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="计划日期快捷筛选"
@change="handleDateShortcut"
+1 -1
View File
@@ -66,7 +66,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row pl-plan-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="计划日期快捷筛选"
@change="handleDateShortcut"
+1 -1
View File
@@ -80,7 +80,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row pl-plan-list-toolbar-row">
<ListToolbarDateShortcuts
variant="select"
variant="pills"
:active="activeDateShortcut"
aria-label="计划日期快捷筛选"
@change="handleDateShortcut"
+22 -7
View File
@@ -250,20 +250,34 @@ shanxia2.0 的 Nginx 写法**参照 CRM 的代理头、超时、Gzip、SPA fallb
## 6. 日常运维
### 6.1 更新后端
### 6.0 一键发版(推荐)
macOS 本机从仓库根目录执行(凭据读自根目录 `生产机器账号密码`,密码不写进脚本):
```bash
./生产环境部署/deploy-release.sh
# 已有 dist 时可跳过构建:
./生产环境部署/deploy-release.sh --skip-build
```
顺序:本地 `pnpm build` → 备份远程 `web` → 同步 `dist`(**保留**远程 `dist/src`)→ 备份远程 `backend` → 替换 `backend/app` → `PORT=9111 WORKERS=1 ENV_FILE=./.env.production ./start-prod.sh restart` → 健康检查。
备份落在 `/iflytek/shanxia2.0/bak/web-*` 与 `backend-*`。依赖:`ssh`、`rsync`、`sshpass`(`brew install hudochenkov/sshpass/sshpass`)、`pnpm`。
### 6.1 更新后端(手工)
```bash
cd /iflytek/shanxia2.0/backend
# 上传新代码后
./venv/bin/pip install -r requirements.txt
PORT=9111 ENV_FILE=./.env.production ./start-prod.sh restart
PORT=9111 WORKERS=1 ENV_FILE=./.env.production ./start-prod.sh restart
```
### 6.2 更新前端
### 6.2 更新前端(手工)
```bash
# 本地 build 后上传 dist 覆盖
rsync -avz --delete web-admin/dist/ root@121.43.228.102:/iflytek/shanxia2.0/web/dist/
# 本地 build 后上传 dist;务必保留远程 dist/src
rsync -avz --delete --exclude 'src/' web-admin/dist/ root@121.43.228.102:/iflytek/shanxia2.0/web/dist/
# 无需重启后端;浏览器强刷或清缓存
```
@@ -314,8 +328,8 @@ systemctl start shanxia2.0-backend
## 7. 回滚策略
1. **前端**:保留上一版 `dist` 备份目录,出问题直接 `rsync` 回滚
2. **后端**:保留上一版代码目录或 tar 包,`start-prod.sh restart`
1. **前端**:`/iflytek/shanxia2.0/bak/web-*` 中对应备份覆盖回 `web`(注意勿误删业务依赖的 `dist/src`)
2. **后端**:从 `bak/backend-*` 还原后,在 `backend` 目录执行 `PORT=9111 WORKERS=1 ENV_FILE=./.env.production ./start-prod.sh restart`
3. **数据库**:增量 SQL 应可逆或先备份;重大变更前 `mysqldump`
---
@@ -334,6 +348,7 @@ systemctl start shanxia2.0-backend
| 文件 | 用途 |
|---|---|
| `README.md` | 本文档 |
| `deploy-release.sh` | macOS 一键发版(前端→后端,含备份与健康检查) |
| `nginx-shanxia2.0.conf` | Nginx 站点模板 |
| `backend.env.production.example` | 后端生产环境变量模板 |
| `web-admin.env.production.example` | 前端构建环境变量模板 |
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env bash
# ============================================
# shanxia2.0 生产一键发版(前端 → 后端)
#
# 用法(在仓库任意目录执行均可):
# ./生产环境部署/deploy-release.sh
# ./生产环境部署/deploy-release.sh --skip-build # 跳过本地 pnpm build,使用已有 dist
#
# 凭据:仓库根目录「生产机器账号密码」
# 生产路径:
# 前端 /iflytek/shanxia2.0/web/dist (保留远程 dist/src,禁止删除)
# 后端 /iflytek/shanxia2.0/backend/app
# 备份 /iflytek/shanxia2.0/bak/
# ============================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CRED_FILE="${CRED_FILE:-$REPO_ROOT/生产机器账号密码}"
REMOTE_BASE="/iflytek/shanxia2.0"
REMOTE_WEB="$REMOTE_BASE/web"
REMOTE_DIST="$REMOTE_WEB/dist"
REMOTE_BACKEND="$REMOTE_BASE/backend"
REMOTE_BAK="$REMOTE_BASE/bak"
LOCAL_WEB_ADMIN="$REPO_ROOT/web-admin"
LOCAL_DIST="$LOCAL_WEB_ADMIN/dist"
LOCAL_APP="$REPO_ROOT/backend/app"
SSH_USER="root"
SSH_OPTS=(-o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30)
SKIP_BUILD=0
for arg in "$@"; do
case "$arg" in
--skip-build) SKIP_BUILD=1 ;;
-h|--help)
sed -n '2,16p' "$0" | sed 's/^# \?//'
exit 0
;;
*)
echo "未知参数: $arg(支持 --skip-build)" >&2
exit 1
;;
esac
done
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die() {
log_err "$@"
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1。$2"
}
# ---------- 前置检查 ----------
require_cmd ssh "macOS 系统自带即可。"
require_cmd rsync "macOS 系统自带即可。"
require_cmd sshpass "请执行: brew install hudochenkov/sshpass/sshpass"
[[ -f "$CRED_FILE" ]] || die "凭据文件不存在: $CRED_FILE"
[[ -d "$LOCAL_APP" ]] || die "本地后端不存在: $LOCAL_APP"
# ---------- 解析凭据 ----------
# 支持:
# ip: 1.2.3.4
# root账号:xxx / root密码:xxx / 密码:xxx
parse_credentials() {
local ip="" pass=""
local line key val
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" || "$line" == \#* ]] && continue
# 勿用 [::] 字符类:bash 会把全角冒号与 : 当成范围,误匹配中文
if [[ "$line" == *:* ]]; then
key="${line%%:*}"
val="${line#*:}"
elif [[ "$line" == *:* ]]; then
key="${line%%:*}"
val="${line#*:}"
else
continue
fi
key="${key//[[:space:]]/}"
val="${val#"${val%%[![:space:]]*}"}"
val="${val%"${val##*[![:space:]]}"}"
case "$key" in
ip|IP|host|Host|HOST) ip="$val" ;;
root账号|root密码|密码|password|Password|PASSWORD|passwd) pass="$val" ;;
esac
done < "$CRED_FILE"
[[ -n "$ip" ]] || die "凭据文件未解析到 IP(期望行如: ip: 121.x.x.x)"
[[ -n "$pass" ]] || die "凭据文件未解析到密码(期望行如: root账号:***)"
REMOTE_HOST="$ip"
REMOTE_PASS="$pass"
}
parse_credentials
log_info "目标主机: ${SSH_USER}@${REMOTE_HOST}"
export SSHPASS="$REMOTE_PASS"
trap 'unset SSHPASS REMOTE_PASS 2>/dev/null || true' EXIT
remote_ssh() {
sshpass -e ssh "${SSH_OPTS[@]}" "${SSH_USER}@${REMOTE_HOST}" "$@"
}
remote_rsync() {
# 用法: remote_rsync [rsync-args...] src dest
sshpass -e rsync -e "ssh ${SSH_OPTS[*]}" "$@"
}
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
GIT_SHA="$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
log_info "发版时间戳: $TIMESTAMP commit: $GIT_SHA"
# ---------- 1. 本地构建前端 ----------
if [[ "$SKIP_BUILD" -eq 1 ]]; then
log_warn "已跳过 pnpm build,使用现有 dist"
else
require_cmd pnpm "请安装 pnpm: npm i -g pnpm"
[[ -f "$LOCAL_WEB_ADMIN/.env.production" ]] || log_warn "未找到 web-admin/.env.production,请确认生产构建配置正确"
log_info "开始构建前端: cd web-admin && pnpm build"
(
cd "$LOCAL_WEB_ADMIN"
pnpm build
)
fi
[[ -d "$LOCAL_DIST" ]] || die "前端产物不存在: $LOCAL_DIST(请先构建或去掉 --skip-build)"
[[ -f "$LOCAL_DIST/index.html" ]] || die "前端产物异常: 缺少 $LOCAL_DIST/index.html"
# ---------- 2. 备份远程 web ----------
log_info "备份远程 web → ${REMOTE_BAK}/web-${TIMESTAMP}"
remote_ssh "mkdir -p '${REMOTE_BAK}' && \
if [[ -d '${REMOTE_WEB}' ]]; then \
cp -a '${REMOTE_WEB}' '${REMOTE_BAK}/web-${TIMESTAMP}'; \
else \
echo '远程 web 目录不存在,跳过备份' >&2; \
exit 1; \
fi"
# ---------- 3. 同步前端 dist(保留远程 dist/src)----------
log_info "同步前端到 ${REMOTE_DIST}(rsync --delete,排除 src/)"
remote_ssh "mkdir -p '${REMOTE_DIST}'"
# --exclude 'src/':不同步也不删除远程 src
# --delete:删除远程 dist 中本地已不存在的构建文件(不含被 exclude 的 src)
remote_rsync -az --delete --exclude 'src/' --exclude '.DS_Store' \
"${LOCAL_DIST}/" \
"${SSH_USER}@${REMOTE_HOST}:${REMOTE_DIST}/"
remote_ssh "[[ -d '${REMOTE_DIST}/src' ]] && echo 'OK: dist/src 仍存在' || echo 'NOTE: 远程 dist/src 当前不存在(若业务依赖请检查)'"
log_info "前端同步完成"
# ---------- 4. 备份远程 backend ----------
log_info "备份远程 backend → ${REMOTE_BAK}/backend-${TIMESTAMP}"
remote_ssh "mkdir -p '${REMOTE_BAK}' && \
if [[ -d '${REMOTE_BACKEND}' ]]; then \
cp -a '${REMOTE_BACKEND}' '${REMOTE_BAK}/backend-${TIMESTAMP}'; \
else \
echo '远程 backend 目录不存在,跳过备份' >&2; \
exit 1; \
fi"
# ---------- 5. 替换 backend/app,并同步依赖清单 ----------
log_info "替换远程 ${REMOTE_BACKEND}/app"
remote_ssh "rm -rf '${REMOTE_BACKEND}/app'"
remote_rsync -az --delete \
--exclude '__pycache__/' \
--exclude '*.pyc' \
--exclude '.DS_Store' \
"${LOCAL_APP}/" \
"${SSH_USER}@${REMOTE_HOST}:${REMOTE_BACKEND}/app/"
if [[ -f "$REPO_ROOT/backend/requirements.txt" ]]; then
log_info "同步 requirements.txt 并安装依赖"
remote_rsync -az \
"$REPO_ROOT/backend/requirements.txt" \
"${SSH_USER}@${REMOTE_HOST}:${REMOTE_BACKEND}/requirements.txt"
remote_ssh "cd '${REMOTE_BACKEND}' && \
./venv/bin/pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple"
fi
log_info "后端 app 同步完成"
# ---------- 6. 重启后端 ----------
log_info "重启后端服务 (PORT=9111 WORKERS=1)"
remote_ssh "cd '${REMOTE_BACKEND}' && \
chmod +x ./start-prod.sh && \
PORT=9111 WORKERS=1 ENV_FILE=./.env.production ./start-prod.sh restart"
# ---------- 7. 健康检查(重启后进程可能尚未监听,重试数秒)----------
log_info "健康检查 /health"
HEALTH_OUT=""
for _i in 1 2 3 4 5 6 7 8 9 10; do
HEALTH_OUT="$(remote_ssh "curl -fsS --max-time 5 http://127.0.0.1:9111/health" 2>/dev/null || true)"
if [[ "$HEALTH_OUT" == *healthy* ]]; then
break
fi
sleep 2
done
if [[ "$HEALTH_OUT" == *healthy* ]]; then
log_info "健康检查通过: $HEALTH_OUT"
else
log_err "健康检查未通过,远程输出: ${HEALTH_OUT:-<empty>}"
log_warn "可手动排查: ssh ${SSH_USER}@${REMOTE_HOST} 'cd ${REMOTE_BACKEND} && ./start-prod.sh status|logs'"
log_warn "备份位置: ${REMOTE_BAK}/web-${TIMESTAMP} ${REMOTE_BAK}/backend-${TIMESTAMP}"
exit 1
fi
unset SSHPASS REMOTE_PASS
echo
log_info "发版成功"
echo " commit: $GIT_SHA"
echo " 前端备份: ${REMOTE_BAK}/web-${TIMESTAMP}"
echo " 后端备份: ${REMOTE_BAK}/backend-${TIMESTAMP}"
echo " 管理端: http://${REMOTE_HOST}:9911"
echo