xsh_1997 1 周之前
父節點
當前提交
3e8abf8d37
共有 3 個文件被更改,包括 62 次插入18 次删除
  1. 4 0
      shop-app/.env.production
  2. 9 5
      shop-app/config/index.js
  3. 49 13
      shop-app/utils/image.js

+ 4 - 0
shop-app/.env.production

@@ -7,5 +7,9 @@ ENV=production
7
 # 若依后端 API(与 ruoyi-ui 一致,由 Nginx 反代到真实后端)
7
 # 若依后端 API(与 ruoyi-ui 一致,由 Nginx 反代到真实后端)
8
 VITE_APP_BASE_API=/shop-api
8
 VITE_APP_BASE_API=/shop-api
9
 
9
 
10
+# H5 生产图片会自动用浏览器当前访问的 IP/域名,一般无需配置
11
+# 仅微信小程序/App 打包时取消注释并填写 https 域名:
12
+# VITE_APP_API_HOST=https://shop.xxx.com
13
+
10
 # H5 部署子路径(须与 manifest.json h5.router.base 一致,如 /bqH5/)
14
 # H5 部署子路径(须与 manifest.json h5.router.base 一致,如 /bqH5/)
11
 VITE_APP_PUBLIC_PATH=bqShop
15
 VITE_APP_PUBLIC_PATH=bqShop

+ 9 - 5
shop-app/config/index.js

@@ -7,8 +7,12 @@
7
  * 打包 H5:uni build -p h5
7
  * 打包 H5:uni build -p h5
8
  */
8
  */
9
 
9
 
10
+/** 开发兜底 / 小程序未配域名时的默认值(生产 H5 不应依赖此项) */
10
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8020').replace(/\/$/, '')
11
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8020').replace(/\/$/, '')
11
 
12
 
13
+/** .env 显式配置的后端域名(小程序/App 生产环境必填 https) */
14
+const hostFromEnv = String(import.meta.env.VITE_APP_API_HOST || '').trim().replace(/\/$/, '')
15
+
12
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
16
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
13
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
17
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
14
 
18
 
