xsh_1997 6 dagen geleden
bovenliggende
commit
66f719bd2c

+ 41 - 39
ruoyi-ui-app/App.vue

@@ -1,40 +1,42 @@
1
-<script>

2
-import { syncUserOnLaunch } from '@/permission'

3
-

4
-export default {

5
-  onLaunch() {

6
-    syncUserOnLaunch().catch(() => {})

7
-  },

8
-  onShow() {

9
-    console.log('App Show')

10
-  },

11
-  onHide() {

12
-    console.log('App Hide')

13
-  }

14
-}

15
-</script>

16
-

17
-<style lang="scss">

18
-/* 须写在首行:与 uni.scss 中的 theme 配合,否则 index 内 $u-* 未定义 */

19
-@import 'uview-plus/index.scss';

20
-@import '@/styles/app-lang.scss';

21
-

22
-/* 全屏高度链:H5 若写死 page height:100%,易与原生 tabBar 抢高度导致底栏「看不见」 */

23
-/* #ifdef H5 */

24
-page {

25
-  min-height: 100%;

26
-  height: auto;

27
-  box-sizing: border-box;

28
-  background-color: #f0ebe5;

29
-}

30
-/* #endif */

31
-

32
-/* #ifndef H5 */

33
-page {

34
-  height: 100%;

35
-  min-height: 100%;

36
-  background-color: #f0ebe5;

37
-}

38
-/* #endif */

39
-</style>

1
+<script>
2
+import { syncUserOnLaunch } from '@/permission'
3
+import { installTabBarApiGuard } from '@/utils/tabBar'
4
+
5
+export default {
6
+  onLaunch() {
7
+    installTabBarApiGuard()
8
+    syncUserOnLaunch().catch(() => {})
9
+  },
10
+  onShow() {
11
+    console.log('App Show')
12
+  },
13
+  onHide() {
14
+    console.log('App Hide')
15
+  }
16
+}
17
+</script>
18
+
19
+<style lang="scss">
20
+/* 须写在首行:与 uni.scss 中的 theme 配合,否则 index 内 $u-* 未定义 */
21
+@import 'uview-plus/index.scss';
22
+@import '@/styles/app-lang.scss';
23
+
24
+/* 全屏高度链:H5 若写死 page height:100%,易与原生 tabBar 抢高度导致底栏「看不见」 */
25
+/* #ifdef H5 */
26
+page {
27
+  min-height: 100%;
28
+  height: auto;
29
+  box-sizing: border-box;
30
+  background-color: #f0ebe5;
31
+}
32
+/* #endif */
33
+
34
+/* #ifndef H5 */
35
+page {
36
+  height: 100%;
37
+  min-height: 100%;
38
+  background-color: #f0ebe5;
39
+}
40
+/* #endif */
41
+</style>
40 42
 

+ 35 - 28
ruoyi-ui-app/config/index.js

@@ -7,43 +7,25 @@
7 7
  * 打包 H5:uni build -p h5
8 8
  */
9 9
 
