feat(h5): unify WeChat OAuth under m.runxian.top path routing
Serve user/shop/partner H5 under /user/, /shop/, /partner/ on a single authorized domain; update nginx, SSL script, and router base paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
|
||||||
|
import { toAppPath } from '@dukang/weixin-sdk';
|
||||||
import { clearAuth, isLoggedIn, request } from '../lib/api';
|
import { clearAuth, isLoggedIn, request } from '../lib/api';
|
||||||
|
|
||||||
export type PartnerAccount = {
|
export type PartnerAccount = {
|
||||||
@@ -43,7 +44,7 @@ export function PartnerSessionProvider({ children }: { children: ReactNode }) {
|
|||||||
const logout = useCallback(() => {
|
const logout = useCallback(() => {
|
||||||
clearAuth();
|
clearAuth();
|
||||||
setAccount(null);
|
setAccount(null);
|
||||||
window.location.href = '/login';
|
window.location.href = toAppPath('/login');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isOnAppPath, toAppPath } from '@dukang/weixin-sdk';
|
||||||
|
|
||||||
export const apiBase = '/api/v1';
|
export const apiBase = '/api/v1';
|
||||||
|
|
||||||
export async function request<T>(clientApp: string, path: string, options: RequestInit = {}): Promise<T> {
|
export async function request<T>(clientApp: string, path: string, options: RequestInit = {}): Promise<T> {
|
||||||
@@ -12,8 +14,8 @@ export async function request<T>(clientApp: string, path: string, options: Reque
|
|||||||
const json = await res.json().catch(() => ({ code: res.status, message: '网络异常' }));
|
const json = await res.json().catch(() => ({ code: res.status, message: '网络异常' }));
|
||||||
if (res.status === 401 || json.code === 401) {
|
if (res.status === 401 || json.code === 401) {
|
||||||
clearAuth();
|
clearAuth();
|
||||||
if (typeof window !== 'undefined' && !window.location.pathname.startsWith('/login')) {
|
if (typeof window !== 'undefined' && !isOnAppPath('/login')) {
|
||||||
window.location.href = '/login';
|
window.location.href = toAppPath('/login');
|
||||||
}
|
}
|
||||||
throw new Error(json.message || '登录已过期,请重新登录');
|
throw new Error(json.message || '登录已过期,请重新登录');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { getRouterBasename } from '@dukang/weixin-sdk';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { PartnerSessionProvider } from './contexts/PartnerSessionContext';
|
import { PartnerSessionProvider } from './contexts/PartnerSessionContext';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename={getRouterBasename()}>
|
||||||
<PartnerSessionProvider>
|
<PartnerSessionProvider>
|
||||||
<App />
|
<App />
|
||||||
</PartnerSessionProvider>
|
</PartnerSessionProvider>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/partner/',
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { getRouterBasename } from '@dukang/weixin-sdk';
|
||||||
import { StoreSessionProvider } from './contexts/StoreSessionContext';
|
import { StoreSessionProvider } from './contexts/StoreSessionContext';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename={getRouterBasename()}>
|
||||||
<StoreSessionProvider>
|
<StoreSessionProvider>
|
||||||
<App />
|
<App />
|
||||||
</StoreSessionProvider>
|
</StoreSessionProvider>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function formatMoney(n: number) {
|
|||||||
function formatScanError(e: unknown): string {
|
function formatScanError(e: unknown): string {
|
||||||
const msg = e instanceof Error ? e.message : '扫码失败,请重试';
|
const msg = e instanceof Error ? e.message : '扫码失败,请重试';
|
||||||
if (/invalid signature/i.test(msg)) {
|
if (/invalid signature/i.test(msg)) {
|
||||||
return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名为 shop.runxian.top,并刷新页面后重试';
|
return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名为 m.runxian.top,并刷新页面后重试';
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/shop/',
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
||||||
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
||||||
|
import { toAppPath } from '@dukang/weixin-sdk';
|
||||||
import { isWechatEnv, weixinSdk } from './weixin';
|
import { isWechatEnv, weixinSdk } from './weixin';
|
||||||
import { request, saveSession, type UserProfile } from './api';
|
import { request, saveSession, type UserProfile } from './api';
|
||||||
|
|
||||||
@@ -48,5 +49,5 @@ export async function authorizeWechatForPay(): Promise<WechatLoginResult | void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildLoginReturnUrl(pathname: string, search: string) {
|
export function buildLoginReturnUrl(pathname: string, search: string) {
|
||||||
return `/login?return=${encodeURIComponent(`${pathname}${search}`)}`;
|
return `${toAppPath('/login')}?return=${encodeURIComponent(`${pathname}${search}`)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { getRouterBasename } from '@dukang/weixin-sdk';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename={getRouterBasename()}>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/user/',
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 为杜康 runxian.top 五域名申请 Let's Encrypt 并切换 HTTPS 配置
|
# 为杜康 runxian.top 申请 Let's Encrypt 并切换 HTTPS 配置
|
||||||
|
# 三端 H5 统一入口 m.runxian.top(微信网页授权单域名)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
DOMAINS=(
|
DOMAINS=(
|
||||||
|
m.runxian.top
|
||||||
user.runxian.top
|
user.runxian.top
|
||||||
shop.runxian.top
|
shop.runxian.top
|
||||||
partner.runxian.top
|
partner.runxian.top
|
||||||
@@ -25,7 +27,8 @@ mkdir -p /var/www/certbot /var/log/nginx/dukang
|
|||||||
|
|
||||||
echo "==> 申请证书..."
|
echo "==> 申请证书..."
|
||||||
certbot certonly --webroot -w /var/www/certbot \
|
certbot certonly --webroot -w /var/www/certbot \
|
||||||
--cert-name user.runxian.top \
|
--cert-name m.runxian.top \
|
||||||
|
-d m.runxian.top \
|
||||||
-d user.runxian.top \
|
-d user.runxian.top \
|
||||||
-d shop.runxian.top \
|
-d shop.runxian.top \
|
||||||
-d partner.runxian.top \
|
-d partner.runxian.top \
|
||||||
@@ -43,13 +46,14 @@ systemctl reload nginx
|
|||||||
|
|
||||||
echo "==> 验证..."
|
echo "==> 验证..."
|
||||||
for url in \
|
for url in \
|
||||||
|
https://m.runxian.top/user/ \
|
||||||
|
https://m.runxian.top/shop/ \
|
||||||
|
https://m.runxian.top/partner/ \
|
||||||
https://user.runxian.top/ \
|
https://user.runxian.top/ \
|
||||||
https://shop.runxian.top/ \
|
|
||||||
https://partner.runxian.top/ \
|
|
||||||
https://webadmin.runxian.top/ \
|
https://webadmin.runxian.top/ \
|
||||||
https://dkapi.runxian.top/api/v1/health; do
|
https://dkapi.runxian.top/api/v1/health; do
|
||||||
code="$(curl -sf -o /dev/null -w '%{http_code}' "$url" || echo fail)"
|
code="$(curl -sf -o /dev/null -w '%{http_code}' "$url" || echo fail)"
|
||||||
echo " $url -> $code"
|
echo " $url -> $code"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "==> runxian.top 杜康域名 HTTPS 已启用"
|
echo "==> runxian.top 杜康域名 HTTPS 已启用(H5 统一入口 m.runxian.top)"
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
# 杜康好客 — runxian.top HTTPS(证书申请后启用)
|
# 杜康好客 — runxian.top HTTPS
|
||||||
# 执行: deploy/enable-runxian-dukang-ssl.sh
|
# 执行: deploy/enable-runxian-dukang-ssl.sh
|
||||||
|
# 三端 H5 统一入口 m.runxian.top/{user,shop,partner}/
|
||||||
map $host $dukang_runxian_port {
|
|
||||||
user.runxian.top 8091;
|
|
||||||
shop.runxian.top 8092;
|
|
||||||
partner.runxian.top 8093;
|
|
||||||
webadmin.runxian.top 8094;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name user.runxian.top shop.runxian.top partner.runxian.top;
|
server_name m.runxian.top user.runxian.top shop.runxian.top partner.runxian.top;
|
||||||
|
|
||||||
location ^~ /.well-known/acme-challenge/ {
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
root /var/www/certbot;
|
root /var/www/certbot;
|
||||||
default_type "text/plain";
|
default_type "text/plain";
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/MP_verify_.*\.txt$ {
|
|
||||||
root /opt/dukang-haoke/apps/h5-user/dist;
|
|
||||||
default_type text/plain;
|
|
||||||
access_log off;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
@@ -30,18 +18,24 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name user.runxian.top shop.runxian.top partner.runxian.top;
|
server_name m.runxian.top;
|
||||||
|
|
||||||
access_log /var/log/nginx/dukang/runxian-h5.access.log main;
|
access_log /var/log/nginx/dukang/runxian-m.access.log main;
|
||||||
error_log /var/log/nginx/dukang/runxian-h5.error.log warn;
|
error_log /var/log/nginx/dukang/runxian-m.error.log warn;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
client_max_body_size 20m;
|
client_max_body_size 20m;
|
||||||
|
|
||||||
|
location ~ ^/MP_verify_.*\.txt$ {
|
||||||
|
root /opt/dukang-haoke/apps/h5-user/dist;
|
||||||
|
default_type text/plain;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://127.0.0.1:8090;
|
proxy_pass http://127.0.0.1:8090;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -52,14 +46,57 @@ server {
|
|||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location = / {
|
||||||
proxy_pass http://127.0.0.1:$dukang_runxian_port;
|
return 302 /user/;
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /user/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-user/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /shop/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-shop/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /partner/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-partner/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name user.runxian.top;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
return 301 https://m.runxian.top/user$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name shop.runxian.top;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
return 301 https://m.runxian.top/shop$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name partner.runxian.top;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
return 301 https://m.runxian.top/partner$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
@@ -83,8 +120,8 @@ server {
|
|||||||
access_log /var/log/nginx/dukang/runxian-api.access.log main;
|
access_log /var/log/nginx/dukang/runxian-api.access.log main;
|
||||||
error_log /var/log/nginx/dukang/runxian-api.error.log warn;
|
error_log /var/log/nginx/dukang/runxian-api.error.log warn;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
@@ -122,8 +159,8 @@ server {
|
|||||||
access_log /var/log/nginx/dukang/runxian-admin.access.log main;
|
access_log /var/log/nginx/dukang/runxian-admin.access.log main;
|
||||||
error_log /var/log/nginx/dukang/runxian-admin.error.log warn;
|
error_log /var/log/nginx/dukang/runxian-admin.error.log warn;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/m.runxian.top/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/m.runxian.top/privkey.pem;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
# 杜康好客 — runxian.top 别名域名(与 lingshivip.cn 同后端)
|
# 杜康好客 — runxian.top(HTTP)
|
||||||
# user/shop/partner → 8091/8092/8093;webadmin → 8094;dkapi → 8090
|
# 三端 H5 统一入口 m.runxian.top/{user,shop,partner}/(微信网页授权单域名)
|
||||||
# 证书:certbot --cert-name user.runxian.top -d user.runxian.top -d shop.runxian.top ...
|
# 旧子域名 user/shop/partner 301 到统一入口;webadmin → 8094;dkapi → 8090
|
||||||
|
|
||||||
map $host $dukang_runxian_port {
|
# --- 统一 H5 入口(证书就绪前 HTTP;HTTPS 见 dukang-runxian-ssl.conf)---
|
||||||
user.runxian.top 8091;
|
|
||||||
shop.runxian.top 8092;
|
|
||||||
partner.runxian.top 8093;
|
|
||||||
webadmin.runxian.top 8094;
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- HTTP: H5 三端(校验文件 + API;证书就绪后见 dukang-runxian-ssl.conf)---
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name user.runxian.top shop.runxian.top partner.runxian.top;
|
server_name m.runxian.top;
|
||||||
|
|
||||||
access_log /var/log/nginx/dukang/runxian-h5.access.log main;
|
access_log /var/log/nginx/dukang/runxian-m.access.log main;
|
||||||
error_log /var/log/nginx/dukang/runxian-h5.error.log warn;
|
error_log /var/log/nginx/dukang/runxian-m.error.log warn;
|
||||||
|
|
||||||
|
client_max_body_size 20m;
|
||||||
|
|
||||||
location ^~ /.well-known/acme-challenge/ {
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
root /var/www/certbot;
|
root /var/www/certbot;
|
||||||
@@ -38,14 +33,43 @@ server {
|
|||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location = / {
|
||||||
proxy_pass http://127.0.0.1:$dukang_runxian_port;
|
return 302 /user/;
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /user/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-user/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /shop/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-shop/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /partner/ {
|
||||||
|
alias /opt/dukang-haoke/apps/h5-partner/dist/;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 旧 H5 子域名 → 统一入口(保留书签/推广链接)---
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name user.runxian.top;
|
||||||
|
return 301 http://m.runxian.top/user$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name shop.runxian.top;
|
||||||
|
return 301 http://m.runxian.top/shop$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name partner.runxian.top;
|
||||||
|
return 301 http://m.runxian.top/partner$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- HTTP: API ---
|
# --- HTTP: API ---
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
function normalizeBase(base?: string): string {
|
||||||
|
if (!base || base === '/') return '';
|
||||||
|
return base.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** React Router basename(来自 Vite `base`) */
|
||||||
|
export function getRouterBasename(baseUrl?: string): string | undefined {
|
||||||
|
const base = normalizeBase(baseUrl ?? import.meta.env?.BASE_URL);
|
||||||
|
return base || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拼接带 Vite base 前缀的应用内路径(用于 window.location 跳转) */
|
||||||
|
export function toAppPath(path: string, baseUrl?: string): string {
|
||||||
|
const base = normalizeBase(baseUrl ?? import.meta.env?.BASE_URL);
|
||||||
|
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
return `${base}${normalized}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOnAppPath(path: string, baseUrl?: string): boolean {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
const target = toAppPath(path, baseUrl);
|
||||||
|
return window.location.pathname === target || window.location.pathname.startsWith(`${target}/`);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export { isWechatBrowser, isMiniProgram, getRuntimePlatform } from './env';
|
export { isWechatBrowser, isMiniProgram, getRuntimePlatform } from './env';
|
||||||
|
export { getRouterBasename, toAppPath, isOnAppPath } from './app-path';
|
||||||
export { initWechatJssdk, ensureJssdkReady, isJssdkReady, normalizeJssdkPageUrl, stripOAuthParamsFromLocation } from './jssdk';
|
export { initWechatJssdk, ensureJssdkReady, isJssdkReady, normalizeJssdkPageUrl, stripOAuthParamsFromLocation } from './jssdk';
|
||||||
export {
|
export {
|
||||||
getWechatLocation,
|
getWechatLocation,
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ const defaultOrigins = [
|
|||||||
'http://127.0.0.1:5173',
|
'http://127.0.0.1:5173',
|
||||||
'http://127.0.0.1:5174',
|
'http://127.0.0.1:5174',
|
||||||
'http://127.0.0.1:5175',
|
'http://127.0.0.1:5175',
|
||||||
|
'http://localhost:5173/user',
|
||||||
|
'http://localhost:5174/shop',
|
||||||
|
'http://localhost:5175/partner',
|
||||||
|
'https://m.runxian.top',
|
||||||
];
|
];
|
||||||
|
|
||||||
const allowedOrigin = (OSS_CORS_ORIGINS ?? defaultOrigins.join(','))
|
const allowedOrigin = (OSS_CORS_ORIGINS ?? defaultOrigins.join(','))
|
||||||
|
|||||||
@@ -26,13 +26,15 @@ MOCK_WECHAT=true
|
|||||||
WX_AUTHORIZE=false
|
WX_AUTHORIZE=false
|
||||||
|
|
||||||
# C 端 H5 落地页(推广码二维码链接前缀)
|
# C 端 H5 落地页(推广码二维码链接前缀)
|
||||||
USER_H5_URL=http://localhost:5173
|
# 本地开发:http://localhost:5173/user 生产统一入口:https://m.runxian.top/user
|
||||||
|
USER_H5_URL=http://localhost:5173/user
|
||||||
|
|
||||||
# 反向代理后提取真实客户端 IP(下单 IP 定位)
|
# 反向代理后提取真实客户端 IP(下单 IP 定位)
|
||||||
TRUST_PROXY=true
|
TRUST_PROXY=true
|
||||||
|
|
||||||
# 微信 SDK(生产:WECHAT_AUTH_ENABLED=true,配置 WX_APP_ID / WX_APP_SECRET)
|
# 微信 SDK(生产:WECHAT_AUTH_ENABLED=true,配置 WX_APP_ID / WX_APP_SECRET)
|
||||||
# OAuth 授权页由 /common/wechat/oauth-url 生成;C/合伙人/总部 H5 均须在微信内置浏览器内授权。
|
# OAuth 授权页由 /common/wechat/oauth-url 生成;三端统一入口 m.runxian.top/{user,shop,partner}。
|
||||||
|
# 微信服务号「网页授权域名」「JS 接口安全域名」均配置 m.runxian.top(仅 1 个名额)。
|
||||||
WX_APP_ID=
|
WX_APP_ID=
|
||||||
WX_APP_SECRET=
|
WX_APP_SECRET=
|
||||||
WECHAT_AUTH_ENABLED=false
|
WECHAT_AUTH_ENABLED=false
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ AUTO_APPROVE_STORE=false
|
|||||||
|
|
||||||
TRUST_PROXY=true
|
TRUST_PROXY=true
|
||||||
|
|
||||||
|
# C 端 H5 落地页(推广码二维码;生产统一入口)
|
||||||
|
USER_H5_URL=https://m.runxian.top/user
|
||||||
|
|
||||||
WECHAT_AUTH_ENABLED=true
|
WECHAT_AUTH_ENABLED=true
|
||||||
WECHAT_PAY_ENABLED=true
|
WECHAT_PAY_ENABLED=true
|
||||||
# 登录后走微信 SDK OAuth 授权(生产/预发建议 true)
|
# 登录后走微信 SDK OAuth 授权(生产/预发建议 true)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { CreatePromoCodeDto } from './dto/admin-mutate.dto';
|
|||||||
import { AdminPromoCodesQueryDto } from './dto/admin-query.dto';
|
import { AdminPromoCodesQueryDto } from './dto/admin-query.dto';
|
||||||
|
|
||||||
function userH5Base(): string {
|
function userH5Base(): string {
|
||||||
return (process.env.USER_H5_URL || 'http://localhost:5173').replace(/\/$/, '');
|
return (process.env.USER_H5_URL || 'http://localhost:5173/user').replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLandingUrl(code: string): string {
|
function buildLandingUrl(code: string): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user