@@ -34,13 +38,13 @@ function resolveBaseApi() {
34
 
38
 
35
 export const BASE_API = resolveBaseApi()
39
 export const BASE_API = resolveBaseApi()
36
 
40
 
37
-/** 后端完整地址(含 IP/域名),来自 .env 的 VITE_APP_API_HOST */
38
-export const API_HOST = apiHost
39
-
40
 /**
41
 /**
41
- * 图片/文件访问根地址(始终用完整 IP/域名
42
- * 说明:<image> 标签不会走 Vite 的 /dev-api 代理,必须用 API_HOST 直连后端
42
+ * 后端完整地址(开发 / 小程序打包用 .env 的 VITE_APP_API_HOST)
43
+ * H5 生产图片在运行时由 getFileBase() 自动取 window.location.origin
43
  */
44
  */
45
+export const API_HOST = hostFromEnv || apiHost
46
+
47
+/** 图片根地址(构建时);H5 生产以运行时 getFileBase() 为准 */
44
 export const FILE_BASE = API_HOST
48
 export const FILE_BASE = API_HOST
45
 
49
 
46
 /**
50
 /**

+ 49 - 13
shop-app/utils/image.js

@@ -1,6 +1,7 @@
1
-import { joinFileUrl, API_HOST } from '@/config'
1
+import { FILE_BASE, API_HOST } from '@/config'
2
 
2
 
3
 const PLACEHOLDER_PATHS = ['/static/logo.png']
3
 const PLACEHOLDER_PATHS = ['/static/logo.png']
4
+const API_PROXY_PREFIX = /^\/(dev-api|prod-api|shop-api)\//i
4
 
5
 
5
 /** 是否外链或 data URL */
6
 /** 是否外链或 data URL */
6
 export function isExternalUrl(url) {
7
 export function isExternalUrl(url) {
@@ -8,7 +9,7 @@ export function isExternalUrl(url) {
8
   return /^(https?:|data:|\/\/)/i.test(String(url).trim())
9
   return /^(https?:|data:|\/\/)/i.test(String(url).trim())
9
 }
10
 }
10
 
11
 
11
-/** 去掉 /dev-api 等代理前缀,得到后端真实路径 */
12
+/** 去掉 /dev-api、/shop-api 等代理前缀,得到后端真实路径 */
12
 function stripApiProxyPrefix(path) {
13
 function stripApiProxyPrefix(path) {
13
   return String(path).replace(/^\/(dev-api|prod-api|shop-api)/, '') || '/'
14
   return String(path).replace(/^\/(dev-api|prod-api|shop-api)/, '') || '/'
14
 }
15
 }
@@ -18,6 +19,32 @@ function isPlaceholderPath(path) {
18
   return PLACEHOLDER_PATHS.includes(raw)
19
   return PLACEHOLDER_PATHS.includes(raw)
19
 }
20
 }
20
 
21
 
22
+/**
23
+ * 图片根地址
24
+ * - H5 生产:自动取浏览器当前访问的 IP/域名(window.location.origin)
25
+ * - H5 开发 / 小程序:用 .env 里的 VITE_APP_API_HOST
26
+ */
27
+export function getFileBase() {
28
+  // #ifdef H5
29
+  if (import.meta.env.PROD && typeof window !== 'undefined') {
30
+    const origin = window.location?.origin
31
+    if (origin && origin !== 'null') {
32
+      return origin.replace(/\/$/, '')
33
+    }
34
+  }
35
+  // #endif
36
+  return String(FILE_BASE).replace(/\/$/, '')
37
+}
38
+
39
+/** 拼接图片完整 URL */
40
+function joinFileUrlRuntime(path) {
41
+  const base = getFileBase()
42
+  if (!path) return base
43
+  if (/^https?:\/\//i.test(path)) return path
44
+  const p = path.startsWith('/') ? path : `/${path}`
45
+  return base + p
46
+}
47
+
21
 /**
48
 /**
22
  * 是否已是可直接使用的图片地址(避免重复拼接)
49
  * 是否已是可直接使用的图片地址(避免重复拼接)
23
  */
50
  */
@@ -26,14 +53,17 @@ export function isResolvedFileUrl(url) {
26
   const raw = String(url).trim()
53
   const raw = String(url).trim()
27
   if (isExternalUrl(raw)) return true
54
   if (isExternalUrl(raw)) return true
28
   if (raw.startsWith('/static/')) return true
55
   if (raw.startsWith('/static/')) return true
56
+  const fileBase = getFileBase()
57
+  if (fileBase && (raw === fileBase || raw.startsWith(`${fileBase}/`))) return true
58
+  if (API_PROXY_PREFIX.test(raw)) return true
29
   const apiHost = String(API_HOST).replace(/\/$/, '')
59
   const apiHost = String(API_HOST).replace(/\/$/, '')
30
-  return !!(apiHost && (raw === apiHost || raw.startsWith(`${apiHost}/`)))
60
+  return !!(apiHost && apiHost.startsWith('http') && (raw === apiHost || raw.startsWith(`${apiHost}/`)))
31
 }
61
 }
32
 
62
 
33
 /**
63
 /**
34
- * 将后端返回的图片路径转为可展示的完整 URL(可重复调用)
35
- * 结果形如:http://192.168.1.6:8020/profile/upload/xxx.jpg
36
- * @param {string} path bannerImage / categoryPic / mainPic 等
64
+ * 将后端返回的图片路径转为可展示的 URL(可重复调用)
65
+ * - H5 生产:http://当前访问IP/profile/upload/xxx.jpg(自动)
66
+ * - 开发:http://VITE_APP_API_HOST/profile/upload/xxx.jpg
37
  */
67
  */
38
 export function resolveFileUrl(path) {
68
 export function resolveFileUrl(path) {
39
   if (path == null || path === '') {
69
   if (path == null || path === '') {
@@ -43,26 +73,32 @@ export function resolveFileUrl(path) {
43
   if (!raw || isPlaceholderPath(raw)) {
73
   if (!raw || isPlaceholderPath(raw)) {
44
     return ''
74
     return ''
45
   }
75
   }
46
-  if (isExternalUrl(raw)) {
76
+  if (isExternalUrl(raw) || raw.startsWith('/static/')) {
77
+    return raw
78
+  }
79
+
80
+  const fileBase = getFileBase()
81
+  if (fileBase && (raw === fileBase || raw.startsWith(`${fileBase}/`))) {
47
     return raw
82
     return raw
48
   }
83
   }
49
-  if (raw.startsWith('/static/')) {
84
+  if (API_PROXY_PREFIX.test(raw)) {
50
     return raw
85
     return raw
51
   }
86
   }
87
+
52
   const apiHost = String(API_HOST).replace(/\/$/, '')
88
   const apiHost = String(API_HOST).replace(/\/$/, '')
53
-  if (apiHost && (raw === apiHost || raw.startsWith(`${apiHost}/`))) {
89
+  if (apiHost && apiHost.startsWith('http') && (raw === apiHost || raw.startsWith(`${apiHost}/`))) {
54
     return raw
90
     return raw
55
   }
91
   }
56
-  if (/^\/(dev-api|prod-api|shop-api)\//i.test(raw)) {
92
+
93
+  if (API_PROXY_PREFIX.test(raw)) {
57
     raw = stripApiProxyPrefix(raw)
94
     raw = stripApiProxyPrefix(raw)
58
   }
95
   }
59
   const normalized = raw.startsWith('/') ? raw : `/${raw}`
96
   const normalized = raw.startsWith('/') ? raw : `/${raw}`
60
-  return joinFileUrl(normalized)
97
+  return joinFileUrlRuntime(normalized)
61
 }
98
 }
62
 
99
 
63
 /**
100
 /**
64
- * 将逗号分隔或数组形式的图片路径转为完整 URL 列表
65
- * @param {string|string[]|null|undefined} pathOrList
101
+ * 将逗号分隔或数组形式的图片路径转为 URL 列表
66
  */
102
  */
67
 export function resolveFileUrlList(pathOrList) {
103
 export function resolveFileUrlList(pathOrList) {
68
   if (pathOrList == null || pathOrList === '') return []
104
   if (pathOrList == null || pathOrList === '') return []