10
-import { isExternal } from '@/utils/validate'
11
-
10
+/** 开发兜底 / 小程序未配域名时的默认值(生产 H5 不应依赖此项) */
12 11
 const apiHost = String(import.meta.env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
13 12
 
13
+/** .env 显式配置的后端域名(小程序/App 生产环境必填 https) */
14
+const hostFromEnv = String(import.meta.env.VITE_APP_API_HOST || '').trim().replace(/\/$/, '')
15
+
14 16
 /** 与 ruoyi-ui 的 VUE_APP_BASE_API 同源配置(dev/prod 由 .env 注入) */
15 17
 const envBaseApi = String(import.meta.env.VITE_APP_BASE_API || '').trim()
16 18
 
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
-
25 19
 /**
26
- * H5 开发是否走 /dev-api 代理(需 vite.config.js rewrite)
27
- * 默认 false:直连 VITE_APP_API_HOST
20
+ * H5 开发是否走 /dev-api 代理(需 vite.config.js server.proxy)
21
+ * true:请求发到当前页同源 /dev-api,由 Vite 转发到 VITE_APP_API_HOST(推荐,避免跨域)
22
+ * false:直连 VITE_APP_API_HOST(仅调试代理问题时使用)
28 23
  */
29
-export const H5_USE_PROXY = false
24
+export const H5_USE_PROXY = true
30 25
 
31 26
 function resolveBaseApi() {
32 27
   if (import.meta.env.PROD) {
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
28
+    return envBaseApi || '/prodapi'
47 29
   }
48 30
   const devBase = envBaseApi || '/dev-api'
49 31
   // #ifdef H5
@@ -56,6 +38,31 @@ function resolveBaseApi() {
56 38
 
57 39
 export const BASE_API = resolveBaseApi()
58 40
 
41
+/**
42
+ * 后端完整地址(开发 / 小程序打包用 .env 的 VITE_APP_API_HOST)
43
+ * H5 生产图片在运行时由 getFileBase() 自动取 window.location.origin
44
+ */
45
+export const API_HOST = hostFromEnv || apiHost
46
+
47
+/** 图片根地址(构建时);H5 生产以运行时 getFileBase() 为准 */
48
+export const FILE_BASE = API_HOST
49
+
50
+/**
51
+ * 拼接图片、上传文件等静态资源地址(profile/upload 等)
52
+ * @param {string} path
53
+ */
54
+export function joinFileUrl(path) {
55
+  if (!path) {
56
+    return FILE_BASE
57
+  }
58
+  if (/^https?:\/\//i.test(path)) {
59
+    return path
60
+  }
61
+  const p = path.startsWith('/') ? path : '/' + path
62
+  const base = String(FILE_BASE).replace(/\/$/, '')
63
+  return base + p
64
+}
65
+
59 66
 /**
60 67
  * 拼接接口或静态资源地址(/login、/profile/...)
61 68
  * @param {string} path
@@ -64,7 +71,7 @@ export function joinApiUrl(path) {
64 71
   if (!path) {
65 72
     return BASE_API
66 73
   }
67
-  if (isExternal(path)) {
74
+  if (/^https?:\/\//i.test(path)) {
68 75
     return path
69 76
   }
70 77
   const p = path.startsWith('/') ? path : '/' + path

+ 1 - 1
ruoyi-ui-app/main.js

@@ -1,5 +1,5 @@
1
-import './uni.promisify.adaptor'
2 1
 import '@/utils/tabBar'
2
+import './uni.promisify.adaptor'
3 3
 import App from './App'
4 4
 
5 5
 // #ifndef VUE3

+ 23 - 9
ruoyi-ui-app/mixins/tabPage.js

@@ -1,4 +1,4 @@
1
-import { syncTabBarText } from '@/utils/tabBar'
1
+import { ensureTabBarEntry, isTabBarPage, syncTabBarText } from '@/utils/tabBar'
2 2
 
3 3
 /**
4 4
  * Tab 子页共用:根节点语言 class、导航标题、底部 tab 文案与 i18n 同步
@@ -19,15 +19,29 @@ export default {
19 19
     }
20 20
   },
21 21
   onShow() {
22
-    syncTabBarText((k) => this.$t(k))
23
-    const key = this.navTitleKey
24
-    if (key) {
25
-      const p = uni.setNavigationBarTitle({
26
-        title: this.$t(key)
27
-      })
28
-      if (p && typeof p.catch === 'function') {
29
-        p.catch(() => {})
22
+    const applyTabPageUi = () => {
23
+      if (!isTabBarPage()) {
24
+        return
30 25
       }
26
+      syncTabBarText((k) => this.$t(k))
27
+      const key = this.navTitleKey
28
+      if (key) {
29
+        const p = uni.setNavigationBarTitle({
30
+          title: this.$t(key)
31
+        })
32
+        if (p && typeof p.catch === 'function') {
33
+          p.catch(() => {})
34
+        }
35
+      }
36
+    }
37
+    if (isTabBarPage()) {
38
+      applyTabPageUi()
39
+      return
31 40
     }
41
+    ensureTabBarEntry().then((ok) => {
42
+      if (ok) {
43
+        applyTabPageUi()
44
+      }
45
+    })
32 46
   }
33 47
 }

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

@@ -70,7 +70,6 @@
70 70
                     img-mode="aspectFill"
71 71
                     :threshold="200"
72 72
                     :index="item.left.id"
73
-                    @click.stop="() => onPreviewCover(item.left)"
74 73
                   />
75 74
                   <text class="ac-card__title">{{ item.left.title }}</text>
76 75
                     <view v-if="item.left.type === 'video'" class="ac-card__foot ac-card__foot--video">
@@ -96,7 +95,6 @@
96 95
                     img-mode="aspectFill"
97 96
                     :threshold="200"
98 97
                     :index="item.right.id"
99
-                    @click.stop="() => onPreviewCover(item.right)"
100 98
                   />
101 99
                   <text class="ac-card__title">{{ item.right.title }}</text>
102 100
                   <view v-if="item.right.type === 'video'" class="ac-card__foot ac-card__foot--video">
@@ -454,11 +452,6 @@ export default {
454 452
       }
455 453
       return this.coverSrc
456 454
     },
457
-    onPreviewCover(card) {
458
-      const url = this.articleCover(card)
459
-      if (!url) return
460
-      uni.previewImage({ urls: [url], current: 0 })
461
-    },
462 455
     openCourseDetail(card) {
463 456
       if (!card) return
464 457
       if (card.type === 'training') {

+ 23 - 10
ruoyi-ui-app/package-a/consult-detail/index.vue

@@ -301,15 +301,9 @@ export default {
301 301
     this.stopPoll()
302 302
   },
303 303
   mounted() {
304
-    if (typeof uni.getRecorderManager === 'function') {
305
-      const rm = uni.getRecorderManager()
306
-      rm.onStop((res) => this.onRecordStop(res))
307
-      rm.onError(() => {
308
-        this.isRecording = false
309
-        uni.showToast({ title: this.$t('consultDetailPage.recordFail'), icon: 'none' })
310
-      })
311
-      this.recorder = rm
312
-    }
304
+    // #ifndef H5
305
+    this.initRecorderManager()
306
+    // #endif
313 307
   },
314 308
   onUnload() {
315 309
     this.stopPoll()
@@ -346,6 +340,26 @@ export default {
346 340
         return String(raw)
347 341
       }
348 342
     },
343
+    initRecorderManager() {
344
+      // #ifdef H5
345
+      return
346
+      // #endif
347
+      if (typeof uni.getRecorderManager !== 'function') {
348
+        return
349
+      }
350
+      const rm = uni.getRecorderManager()
351
+      if (!rm || typeof rm.onStop !== 'function') {
352
+        return
353
+      }
354
+      rm.onStop((res) => this.onRecordStop(res))
355
+      if (typeof rm.onError === 'function') {
356
+        rm.onError(() => {
357
+          this.isRecording = false
358
+          uni.showToast({ title: this.$t('consultDetailPage.recordFail'), icon: 'none' })
359
+        })
360
+      }
361
+      this.recorder = rm
362
+    },
349 363
     openSession() {
350 364
       uni.showLoading({ title: this.$t('consultDetailPage.loading'), mask: true })
351 365
       const vetId = Number(this.vetResourceId) || this.vetResourceId
@@ -710,7 +724,6 @@ export default {
710 724
     },
711 725
     async send() {
712 726
       if (this.sendDisabled) return
713
-      console.log(111)
714 727
       const text = (this.draft || '').trim()
715 728
       const pending = this.pendingMedia
716 729
       this.sending = true

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

@@ -49,7 +49,6 @@
49 49
                       img-mode="aspectFill"
50 50
                       :threshold="200"
51 51
                       :index="item.left.id"
52
-                      @click.stop="() => onPreviewCover(item.left)"
53 52
                     />
54 53
                     <text class="me-card__title">{{ item.left.title }}</text>
55 54
                     <view class="me-card__foot">
@@ -71,7 +70,6 @@
71 70
                       img-mode="aspectFill"
72 71
                       :threshold="200"
73 72
                       :index="item.right.id"
74
-                      @click.stop="() => onPreviewCover(item.right)"
75 73
                     />
76 74
                     <text class="me-card__title">{{ item.right.title }}</text>
77 75
                     <view class="me-card__foot">
@@ -366,11 +364,6 @@ export default {
366 364
       }
367 365
       return this.coverSrc
368 366
     },
369
-    onPreviewCover(card) {
370
-      const url = this.cardCover(card)
371
-      if (!url) return
372
-      uni.previewImage({ urls: [url], current: 0 })
373
-    },
374 367
     openTrainingDetail(card) {
375 368
       if (!card) return
376 369
       const q = [

+ 21 - 7
ruoyi-ui-app/pages/mine/index.vue

@@ -69,7 +69,7 @@ import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
69 69
 import UButton from 'uview-plus/components/u-button/u-button.vue'
70 70
 import tabPage from '@/mixins/tabPage'
71 71
 import { setStoredLocale } from '@/utils/locale'
72
-import { syncTabBarText } from '@/utils/tabBar'
72
+import { ensureTabBarEntry, isTabBarPage, syncTabBarText } from '@/utils/tabBar'
73 73
 import { useUserStore } from '@/store/user'
74 74
 
75 75
 export default {
@@ -168,13 +168,27 @@ export default {
168 168
       this.$i18n.locale = next
169 169
       this.layoutKey += 1
170 170
       this.$nextTick(() => {
171
-        syncTabBarText((k) => this.$t(k))
172
-        const p = uni.setNavigationBarTitle({
173
-          title: this.$t(this.navTitleKey)
174
-        })
175
-        if (p && typeof p.catch === 'function') {
176
-          p.catch(() => {})
171
+        const applyLangUi = () => {
172
+          if (!isTabBarPage()) {
173
+            return
174
+          }
175
+          syncTabBarText((k) => this.$t(k))
176
+          const p = uni.setNavigationBarTitle({
177
+            title: this.$t(this.navTitleKey)
178
+          })
179
+          if (p && typeof p.catch === 'function') {
180
+            p.catch(() => {})
181
+          }
177 182
         }
183
+        if (isTabBarPage()) {
184
+          applyLangUi()
185
+          return
186
+        }
187
+        ensureTabBarEntry().then((ok) => {
188
+          if (ok) {
189
+            applyLangUi()
190
+          }
191
+        })
178 192
       })
179 193
     }
180 194
   }

+ 114 - 20
ruoyi-ui-app/utils/tabBar.js

@@ -21,6 +21,8 @@ const TABBAR_API_NAMES = [
21 21
   'setTabBarMidButton'
22 22
 ]
23 23
 
24
+let tabBarEntryTask = null
25
+
24 26
 function readUniTabPagePaths() {
25 27
   try {
26 28
     const list =
@@ -39,6 +41,24 @@ function readUniTabPagePaths() {
39 41
   }
40 42
 }
41 43
 
44
+function getPublicPathPrefixes() {
45
+  const prefixes = ['bqH5/', 'bqjy/']
46
+  try {
47
+    const envPath = String(import.meta.env.VITE_APP_PUBLIC_PATH || '')
48
+      .trim()
49
+      .replace(/^\/|\/$/g, '')
50
+    if (envPath) {
51
+      const withSlash = `${envPath}/`
52
+      if (!prefixes.includes(withSlash)) {
53
+        prefixes.unshift(withSlash)
54
+      }
55
+    }
56
+  } catch (e) {
57
+    // ignore
58
+  }
59
+  return prefixes
60
+}
61
+
42 62
 function normalizePageRoute(route) {
43 63
   if (!route) {
44 64
     return ''
@@ -47,8 +67,7 @@ function normalizePageRoute(route) {
47 67
   if (r.endsWith('.html')) {
48 68
     r = r.slice(0, -5)
49 69
   }
50
-  const prefixes = ['bqH5/', 'bqjy/']
51
-  for (const prefix of prefixes) {
70
+  for (const prefix of getPublicPathPrefixes()) {
52 71
     if (r.startsWith(prefix)) {
53 72
       r = r.slice(prefix.length)
54 73
     }
@@ -57,20 +76,71 @@ function normalizePageRoute(route) {
57 76
 }
58 77
 
59 78
 function getCurrentPageRoute() {
79
+  const stack = getCurrentPages()
80
+  if (stack.length) {
81
+    const cur = stack[stack.length - 1]
82
+    return cur.route || cur.$page?.route || cur.$page?.fullPath || ''
83
+  }
84
+  if (typeof window !== 'undefined' && window.location && window.location.pathname) {
85
+    return window.location.pathname
86
+  }
87
+  return ''
88
+}
89
+
90
+function getCurrentPageMeta() {
60 91
   const stack = getCurrentPages()
61 92
   if (!stack.length) {
62
-    return ''
93
+    return null
63 94
   }
64 95
   const cur = stack[stack.length - 1]
65
-  return cur.route || cur.$page?.route || cur.$page?.fullPath || ''
96
+  return cur.$page?.meta || cur.$vm?.$page?.meta || null
66 97
 }
67 98
 
68 99
 /**
69
- * 当前栈顶是否为原生 TabBar 页
100
+ * 路由是否对应 tabBar.list 中的页面(不等于当前栈顶一定是 Tab 上下文)
101
+ */
102
+export function isTabBarRoute(route) {
103
+  const normalized = normalizePageRoute(route || getCurrentPageRoute())
104
+  return !!normalized && TAB_PAGE_PATHS.has(normalized)
105
+}
106
+
107
+/**
108
+ * 当前栈顶是否为原生 TabBar 页(须 meta.isTabBar,不能只看 route)
109
+ * H5 history 直开 /bqH5/pages/home/index 时 route 会匹配 Tab,但 meta.isTabBar 为 false
70 110
  */
71 111
 export function isTabBarPage() {
112
+  const meta = getCurrentPageMeta()
113
+  if (meta && typeof meta.isTabBar === 'boolean') {
114
+    return meta.isTabBar
115
+  }
116
+  return false
117
+}
118
+
119
+/**
120
+ * H5 生产环境直开/刷新 Tab URL 时,用 switchTab 切回真正的 Tab 上下文
121
+ * @returns {Promise<boolean>} 切换后是否为 TabBar 页
122
+ */
123
+export function ensureTabBarEntry() {
124
+  if (isTabBarPage()) {
125
+    return Promise.resolve(true)
126
+  }
127
+  if (!isTabBarRoute()) {
128
+    return Promise.resolve(false)
129
+  }
130
+  if (tabBarEntryTask) {
131
+    return tabBarEntryTask
132
+  }
72 133
   const route = normalizePageRoute(getCurrentPageRoute())
73
-  return !!route && TAB_PAGE_PATHS.has(route)
134
+  tabBarEntryTask = new Promise((resolve) => {
135
+    uni.switchTab({
136
+      url: `/${route}`,
137
+      complete: () => {
138
+        tabBarEntryTask = null
139
+        resolve(isTabBarPage())
140
+      }
141
+    })
142
+  })
143
+  return tabBarEntryTask
74 144
 }
75 145
 
76 146
 function noopTabBarResult(name) {
@@ -88,15 +158,23 @@ function finishNoopTabBarCall(name, options) {
88 158
   return Promise.resolve(result)
89 159
 }
90 160
 
161
+function isTabBarFailMsg(msg) {
162
+  return String(msg || '').includes('TabBar') && String(msg || '').includes('fail')
163
+}
164
+
91 165
 /**
92
- * H5 生产包中 addInterceptor 偶发拦不住框架内部调用,直接包装 uni API 更稳
166
+ * H5 生产包中 addInterceptor 偶发拦不住框架内部调用,直接包装 uni/wx API 更稳
93 167
  */
94
-function wrapTabBarApi(name) {
95
-  if (typeof uni === 'undefined' || typeof uni[name] !== 'function') {
168
+function wrapTabBarApi(target, name) {
169
+  if (!target || typeof target[name] !== 'function') {
96 170
     return
97 171
   }
98
-  const raw = uni[name].bind(uni)
99
-  uni[name] = function patchedTabBarApi(options) {
172
+  const current = target[name]
173
+  if (current._tabBarGuardPatched) {
174
+    return
175
+  }
176
+  const raw = current.bind(target)
177
+  function patchedTabBarApi(options) {
100 178
     const opts = options || {}
101 179
     if (!isTabBarPage()) {
102 180
       return finishNoopTabBarCall(name, opts)
@@ -105,28 +183,33 @@ function wrapTabBarApi(name) {
105 183
       const ret = raw(opts)
106 184
       if (ret && typeof ret.catch === 'function') {
107 185
         ret.catch((err) => {
108
-          const msg = err && err.errMsg ? String(err.errMsg) : ''
109
-          if (msg.includes('TabBar') && msg.includes('fail')) {
110
-            return
186
+          if (isTabBarFailMsg(err && err.errMsg)) {
187
+            return finishNoopTabBarCall(name, opts)
111 188
           }
112 189
           throw err
113 190
         })
114 191
       }
115 192
       return ret
116 193
     } catch (err) {
117
-      const msg = err && err.errMsg ? String(err.errMsg) : ''
118
-      if (msg.includes('TabBar') && msg.includes('fail')) {
194
+      if (isTabBarFailMsg(err && err.errMsg)) {
119 195
         return finishNoopTabBarCall(name, opts)
120 196
       }
121 197
       throw err
122 198
     }
123 199
   }
200
+  patchedTabBarApi._tabBarGuardPatched = true
201
+  target[name] = patchedTabBarApi
124 202
 }
125 203
 
126
-function installTabBarApiGuard() {
127
-  readUniTabPagePaths()
128
-  TABBAR_API_NAMES.forEach(wrapTabBarApi)
129
-  if (!uni.addInterceptor) {
204
+function patchTabBarApis(target) {
205
+  if (!target) {
206
+    return
207
+  }
208
+  TABBAR_API_NAMES.forEach((name) => wrapTabBarApi(target, name))
209
+}
210
+
211
+function installTabBarInterceptors() {
212
+  if (typeof uni === 'undefined' || !uni.addInterceptor) {
130 213
     return
131 214
   }
132 215
   TABBAR_API_NAMES.forEach((name) => {
@@ -143,6 +226,17 @@ function installTabBarApiGuard() {
143 226
   })
144 227
 }
145 228
 
229
+export function installTabBarApiGuard() {
230
+  readUniTabPagePaths()
231
+  if (typeof uni !== 'undefined') {
232
+    patchTabBarApis(uni)
233
+    installTabBarInterceptors()
234
+  }
235
+  if (typeof wx !== 'undefined') {
236
+    patchTabBarApis(wx)
237
+  }
238
+}
239
+
146 240
 installTabBarApiGuard()
147 241
 
148 242
 /**

+ 8 - 26
ruoyi-ui-app/vite.config.js

@@ -1,38 +1,25 @@
1 1
 import { defineConfig, loadEnv } from 'vite'
2 2
 import uni from '@dcloudio/vite-plugin-uni'
3
-import { LLM_PROXY_TARGET } from './config/llm.js'
4
-
5
-const llmTarget = String(LLM_PROXY_TARGET || '').replace(/\/$/, '')
6
-
7
-/** 与 manifest.json h5.router.base 一致:生产 /bqH5/,开发 / */
8
-function resolvePublicPath(mode, env) {
9
-  if (mode !== 'production') {
10
-    return '/'
11
-  }
12
-  const raw = String(env.VITE_APP_PUBLIC_PATH || 'bqH5').trim()
13
-  if (!raw || raw === '/') {
14
-    return '/'
15
-  }
16
-  return `/${raw.replace(/^\/+|\/+$/g, '')}/`
17
-}
18 3
 
19 4
 /**
20 5
  * uni-app Vue3 H5 开发代理
21 6
  * - /dev-api → 若依后端(.env.development 的 VITE_APP_API_HOST)
22
- * - /llm-dev-proxy → 大模型网关
23 7
  */
24 8
 export default defineConfig(({ mode }) => {
25 9
   const env = loadEnv(mode, process.cwd(), '')
26
-  const base = resolvePublicPath(mode, env)
27 10
   const devApiTarget = String(env.VITE_APP_API_HOST || 'http://192.168.1.6:8010').replace(/\/$/, '')
28 11
   const devApiPrefix = String(env.VITE_APP_BASE_API || '/dev-api')
29 12
 
30 13
   return {
31
-    base,
32 14
     plugins: [uni()],
33
-    build: {
34
-      // iOS Safari 对 modulepreload 更敏感,预加载未使用的 chunk 易拖慢首屏
35
-      modulePreload: false
15
+    css: {
16
+      preprocessorOptions: {
17
+        scss: {
18
+          // 让每个 .vue 的 scss(含 uview-plus 组件)都能用到 $u-primary、@include flex 等
19
+          additionalData: '@import "uview-plus/theme.scss";',
20
+          silenceDeprecations: ['legacy-js-api', 'import']
21
+        }
22
+      }
36 23
     },
37 24
     server: {
38 25
       proxy: {
@@ -40,11 +27,6 @@ export default defineConfig(({ mode }) => {
40 27
           target: devApiTarget,
41 28
           changeOrigin: true,
42 29
           rewrite: (path) => path.replace(new RegExp(`^${devApiPrefix}`), '')
43
-        },
44
-        '/llm-dev-proxy': {
45
-          target: llmTarget,
46
-          changeOrigin: true,
47
-          rewrite: (path) => path.replace(/^\/llm-dev-proxy/, '')
48 30
         }
49 31
       }
50 32
     }