diff --git a/src/App.vue b/src/App.vue
index 6368c2f..7807a41 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -6,6 +6,7 @@ import { audioManager } from '@/utils/audioManager'
import util from '@/common/util.js';
import md5 from '@/common/md5.js';
+ import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js';
export default {
globalData: {
AppName: '',
@@ -30,6 +31,14 @@ import { audioManager } from '@/utils/audioManager'
audioManager.init();
// 初始化通知系统
this.$store.dispatch('initNotification');
+
+ // 已登录且本地无扫码配置时,自动匹配一次
+ const loginData = uni.getStorageSync('RemoteTokenData');
+ 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 fb3eae8..619a439 100644
--- a/src/common/scanConfig.js
+++ b/src/common/scanConfig.js
@@ -13,6 +13,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) {
@@ -64,13 +161,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 || '');
@@ -100,11 +197,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);
const normalized = normalizeConfig(data, builtinConfigs);
@@ -123,6 +220,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) {
@@ -152,3 +253,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 8e42082..f94dd0e 100644
--- a/src/common/scanMixin.js
+++ b/src/common/scanMixin.js
@@ -1,19 +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', 'nlscan', 'sunmi', 'urovo', 'zebra', 'honeywell',
- 'idata', 'seuic', 'autoid', '东集', '东大',
-];
-
export default {
data() {
return {
@@ -28,46 +25,7 @@ export default {
methods: {
getScannerConfigs() {
- return [
- {
- // 东大集成 SEUIC / AUTOID 系列(如 AUTOID9),勿与新大陆 nlscan 混淆
- brand: 'seuic',
- name: '东大集成(AUTOID)',
- action: 'com.android.server.scannerservice.broadcast',
- dataKey: 'scannerdata',
- needSetup: true,
- setupAction: 'com.android.scanner.service_settings',
- setupParams: {
- barcode_send_mode: 'BROADCAST',
- action_barcode_broadcast: 'com.android.server.scannerservice.broadcast',
- key_barcode_broadcast: 'scannerdata',
- endchar: 'NONE'
- }
- },
- {
- brand: 'sunmi',
- name: '商米',
- action: 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED',
- dataKey: 'data',
- needSetup: false
- },
- {
- brand: 'urovo',
- name: '优博讯',
- action: 'android.intent.ACTION_DECODE_DATA',
- dataKey: 'barcode_string',
- needSetup: false
- },
- {
- // 新大陆 Newland,广播为 nlscan.action.SCANNER_RESULT
- brand: 'nlscan',
- name: '新大陆',
- action: 'nlscan.action.SCANNER_RESULT',
- dataKey: 'SCAN_BARCODE1',
- needSetup: false
- },
- { ...PHONE_SCAN_CONFIG },
- ];
+ return BUILTIN_SCANNER_CONFIGS.map((item) => ({ ...item }));
},
getPhoneScanConfig() {
@@ -75,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 9154b79..c5c63b9 100644
--- a/src/pages/sys/login/index.vue
+++ b/src/pages/sys/login/index.vue
@@ -125,6 +125,7 @@ import md5 from '@/common/md5.js';
import manifest from '@/manifest.json';
import { mapState } from 'vuex'
import app_upgrade from '@/uni_modules/app-upgrade/js_sdk/index.js'
+import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js'
export default {
data() {
return {
@@ -325,7 +326,7 @@ export default {
this.$u.api.pdaLogin({
'phone': this.login.username,
'password': this.login.password,
- }).then((res) => {
+ }).then(async (res) => {
console.log('login res', res)
var aLoginUserData = res;
@@ -356,6 +357,13 @@ export default {
getApp().globalData.StoreNameID = aLoginUserData.default_physical_warehouse_id;
getApp().globalData.StoreName = aLoginUserData.default_physical_warehouse_name;
getApp().globalData.StoreTypeNo = '';
+
+ 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 8917f9b..d79ea94 100644
--- a/src/pages/sys/user/index.vue
+++ b/src/pages/sys/user/index.vue
@@ -36,21 +36,19 @@
!-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ appName }}
@@ -146,20 +144,52 @@ export default {
};