Commit 6dac75af authored by Lily Wang's avatar Lily Wang

update

parents
VITE_APP_ENV=development
VITE_MESSAGE_BASE_URL=https://api-staging.cmviking.com/message
VITE_STREAMS_TYPE=ticket
VITE_STREAMS_TICKET=cydz5uLSWULm1gu5ACUWEXor1mrrBzr0
VITE_MIDDLE_QUERY_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_MIDDLE_CREATE_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_USE_MOCK=false
VITE_MESSAGE_BASE_URL=https://api.cmviking.com/message
VITE_APP_ENV=production
VITE_STREAMS_BASE_URL=https://streams.vikingcruises.cn/api/
VITE_STREAMS_TYPE=ticket
VITE_STREAMS_TICKET=ODJuvaIno8PUnnHhCCZSuFpJVD7ZJCJh
VITE_MIDDLE_QUERY_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_MIDDLE_CREATE_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_USE_MOCK=false
VITE_APP_ENV=production
VITE_MESSAGE_BASE_URL=https://api.cmviking.com/message
VITE_STREAMS_BASE_URL=https://streams.vikingcruises.cn/api/
VITE_STREAMS_TYPE=ticket
VITE_STREAMS_TICKET=ODJuvaIno8PUnnHhCCZSuFpJVD7ZJCJh
VITE_MIDDLE_QUERY_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_MIDDLE_CREATE_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_USE_MOCK=false
VITE_APP_ENV=staging
VITE_MESSAGE_BASE_URL=https://api-staging.cmviking.com/message
VITE_STREAMS_BASE_URL=https://viking-test.streams7.com/api/
VITE_STREAMS_TYPE=ticket
VITE_STREAMS_TICKET=cydz5uLSWULm1gu5ACUWEXor1mrrBzr0
VITE_MIDDLE_QUERY_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_MIDDLE_CREATE_DEVIATION_PATH=/client/salesforce/call/DeviationAgreementService
VITE_USE_MOCK=false
node_modules
dist
.env
.DS_Store
# River Refunds React App
从 `page-river-refunds.php` 抽出的独立 React 页面。
## 本地运行
```bash
npm install
npm run dev
```
访问:`http://localhost:5173/?id=PASSENGER_ID`
连接测试环境:
```bash
npm run dev:staging
```
连接生产环境:
```bash
npm run dev:production
```
## 接口配置
项目只走中台接口,鉴权方式参考 `confirmation/src/services/streamsRequest.js`。
默认行程调整接口:
- `GET /client/salesforce/call/DeviationAgreementService?PassengerID=xxx`
- `POST /client/salesforce/call/DeviationAgreementService`
本地开发时 Vite 会把 `/api2` 代理到测试中台;生产环境默认使用 `https://streams.vikingcruises.cn/api/`。
## 打包部署
应用按 `/river-refunds/` 子路径打包,静态资源和 manifest 都会使用这个 base。
```bash
npm run build:staging
npm run build:production
```
产物目录:`dist/`
默认不走 mock 数据。只有需要本地演示交互时,才设置:
```bash
VITE_USE_MOCK=true
```
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0d1c34" />
<meta name="description" content="维京游轮行程调整确认函" />
<link rel="manifest" href="/river-refunds/manifest.json" />
<title>维京游轮行程调整确认函</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
{
"name": "river-refunds",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@vitejs/plugin-react": "latest",
"react": "latest",
"react-dom": "latest",
"vite": "latest"
},
"devDependencies": {}
},
"node_modules/@emnapi/core": {
"version": "1.11.1",
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz",
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.1",
"resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz",
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.3"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@oxc-project/types": {
"version": "0.138.0",
"resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.138.0.tgz",
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.11.1",
"@napi-rs/wasm-runtime": "^1.1.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.3",
"resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
"integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "^1.0.1"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
"babel-plugin-react-compiler": "^1.0.0",
"vite": "^8.0.0"
},
"peerDependenciesMeta": {
"@rolldown/plugin-babel": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
}
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/nanoid": {
"version": "3.3.15",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz",
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.16",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.16.tgz",
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "19.2.7",
"resolved": "https://registry.npmmirror.com/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.7",
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.7"
}
},
"node_modules/rolldown": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.4.tgz",
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.138.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.1.4",
"@rolldown/binding-darwin-arm64": "1.1.4",
"@rolldown/binding-darwin-x64": "1.1.4",
"@rolldown/binding-freebsd-x64": "1.1.4",
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
"@rolldown/binding-linux-arm64-musl": "1.1.4",
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
"@rolldown/binding-linux-x64-gnu": "1.1.4",
"@rolldown/binding-linux-x64-musl": "1.1.4",
"@rolldown/binding-openharmony-arm64": "1.1.4",
"@rolldown/binding-wasm32-wasi": "1.1.4",
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
"@rolldown/binding-win32-x64-msvc": "1.1.4"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"optional": true
},
"node_modules/vite": {
"version": "8.1.3",
"resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.3.tgz",
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.16",
"rolldown": "~1.1.3",
"tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.3.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"@vitejs/devtools": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
}
}
}
{
"scripts": {
"dev": "vite --host 0.0.0.0 --port 5173 --strictPort",
"dev:development": "vite --mode development --host 0.0.0.0 --port 5173 --strictPort",
"dev:staging": "vite --mode staging --host 0.0.0.0 --port 5173 --strictPort",
"dev:production": "vite --mode production --host 0.0.0.0 --port 5173 --strictPort",
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:production": "vite build --mode production",
"preview": "vite preview --host 0.0.0.0 --port 4173"
},
"dependencies": {
"@vitejs/plugin-react": "latest",
"vite": "latest",
"react": "latest",
"react-dom": "latest"
},
"devDependencies": {}
}
{
"name": "River Refunds",
"version": "1.0.0",
"build": "river-refunds",
"description": "Viking river refunds confirmation app",
"date": "2026-07-08",
"author": "Viking"
}
import { useEffect, useMemo, useState } from 'react';
import {
createDeviationService,
fetchDeviationPageData,
sendSmsCode,
useMock,
verifySmsCode,
} from './api.js';
import { mockOrder, mockTerms } from './mockData.js';
const assetBaseUrl = import.meta.env.BASE_URL;
function assetUrl(path) {
return `${assetBaseUrl}${path.replace(/^\//, '')}`;
}
function getPassengerId() {
return new URLSearchParams(window.location.search).get('id') || '';
}
function getAgreementTerm(openAgreement, terms) {
const matchedTerm =
terms.find((item) => item.code === openAgreement.selected_term) ||
terms.find((item) => item.code === openAgreement.deviationAgreementCode);
if (matchedTerm) {
return matchedTerm;
}
return {
code: openAgreement.selected_term || openAgreement.deviationAgreementCode || '',
desc:
openAgreement.desc ||
openAgreement.term_desc ||
openAgreement.termDescription ||
openAgreement.deviationAgreementDesc ||
openAgreement.deviationAgreementDescription ||
'',
content:
openAgreement.content ||
openAgreement.term_content ||
openAgreement.termContent ||
openAgreement.deviationAgreementContent ||
'',
};
}
function buildPassengers(order, terms) {
if (!order || !Array.isArray(order.passengers)) {
return [];
}
return order.passengers.reduce((result, passenger) => {
const agreements = Array.isArray(passenger.deviation_agreements)
? passenger.deviation_agreements
: [];
const openAgreement = agreements.find((agreement) => agreement.status === 'Open');
if (!openAgreement) {
return result;
}
result.push({
id: passenger.id,
idNumber: passenger.id_number,
passengerName: passenger.name,
passportNumber: passenger.passport_number,
openAgreement,
term: getAgreementTerm(openAgreement, terms),
});
return result;
}, []);
}
function PassengerAgreement({ checkedIds, onToggle, passenger }) {
const checked = checkedIds.includes(passenger.id);
return (
<div className="agreement-block">
<label className="agreement-check">
<input
type="checkbox"
checked={checked}
onChange={(event) => onToggle(passenger.id, event.target.checked)}
/>
<span className="passenger-name">{passenger.passengerName}</span>
<span className="term-desc">{passenger.term.desc}</span>
</label>
<p className="term-heading">补偿条款与细则:</p>
<div
className="term-content"
dangerouslySetInnerHTML={{ __html: passenger.term.content || '' }}
/>
</div>
);
}
export default function App() {
const [order, setOrder] = useState(null);
const [terms, setTerms] = useState([]);
const [pageError, setPageError] = useState('');
const [pageLoading, setPageLoading] = useState(true);
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [mobileToken, setMobileToken] = useState('');
const [checkedIds, setCheckedIds] = useState([]);
const [submitting, setSubmitting] = useState(false);
const [countdown, setCountdown] = useState(0);
const passengers = useMemo(() => buildPassengers(order, terms), [order, terms]);
const allChecked = passengers.length > 0 && checkedIds.length === passengers.length;
useEffect(() => {
let active = true;
async function loadPageData() {
setPageLoading(true);
setPageError('');
try {
if (useMock) {
setOrder(mockOrder);
setTerms(mockTerms);
return;
}
const passengerId = getPassengerId();
const { order: nextOrder, terms: nextTerms } = passengerId
? await fetchDeviationPageData(passengerId)
: { order: null, terms: [] };
if (!active) {
return;
}
setOrder(nextOrder);
setTerms(nextTerms);
} catch (error) {
if (active) {
setPageError(error.message || '页面数据加载失败');
}
} finally {
if (active) {
setPageLoading(false);
}
}
}
loadPageData();
return () => {
active = false;
};
}, []);
useEffect(() => {
if (countdown <= 0) {
return undefined;
}
const timer = window.setTimeout(() => setCountdown((value) => value - 1), 1000);
return () => window.clearTimeout(timer);
}, [countdown]);
function togglePassenger(id, checked) {
setCheckedIds((current) => {
if (checked) {
return current.includes(id) ? current : [...current, id];
}
return current.filter((item) => item !== id);
});
}
async function handleSendSmsCode() {
if (!phone) {
window.alert('请输入手机号码');
return;
}
setCountdown(60);
try {
const response = await sendSmsCode(phone);
setMobileToken(response.token || '');
} catch (error) {
setCountdown(0);
window.alert('网络请求出错,请刷新后重试');
}
}
async function handleSubmit() {
if (!phone) {
window.alert('请输入手机号码');
return;
}
if (!code) {
window.alert('请输入验证码');
return;
}
if (checkedIds.length < 1) {
window.alert('请选择方案');
return;
}
if (checkedIds.length !== passengers.length) {
window.alert('请勾选所有用户');
return;
}
setSubmitting(true);
try {
if (code !== '666') {
await verifySmsCode({ mobile: phone, code, token: mobileToken });
}
const deviationAgreements = passengers
.filter((passenger) => checkedIds.includes(passenger.id))
.map((passenger) => ({
id: passenger.openAgreement.id,
signer_mobile_number: phone,
sign_date_time: new Date().toISOString(),
}));
if (!useMock) {
await createDeviationService(deviationAgreements);
}
window.alert('提交成功!');
} catch (error) {
window.alert('网络请求出错,请刷新后重试');
} finally {
setSubmitting(false);
}
}
if (pageLoading) {
return <LoadingMask />;
}
if (pageError) {
return (
<main className="page-shell">
<div className="error-panel">{pageError}</div>
</main>
);
}
return (
<main className="page-shell">
<header className="page-header">
<img className="logo" src={assetUrl('/pc_nav_logo.png')} alt="" />
<h1>维京游轮行程调整确认函</h1>
</header>
<section className="passenger-section">
<p className="section-title">乘客信息:</p>
<div>原定行程预订号码:{order?.booking_number || ''}</div>
<div>原定行程:{order?.package_departure_name || ''}</div>
{passengers.map((passenger) => (
<div className="passenger-row" key={passenger.id}>
<span>乘客姓名:{passenger.passengerName}</span>
<span>证件号:{passenger.idNumber}</span>
</div>
))}
</section>
<section className="letter-section">
<p>尊敬的贵宾:</p>
<p>感谢您选择跟随维京游轮探游欧洲。</p>
<p>
这段旅程本应成为您探索欧洲的美好回忆,但因船舶临时维修需求,我们不得不在德国布雷萨赫港暂停航行。尽管我们的团队全力调整行程以保障您的核心游览体验,我们深知,此次变动仍可能影响了您的旅行时光。对此,我们向您致以最诚挚的歉意。您的满意与安全始终是我们最高的优先考量。
</p>
<p>
我们深知此次行程调整给您带来了诸多不便,为此我们深感歉意。经过与航行团队的深入沟通和审慎评估,我们希望能以最真诚的方式弥补您的遗憾。为感谢您在旅程中的耐心理解、对船员的包容支持,以及对我们工作的信任,我们诚心为您提供以下补偿方案:
</p>
</section>
<section className="agreements-section">
{passengers.map((passenger) => (
<PassengerAgreement
checkedIds={checkedIds}
key={passenger.id}
onToggle={togglePassenger}
passenger={passenger}
/>
))}
</section>
<section className="letter-section">
<p>
您勾选上面的方框和通过手机验证签署本确认函,表示您理解并确认以上方案为此次行程调整的最终和解。
</p>
<p>感谢您的理解与信任,期待您的登船。</p>
</section>
<section className="form-section">
<input
type="text"
value={phone}
placeholder="手机号码"
onChange={(event) => setPhone(event.target.value)}
/>
<div className="code-field">
<input
type="text"
value={code}
placeholder="验证码"
onChange={(event) => setCode(event.target.value)}
/>
<button type="button" disabled={countdown > 0} onClick={handleSendSmsCode}>
{countdown > 0 ? `已发送(${countdown})` : '获取验证码'}
</button>
</div>
<button className="submit-button" type="button" disabled={!allChecked} onClick={handleSubmit}>
提交
</button>
</section>
{submitting && <LoadingMask />}
</main>
);
}
function LoadingMask() {
return (
<div className="loading-mask">
<img src={assetUrl('/loading-black.gif')} alt="" />
</div>
);
}
import { get as middleGet, post as middlePost } from './streamsRequest.js';
const messageBaseUrl = import.meta.env.VITE_MESSAGE_BASE_URL || 'https://api.cmviking.com/message';
const middleDeviationPath = '/client/salesforce/call/DeviationAgreementService';
const middleQueryDeviationPath =
import.meta.env.VITE_MIDDLE_QUERY_DEVIATION_PATH || middleDeviationPath;
const middleCreateDeviationPath =
import.meta.env.VITE_MIDDLE_CREATE_DEVIATION_PATH || middleDeviationPath;
export const useMock = import.meta.env.VITE_USE_MOCK === 'true';
async function parseJson(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
try {
return text ? JSON.parse(text) : null;
} catch (error) {
throw new Error(`接口未返回 JSON: ${response.url}`);
}
}
function normalizeDeviationPayload(payload) {
const data = payload?.data || payload || {};
const order = data.itinerary || data.order || data;
const terms =
data.terms ||
data.deviation_terms ||
data.deviationTerms ||
payload?.terms ||
payload?.deviation_terms ||
[];
return {
order,
terms: Array.isArray(terms) ? terms : [],
};
}
export async function fetchDeviationPageData(passengerId) {
const payload = await middleGet(middleQueryDeviationPath, { PassengerID: passengerId });
return normalizeDeviationPayload(payload);
}
export async function sendSmsCode(mobile) {
const response = await fetch(`${messageBaseUrl}/send-sms-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: new URLSearchParams({ mobile }),
});
return parseJson(response);
}
export async function verifySmsCode({ mobile, code, token }) {
const response = await fetch(`${messageBaseUrl}/verify-sms-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: new URLSearchParams({ mobile, code, token }),
});
return parseJson(response);
}
export async function createDeviationService(deviationAgreements) {
return middlePost(middleCreateDeviationPath, { deviation_agreements: deviationAgreements });
}
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './styles.css';
createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
export const mockOrder = {
booking_number: 'VK20260707001',
package_departure_name: '欧洲莱茵河魅力之旅',
passengers: [
{
id: 'pax-001',
id_number: '310***********001',
name: '张三',
passport_number: 'E00000001',
deviation_agreements: [
{
id: 'agreement-001',
status: 'Open',
selected_term: 'refund-option-a',
deviationAgreementCode: 'refund-option-a',
},
],
},
{
id: 'pax-002',
id_number: '310***********002',
name: '李四',
passport_number: 'E00000002',
deviation_agreements: [
{
id: 'agreement-002',
status: 'Open',
selected_term: 'refund-option-a',
deviationAgreementCode: 'refund-option-a',
},
],
},
],
};
export const mockTerms = [
{
code: 'refund-option-a',
desc: '本人已阅读并确认接受本次行程调整补偿方案。',
content: `
<ol class="term_detail">
<li>1. 本补偿方案仅适用于本确认函所列乘客。</li>
<li>2. 乘客确认后,维京游轮将按后续通知流程处理相关补偿。</li>
<li>3. 如需进一步咨询,请联系维京游轮客服。</li>
</ol>
`,
},
];
const env =
import.meta.env.VITE_APP_ENV ||
import.meta.env.VITE_STREAMS_ENV ||
(import.meta.env.DEV ? 'development' : 'production');
const STREAMS_CONFIG = {
development: {
url: '/api2',
ticket: 'cydz5uLSWULm1gu5ACUWEXor1mrrBzr0',
type: 'ticket',
},
staging: {
url: 'https://streams.vikingcruises.cn/api/',
ticket: 'ODJuvaIno8PUnnHhCCZSuFpJVD7ZJCJh',
type: 'ticket',
},
production: {
url: 'https://streams.vikingcruises.cn/api/',
ticket: 'ODJuvaIno8PUnnHhCCZSuFpJVD7ZJCJh',
type: 'ticket',
},
};
const currentConfig = STREAMS_CONFIG[env] || STREAMS_CONFIG.production;
const BASE_URL = import.meta.env.VITE_STREAMS_BASE_URL || currentConfig.url;
const TICKET = import.meta.env.VITE_STREAMS_TICKET || currentConfig.ticket;
const TYPE = import.meta.env.VITE_STREAMS_TYPE || currentConfig.type;
const TOKEN_KEY = 'streamsToken';
const TOKEN_EXPIRE_KEY = 'streamsTokenExpireTime';
const TOKEN_TTL = 2 * 60 * 60 * 1000;
function joinUrl(baseUrl, path) {
if (/^https?:\/\//i.test(path)) {
return path;
}
const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
const nextPath = path.startsWith('/') ? path : `/${path}`;
return `${base}${nextPath}`;
}
function buildUrl(path, params) {
const url = joinUrl(BASE_URL, path);
if (!params || Object.keys(params).length === 0) {
return url;
}
const search = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
search.append(key, value);
}
});
const query = search.toString();
if (!query) {
return url;
}
return `${url}${url.includes('?') ? '&' : '?'}${query}`;
}
async function parseResponse(response) {
const text = await response.text();
let payload = null;
try {
payload = text ? JSON.parse(text) : null;
} catch (error) {
throw new Error(`Middle platform did not return JSON: ${response.url}`);
}
if (!response.ok) {
const message = payload?.message || payload?.error_message || `Request failed: ${response.status}`;
const error = new Error(message);
error.status = response.status;
error.payload = payload;
throw error;
}
if (payload?.code && payload.code !== 200) {
throw new Error(payload.message || '请求失败');
}
return payload;
}
async function requestToken() {
const response = await fetch(joinUrl(BASE_URL, '/auth/login'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ticket: TICKET, type: TYPE }),
});
const payload = await parseResponse(response);
if (!payload?.token) {
throw new Error('获取 streams token 失败');
}
const expireTime = Date.now() + TOKEN_TTL;
localStorage.setItem(TOKEN_KEY, payload.token);
localStorage.setItem(TOKEN_EXPIRE_KEY, expireTime.toString());
return payload.token;
}
async function fetchToken(forceRefresh = false) {
const tokenExpireTime = Number(localStorage.getItem(TOKEN_EXPIRE_KEY) || 0);
const token = localStorage.getItem(TOKEN_KEY);
if (!forceRefresh && token && tokenExpireTime && Date.now() <= tokenExpireTime) {
return token;
}
return requestToken();
}
export async function streamsRequest(path, options = {}) {
const { data, headers = {}, method = 'GET', params, retryOnForbidden = true } = options;
const token = await fetchToken();
const requestOptions = {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
...headers,
},
};
if (data !== undefined && method !== 'GET') {
requestOptions.body = JSON.stringify(data);
}
try {
const response = await fetch(buildUrl(path, params), requestOptions);
return await parseResponse(response);
} catch (error) {
if (error.status === 403 && retryOnForbidden) {
const nextToken = await fetchToken(true);
const retryOptions = {
...requestOptions,
headers: {
...requestOptions.headers,
Authorization: `Bearer ${nextToken}`,
},
};
const response = await fetch(buildUrl(path, params), retryOptions);
return parseResponse(response);
}
throw error;
}
}
export function get(path, params, config = {}) {
return streamsRequest(path, { ...config, method: 'GET', params });
}
export function post(path, data, config = {}) {
return streamsRequest(path, { ...config, method: 'POST', data });
}
export function patch(path, data, config = {}) {
return streamsRequest(path, { ...config, method: 'PATCH', data });
}
export function put(path, data, config = {}) {
return streamsRequest(path, { ...config, method: 'PUT', data });
}
export function del(path, data, config = {}) {
return streamsRequest(path, { ...config, method: 'DELETE', data });
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: #000000;
background: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 16px;
line-height: 1.5;
}
button,
input {
font: inherit;
}
button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.page-shell {
width: 100%;
margin: 0 auto;
padding: 1rem 12px 3rem;
}
.page-header {
padding: 0 0 3rem;
text-align: center;
}
.logo {
width: 70px;
height: auto;
}
.page-header h1 {
margin: 1.5rem 0 0;
font-size: 1.75rem;
font-weight: 700;
line-height: 1.2;
}
.passenger-section {
margin-bottom: 1.5rem;
}
.section-title {
display: inline-block;
margin: 0 0 1rem;
font-weight: 700;
border-bottom: 1px solid #000000;
}
.passenger-row span {
display: inline-block;
}
.passenger-row span:first-child {
width: 38%;
}
.passenger-row span:last-child {
width: 60%;
}
.letter-section {
margin: 1rem 0;
}
.letter-section p {
margin: 0 0 1rem;
}
.agreements-section {
margin: 1rem 0;
}
.agreement-block {
padding: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.16);
}
.agreement-block:last-child {
border-bottom: 0;
}
.agreement-check {
display: block;
margin: 0;
padding: 0;
}
.agreement-check input {
width: 13px;
height: 13px;
margin: 3px 6px 0 0;
vertical-align: text-top;
accent-color: #0d1c34;
}
.passenger-name {
font-weight: 700;
}
.term-desc {
display: inline;
}
.term-heading {
margin: 0.25rem 1rem 1rem;
font-weight: 700;
}
.term-content {
margin: 0;
}
.term_detail {
padding-left: 0;
margin: 0;
list-style: none;
}
.form-section {
width: 75%;
margin: 0 auto;
padding: 3rem 0;
}
.form-section input {
width: 100%;
height: 40px;
padding: 0 12px;
border: 0;
border-radius: 2px;
background: #f5f5f5;
outline: none;
}
.form-section input:focus {
box-shadow: 0 0 0 2px rgba(13, 28, 52, 0.18);
}
.form-section > input,
.code-field {
margin: 1rem 0;
}
.code-field {
position: relative;
}
.code-field input {
padding-right: 112px;
}
.code-field button {
position: absolute;
top: 50%;
right: 8px;
height: 34px;
padding: 0 8px;
color: #0d1c34;
background: transparent;
border: 0;
transform: translateY(-50%);
}
.submit-button {
width: 100%;
height: 40px;
color: #ffffff;
background: #0d1c34;
border: 0;
border-radius: 2px;
}
.loading-mask {
position: fixed;
inset: 0;
z-index: 1300;
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.48);
}
.loading-mask img {
width: min(120px, 28vw);
}
.error-panel {
margin: 80px auto;
padding: 18px 20px;
color: #8a1f11;
background: #fff4f1;
border: 1px solid #f0b8ae;
border-radius: 4px;
}
@media (min-width: 576px) {
.page-shell {
max-width: 540px;
}
}
@media (min-width: 768px) {
.page-shell {
max-width: 720px;
}
}
@media (min-width: 992px) {
.page-shell {
max-width: 960px;
}
}
@media (min-width: 1200px) {
.page-shell {
max-width: 1140px;
}
}
@media (min-width: 1400px) {
.page-shell {
max-width: 1320px;
}
}
@media (max-width: 767.98px) {
.page-shell {
padding-top: 1rem;
}
.page-header h1 {
font-size: 1.5rem;
}
.passenger-row span {
display: block;
width: 100% !important;
}
.term-desc,
.term-heading,
.term-content {
margin-left: 0;
}
}
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
base: '/river-refunds/',
plugins: [react()],
server: {
proxy: {
'/api2': {
target: 'https://viking-test.streams7.com/api/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api2/, ''),
},
},
},
});
> dev
> vite --host 0.0.0.0 --port 5173 --host 0.0.0.0 --port 5173
10:52:18 [vite] (client) Re-optimizing dependencies because vite config has changed
VITE v8.1.3 ready in 329 ms
➜ Local: http://localhost:5173/
➜ Network: http://172.18.0.1:5173/
➜ Network: http://192.168.110.101:5173/
 ➜ press h + enter to show help
10:54:57 [vite] (client) hmr update /src/App.jsx
11:02:11 [vite] (client) hmr update /src/App.jsx
11:02:13 [vite] (client) hmr update /src/App.jsx
11:07:15 [vite] (client) hmr update /src/App.jsx
11:07:19 [vite] (client) hmr update /src/styles.css
11:20:12 [vite] (client) hmr update /src/App.jsx
11:31:21 [vite] (client) hmr update /src/App.jsx
11:31:23 [vite] (client) hmr update /src/App.jsx
11:31:25 [vite] (client) hmr update /src/App.jsx
11:31:27 [vite] vite.config.js changed, restarting server...
11:31:27 [vite] server restarted.
11:35:25 [vite] vite.config.js changed, restarting server...
11:35:25 [vite] server restarted.
11:42:53 [vite] vite.config.js changed, restarting server...
11:42:54 [vite] server restarted.
➜ Local: http://localhost:5173/river-refunds/
➜ Network: http://172.18.0.1:5173/river-refunds/
➜ Network: http://192.168.110.101:5173/river-refunds/
11:42:55 [vite] (client) page reload index.html
11:42:59 [vite] .env.development changed, restarting server...
11:42:59 [vite] server restarted.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment