xsh_1997 пре 1 недеља
родитељ
комит
359f867044
29 измењених фајлова са 359 додато и 84 уклоњено
  1. 80 0
      ruoyi-jiaoyi/components/ImagePreview/index.vue
  2. 25 2
      ruoyi-jiaoyi/config/index.js
  3. 3 7
      ruoyi-jiaoyi/pages/distributor/order-detail/index.vue
  4. 2 4
      ruoyi-jiaoyi/pages/distributor/yak-detail/index.vue
  5. 5 8
      ruoyi-jiaoyi/pages/supplier/order-detail/index.vue
  6. 2 12
      ruoyi-jiaoyi/store/user.js
  7. 38 0
      ruoyi-jiaoyi/utils/resourceUrl.js
  8. 12 0
      ruoyi-jiaoyi/utils/validate.js
  9. 80 0
      ruoyi-ui-app/components/ImagePreview/index.vue
  10. 25 2
      ruoyi-ui-app/config/index.js
  11. 2 2
      ruoyi-ui-app/package-a/agri-classroom/index.vue
  12. 2 2
      ruoyi-ui-app/package-a/booking-expert/index.vue
  13. 2 2
      ruoyi-ui-app/package-a/booking-org/index.vue
  14. 2 2
      ruoyi-ui-app/package-a/booking-service/index.vue
  15. 2 2
      ruoyi-ui-app/package-a/booking-vet/index.vue
  16. 2 2
      ruoyi-ui-app/package-a/breeding-news/index.vue
  17. 3 3
      ruoyi-ui-app/package-a/course-detail/index.vue
  18. 2 2
      ruoyi-ui-app/package-a/livestock-resources/index.vue
  19. 2 2
      ruoyi-ui-app/package-a/my-enrollment/index.vue
  20. 3 3
      ruoyi-ui-app/package-a/news-detail/index.vue
  21. 2 2
      ruoyi-ui-app/package-a/online-clinic/index.vue
  22. 3 3
      ruoyi-ui-app/package-a/training-detail/index.vue
  23. 2 2
      ruoyi-ui-app/package-a/vet-profile/index.vue
  24. 2 2
      ruoyi-ui-app/pages/message/index.vue
  25. 2 12
      ruoyi-ui-app/store/user.js
  26. 2 4
      ruoyi-ui-app/utils/aiConsult.js
  27. 2 2
      ruoyi-ui-app/utils/filePreview.js
  28. 38 0
      ruoyi-ui-app/utils/resourceUrl.js
  29. 12 0
      ruoyi-ui-app/utils/validate.js

+ 80 - 0
ruoyi-jiaoyi/components/ImagePreview/index.vue

@@ -0,0 +1,80 @@
1
+<template>
2
+  <image
3
+    v-if="realSrc"
4
+    :src="realSrc"
5
+    :mode="mode"
6
+    :class="imgClass"
7
+    :style="imgStyle"
8
+    @click="onImageClick"
9
+    @error="$emit('error', $event)"
10
+  />
11
+</template>
12
+
13
+<script>
14
+import { resolveResourceUrl, resolveResourceUrlList } from '@/utils/resourceUrl'
15
+
16
+/**
17
+ * 接口图片预览(对齐 ruoyi-ui/src/components/ImagePreview)
18
+ * 相对路径自动拼接 BASE_API;H5 生产环境为当前浏览器 origin + /prod-api
19
+ */
20
+export default {
21
+  name: 'ImagePreview',
22
+  props: {
23
+    src: {
24
+      type: String,
25
+      default: ''
26
+    },
27
+    mode: {
28
+      type: String,
29
+      default: 'aspectFill'
30
+    },
31
+    width: {
32
+      type: [Number, String],
33
+      default: ''
34
+    },
35
+    height: {
36
+      type: [Number, String],
37
+      default: ''
38
+    },
39
+    preview: {
40
+      type: Boolean,
41
+      default: false
42
+    },
43
+    imgClass: {
44
+      type: String,
45
+      default: ''
46
+    }
47
+  },
48
+  emits: ['error', 'click'],
49
+  computed: {
50
+    realSrc() {
51
+      return resolveResourceUrl(this.src)
52
+    },
53
+    realSrcList() {
54
+      return resolveResourceUrlList(this.src)
55
+    },
56
+    imgStyle() {
57
+      const style = {}
58
+      if (this.width !== '' && this.width != null) {
59
+        style.width = typeof this.width === 'string' ? this.width : `${this.width}px`
60
+      }
61
+      if (this.height !== '' && this.height != null) {
62
+        style.height = typeof this.height === 'string' ? this.height : `${this.height}px`
63
+      }
64
+      return style
65
+    }
66
+  },
67
+  methods: {
68
+    onImageClick(e) {
69
+      this.$emit('click', e)
70
+      if (!this.preview || !this.realSrcList.length) {
71
+        return
72
+      }
73
+      uni.previewImage({
74
+        urls: this.realSrcList,
75
+        current: this.realSrc
76
+      })
77
+    }
78
+  }
79
+}
80
+</script>

+ 25 - 2
ruoyi-jiaoyi/config/index.js

@@ -7,11 +7,21 @@
7
  * 打包 H5:uni build -p h5
7
  * 打包 H5:uni build -p h5
8
  */
8
  */
9
 
9
 
10
+import { isExternal } from '@/utils/validate'
11
+
10
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
12
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
11
 
13
 
12
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
14
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
13
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
15
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
14
 
16
 
17
+/** H5 打包后取浏览器当前 host(IP/域名),与 ruoyi-ui 生产 /prod-api 同域反代一致 */
18
+function getBrowserOrigin() {
19
+  if (typeof window === 'undefined' || !window.location) {
20
+    return ''
21
+  }
22
+  return String(window.location.origin || '').replace(/\/$/, '')
23
+}
24
+
15
 /**
25
 /**
16
  * H5 开发是否走 /dev-api 代理(需 vite.config.js rewrite)
26
  * H5 开发是否走 /dev-api 代理(需 vite.config.js rewrite)
17
  * 默认 false:直连 VITE_APP_API_HOST
27
  * 默认 false:直连 VITE_APP_API_HOST
@@ -20,7 +30,20 @@ export const H5_USE_PROXY = false
20
 
30
 
21
 function resolveBaseApi() {
31
 function resolveBaseApi() {
22
   if (import.meta.env.PROD) {
32
   if (import.meta.env.PROD) {
23
-    return envBaseApi || '/prod-api'
33
+    const apiPath = envBaseApi || '/prod-api'
34
+    // #ifdef H5
35
+    const origin = getBrowserOrigin()
36
+    if (origin) {
37
+      const normalized = apiPath.startsWith('/') ? apiPath : `/${apiPath}`
38
+      return `${origin}${normalized}`.replace(/\/$/, '')
39
+    }
40
+    // #endif
41
+    // #ifndef H5
42
+    if (/^https?:\/\//i.test(apiHost)) {
43
+      return apiHost
44
+    }
45
+    // #endif
46
+    return apiPath
24
   }
47
   }
25
   const devBase = envBaseApi || '/dev-api'
48
   const devBase = envBaseApi || '/dev-api'
26
   // #ifdef H5
49
   // #ifdef H5
@@ -41,7 +64,7 @@ export function joinApiUrl(path) {
41
   if (!path) {
64
   if (!path) {
42
     return BASE_API
65
     return BASE_API
43
   }
66
   }
44
-  if (/^https?:\/\//i.test(path)) {
67
+  if (isExternal(path)) {
45
     return path
68
     return path
46
   }
69
   }
47
   const p = path.startsWith('/') ? path : '/' + path
70
   const p = path.startsWith('/') ? path : '/' + path

+ 3 - 7
ruoyi-jiaoyi/pages/distributor/order-detail/index.vue

@@ -68,7 +68,7 @@
68
 
68
 
69
 <script>
69
 <script>
70
 import { getDistributorOrder } from '@/api/distributor/order'
70
 import { getDistributorOrder } from '@/api/distributor/order'
71
-import { joinApiUrl } from '@/config'
71
+import { resolveResourceUrl } from '@/utils/resourceUrl'
72
 import { DASH, formatDateTime, formatPrice } from '@/utils/format'
72
 import { DASH, formatDateTime, formatPrice } from '@/utils/format'
73
 
73
 
74
 export default {
74
 export default {
@@ -105,7 +105,8 @@ export default {
105
       ]
105
       ]
106
     },
106
     },
107
     payVoucherUrl() {
107
     payVoucherUrl() {
108
-      return this.resolveMediaUrl(this.detail && (this.detail.payVoucherUrl || this.detail.payVoucherPath))
108
+      const d = this.detail
109
+      return resolveResourceUrl(d && (d.payVoucherUrl || d.payVoucherPath))
109
     }
110
     }
110
   },
111
   },
111
   onLoad(query) {
112
   onLoad(query) {
@@ -129,11 +130,6 @@ export default {
129
           this.loading = false
130
           this.loading = false
130
         })
131
         })
131
     },
132
     },
132
-    resolveMediaUrl(path) {
133
-      if (!path) return ''
134
-      if (/^https?:\/\//i.test(path)) return path
135
-      return joinApiUrl(path)
136
-    },
137
     previewImage(url) {
133
     previewImage(url) {
138
       if (!url) return
134
       if (!url) return
139
       uni.previewImage({ urls: [url], current: url })
135
       uni.previewImage({ urls: [url], current: url })

+ 2 - 4
ruoyi-jiaoyi/pages/distributor/yak-detail/index.vue

@@ -47,7 +47,7 @@
47
 
47
 
48
 <script>
48
 <script>
49
 import { getDistributorYakBasic } from '@/api/distributor/yak'
49
 import { getDistributorYakBasic } from '@/api/distributor/yak'
50
-import { joinApiUrl } from '@/config'
50
+import { resolveResourceUrl } from '@/utils/resourceUrl'
51
 import { DASH, formatDateTime } from '@/utils/format'
51
 import { DASH, formatDateTime } from '@/utils/format'
52
 
52
 
53
 export default {
53
 export default {
@@ -105,9 +105,7 @@ export default {
105
     },
105
     },
106
     resolvePhoto(item) {
106
     resolvePhoto(item) {
107
       const path = item && (item.photoUrl || item.photoPath)
107
       const path = item && (item.photoUrl || item.photoPath)
108
-      if (!path) return ''
109
-      if (/^https?:\/\//i.test(path)) return path
110
-      return joinApiUrl(path)
108
+      return resolveResourceUrl(path)
111
     },
109
     },
112
     previewImage(url) {
110
     previewImage(url) {
113
       if (!url) return
111
       if (!url) return

+ 5 - 8
ruoyi-jiaoyi/pages/supplier/order-detail/index.vue

@@ -95,7 +95,7 @@
95
 
95
 
96
 <script>
96
 <script>
97
 import { getSupplierOrder, getSupplierSettlement } from '@/api/supplier/order'
97
 import { getSupplierOrder, getSupplierSettlement } from '@/api/supplier/order'
98
-import { joinApiUrl } from '@/config'
98
+import { resolveResourceUrl } from '@/utils/resourceUrl'
99
 import { DASH, formatDateTime, formatPrice } from '@/utils/format'
99
 import { DASH, formatDateTime, formatPrice } from '@/utils/format'
100
 
100
 
101
 export default {
101
 export default {
@@ -137,10 +137,12 @@ export default {
137
       ]
137
       ]
138
     },
138
     },
139
     payVoucherUrl() {
139
     payVoucherUrl() {
140
-      return this.resolveMediaUrl(this.detail && (this.detail.payVoucherUrl || this.detail.payVoucherPath))
140
+      const d = this.detail
141
+      return resolveResourceUrl(d && (d.payVoucherUrl || d.payVoucherPath))
141
     },
142
     },
142
     settleVoucherUrl() {
143
     settleVoucherUrl() {
143
-      return this.resolveMediaUrl(this.detail && (this.detail.settleVoucherUrl || this.detail.settleVoucherPath))
144
+      const d = this.detail
145
+      return resolveResourceUrl(d && (d.settleVoucherUrl || d.settleVoucherPath))
144
     }
146
     }
145
   },
147
   },
146
   onLoad(query) {
148
   onLoad(query) {
@@ -168,11 +170,6 @@ export default {
168
           this.loading = false
170
           this.loading = false
169
         })
171
         })
170
     },
172
     },
171
-    resolveMediaUrl(path) {
172
-      if (!path) return ''
173
-      if (/^https?:\/\//i.test(path)) return path
174
-      return joinApiUrl(path)
175
-    },
176
     previewImage(url) {
173
     previewImage(url) {
177
       if (!url) return
174
       if (!url) return
178
       uni.previewImage({ urls: [url], current: url })
175
       uni.previewImage({ urls: [url], current: url })

+ 2 - 12
ruoyi-jiaoyi/store/user.js

@@ -1,7 +1,7 @@
1
 import { reactive } from 'vue'
1
 import { reactive } from 'vue'
2
 import { login as loginApi, logout as logoutApi, getInfo } from '@/api/login'
2
 import { login as loginApi, logout as logoutApi, getInfo } from '@/api/login'
3
 import { getToken, setToken, removeToken } from '@/utils/auth'
3
 import { getToken, setToken, removeToken } from '@/utils/auth'
4
-import { joinApiUrl } from '@/config'
4
+import { resolveResourceUrl } from '@/utils/resourceUrl'
5
 import { hasPartnerRole, normalizeRoles, PARTNER_ROLE_DENIED_MSG, shouldDenyPartnerLogin } from '@/utils/partnerRole'
5
 import { hasPartnerRole, normalizeRoles, PARTNER_ROLE_DENIED_MSG, shouldDenyPartnerLogin } from '@/utils/partnerRole'
6
 
6
 
7
 const state = reactive({
7
 const state = reactive({
@@ -15,18 +15,8 @@ const state = reactive({
15
   permissions: []
15
   permissions: []
16
 })
16
 })
17
 
17
 
18
-function isHttp(url) {
19
-  return /^https?:\/\//i.test(url || '')
20
-}
21
-
22
 function resolveAvatar(path) {
18
 function resolveAvatar(path) {
23
-  if (!path) {
24
-    return ''
25
-  }
26
-  if (isHttp(path)) {
27
-    return path
28
-  }
29
-  return joinApiUrl(path)
19
+  return resolveResourceUrl(path)
30
 }
20
 }
31
 
21
 
32
 function clearSessionState() {
22
 function clearSessionState() {

+ 38 - 0
ruoyi-jiaoyi/utils/resourceUrl.js

@@ -0,0 +1,38 @@
1
+import { joinApiUrl } from '@/config'
2
+import { isExternal } from '@/utils/validate'
3
+
4
+/**
5
+ * 将接口返回的资源路径转为可访问 URL(对齐 ruoyi-ui ImagePreview)
6
+ * - 外链 / data: / blob: 原样返回
7
+ * - 相对路径拼接 BASE_API(H5 生产为当前浏览器 origin + /prod-api)
8
+ * @param {string} src 支持逗号分隔时取第一项
9
+ */
10
+export function resolveResourceUrl(src) {
11
+  if (!src) {
12
+    return ''
13
+  }
14
+  const path = String(src).split(',')[0].trim()
15
+  if (!path) {
16
+    return ''
17
+  }
18
+  if (isExternal(path)) {
19
+    return path
20
+  }
21
+  return joinApiUrl(path)
22
+}
23
+
24
+/**
25
+ * 多图预览列表(对齐 ImagePreview realSrcList)
26
+ * @param {string} src 逗号分隔
27
+ * @returns {string[]}
28
+ */
29
+export function resolveResourceUrlList(src) {
30
+  if (!src) {
31
+    return []
32
+  }
33
+  return String(src)
34
+    .split(',')
35
+    .map((item) => item.trim())
36
+    .filter(Boolean)
37
+    .map((item) => (isExternal(item) ? item : joinApiUrl(item)))
38
+}

+ 12 - 0
ruoyi-jiaoyi/utils/validate.js

@@ -0,0 +1,12 @@
1
+/**
2
+ * 判断 path 是否为外链(对齐 ruoyi-ui/src/utils/validate.js)
3
+ * @param {string} path
4
+ */
5
+export function isExternal(path) {
6
+  return /^(https?:|mailto:|tel:|data:|blob:)/.test(path || '')
7
+}
8
+
9
+/** 是否为 http(s) 绝对地址 */
10
+export function isHttp(url) {
11
+  return /^https?:\/\//i.test(url || '')
12
+}

+ 80 - 0
ruoyi-ui-app/components/ImagePreview/index.vue

@@ -0,0 +1,80 @@
1
+<template>
2
+  <image
3
+    v-if="realSrc"
4
+    :src="realSrc"
5
+    :mode="mode"
6
+    :class="imgClass"
7
+    :style="imgStyle"
8
+    @click="onImageClick"
9
+    @error="$emit('error', $event)"
10
+  />
11
+</template>
12
+
13
+<script>
14
+import { resolveResourceUrl, resolveResourceUrlList } from '@/utils/resourceUrl'
15
+
16
+/**
17
+ * 接口图片预览(对齐 ruoyi-ui/src/components/ImagePreview)
18
+ * 相对路径自动拼接 BASE_API;H5 生产环境为当前浏览器 origin + /prod-api
19
+ */
20
+export default {
21
+  name: 'ImagePreview',
22
+  props: {
23
+    src: {
24
+      type: String,
25
+      default: ''
26
+    },
27
+    mode: {
28
+      type: String,
29
+      default: 'aspectFill'
30
+    },
31
+    width: {
32
+      type: [Number, String],
33
+      default: ''
34
+    },
35
+    height: {
36
+      type: [Number, String],
37
+      default: ''
38
+    },
39
+    preview: {
40
+      type: Boolean,
41
+      default: false
42
+    },
43
+    imgClass: {
44
+      type: String,
45
+      default: ''
46
+    }
47
+  },
48
+  emits: ['error', 'click'],
49
+  computed: {
50
+    realSrc() {
51
+      return resolveResourceUrl(this.src)
52
+    },
53
+    realSrcList() {
54
+      return resolveResourceUrlList(this.src)
55
+    },
56
+    imgStyle() {
57
+      const style = {}
58
+      if (this.width !== '' && this.width != null) {
59
+        style.width = typeof this.width === 'string' ? this.width : `${this.width}px`
60
+      }
61
+      if (this.height !== '' && this.height != null) {
62
+        style.height = typeof this.height === 'string' ? this.height : `${this.height}px`
63
+      }
64
+      return style
65
+    }
66
+  },
67
+  methods: {
68
+    onImageClick(e) {
69
+      this.$emit('click', e)
70
+      if (!this.preview || !this.realSrcList.length) {
71
+        return
72
+      }
73
+      uni.previewImage({
74
+        urls: this.realSrcList,
75
+        current: this.realSrc
76
+      })
77
+    }
78
+  }
79
+}
80
+</script>

+ 25 - 2
ruoyi-ui-app/config/index.js

@@ -7,11 +7,21 @@
7
  * 打包 H5:uni build -p h5
7
  * 打包 H5:uni build -p h5
8
  */
8
  */
9
 
9
 
10
+import { isExternal } from '@/utils/validate'
11
+
10
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
12
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
11
 
13
 
12
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
14
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
13
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
15
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
14
 
16
 
17
+/** H5 打包后取浏览器当前 host(IP/域名),与 ruoyi-ui 生产 /prod-api 同域反代一致 */
18
+function getBrowserOrigin() {
19
+  if (typeof window === 'undefined' || !window.location) {
20
+    return ''
21
+  }
22
+  return String(window.location.origin || '').replace(/\/$/, '')
23
+}
24
+
15
 /**
25
 /**
16
  * H5 开发是否走 /dev-api 代理(需 vite.config.js rewrite)
26
  * H5 开发是否走 /dev-api 代理(需 vite.config.js rewrite)
17
  * 默认 false:直连 VITE_APP_API_HOST
27
  * 默认 false:直连 VITE_APP_API_HOST
@@ -20,7 +30,20 @@ export const H5_USE_PROXY = false
20
 
30
 
21
 function resolveBaseApi() {
31
 function resolveBaseApi() {
22
   if (import.meta.env.PROD) {
32
   if (import.meta.env.PROD) {
23
-    return envBaseApi || '/prod-api'
33
+    const apiPath = envBaseApi || '/prod-api'
34
+    // #ifdef H5
35
+    const origin = getBrowserOrigin()
36
+    if (origin) {
37
+      const normalized = apiPath.startsWith('/') ? apiPath : `/${apiPath}`
38
+      return `${origin}${normalized}`.replace(/\/$/, '')
39
+    }
40
+    // #endif
41
+    // #ifndef H5
42
+    if (/^https?:\/\//i.test(apiHost)) {
43
+      return apiHost
44
+    }
45
+    // #endif
46
+    return apiPath
24
   }
47
   }
25
   const devBase = envBaseApi || '/dev-api'
48
   const devBase = envBaseApi || '/dev-api'
26
   // #ifdef H5
49
   // #ifdef H5
@@ -41,7 +64,7 @@ export function joinApiUrl(path) {
41
   if (!path) {
64
   if (!path) {
42
     return BASE_API
65
     return BASE_API
43
   }
66
   }
44
-  if (/^https?:\/\//i.test(path)) {
67
+  if (isExternal(path)) {
45
     return path
68
     return path
46
   }
69
   }
47
   const p = path.startsWith('/') ? path : '/' + path
70
   const p = path.startsWith('/') ? path : '/' + path

+ 2 - 2
ruoyi-ui-app/package-a/agri-classroom/index.vue

@@ -128,7 +128,7 @@ import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
128
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
128
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
129
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
129
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
130
 import tabPage from '@/mixins/tabPage'
130
 import tabPage from '@/mixins/tabPage'
131
-import { joinApiUrl } from '@/config'
131
+import { resolveResourceUrl } from '@/utils/resourceUrl'
132
 import { listAgriculturalCourse } from '@/api/agriculturalCourse'
132
 import { listAgriculturalCourse } from '@/api/agriculturalCourse'
133
 
133
 
134
 /** 视频课程 course_topic(005001–005004) */
134
 /** 视频课程 course_topic(005001–005004) */
@@ -450,7 +450,7 @@ export default {
450
     },
450
     },
451
     articleCover(card) {
451
     articleCover(card) {
452
       if (card.coverFileUrl) {
452
       if (card.coverFileUrl) {
453
-        return joinApiUrl(card.coverFileUrl)
453
+        return resolveResourceUrl(card.coverFileUrl)
454
       }
454
       }
455
       return this.coverSrc
455
       return this.coverSrc
456
     },
456
     },

+ 2 - 2
ruoyi-ui-app/package-a/booking-expert/index.vue

@@ -203,7 +203,7 @@ import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
203
 import URate from 'uview-plus/components/u-rate/u-rate.vue'
203
 import URate from 'uview-plus/components/u-rate/u-rate.vue'
204
 import UTextarea from 'uview-plus/components/u-textarea/u-textarea.vue'
204
 import UTextarea from 'uview-plus/components/u-textarea/u-textarea.vue'
205
 import tabPage from '@/mixins/tabPage'
205
 import tabPage from '@/mixins/tabPage'
206
-import { joinApiUrl } from '@/config'
206
+import { resolveResourceUrl } from '@/utils/resourceUrl'
207
 import { ensureApiToken } from '@/utils/apiAuth'
207
 import { ensureApiToken } from '@/utils/apiAuth'
208
 import { useUserStore } from '@/store/user'
208
 import { useUserStore } from '@/store/user'
209
 import {
209
 import {
@@ -347,7 +347,7 @@ export default {
347
     },
347
     },
348
     photoSrc() {
348
     photoSrc() {
349
       const url = this.resource && this.resource.photoFileUrl
349
       const url = this.resource && this.resource.photoFileUrl
350
-      return url ? joinApiUrl(url) : ''
350
+      return url ? resolveResourceUrl(url) : ''
351
     },
351
     },
352
     selectedDateBooked() {
352
     selectedDateBooked() {
353
       return !!this.bookedDateMap[this.selectedDateKey]
353
       return !!this.bookedDateMap[this.selectedDateKey]

+ 2 - 2
ruoyi-ui-app/package-a/booking-org/index.vue

@@ -132,7 +132,7 @@ import UFormItem from 'uview-plus/components/u-form-item/u-form-item.vue'
132
 import UInput from 'uview-plus/components/u-input/u-input.vue'
132
 import UInput from 'uview-plus/components/u-input/u-input.vue'
133
 import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
133
 import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
134
 import tabPage from '@/mixins/tabPage'
134
 import tabPage from '@/mixins/tabPage'
135
-import { joinApiUrl } from '@/config'
135
+import { resolveResourceUrl } from '@/utils/resourceUrl'
136
 import { ensureApiToken } from '@/utils/apiAuth'
136
 import { ensureApiToken } from '@/utils/apiAuth'
137
 import { useUserStore } from '@/store/user'
137
 import { useUserStore } from '@/store/user'
138
 import {
138
 import {
@@ -213,7 +213,7 @@ export default {
213
     },
213
     },
214
     photoSrc() {
214
     photoSrc() {
215
       const url = this.resource && this.resource.photoFileUrl
215
       const url = this.resource && this.resource.photoFileUrl
216
-      return url ? joinApiUrl(url) : ''
216
+      return url ? resolveResourceUrl(url) : ''
217
     },
217
     },
218
     selectedDateBooked() {
218
     selectedDateBooked() {
219
       return !!this.bookedDateMap[this.selectedDateKey]
219
       return !!this.bookedDateMap[this.selectedDateKey]

+ 2 - 2
ruoyi-ui-app/package-a/booking-service/index.vue

@@ -115,7 +115,7 @@ import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
115
 import UButton from 'uview-plus/components/u-button/u-button.vue'
115
 import UButton from 'uview-plus/components/u-button/u-button.vue'
116
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
116
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
117
 import tabPage from '@/mixins/tabPage'
117
 import tabPage from '@/mixins/tabPage'
118
-import { joinApiUrl } from '@/config'
118
+import { resolveResourceUrl } from '@/utils/resourceUrl'
119
 import { ensureApiToken } from '@/utils/apiAuth'
119
 import { ensureApiToken } from '@/utils/apiAuth'
120
 import {
120
 import {
121
   listBookingDates,
121
   listBookingDates,
@@ -445,7 +445,7 @@ export default {
445
     },
445
     },
446
     avatarUrl(item) {
446
     avatarUrl(item) {
447
       const url = item && (item.photoFileUrl || (item.raw && item.raw.photoFileUrl))
447
       const url = item && (item.photoFileUrl || (item.raw && item.raw.photoFileUrl))
448
-      const result = url ? joinApiUrl(url) : ''
448
+      const result = url ? resolveResourceUrl(url) : ''
449
       return result
449
       return result
450
     },
450
     },
451
     onBook(item) {
451
     onBook(item) {

+ 2 - 2
ruoyi-ui-app/package-a/booking-vet/index.vue

@@ -184,7 +184,7 @@ import UPicker from 'uview-plus/components/u-picker/u-picker.vue'
184
 import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
184
 import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
185
 import UTextarea from 'uview-plus/components/u-textarea/u-textarea.vue'
185
 import UTextarea from 'uview-plus/components/u-textarea/u-textarea.vue'
186
 import tabPage from '@/mixins/tabPage'
186
 import tabPage from '@/mixins/tabPage'
187
-import { joinApiUrl } from '@/config'
187
+import { resolveResourceUrl } from '@/utils/resourceUrl'
188
 import { ensureApiToken } from '@/utils/apiAuth'
188
 import { ensureApiToken } from '@/utils/apiAuth'
189
 import { useUserStore } from '@/store/user'
189
 import { useUserStore } from '@/store/user'
190
 import {
190
 import {
@@ -342,7 +342,7 @@ export default {
342
     },
342
     },
343
     photoSrc() {
343
     photoSrc() {
344
       const url = this.resource && this.resource.photoFileUrl
344
       const url = this.resource && this.resource.photoFileUrl
345
-      return url ? joinApiUrl(url) : ''
345
+      return url ? resolveResourceUrl(url) : ''
346
     },
346
     },
347
     avatarText() {
347
     avatarText() {
348
       const name = (this.resource && this.resource.resourceName) || ''
348
       const name = (this.resource && this.resource.resourceName) || ''

+ 2 - 2
ruoyi-ui-app/package-a/breeding-news/index.vue

@@ -127,7 +127,7 @@ import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
127
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
127
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
128
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
128
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
129
 import tabPage from '@/mixins/tabPage'
129
 import tabPage from '@/mixins/tabPage'
130
-import { joinApiUrl } from '@/config'
130
+import { resolveResourceUrl } from '@/utils/resourceUrl'
131
 import { putNewsDetailPayload } from '@/utils/newsDetailCache'
131
 import { putNewsDetailPayload } from '@/utils/newsDetailCache'
132
 import { listInformationCategoryTree } from '@/api/category/informationCategory'
132
 import { listInformationCategoryTree } from '@/api/category/informationCategory'
133
 import { listFarmingNews } from '@/api/farmingNews'
133
 import { listFarmingNews } from '@/api/farmingNews'
@@ -500,7 +500,7 @@ export default {
500
     /** 列表项封面 */
500
     /** 列表项封面 */
501
     articleCover(item) {
501
     articleCover(item) {
502
       if (item.coverFileUrl) {
502
       if (item.coverFileUrl) {
503
-        return joinApiUrl(item.coverFileUrl)
503
+        return resolveResourceUrl(item.coverFileUrl)
504
       }
504
       }
505
       return this.coverSrc
505
       return this.coverSrc
506
     },
506
     },

+ 3 - 3
ruoyi-ui-app/package-a/course-detail/index.vue

@@ -30,7 +30,7 @@
30
 
30
 
31
 <script>
31
 <script>
32
 import tabPage from '@/mixins/tabPage'
32
 import tabPage from '@/mixins/tabPage'
33
-import { joinApiUrl } from '@/config'
33
+import { resolveResourceUrl } from '@/utils/resourceUrl'
34
 
34
 
35
 const POSTER = '/static/ai/hero.png'
35
 const POSTER = '/static/ai/hero.png'
36
 
36
 
@@ -65,10 +65,10 @@ export default {
65
     this.displayDate = this.decodeQuery(q, 'date') || this.$t('courseDetailPage.noDate')
65
     this.displayDate = this.decodeQuery(q, 'date') || this.$t('courseDetailPage.noDate')
66
 
66
 
67
     const src = this.decodeQuery(q, 'src')
67
     const src = this.decodeQuery(q, 'src')
68
-    this.videoSrc = src ? joinApiUrl(src) : ''
68
+    this.videoSrc = src ? resolveResourceUrl(src) : ''
69
 
69
 
70
     const cover = this.decodeQuery(q, 'cover')
70
     const cover = this.decodeQuery(q, 'cover')
71
-    this.posterSrc = cover ? joinApiUrl(cover) : POSTER
71
+    this.posterSrc = cover ? resolveResourceUrl(cover) : POSTER
72
   },
72
   },
73
   onShow() {
73
   onShow() {
74
     const p = uni.setNavigationBarTitle({
74
     const p = uni.setNavigationBarTitle({

+ 2 - 2
ruoyi-ui-app/package-a/livestock-resources/index.vue

@@ -84,7 +84,7 @@ import UTabs from 'uview-plus/components/u-tabs/u-tabs.vue'
84
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
84
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
85
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
85
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
86
 import tabPage from '@/mixins/tabPage'
86
 import tabPage from '@/mixins/tabPage'
87
-import { joinApiUrl } from '@/config'
87
+import { resolveResourceUrl } from '@/utils/resourceUrl'
88
 import { putNewsDetailPayload } from '@/utils/newsDetailCache'
88
 import { putNewsDetailPayload } from '@/utils/newsDetailCache'
89
 import { listLivestockResource } from '@/api/livestockResource'
89
 import { listLivestockResource } from '@/api/livestockResource'
90
 
90
 
@@ -351,7 +351,7 @@ export default {
351
     },
351
     },
352
     articleCover(item) {
352
     articleCover(item) {
353
       if (item.coverFileUrl) {
353
       if (item.coverFileUrl) {
354
-        return joinApiUrl(item.coverFileUrl)
354
+        return resolveResourceUrl(item.coverFileUrl)
355
       }
355
       }
356
       return this.coverSrc
356
       return this.coverSrc
357
     },
357
     },

+ 2 - 2
ruoyi-ui-app/package-a/my-enrollment/index.vue

@@ -97,7 +97,7 @@ import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
97
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
97
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
98
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
98
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
99
 import tabPage from '@/mixins/tabPage'
99
 import tabPage from '@/mixins/tabPage'
100
-import { joinApiUrl } from '@/config'
100
+import { resolveResourceUrl } from '@/utils/resourceUrl'
101
 import { listMyEnroll } from '@/api/practicalTraining'
101
 import { listMyEnroll } from '@/api/practicalTraining'
102
 
102
 
103
 const TRAINING_STATUS_CODES = ['006001', '006002', '006003', '006004']
103
 const TRAINING_STATUS_CODES = ['006001', '006002', '006003', '006004']
@@ -362,7 +362,7 @@ export default {
362
     },
362
     },
363
     cardCover(card) {
363
     cardCover(card) {
364
       if (card.coverFileUrl) {
364
       if (card.coverFileUrl) {
365
-        return joinApiUrl(card.coverFileUrl)
365
+        return resolveResourceUrl(card.coverFileUrl)
366
       }
366
       }
367
       return this.coverSrc
367
       return this.coverSrc
368
     },
368
     },

+ 3 - 3
ruoyi-ui-app/package-a/news-detail/index.vue

@@ -93,7 +93,7 @@
93
 <script>
93
 <script>
94
 import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
94
 import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
95
 import tabPage from '@/mixins/tabPage'
95
 import tabPage from '@/mixins/tabPage'
96
-import { joinApiUrl } from '@/config'
96
+import { resolveResourceUrl } from '@/utils/resourceUrl'
97
 import { fileNameFromPath, openOnlinePreview, resolveMediaKind } from '@/utils/filePreview'
97
 import { fileNameFromPath, openOnlinePreview, resolveMediaKind } from '@/utils/filePreview'
98
 import { takeNewsDetailPayload } from '@/utils/newsDetailCache'
98
 import { takeNewsDetailPayload } from '@/utils/newsDetailCache'
99
 
99
 
@@ -154,10 +154,10 @@ export default {
154
       return !!(this.contentFileUrl || '').trim()
154
       return !!(this.contentFileUrl || '').trim()
155
     },
155
     },
156
     coverFullUrl() {
156
     coverFullUrl() {
157
-      return joinApiUrl(this.coverFileUrl)
157
+      return resolveResourceUrl(this.coverFileUrl)
158
     },
158
     },
159
     contentFullUrl() {
159
     contentFullUrl() {
160
-      return joinApiUrl(this.contentFileUrl)
160
+      return resolveResourceUrl(this.contentFileUrl)
161
     },
161
     },
162
     attachmentKind() {
162
     attachmentKind() {
163
       return resolveMediaKind(this.contentFileUrl)
163
       return resolveMediaKind(this.contentFileUrl)

+ 2 - 2
ruoyi-ui-app/package-a/online-clinic/index.vue

@@ -95,7 +95,7 @@ import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
95
 import UButton from 'uview-plus/components/u-button/u-button.vue'
95
 import UButton from 'uview-plus/components/u-button/u-button.vue'
96
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
96
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
97
 import tabPage from '@/mixins/tabPage'
97
 import tabPage from '@/mixins/tabPage'
98
-import { joinApiUrl } from '@/config'
98
+import { resolveResourceUrl } from '@/utils/resourceUrl'
99
 import { ensureApiToken } from '@/utils/apiAuth'
99
 import { ensureApiToken } from '@/utils/apiAuth'
100
 import {
100
 import {
101
   listOnlineConsultVets,
101
   listOnlineConsultVets,
@@ -330,7 +330,7 @@ export default {
330
     },
330
     },
331
     avatarUrl(item) {
331
     avatarUrl(item) {
332
       if (item && item.photoFileUrl) {
332
       if (item && item.photoFileUrl) {
333
-        return joinApiUrl(item.photoFileUrl)
333
+        return resolveResourceUrl(item.photoFileUrl)
334
       }
334
       }
335
       return ''
335
       return ''
336
     },
336
     },

+ 3 - 3
ruoyi-ui-app/package-a/training-detail/index.vue

@@ -45,7 +45,7 @@
45
 import UButton from 'uview-plus/components/u-button/u-button.vue'
45
 import UButton from 'uview-plus/components/u-button/u-button.vue'
46
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
46
 import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
47
 import tabPage from '@/mixins/tabPage'
47
 import tabPage from '@/mixins/tabPage'
48
-import { joinApiUrl } from '@/config'
48
+import { resolveResourceUrl } from '@/utils/resourceUrl'
49
 import {
49
 import {
50
   getMyEnrollDetail,
50
   getMyEnrollDetail,
51
   enrollPracticalTraining,
51
   enrollPracticalTraining,
@@ -213,7 +213,7 @@ export default {
213
     this.total = Number.isFinite(tot) && tot >= 0 ? tot : 0
213
     this.total = Number.isFinite(tot) && tot >= 0 ? tot : 0
214
 
214
 
215
     const cover = this.decodeQuery(q, 'cover')
215
     const cover = this.decodeQuery(q, 'cover')
216
-    this.coverSrc = cover ? joinApiUrl(cover) : COVER
216
+    this.coverSrc = cover ? resolveResourceUrl(cover) : COVER
217
 
217
 
218
     this.applyQueryDates(q)
218
     this.applyQueryDates(q)
219
   },
219
   },
@@ -273,7 +273,7 @@ export default {
273
         this.total = Number(d.plannedHeadCount)
273
         this.total = Number(d.plannedHeadCount)
274
       }
274
       }
275
       if (d.coverFileUrl) {
275
       if (d.coverFileUrl) {
276
-        this.coverSrc = joinApiUrl(d.coverFileUrl)
276
+        this.coverSrc = resolveResourceUrl(d.coverFileUrl)
277
       }
277
       }
278
       uni.setNavigationBarTitle({
278
       uni.setNavigationBarTitle({
279
         title: this.displayTitleRaw || this.$t(this.navTitleKey)
279
         title: this.displayTitleRaw || this.$t(this.navTitleKey)

+ 2 - 2
ruoyi-ui-app/package-a/vet-profile/index.vue

@@ -49,7 +49,7 @@
49
 import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
49
 import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
50
 import UButton from 'uview-plus/components/u-button/u-button.vue'
50
 import UButton from 'uview-plus/components/u-button/u-button.vue'
51
 import tabPage from '@/mixins/tabPage'
51
 import tabPage from '@/mixins/tabPage'
52
-import { joinApiUrl } from '@/config'
52
+import { resolveResourceUrl } from '@/utils/resourceUrl'
53
 import { ensureApiToken } from '@/utils/apiAuth'
53
 import { ensureApiToken } from '@/utils/apiAuth'
54
 import {
54
 import {
55
   loadVetProfileCache,
55
   loadVetProfileCache,
@@ -80,7 +80,7 @@ export default {
80
     },
80
     },
81
     photoSrc() {
81
     photoSrc() {
82
       const url = this.vet && this.vet.photoFileUrl
82
       const url = this.vet && this.vet.photoFileUrl
83
-      return url ? joinApiUrl(url) : ''
83
+      return url ? resolveResourceUrl(url) : ''
84
     },
84
     },
85
     vetTitle() {
85
     vetTitle() {
86
       return (this.vet && this.vet.resourceName) || this.$t('vetProfilePage.nameFallback')
86
       return (this.vet && this.vet.resourceName) || this.$t('vetProfilePage.nameFallback')

+ 2 - 2
ruoyi-ui-app/pages/message/index.vue

@@ -82,7 +82,7 @@ import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
82
 import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
82
 import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
83
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
83
 import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
84
 import tabPage from '@/mixins/tabPage'
84
 import tabPage from '@/mixins/tabPage'
85
-import { joinApiUrl } from '@/config'
85
+import { resolveResourceUrl } from '@/utils/resourceUrl'
86
 import { ensureApiToken } from '@/utils/apiAuth'
86
 import { ensureApiToken } from '@/utils/apiAuth'
87
 import {
87
 import {
88
   listAskerConsultSessions,
88
   listAskerConsultSessions,
@@ -316,7 +316,7 @@ export default {
316
       const fromProfile = loadVetProfileCache(id)
316
       const fromProfile = loadVetProfileCache(id)
317
       const fromBooking = loadBookingResourceCache('004001', id)
317
       const fromBooking = loadBookingResourceCache('004001', id)
318
       const url = (fromProfile && fromProfile.photoFileUrl) || (fromBooking && fromBooking.photoFileUrl)
318
       const url = (fromProfile && fromProfile.photoFileUrl) || (fromBooking && fromBooking.photoFileUrl)
319
-      return url ? joinApiUrl(url) : ''
319
+      return url ? resolveResourceUrl(url) : ''
320
     },
320
     },
321
     avatarBg(row) {
321
     avatarBg(row) {
322
       const colors = ['#B8C5B8', '#C9B8A8', '#A8B4C9', '#C4B8C8', '#B5A896']
322
       const colors = ['#B8C5B8', '#C9B8A8', '#A8B4C9', '#C4B8C8', '#B5A896']

+ 2 - 12
ruoyi-ui-app/store/user.js

@@ -1,7 +1,7 @@
1
 import { reactive } from 'vue'
1
 import { reactive } from 'vue'
2
 import { login as loginApi, logout as logoutApi, getInfo } from '@/api/login'
2
 import { login as loginApi, logout as logoutApi, getInfo } from '@/api/login'
3
 import { getToken, setToken, removeToken } from '@/utils/auth'
3
 import { getToken, setToken, removeToken } from '@/utils/auth'
4
-import { joinApiUrl } from '@/config'
4
+import { resolveResourceUrl } from '@/utils/resourceUrl'
5
 
5
 
6
 const state = reactive({
6
 const state = reactive({
7
   token: getToken(),
7
   token: getToken(),
@@ -13,18 +13,8 @@ const state = reactive({
13
   permissions: []
13
   permissions: []
14
 })
14
 })
15
 
15
 
16
-function isHttp(url) {
17
-  return /^https?:\/\//i.test(url || '')
18
-}
19
-
20
 function resolveAvatar(path) {
16
 function resolveAvatar(path) {
21
-  if (!path) {
22
-    return ''
23
-  }
24
-  if (isHttp(path)) {
25
-    return path
26
-  }
27
-  return joinApiUrl(path)
17
+  return resolveResourceUrl(path)
28
 }
18
 }
29
 
19
 
30
 export function useUserStore() {
20
 export function useUserStore() {

+ 2 - 4
ruoyi-ui-app/utils/aiConsult.js

@@ -1,4 +1,4 @@
1
-import { joinApiUrl } from '@/config'
1
+import { resolveResourceUrl } from '@/utils/resourceUrl'
2
 
2
 
3
 /** 与后端 ConsultSessionRules 一致 */
3
 /** 与后端 ConsultSessionRules 一致 */
4
 export const SENDER_ROLE_USER = 1
4
 export const SENDER_ROLE_USER = 1
@@ -142,9 +142,7 @@ export function isVetMessage(m) {
142
 }
142
 }
143
 
143
 
144
 export function mediaUrl(url) {
144
 export function mediaUrl(url) {
145
-  if (!url) return ''
146
-  if (/^https?:\/\//i.test(url)) return url
147
-  return joinApiUrl(url)
145
+  return resolveResourceUrl(url)
148
 }
146
 }
149
 
147
 
150
 export function normalizeMessage(m) {
148
 export function normalizeMessage(m) {

+ 2 - 2
ruoyi-ui-app/utils/filePreview.js

@@ -1,4 +1,4 @@
1
-import { joinApiUrl } from '@/config'
1
+import { resolveResourceUrl } from '@/utils/resourceUrl'
2
 
2
 
3
 const IMAGE_RE = /\.(png|jpe?g|gif|webp|bmp)$/i
3
 const IMAGE_RE = /\.(png|jpe?g|gif|webp|bmp)$/i
4
 const VIDEO_RE = /\.(mp4|mov|m4v|webm|avi)$/i
4
 const VIDEO_RE = /\.(mp4|mov|m4v|webm|avi)$/i
@@ -29,7 +29,7 @@ export function fileNameFromPath(path) {
29
 
29
 
30
 /** 在线预览完整 URL(/profile/** 后端 permitAll,可直接访问) */
30
 /** 在线预览完整 URL(/profile/** 后端 permitAll,可直接访问) */
31
 export function getOnlinePreviewUrl(path) {
31
 export function getOnlinePreviewUrl(path) {
32
-  return joinApiUrl(path)
32
+  return resolveResourceUrl(path)
33
 }
33
 }
34
 
34
 
35
 const FILE_PREVIEW_PAGE = '/package-a/file-preview/index'
35
 const FILE_PREVIEW_PAGE = '/package-a/file-preview/index'

+ 38 - 0
ruoyi-ui-app/utils/resourceUrl.js

@@ -0,0 +1,38 @@
1
+import { joinApiUrl } from '@/config'
2
+import { isExternal } from '@/utils/validate'
3
+
4
+/**
5
+ * 将接口返回的资源路径转为可访问 URL(对齐 ruoyi-ui ImagePreview)
6
+ * - 外链 / data: / blob: 原样返回
7
+ * - 相对路径拼接 BASE_API(H5 生产为当前浏览器 origin + /prod-api)
8
+ * @param {string} src 支持逗号分隔时取第一项
9
+ */
10
+export function resolveResourceUrl(src) {
11
+  if (!src) {
12
+    return ''
13
+  }
14
+  const path = String(src).split(',')[0].trim()
15
+  if (!path) {
16
+    return ''
17
+  }
18
+  if (isExternal(path)) {
19
+    return path
20
+  }
21
+  return joinApiUrl(path)
22
+}
23
+
24
+/**
25
+ * 多图预览列表(对齐 ImagePreview realSrcList)
26
+ * @param {string} src 逗号分隔
27
+ * @returns {string[]}
28
+ */
29
+export function resolveResourceUrlList(src) {
30
+  if (!src) {
31
+    return []
32
+  }
33
+  return String(src)
34
+    .split(',')
35
+    .map((item) => item.trim())
36
+    .filter(Boolean)
37
+    .map((item) => (isExternal(item) ? item : joinApiUrl(item)))
38
+}

+ 12 - 0
ruoyi-ui-app/utils/validate.js

@@ -0,0 +1,12 @@
1
+/**
2
+ * 判断 path 是否为外链(对齐 ruoyi-ui/src/utils/validate.js)
3
+ * @param {string} path
4
+ */
5
+export function isExternal(path) {
6
+  return /^(https?:|mailto:|tel:|data:|blob:)/.test(path || '')
7
+}
8
+
9
+/** 是否为 http(s) 绝对地址 */
10
+export function isHttp(url) {
11
+  return /^https?:\/\//i.test(url || '')
12
+}