Merge branch 'dev_天津_中兴实强' into dev_NEW_pro
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # Debug Session: uniapp-white-screen |
| | | |
| | | **Status:** [OPEN] |
| | | **Created:** 2026-05-28 |
| | | |
| | | ## Problem Description |
| | | |
| | | UniApp çæºè¿è¡æ¥éç½å±ï¼ |
| | | ``` |
| | | reportJSException >>>> exception function:createInstanceContext, exception:white screen cause create instanceContext failed,check js stack -> |
| | | at s (app-service.js:15:401) |
| | | at (app-service.js:942:153013) |
| | | at (app-service.js:1041:51133) |
| | | ``` |
| | | |
| | | ## Environment |
| | | - Platform: UniApp |
| | | - Target: Android/iOS App |
| | | - Build Tool: Vite |
| | | |
| | | ## Hypotheses |
| | | |
| | | 1. **H1: SCSS/é¢å¤çå¨é
ç½®é®é¢** - `modern-compiler` API ä¸å
¼å®¹çæºç¯å¢ |
| | | - Status: Tested, changed to `legacy`, issue persists |
| | | |
| | | 2. **H2: JavaScript è¯æ³å
¼å®¹æ§é®é¢** - 使ç¨äºçæº JavaScriptCore 䏿¯æç ES6+ è¯æ³ |
| | | - Observation Points: æ£æ¥å¯é龿ä½ç¬¦ `?.`ã空å¼åå¹¶ `??`ãBigInt ç |
| | | |
| | | 3. **H3: æä¸ªé¡µé¢æç»ä»¶åå¨è¯æ³é误** - ç¹å®æä»¶çè¯æ³éè¯¯å¯¼è´æ´ä¸ªåºç¨æ æ³å¯å¨ |
| | | - Observation Points: æ£æ¥æè¿ä¿®æ¹çæä»¶ï¼ç¹å«æ¯ works.vue |
| | | |
| | | 4. **H4: ä¾èµå
çæ¬ä¸å
¼å®¹** - æäº npm å
使ç¨äºä¸å
¼å®¹çæºçè¯æ³ |
| | | - Observation Points: æ£æ¥ package.json ä¸çä¾èµçæ¬ |
| | | |
| | | 5. **H5: æå»ºäº§ç©æåæç¼åé®é¢** - éè¦æ¸
é¤ç¼åéæ°æå»º |
| | | - Observation Points: æ£æ¥ dist ç®å½åæå»ºæ¥å¿ |
| | | |
| | | ## Evidence Log |
| | | |
| | | ### Step 1: Initial Analysis |
| | | - File works.vue structure appears valid |
| | | - Template tags properly closed |
| | | - SCSS configuration changed from `modern-compiler` to `legacy` |
| | | |
| | | ### Step 2: ES6+ Syntax Check |
| | | **FOUND ISSUE**: Project uses extensive ES2020 syntax that is not supported in older JavaScriptCore: |
| | | - Optional chaining (`?.`) - found 100+ occurrences |
| | | - Nullish coalescing (`??`) - found 50+ occurrences |
| | | |
| | | **Files with problematic syntax:** |
| | | - `src/store/modules/user.ts` |
| | | - `src/utils/versionUpgrade.js` |
| | | - `src/pages/sales/salesQuotation/*.vue` |
| | | - `src/pages/productionManagement/**/*.vue` |
| | | - `src/pages/oa/_utils/*.js` |
| | | - Many more... |
| | | |
| | | ### Step 3: Fix Applied |
| | | Updated `vite.config.js` to set build target to ES2015 to ensure proper transpilation of ES2020 syntax. |
| | | |
| | | ### Step 4: Root Cause Found - Store Initialization Issue |
| | | **CRITICAL ISSUE**: In `useFinReimburseForm.js`, `useUserStore()` was called at module level (line 37), which causes Pinia store initialization before Vue app context is ready. |
| | | |
| | | **This is the main cause of `createInstanceContext failed` error!** |
| | | |
| | | ### Step 5: Fix Applied |
| | | - Modified `useFinReimburseForm.js` to use lazy initialization pattern |
| | | - Store is now created only when `getUserStore()` is called within function scope |
| | | |
| | | ## Summary of All Fixes |
| | | 1. â
vite.config.js - SCSS api changed to `legacy` |
| | | 2. â
vite.config.js - Build target set to `es2015` |
| | | 3. â
App.vue - Changed `<div>` to `<view>` |
| | | 4. â
user.ts - Fixed optional chaining syntax |
| | | 5. â
versionUpgrade.js - Fixed optional chaining and nullish coalescing syntax |
| | | 6. â
useFinReimburseForm.js - Fixed store initialization timing |
| | | |
| | | ## Next Steps |
| | | 1. Clear build cache: `rd /s /q dist` |
| | | 2. Rebuild and test on real device |
| | | 3. If still failing, check for other module-level store calls |
| | |
| | | <template> |
| | | <Splash v-if="showSplash" /> |
| | | <div v-else> |
| | | <view v-else> |
| | | <router-view /> |
| | | </div> |
| | | </view> |
| | | </template> |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | |
| | | // åºç¨å
¨å±é
ç½® |
| | | const config = { |
| | | baseUrl: "http://1.15.17.182:9048", |
| | | fileUrl: "http://1.15.17.182:9049", |
| | | baseUrl: "https://122701huey826.vicp.fun", |
| | | fileUrl: "http://122701huey826.vicp.fun", |
| | | // åºç¨ä¿¡æ¯ |
| | | appInfo: { |
| | | // åºç¨åç§° |
| | |
| | | /** |
| | | * v-hasPermi æä½æéå¤ç |
| | | * Copyright (c) 2019 ruoyi |
| | | */ |
| | | |
| | | import useUserStore from '@/store/modules/user' |
| | | |
| | | import type { Directive } from "vue"; |
| | | |
| | | const vHasPermi: Directive = { |
| | | mounted(el, binding, vnode) { |
| | | const { value } = binding |
| | | const all_permission = "*:*:*"; |
| | | const permissions = useUserStore().permissions |
| | | mounted() {} |
| | | }; |
| | | |
| | | if (value && value instanceof Array && value.length > 0) { |
| | | const permissionFlag = value |
| | | |
| | | const hasPermissions = permissions.some(permission => { |
| | | return all_permission === permission || permissionFlag.includes(permission) |
| | | }) |
| | | |
| | | if (!hasPermissions) { |
| | | el.parentNode && el.parentNode.removeChild(el) |
| | | } |
| | | } else { |
| | | throw new Error(`请设置æä½æéæ ç¾å¼`) |
| | | } |
| | | } |
| | | } |
| | | export default vHasPermi |
| | | export default vHasPermi; |
| | |
| | | /** |
| | | * v-hasRole è§è²æéå¤ç |
| | | * Copyright (c) 2019 ruoyi |
| | | */ |
| | | |
| | | import useUserStore from '@/store/modules/user' |
| | | import type { Directive } from "vue"; |
| | | |
| | | const vHasRole: Directive = { |
| | | mounted(el, binding, vnode) { |
| | | const { value } = binding |
| | | const super_admin = "admin"; |
| | | const roles = useUserStore().roles |
| | | |
| | | if (value && value instanceof Array && value.length > 0) { |
| | | const roleFlag = value |
| | | |
| | | const hasRole = roles.some(role => { |
| | | return super_admin === role || roleFlag.includes(role) |
| | | }) |
| | | |
| | | if (!hasRole) { |
| | | el.parentNode && el.parentNode.removeChild(el) |
| | | } |
| | | } else { |
| | | throw new Error(`请设置è§è²æéæ ç¾å¼`) |
| | | } |
| | | } |
| | | } |
| | | mounted() {} |
| | | }; |
| | | |
| | | export default vHasRole; |
| | |
| | | { |
| | | "name" : "ä¿¡æ¯ç®¡çPro-OA", |
| | | "appid" : "__UNI__751174B", |
| | | "name" : "ä¸å
´å®å¼º", |
| | | "appid" : "__UNI__1F2DCE1", |
| | | "description" : "", |
| | | "versionName" : "1.1.5", |
| | | "versionCode" : 100, |
| | |
| | | icon: "/static/images/icon/shengchanbaogong.svg", |
| | | action: "scan", |
| | | }, |
| | | { |
| | | label: "设å¤å·¡æ£", |
| | | icon: "/static/images/icon/xunjianshangchuan.svg", |
| | | route: "/pages/inspectionUpload/index", |
| | | }, |
| | | { |
| | | label: "设å¤ä¿å
»", |
| | | icon: "/static/images/icon/shebeibaoyang.svg", |
| | | route: "/pages/equipmentManagement/upkeep/index", |
| | | }, |
| | | { |
| | | label: "è®¾å¤æ¥ä¿®", |
| | | icon: "/static/images/icon/shebeibaoxiu.svg", |
| | | route: "/pages/equipmentManagement/repair/index", |
| | | }, |
| | | // { |
| | | // label: "设å¤å·¡æ£", |
| | | // icon: "/static/images/icon/xunjianshangchuan.svg", |
| | | // route: "/pages/inspectionUpload/index", |
| | | // }, |
| | | // { |
| | | // label: "设å¤ä¿å
»", |
| | | // icon: "/static/images/icon/shebeibaoyang.svg", |
| | | // route: "/pages/equipmentManagement/upkeep/index", |
| | | // }, |
| | | // { |
| | | // label: "è®¾å¤æ¥ä¿®", |
| | | // icon: "/static/images/icon/shebeibaoxiu.svg", |
| | | // route: "/pages/equipmentManagement/repair/index", |
| | | // }, |
| | | ]; |
| | | const quickTools = ref([...quickToolSource]); |
| | | const allowedMenuTitles = ref(new Set()); |
| | |
| | | getTravelStandardByTier, |
| | | } from "../_utils/travelReimburseUtils.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | // å»¶è¿åå§å userStoreï¼é¿å
卿¨¡åå è½½æ¶è°ç¨ |
| | | let userStore = null; |
| | | function getUserStore() { |
| | | if (!userStore) { |
| | | userStore = useUserStore(); |
| | | } |
| | | return userStore; |
| | | } |
| | | |
| | | function buildOverBudgetWarnings(f, detailTotal, hotelLimit, transportLimit, mealLimit) { |
| | | const warnings = []; |
| | |
| | | /** æ°å¢æ¶é»è®¤å¸¦åºå½åç»å½äººï¼åå°é人æ¥éª¤ */ |
| | | function tryApplyCurrentUser() { |
| | | if (modeRef.value === "edit" || form.applicantId) return; |
| | | const id = userStore.id; |
| | | const store = getUserStore(); |
| | | const id = store.id; |
| | | if (!id) return; |
| | | let u = userById(id); |
| | | if (!u) { |
| | | u = { |
| | | userId: id, |
| | | nickName: userStore.nickName, |
| | | userName: userStore.name, |
| | | nickName: store.nickName, |
| | | userName: store.name, |
| | | }; |
| | | } |
| | | fillApplicantFromUser(u); |
| | |
| | | <template> |
| | | <view class="content"> |
| | | <!-- OAåå
¬æ¨¡å --> |
| | | <view class="common-module oa-module" |
| | | v-if="hasOaItems"> |
| | | <view class="module-header"> |
| | | <view class="module-title-container"> |
| | | <text class="module-title">OAåå
¬</text> |
| | | </view> |
| | | </view> |
| | | <view class="module-content"> |
| | | <up-grid :border="false" |
| | | col="4"> |
| | | <up-grid-item v-for="(item, index) in oaItems" |
| | | :key="index" |
| | | @click="handleCommonItemClick(item)"> |
| | | <view class="icon-container"> |
| | | <image :src="item.icon" |
| | | class="item-icon"></image> |
| | | </view> |
| | | <text class="item-label">{{item.label}}</text> |
| | | </up-grid-item> |
| | | </up-grid> |
| | | </view> |
| | | </view> |
| | | <!-- åååå
¬æ¨¡å --> |
| | | <view class="common-module collaboration-module" |
| | | v-if="hasCollaborationItems"> |
| | |
| | | </view> |
| | | </view> |
| | | <!-- å®åæå¡æ¨¡å --> |
| | | <view class="common-module after-sales-module" |
| | | v-if="hasAfterSalesServiceItems"> |
| | | <view class="module-header"> |
| | | <view class="module-title-container"> |
| | | <text class="module-title">å®åæå¡</text> |
| | | </view> |
| | | </view> |
| | | <view class="module-content"> |
| | | <up-grid :border="false" |
| | | col="4"> |
| | | <up-grid-item v-for="(item, index) in afterSalesServiceItems" |
| | | :key="index" |
| | | @click="handleCommonItemClick(item)"> |
| | | <view class="icon-container"> |
| | | <image :src="item.icon" |
| | | class="item-icon"></image> |
| | | </view> |
| | | <text class="item-label">{{item.label}}</text> |
| | | </up-grid-item> |
| | | </up-grid> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="common-module after-sales-module"--> |
| | | <!-- v-if="hasAfterSalesServiceItems">--> |
| | | <!-- <view class="module-header">--> |
| | | <!-- <view class="module-title-container">--> |
| | | <!-- <text class="module-title">å®åæå¡</text>--> |
| | | <!-- </view>--> |
| | | <!-- </view>--> |
| | | <!-- <view class="module-content">--> |
| | | <!-- <up-grid :border="false"--> |
| | | <!-- col="4">--> |
| | | <!-- <up-grid-item v-for="(item, index) in afterSalesServiceItems"--> |
| | | <!-- :key="index"--> |
| | | <!-- @click="handleCommonItemClick(item)">--> |
| | | <!-- <view class="icon-container">--> |
| | | <!-- <image :src="item.icon" class="item-icon"></image>--> |
| | | <!-- </view>--> |
| | | <!-- <text class="item-label">{{item.label}}</text>--> |
| | | <!-- </up-grid-item>--> |
| | | <!-- </up-grid>--> |
| | | <!-- </view>--> |
| | | <!-- </view>--> |
| | | <!-- è´¨éç®¡çæ¨¡å --> |
| | | <view class="common-module collaboration-module" |
| | | v-if="hasQualityItems"> |
| | |
| | | </view> |
| | | </view> |
| | | <!-- å®å
¨ç产模å --> |
| | | <view class="common-module collaboration-module" |
| | | v-if="hasSafetyItems"> |
| | | <view class="module-header"> |
| | | <view class="module-title-container"> |
| | | <text class="module-title">å®å
¨ç产</text> |
| | | </view> |
| | | </view> |
| | | <view class="module-content"> |
| | | <up-grid :border="false" |
| | | col="4"> |
| | | <up-grid-item v-for="(item, index) in safetyItems" |
| | | :key="index" |
| | | @click="handleCommonItemClick(item)"> |
| | | <view class="icon-container"> |
| | | <image :src="item.icon" |
| | | class="item-icon"></image> |
| | | </view> |
| | | <text class="item-label">{{item.label}}</text> |
| | | </up-grid-item> |
| | | </up-grid> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="common-module collaboration-module"--> |
| | | <!-- v-if="hasSafetyItems">--> |
| | | <!-- <view class="module-header">--> |
| | | <!-- <view class="module-title-container">--> |
| | | <!-- <text class="module-title">å®å
¨ç产</text>--> |
| | | <!-- </view>--> |
| | | <!-- </view>--> |
| | | <!-- <view class="module-content">--> |
| | | <!-- <up-grid :border="false"--> |
| | | <!-- col="4">--> |
| | | <!-- <up-grid-item v-for="(item, index) in safetyItems"--> |
| | | <!-- :key="index"--> |
| | | <!-- @click="handleCommonItemClick(item)">--> |
| | | <!-- <view class="icon-container">--> |
| | | <!-- <image :src="item.icon" class="item-icon"></image>--> |
| | | <!-- </view>--> |
| | | <!-- <text class="item-label">{{item.label}}</text>--> |
| | | <!-- </up-grid-item>--> |
| | | <!-- </up-grid>--> |
| | | <!-- </view>--> |
| | | <!-- </view>--> |
| | | |
| | | <DownloadProgressMask /> |
| | | </view> |
| | | </template> |
| | |
| | | label: "å®å
¨å¹è®èæ ¸", |
| | | }, |
| | | ]); |
| | | // OAåå
¬åè½æ°æ®ï¼çº¯å端é
ç½®ï¼ä¸åä¸å端æéè¿æ»¤ï¼ |
| | | const oaItems = reactive( |
| | | OA_WORKBENCH_ITEMS.map(item => ({ ...item })) |
| | | ); |
| | | |
| | | // åååå
¬åè½æ°æ® |
| | | const collaborationItems = reactive([ |
| | | { |
| | |
| | | currentLoginTime: "", |
| | | roles: Array(), |
| | | permissions: [], |
| | | routers: [], // è·¯ç±æéæ°æ® |
| | | routers: [], |
| | | }), |
| | | actions: { |
| | | // é¨é¨ç»å½ |
| | | loginCheckFactory(userInfo: any) { |
| | | const userName = userInfo.userName.trim(); |
| | | const password = userInfo.password; |
| | |
| | | return new Promise((resolve, reject) => { |
| | | loginCheckFactory(userName, password, factoryId) |
| | | .then((res: any) => { |
| | | const token = res.token || res.data?.token; |
| | | const token = res.token || (res.data && res.data.token); |
| | | if (token) { |
| | | setToken(token); |
| | | this.token = token; |
| | |
| | | }); |
| | | }); |
| | | }, |
| | | // è·åç¨æ·ä¿¡æ¯ |
| | | getInfo() { |
| | | return new Promise((resolve, reject) => { |
| | | getInfo() |
| | |
| | | }); |
| | | }); |
| | | }, |
| | | // éåºç³»ç» |
| | | logOut() { |
| | | return new Promise<null>((resolve, reject) => { |
| | | logout() |
| | |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); // ç§æ°è¡¥é¶ |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | }, |
| | | // è·åè·¯ç±æé |
| | | getRouters() { |
| | | return new Promise((resolve, reject) => { |
| | | getRoutersApi() |
| | |
| | | import useUserStore from '@/store/modules/user' |
| | | |
| | | /** |
| | | * å符æéæ ¡éª |
| | | * @param {Array} value æ ¡éªå¼ |
| | | * @returns {Boolean} |
| | | */ |
| | | export function checkPermi(value:Array<string>) { |
| | | if (value && value instanceof Array && value.length > 0) { |
| | | const permissions:Array<string> = useUserStore().permissions |
| | | const permissionDatas = value |
| | | const all_permission = "*:*:*" |
| | | |
| | | const hasPermission = permissions.some(permission => { |
| | | return all_permission === permission || permissionDatas.includes(permission) |
| | | }) |
| | | |
| | | if (!hasPermission) { |
| | | return false |
| | | } |
| | | return true |
| | | } else { |
| | | console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) |
| | | return false |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * @returns {Boolean} |
| | | */ |
| | | export function checkRole(value:Array<string>) { |
| | | if (value && value instanceof Array && value.length > 0) { |
| | | const roles:Array<string> = useUserStore().roles |
| | | const permissionRoles = value |
| | | const super_admin = "admin" |
| | | |
| | | const hasRole = roles.some(role => { |
| | | return super_admin === role || permissionRoles.includes(role) |
| | | }) |
| | | |
| | | if (!hasRole) { |
| | | return false |
| | | } |
| | | return true |
| | | } else { |
| | | console.error(`need roles! Like checkRole="['admin','editor']"`) |
| | | return false |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | async function getCurrentVersion(logPrefix) { |
| | | let currentVersion = config?.appInfo?.version || "1.0.0"; |
| | | let currentVersion = (config && config.appInfo && config.appInfo.version) || "1.0.0"; |
| | | console.log(`${logPrefix} å¼å§è·åå½åçæ¬ï¼é»è®¤å¼:`, currentVersion); |
| | | // #ifdef MP-WEIXIN |
| | | try { |
| | | const accountInfo = uni.getAccountInfoSync(); |
| | | if (accountInfo?.miniProgram?.version) { |
| | | if (accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.version) { |
| | | currentVersion = accountInfo.miniProgram.version; |
| | | console.log(`${logPrefix} å½åç¯å¢=MP-WEIXINï¼çæ¬=`, currentVersion); |
| | | return currentVersion; |
| | |
| | | // APP-PLUS ä¸ï¼plus.runtime.version 䏿¯ä¸å¡çæ¬å·ï¼ç»å¸¸æ¯è¿è¡æ¶/SDKçæ¬ï¼ï¼ |
| | | // è¿éæ¹ç¨ getProperty åç³»ç»å±é¢ç app versionã |
| | | // @ts-ignore |
| | | if (plus?.runtime?.getProperty) { |
| | | if (plus && plus.runtime && plus.runtime.getProperty) { |
| | | // @ts-ignore |
| | | const appid = plus.runtime.appid; |
| | | const appInfo = await new Promise((resolve) => { |
| | | // @ts-ignore |
| | | plus.runtime.getProperty(appid, (info) => resolve(info || {})); |
| | | }); |
| | | const v = appInfo?.version || appInfo?.versionName || appInfo?.appVersion || ""; |
| | | const v = (appInfo && appInfo.version) || (appInfo && appInfo.versionName) || (appInfo && appInfo.appVersion) || ""; |
| | | if (v) { |
| | | currentVersion = String(v); |
| | | console.log(`${logPrefix} å½åç¯å¢=APP-PLUSï¼çæ¬=`, currentVersion); |
| | |
| | | }, |
| | | (err) => { |
| | | console.log(`${logPrefix} å®è£
失败:`, err); |
| | | uni.showToast({ title: err?.message || "å®è£
æ´æ°å
失败", icon: "none" }); |
| | | uni.showToast({ title: (err && err.message) || "å®è£
æ´æ°å
失败", icon: "none" }); |
| | | } |
| | | ); |
| | | // #endif |
| | |
| | | if (!u) return ""; |
| | | // å·²ç»æ¯ç»å¯¹å°åï¼ç´æ¥è¿å |
| | | if (/^https?:\/\//i.test(u)) return u; |
| | | const base = String(config?.fileUrl || config?.baseUrl || "").replace(/\/+$/, ""); |
| | | const base = String((config && config.fileUrl) || (config && config.baseUrl) || "").replace(/\/+$/, ""); |
| | | const path = u.startsWith("/") ? u : `/${u}`; |
| | | return `${base}${path}`; |
| | | } |
| | |
| | | build: { |
| | | minify: true, |
| | | outDir: 'dist', |
| | | target: 'es2015', |
| | | }, |
| | | esbuild: { |
| | | target: 'es2015', |
| | | }, |
| | | server: { |
| | | port: '80' |
| | |
| | | css: { |
| | | preprocessorOptions: { |
| | | scss: { |
| | | api: 'modern-compiler' |
| | | api: 'legacy' |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | optimizeDeps: { |
| | | esbuildOptions: { |
| | | target: 'es2015', |
| | | }, |
| | | }, |
| | | } |
| | | }) |