diff --git a/src/App.vue b/src/App.vue index fc85d8e..6732eb0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,7 +6,8 @@ import { audioManager } from '@/utils/audioManager' import util from '@/common/util.js'; import md5 from '@/common/md5.js'; - import { restoreUserSession } from '@/common/userSession.js'; + import { getLoginData, restoreUserSession } from '@/common/userSession.js'; + import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js'; export default { globalData: { AppName: '', @@ -32,6 +33,14 @@ import { audioManager } from '@/utils/audioManager' audioManager.init(); // 初始化通知系统 this.$store.dispatch('initNotification'); + + // 已登录且本地无扫码配置时,自动匹配一次 + const loginData = getLoginData(); + if (loginData && loginData.token && this.$u && this.$u.api) { + ensureScanConfigAfterLogin(this.$u.api).catch((error) => { + console.log('启动时自动匹配扫码设置失败', error); + }); + } } } diff --git a/src/common/scanAutoConfig.js b/src/common/scanAutoConfig.js new file mode 100644 index 0000000..db990c0 --- /dev/null +++ b/src/common/scanAutoConfig.js @@ -0,0 +1,43 @@ +/** + * 登录后 / 冷启动:拉取扫码字典并在本地无配置时自动匹配写入 + */ +import { + DICTIONARY_TYPE_SCANNER, + autoConfigureScanSettings, +} from '@/common/scanConfig.js'; + +function extractDictionaryList(res) { + if (Array.isArray(res)) return res; + if (res && Array.isArray(res.list)) return res.list; + return []; +} + +/** + * @param {object} api - this.$u.api + * @returns {Promise<{saved:boolean,skipped:boolean,config:object|null,reason?:string}>} + */ +export async function ensureScanConfigAfterLogin(api) { + if (!api || !api.dictionary || !api.dictionary.getDetailEnumList) { + return autoConfigureScanSettings({ remoteConfigs: [] }); + } + + let remoteConfigs = []; + try { + const res = await api.dictionary.getDetailEnumList({ + dictionary_id: DICTIONARY_TYPE_SCANNER, + }); + remoteConfigs = extractDictionaryList(res); + } catch (error) { + console.log('拉取扫码字典失败,改用内置配置自动匹配', error); + } + + const result = autoConfigureScanSettings({ remoteConfigs }); + if (result.skipped) { + console.log('本地已有扫码设置,跳过自动匹配', result.config && result.config.name); + } else if (result.saved) { + console.log('已自动写入扫码设置', result.config && result.config.name); + } else { + console.log('自动匹配扫码设置未写入', result.reason); + } + return result; +} diff --git a/src/common/scanConfig.js b/src/common/scanConfig.js index 1a3d4da..51b9fc5 100644 --- a/src/common/scanConfig.js +++ b/src/common/scanConfig.js @@ -10,6 +10,103 @@ export const PHONE_SCAN_CONFIG = { isPhoneScan: true, }; +/** 内置扫码配置:补 needSetup,并在字典拉不到时兜底 */ +export const BUILTIN_SCANNER_CONFIGS = [ + { + brand: 'newland', + name: '东大集成', + action: 'com.android.server.scannerservice.broadcast', + dataKey: 'scannerdata', + needSetup: true, + setupAction: 'com.android.scanner.service_settings', + setupParams: { + action_barcode_broadcast: 'com.android.server.scannerservice.broadcast', + key_barcode_broadcast: 'scannerdata', + endchar: 'ENTER', + }, + }, + { + brand: 'sunmi', + name: '商米', + action: 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED', + dataKey: 'data', + needSetup: false, + }, + { + brand: 'nlscan', + name: '新大陆', + action: 'nlscan.action.SCANNER_RESULT', + dataKey: 'SCAN_BARCODE1', + needSetup: false, + }, + { + brand: 'urovo', + name: '优博讯', + action: 'android.intent.ACTION_DECODE_DATA', + dataKey: 'barcode_string', + needSetup: false, + }, + { + brand: 'idata', + name: '盈达', + action: 'android.intent.action.SCANRESULT', + dataKey: 'value', + needSetup: false, + }, + { + brand: 'iData', + name: '海雅达', + action: 'com.android.scanner.broadcast', + dataKey: 'scandata', + needSetup: false, + }, + { ...PHONE_SCAN_CONFIG }, +]; + +/** 设备关键词 / 常见扫码相关包名,用于自动识别 */ +const DETECT_RULES = [ + { + matchNames: ['商米'], + matchActions: ['com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED'], + keywords: ['sunmi'], + packages: ['com.sunmi.scanner', 'woyou.aidlservice.jiuiv5'], + }, + { + matchNames: ['新大陆'], + matchActions: ['nlscan.action.SCANNER_RESULT'], + keywords: ['newland', 'nls', 'nlscan'], + packages: ['com.nlscan.barcodescanner', 'com.newland'], + }, + { + matchNames: ['优博讯'], + matchActions: ['android.intent.ACTION_DECODE_DATA'], + keywords: ['urovo'], + packages: ['com.urovo', 'com.ubx'], + }, + { + matchNames: ['盈达'], + matchActions: ['android.intent.action.SCANRESULT'], + keywords: ['idata', 'yingda'], + packages: ['com.idata', 'com.android.scanner'], + }, + { + matchNames: ['海雅达'], + matchActions: ['com.android.scanner.broadcast'], + keywords: ['iData', 'seui', 'haiyada'], + packages: ['com.android.scanner'], + }, + { + matchNames: ['东大集成', '东集'], + matchActions: ['com.android.server.scannerservice.broadcast'], + keywords: ['seuic', 'eastcom', 'newland', 'nls'], + packages: ['com.android.scanner', 'com.seuic.scanner'], + }, +]; + +const PDA_DEVICE_KEYWORDS = [ + 'newland', 'nls', 'sunmi', 'urovo', 'honeywell', 'idata', 'seuic', 'zebra', 'ubx', +]; + const NO_SETUP_HINT_KEY = 'pda_scan_config_hint_shown'; export function isPhoneScanConfig(config) { @@ -33,13 +130,13 @@ function enrichFromBuiltinConfig(config, builtinConfigs = []) { return { ...config, - needSetup: builtin.needSetup, - setupAction: builtin.setupAction || '', - setupParams: builtin.setupParams || null, + needSetup: config.needSetup != null ? config.needSetup : builtin.needSetup, + setupAction: config.setupAction || builtin.setupAction || '', + setupParams: config.setupParams || builtin.setupParams || null, }; } -export function normalizeConfig(raw, builtinConfigs = []) { +export function normalizeConfig(raw, builtinConfigs = BUILTIN_SCANNER_CONFIGS) { if (!raw || typeof raw !== 'object') return null; const brand = String(raw.brand || raw.id || raw.dictionary_detail_id || raw.name || ''); @@ -66,11 +163,11 @@ export function normalizeConfig(raw, builtinConfigs = []) { return config; } -export function mapDictionaryToScannerConfig(raw, builtinConfigs = []) { +export function mapDictionaryToScannerConfig(raw, builtinConfigs = BUILTIN_SCANNER_CONFIGS) { return normalizeConfig(raw, builtinConfigs); } -export function getScanConfig(builtinConfigs = []) { +export function getScanConfig(builtinConfigs = BUILTIN_SCANNER_CONFIGS) { try { const data = uni.getStorageSync(STORAGE_KEY); return normalizeConfig(data, builtinConfigs); @@ -80,6 +177,10 @@ export function getScanConfig(builtinConfigs = []) { } } +export function hasLocalScanConfig(builtinConfigs = BUILTIN_SCANNER_CONFIGS) { + return !!getScanConfig(builtinConfigs); +} + export function saveScanConfig(config) { const normalized = normalizeConfig(config); if (!normalized) { @@ -109,3 +210,175 @@ export function markNoConfigHintShown() { console.log('记录扫码枪提示状态失败', e); } } + +export function mergeScannerConfigs(remoteConfigs = []) { + const configMap = new Map(); + BUILTIN_SCANNER_CONFIGS.forEach((item) => { + if (!isPhoneScanConfig(item)) { + configMap.set(item.action || item.brand, item); + } + }); + (remoteConfigs || []).forEach((raw) => { + const normalized = mapDictionaryToScannerConfig(raw); + if (!normalized || isPhoneScanConfig(normalized)) return; + configMap.set(normalized.action || normalized.brand, normalized); + }); + const list = Array.from(configMap.values()); + list.push({ ...PHONE_SCAN_CONFIG }); + return list; +} + +export function getDeviceIdentity() { + // #ifdef APP-PLUS + try { + const Build = plus.android.importClass('android.os.Build'); + return { + manufacturer: String(Build.MANUFACTURER || '').toLowerCase(), + model: String(Build.MODEL || '').toLowerCase(), + brand: String(Build.BRAND || '').toLowerCase(), + }; + } catch (e) { + console.log('读取设备信息失败', e); + } + // #endif + return { manufacturer: '', model: '', brand: '' }; +} + +export function isPdaDevice() { + const identity = getDeviceIdentity(); + const text = `${identity.manufacturer} ${identity.model} ${identity.brand}`; + return PDA_DEVICE_KEYWORDS.some((keyword) => text.includes(keyword.toLowerCase())); +} + +function findDetectRule(config) { + return DETECT_RULES.find((rule) => { + if (rule.matchNames.includes(config.name)) return true; + if (rule.matchActions.includes(config.action)) return true; + return false; + }) || null; +} + +function isPackageInstalled(packageName) { + // #ifdef APP-PLUS + try { + const main = plus.android.runtimeMainActivity(); + const pm = main.getPackageManager(); + plus.android.importClass(pm); + pm.getPackageInfo(packageName, 0); + return true; + } catch (e) { + return false; + } + // #endif + return false; +} + +function canResolveBroadcast(action) { + if (!action) return false; + // #ifdef APP-PLUS + try { + const main = plus.android.runtimeMainActivity(); + const Intent = plus.android.importClass('android.content.Intent'); + const pm = main.getPackageManager(); + plus.android.importClass(pm); + const intent = new Intent(action); + const list = pm.queryBroadcastReceivers(intent, 0); + return !!(list && list.size && list.size() > 0); + } catch (e) { + console.log('探测广播接收方失败', action, e); + return false; + } + // #endif + return false; +} + +function scoreScannerConfig(config, identity) { + if (!config || isPhoneScanConfig(config)) { + return 0; + } + + let score = 0; + const rule = findDetectRule(config); + const deviceText = `${identity.manufacturer} ${identity.model} ${identity.brand}`; + + if (canResolveBroadcast(config.action)) { + score += 100; + } + + if (rule) { + if ((rule.packages || []).some((pkg) => isPackageInstalled(pkg))) { + score += 60; + } + if ((rule.keywords || []).some((keyword) => deviceText.includes(String(keyword).toLowerCase()))) { + score += 40; + } + } + + return score; +} + +/** + * 在候选配置中选出最匹配当前设备的一项;非 PDA 且无命中则回落手机扫码 + */ +export function matchBestScanConfig(candidateConfigs = []) { + const configs = (candidateConfigs || []) + .map((item) => normalizeConfig(item)) + .filter(Boolean); + const pdaConfigs = configs.filter((item) => !isPhoneScanConfig(item)); + const identity = getDeviceIdentity(); + + let best = null; + let bestScore = 0; + pdaConfigs.forEach((config) => { + const score = scoreScannerConfig(config, identity); + if (score > bestScore) { + bestScore = score; + best = config; + } + }); + + if (best && bestScore > 0) { + return best; + } + + if (!isPdaDevice()) { + return { ...PHONE_SCAN_CONFIG }; + } + + return pdaConfigs[0] || { ...PHONE_SCAN_CONFIG }; +} + +/** + * 仅本地没有有效配置时,自动匹配并写入扫描设置 + * @returns {{ saved: boolean, config: object|null, skipped: boolean, reason?: string }} + */ +export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) { + const existing = getScanConfig(); + if (existing) { + return { + saved: false, + skipped: true, + config: existing, + reason: 'local_exists', + }; + } + + const candidates = mergeScannerConfigs(remoteConfigs); + const matched = matchBestScanConfig(candidates); + if (!matched) { + return { + saved: false, + skipped: false, + config: null, + reason: 'no_match', + }; + } + + const saved = saveScanConfig(matched); + return { + saved, + skipped: false, + config: matched, + reason: saved ? 'saved' : 'save_failed', + }; +} diff --git a/src/common/scanMixin.js b/src/common/scanMixin.js index 91710a1..f94dd0e 100644 --- a/src/common/scanMixin.js +++ b/src/common/scanMixin.js @@ -1,18 +1,16 @@ // common/scanMixin.js // 多厂商PDA扫码器兼容方案 - 策略模式 + 配置表驱动 import { + BUILTIN_SCANNER_CONFIGS, getScanConfig, hasShownNoConfigHint, + isPdaDevice as detectPdaDevice, isPhoneScanConfig, markNoConfigHintShown, normalizeConfig, PHONE_SCAN_CONFIG, } from '@/common/scanConfig.js'; -const PDA_DEVICE_KEYWORDS = [ - 'newland', 'nls', 'sunmi', 'urovo', 'zebra', 'honeywell', 'idata', 'seuic', -]; - export default { data() { return { @@ -27,36 +25,7 @@ export default { methods: { getScannerConfigs() { - return [ - { - brand: 'newland', - name: '东集', - action: 'com.android.server.scannerservice.broadcast', - dataKey: 'scannerdata', - needSetup: true, - setupAction: 'com.android.scanner.service_settings', - setupParams: { - action_barcode_broadcast: 'com.android.server.scannerservice.broadcast', - key_barcode_broadcast: 'scannerdata', - endchar: 'ENTER' - } - }, - { - brand: 'sunmi', - name: '商米', - action: 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED', - dataKey: 'data', - needSetup: false - }, - { - brand: 'nlscan', - name: '新大陆', - action: 'nlscan.action.SCANNER_RESULT', - dataKey: 'SCAN_BARCODE1', - needSetup: false - }, - { ...PHONE_SCAN_CONFIG }, - ]; + return BUILTIN_SCANNER_CONFIGS.map((item) => ({ ...item })); }, getPhoneScanConfig() { @@ -64,20 +33,7 @@ export default { }, isPdaDevice() { - // #ifdef APP-PLUS - try { - const Build = plus.android.importClass('android.os.Build'); - const manufacturer = String(Build.MANUFACTURER || '').toLowerCase(); - const model = String(Build.MODEL || '').toLowerCase(); - return PDA_DEVICE_KEYWORDS.some((keyword) => { - return manufacturer.includes(keyword) || model.includes(keyword); - }); - } catch (error) { - console.log('检测设备类型失败', error); - return false; - } - // #endif - return false; + return detectPdaDevice(); }, shouldUsePhoneScan() { diff --git a/src/pages/common/aidex.scss b/src/pages/common/aidex.scss index 776ae3e..056a364 100644 --- a/src/pages/common/aidex.scss +++ b/src/pages/common/aidex.scss @@ -58,10 +58,11 @@ } .u-cell_title { + flex: 1; + min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - min-width: 655rpx; } .u-border-bottom:last-child:after { diff --git a/src/pages/sys/login/index.vue b/src/pages/sys/login/index.vue index 6f3440a..62f36d0 100644 --- a/src/pages/sys/login/index.vue +++ b/src/pages/sys/login/index.vue @@ -127,6 +127,7 @@ import manifest from '@/manifest.json'; import { mapState } from 'vuex' import app_upgrade from '@/uni_modules/app-upgrade/js_sdk/index.js' import { applyLoginData, applyUserInformation } from '@/common/userSession.js' +import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js' export default { data() { return { @@ -356,6 +357,12 @@ export default { console.log('information error', infoError); } + try { + await ensureScanConfigAfterLogin(this.$u.api); + } catch (scanError) { + console.log('自动匹配扫码设置失败', scanError); + } + setTimeout(() => { uni.reLaunch({ url: '/pages/sys/workbench/index' diff --git a/src/pages/sys/user/index.vue b/src/pages/sys/user/index.vue index 38e6a1b..8ce02c5 100644 --- a/src/pages/sys/user/index.vue +++ b/src/pages/sys/user/index.vue @@ -36,21 +36,19 @@ !--> - - - - - - - - - - - - - - + + + + + + + + + + + + + {{ appName }} @@ -145,20 +143,52 @@ export default { };