Files
MrleeAdmin/frontend/app/pages/settings/index.vue
T

709 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="settings-page" :class="`theme-${themeMode}`">
<view class="page-accent"></view>
<view class="settings-card">
<view class="card-header">
<text class="card-title">基础配置</text>
<text class="card-subtitle">扫码与反馈</text>
</view>
<view class="setting-row tappable" @tap="selectScanInterval">
<view class="setting-main">
<text class="setting-label">连续扫码时间间隔</text>
</view>
<view class="setting-right interval-right">
<view class="value-line">
<text class="setting-value">{{ settings.scanInterval }}s</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
</view>
<view class="setting-row tappable" @tap="selectScanRegionPreset">
<view class="setting-main">
<text class="setting-label">有效识别区域</text>
<text class="setting-desc">{{ scanRegionSummary }}</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ scanRegionPresetLabel }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view v-if="settings.scanRegionPreset === 'barcode'" class="setting-row tappable" @tap="selectScanRegionPosition">
<view class="setting-main">
<text class="setting-label">横向条码框位置</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ scanRegionPositionLabel }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view v-if="settings.scanRegionPreset === 'barcode'" class="setting-row tappable" @tap="selectScanRegionHeight">
<view class="setting-main">
<text class="setting-label">扫描框高度</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ scanRegionHeightLabel }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view v-if="settings.scanRegionPreset === 'barcode' && settings.scanRegionPosition === 'custom'" class="setting-row tappable" @tap="adjustCustomScanRegion">
<view class="setting-main">
<text class="setting-label">自定义条码框位置</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ regionPercent(settings.scanRegion.centerY) }}%</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view class="setting-row">
<view class="setting-main">
<text class="setting-label">成功提示音</text>
</view>
<u-switch v-model="settings.successSound" active-color="#2f6cf6" />
</view>
<view class="setting-row">
<view class="setting-main">
<text class="setting-label">失败提示音</text>
</view>
<u-switch v-model="settings.failSound" active-color="#ef4f4f" />
</view>
<view class="setting-row">
<view class="setting-main">
<text class="setting-label">震动反馈</text>
</view>
<u-switch v-model="settings.vibration" active-color="#35bfa3" />
</view>
<view class="setting-row">
<view class="setting-main">
<text class="setting-label">PDA 模式</text>
</view>
<u-switch v-model="settings.pdaMode" active-color="#1677ff" />
</view>
</view>
<view class="settings-card">
<view class="card-header">
<text class="card-title">识别处理</text>
<text class="card-subtitle">扫码识别与界面偏好</text>
</view>
<view class="setting-row tappable" @tap="selectTheme">
<view class="setting-main">
<text class="setting-label">主题模式</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ settings.theme === 'light' ? '浅色' : '深色' }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view class="setting-row tappable" @tap="selectLanguage">
<view class="setting-main">
<text class="setting-label">语言</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ settings.language === 'zh' ? '中文' : 'EN' }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view class="setting-row">
<view class="setting-main">
<text class="setting-label">自动保存草稿</text>
</view>
<u-switch v-model="settings.autoSave" active-color="#2f6cf6" />
</view>
<view class="setting-row tappable" @tap="selectUploadMode">
<view class="setting-main">
<text class="setting-label">图片上传方式</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ settings.uploadMode === 'wifi' ? '仅 Wi-Fi' : '任意网络' }}</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
</view>
<view class="settings-card">
<view class="card-header">
<text class="card-title">软件维护</text>
<text class="card-subtitle">本地与维护</text>
</view>
<view class="setting-row tappable" @tap="selectCacheDays">
<view class="setting-main">
<text class="setting-label">历史缓存保留</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ settings.cacheDays }} 天</text>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view class="setting-row tappable" @tap="checkForUpdate">
<view class="setting-main">
<text class="setting-label">检查更新</text>
</view>
<view class="setting-right">
<text class="setting-value">v{{ currentVersion }}</text>
<u-icon name="arrow-right" color="#c1c7d0" size="24" />
</view>
</view>
<view class="setting-row tappable" @tap="clearBusinessCache">
<view class="setting-main">
<text class="setting-label">清理缓存</text>
</view>
<u-icon name="arrow-right" color="#c1c7d0" size="24" />
</view>
<view class="setting-row tappable" @tap="resetSettings">
<view class="setting-main">
<text class="setting-label">恢复默认设置</text>
</view>
<u-icon name="arrow-right" color="#c1c7d0" size="24" />
</view>
</view>
<view class="settings-card">
<view class="card-header">
<text class="card-title">帮助与支持</text>
<text class="card-subtitle">问题反馈与应用信息</text>
</view>
<view class="setting-row tappable" @tap="goFeedback">
<view class="setting-main">
<text class="setting-label">问题反馈</text>
</view>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
<view class="setting-row tappable" @tap="goAbout">
<view class="setting-main">
<text class="setting-label">关于我们</text>
</view>
<u-icon name="arrow-right" color="#d0d6e2" size="24" />
</view>
</view>
<view class="footer-tip">设置会自动保存在本机,切换账号后设置相互独立</view>
</view>
</template>
<script>
import {
compareVersions,
getCurrentAppVersion,
getLatestRelease,
getReleaseUpdateInfo
} from '@/api/app-update.js'
import { applyAppTheme } from '@/utils/theme.js'
import { getScanappSettings, getScanappSettingsStorageKey } from '@/utils/scan-settings.js'
export default {
data() {
const defaultSettings = {
scanInterval: 1,
successSound: true,
failSound: true,
vibration: true,
pdaMode: false,
theme: 'light',
language: 'zh',
autoSave: true,
cacheDays: 30,
uploadMode: 'wifi',
scanRegionPreset: 'fullscreen',
scanRegionPosition: 'middle',
scanRegionHeight: 'medium',
scanRegion: { fullScreen: true }
}
return {
settingsStorageKey: getScanappSettingsStorageKey(),
isLoadingSettings: true,
intervalOptions: [0.3, 0.5, 1, 1.5, 2, 3, 5],
cacheDayOptions: [7, 15, 30, 60, 90],
scanRegionPresets: [
{ key: 'fullscreen', label: '全屏识别', description: '整画面均可识别,不显示扫描框' },
{ key: 'qrcode', label: '二维码正方形框', description: '适合二维码与方形标签' },
{ key: 'barcode', label: '横向条码框', description: '适合商品条码,可调整位置和高度' }
],
scanRegionPositions: [
{ key: 'top', label: '上方', centerY: 0.28 },
{ key: 'middle', label: '居中', centerY: 0.5 },
{ key: 'bottom', label: '下方', centerY: 0.72 },
{ key: 'custom', label: '自定义' }
],
scanRegionHeights: [
{ key: 'medium', label: '中(24%)', height: 0.24 },
{ key: 'large', label: '大(32%)', height: 0.32 },
{ key: 'xlarge', label: '特大(40%)', height: 0.4 }
],
defaultSettings,
settings: { ...defaultSettings, scanRegion: { ...defaultSettings.scanRegion } },
checkingUpdate: false,
currentVersion: getCurrentAppVersion()
}
},
onLoad() {
this.settingsStorageKey = getScanappSettingsStorageKey()
this.loadSettings()
this.isLoadingSettings = false
},
watch: {
settings: {
deep: true,
handler() {
if (this.isLoadingSettings) return
this.saveSettings()
}
}
},
methods: {
loadSettings() {
const savedSettings = getScanappSettings()
if (savedSettings && typeof savedSettings === 'object') {
const preset = savedSettings.scanRegionPreset || (savedSettings.scanRegion?.fullScreen ? 'fullscreen' : 'barcode')
const savedRegion = savedSettings.scanRegion || {}
let scanRegion = { ...this.defaultSettings.scanRegion }
if (preset === 'fullscreen') {
scanRegion = { fullScreen: true }
} else if (preset === 'qrcode') {
scanRegion = { width: 0.74, height: 0.74, centerX: 0.5, centerY: 0.5 }
} else {
scanRegion = {
width: Number(savedRegion.width) || 0.96,
height: Number(savedRegion.height) || 0.24,
centerX: Number.isFinite(Number(savedRegion.centerX)) ? Number(savedRegion.centerX) : 0.5,
centerY: Number.isFinite(Number(savedRegion.centerY)) ? Number(savedRegion.centerY) : 0.5
}
}
this.settings = {
...this.defaultSettings,
...savedSettings,
scanRegionPreset: preset,
scanRegion
}
}
applyAppTheme(this.settings.theme)
},
saveSettings() {
uni.setStorageSync(this.settingsStorageKey, { ...this.settings })
},
checkForUpdate() {
if (this.checkingUpdate) return
this.checkingUpdate = true
uni.showLoading({ title: '正在检查更新', mask: true })
getLatestRelease()
.then((release) => {
const currentVersion = getCurrentAppVersion()
const updateInfo = getReleaseUpdateInfo(release)
if (compareVersions(updateInfo.latestVersion, currentVersion) <= 0) {
uni.showModal({
title: '检查更新',
content: `当前版本 v${currentVersion},已是最新版本`,
showCancel: false,
confirmText: '知道了'
})
return
}
const content = `发现新版本 v${updateInfo.latestVersion}\n\n${updateInfo.updateNote}${updateInfo.apkUrl ? '\n\n是否立即下载并安装?' : '\n\n更新包暂未发布,请稍后再试。'}`
uni.showModal({
title: '发现新版本',
content,
confirmText: updateInfo.apkUrl ? '立即更新' : '知道了',
showCancel: Boolean(updateInfo.apkUrl),
cancelText: '暂不更新',
success: (modalResult) => {
if (modalResult.confirm && updateInfo.apkUrl) {
this.downloadAndInstallApk(updateInfo.apkUrl)
}
}
})
})
.catch((error) => {
uni.showModal({
title: '检查更新失败',
content: error.message || '无法获取最新版本信息,请稍后重试',
showCancel: false,
confirmText: '知道了'
})
})
.finally(() => {
this.checkingUpdate = false
uni.hideLoading()
})
},
downloadAndInstallApk(downloadUrl) {
// #ifdef APP-PLUS
if (!downloadUrl) return
uni.showLoading({ title: '正在下载更新包', mask: true })
uni.downloadFile({
url: downloadUrl,
success: (result) => {
if (result.statusCode !== 200) {
uni.showToast({ title: '更新包下载失败', icon: 'none' })
return
}
plus.runtime.install(result.tempFilePath, {}, () => {}, (error) => {
uni.showModal({ title: '安装失败', content: error.message || '请允许安装未知来源应用后重试', showCancel: false })
})
},
fail: () => uni.showToast({ title: '更新包下载失败,请检查网络', icon: 'none' }),
complete: () => uni.hideLoading()
})
// #endif
// #ifndef APP-PLUS
this.openReleasePage(downloadUrl)
// #endif
},
openReleasePage(url) {
const releaseUrl = url || 'https://gitee.com/li-hua-88/scanapp/releases/latest'
// #ifdef APP-PLUS
plus.runtime.openURL(releaseUrl)
// #endif
// #ifndef APP-PLUS
window.location.href = releaseUrl
// #endif
},
clearBusinessCache() {
uni.showModal({
title: '清理缓存',
content: '将清理扫码业务缓存,保留登录状态和当前设置,是否继续?',
confirmText: '清理',
confirmColor: '#1677ff',
success: (res) => {
if (!res.confirm) return
const protectedKeys = new Set(['scanapp_token', 'scanapp_refresh_token', 'scanapp_user'])
const storageInfo = uni.getStorageInfoSync()
;(storageInfo.keys || []).forEach((key) => {
const isSettingsKey = key === 'scanapp_settings' || key.indexOf('scanapp_settings_') === 0
if (key.indexOf('scanapp_') === 0 && !protectedKeys.has(key) && !isSettingsKey) {
uni.removeStorageSync(key)
}
})
uni.showToast({ title: '缓存已清理', icon: 'success' })
}
})
},
resetSettings() {
uni.showModal({
title: '恢复默认设置',
content: '所有设置项将恢复为默认值,是否继续?',
confirmText: '恢复',
confirmColor: '#1677ff',
success: (res) => {
if (!res.confirm) return
this.settings = { ...this.defaultSettings, scanRegion: { ...this.defaultSettings.scanRegion } }
this.saveSettings()
applyAppTheme(this.settings.theme)
uni.showToast({ title: '已恢复默认设置', icon: 'success' })
}
})
},
selectScanInterval() {
this.showSettingOptions('连续扫码时间间隔', this.intervalOptions.map((item) => `${item}S`), (index) => {
this.settings.scanInterval = this.intervalOptions[index]
})
},
selectScanRegionPreset() {
this.showSettingOptions('有效识别区域', this.scanRegionPresets.map((item) => item.label), (index) => {
const preset = this.scanRegionPresets[index]
if (!preset) return
this.settings.scanRegionPreset = preset.key
if (preset.key === 'fullscreen') {
this.settings.scanRegion = { fullScreen: true }
return
}
if (preset.key === 'qrcode') {
this.settings.scanRegion = { width: 0.74, height: 0.74, centerX: 0.5, centerY: 0.5 }
return
}
const position = this.scanRegionPositions.find((item) => item.key === this.settings.scanRegionPosition) || this.scanRegionPositions[1]
const height = this.scanRegionHeights.find((item) => item.key === this.settings.scanRegionHeight) || this.scanRegionHeights[0]
this.settings.scanRegion = { width: 0.96, height: height.height, centerX: 0.5, centerY: position.centerY || 0.5 }
})
},
selectScanRegionPosition() {
this.showSettingOptions('横向条码框位置', this.scanRegionPositions.map((item) => item.label), (index) => {
const position = this.scanRegionPositions[index]
if (!position) return
this.settings.scanRegionPosition = position.key
if (position.key !== 'custom') this.settings.scanRegion.centerY = position.centerY
})
},
selectScanRegionHeight() {
this.showSettingOptions('扫描框高度', this.scanRegionHeights.map((item) => item.label), (index) => {
const height = this.scanRegionHeights[index]
if (!height) return
this.settings.scanRegionHeight = height.key
this.settings.scanRegion.height = height.height
const minCenterY = height.height / 2
this.settings.scanRegion.centerY = Math.max(minCenterY, Math.min(1 - minCenterY, Number(this.settings.scanRegion.centerY || 0.5)))
})
},
adjustCustomScanRegion() {
this.showSettingOptions('调整自定义位置', ['上移 5%', '下移 5%'], (index) => {
const delta = index === 0 ? -0.05 : 0.05
const height = Number(this.settings.scanRegion.height || 0.24)
const minCenterY = height / 2
const maxCenterY = 1 - minCenterY
this.settings.scanRegion.centerY = Math.max(minCenterY, Math.min(maxCenterY, Number((Number(this.settings.scanRegion.centerY || 0.5) + delta).toFixed(2))))
})
},
regionPercent(value) {
return Math.round(Number(value || 0) * 100)
},
selectTheme() {
this.showSettingOptions('主题模式', ['浅色模式', '深色模式'], (index) => {
this.settings.theme = index === 0 ? 'light' : 'dark'
applyAppTheme(this.settings.theme)
})
},
selectLanguage() {
this.showSettingOptions('语言', ['简体中文', 'English'], (index) => {
this.settings.language = index === 0 ? 'zh' : 'en'
})
},
selectUploadMode() {
this.showSettingOptions('图片上传方式', ['仅 Wi-Fi', '任意网络'], (index) => {
this.settings.uploadMode = index === 0 ? 'wifi' : 'all'
})
},
selectCacheDays() {
this.showSettingOptions('历史缓存保留', this.cacheDayOptions.map((item) => `${item} 天`), (index) => {
this.settings.cacheDays = this.cacheDayOptions[index]
})
},
showSettingOptions(title, itemList, onSelect) {
uni.showActionSheet({
title,
itemList,
success: (res) => onSelect(res.tapIndex)
})
},
goFeedback() {
uni.navigateTo({
url: '/pages/settings/feedback'
})
},
goAbout() {
uni.navigateTo({
url: '/pages/settings/about'
})
},
toggleAutoSave() {
this.settings.autoSave = !this.settings.autoSave
},
showMockToast(title) {
uni.showToast({ title, icon: 'none' })
}
},
computed: {
scanRegionPresetLabel() {
return this.scanRegionPresets.find((item) => item.key === this.settings.scanRegionPreset)?.label || '横向条码框'
},
scanRegionPositionLabel() {
return this.scanRegionPositions.find((item) => item.key === this.settings.scanRegionPosition)?.label || '居中'
},
scanRegionHeightLabel() {
return this.scanRegionHeights.find((item) => item.key === this.settings.scanRegionHeight)?.label || '中(24%)'
},
scanRegionSummary() {
if (this.settings.scanRegionPreset === 'fullscreen') return '整画面识别,不显示扫描框'
const region = this.settings.scanRegion || {}
return `宽 ${this.regionPercent(region.width)}% · 高 ${this.regionPercent(region.height)}% · 中心 ${this.regionPercent(region.centerX)}%, ${this.regionPercent(region.centerY)}%`
}
}
}
</script>
<style lang="scss" scoped>
.settings-page {
min-height: 100vh;
padding: 0 22rpx 40rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eaf2ff 0%, #f6f8fd 18%, #f4f7fb 100%);
}
.page-accent {
height: 10rpx;
margin: 0 -22rpx 18rpx;
background: linear-gradient(90deg, #2f6cf6 0%, #35bfa3 100%);
}
.settings-card {
margin-top: 18rpx;
padding: 30rpx 26rpx;
border-radius: 30rpx;
background: #ffffff;
box-shadow: 0 14rpx 32rpx rgba(24, 37, 68, 0.06);
}
.card-header {
margin-bottom: 14rpx;
}
.card-title {
display: block;
color: #8a94a6;
font-size: 24rpx;
font-weight: 700;
line-height: 1.35;
}
.card-subtitle {
display: block;
margin-top: 6rpx;
color: #c1c7d0;
font-size: 18rpx;
line-height: 1.35;
}
.setting-row,
.action-list .setting-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid #eef1f6;
}
.setting-row:last-child,
.action-list .setting-row:last-child {
padding-bottom: 0;
border-bottom: 0;
}
.setting-main {
min-width: 0;
flex: 1;
}
.setting-label {
display: block;
color: #17233d;
font-size: 26rpx;
font-weight: 700;
line-height: 1.35;
}
.setting-desc {
display: block;
margin-top: 10rpx;
color: #8f98a8;
font-size: 18rpx;
line-height: 1.45;
}
.setting-right {
display: flex;
align-items: center;
gap: 10rpx;
flex: none;
}
.setting-row :deep(.u-switch) {
transform: scale(0.82);
transform-origin: right center;
}
.value-line {
display: flex;
align-items: center;
gap: 8rpx;
}
.setting-value {
color: #5f6b7a;
font-size: 22rpx;
font-weight: 700;
line-height: 1.35;
}
.tappable {
cursor: pointer;
}
.footer-tip {
margin-top: 18rpx;
padding: 0 8rpx;
color: #9aa4b4;
font-size: 18rpx;
line-height: 1.45;
text-align: center;
}
.theme-dark {
background: #151923;
}
.theme-dark .settings-card {
background: #222936;
box-shadow: none;
}
.theme-dark .card-title,
.theme-dark .setting-desc,
.theme-dark .footer-tip {
color: #aeb9ca;
}
.theme-dark .card-subtitle {
color: #718096;
}
.theme-dark .setting-label {
color: #f2f5fa;
}
.theme-dark .setting-value {
color: #c6d0df;
}
.theme-dark .setting-row,
.theme-dark .action-list .setting-row {
border-bottom-color: #343e4d;
}
.theme-dark.settings-page {
background: #151923;
}
.theme-dark .settings-card {
border-color: #364152;
background: #222936;
box-shadow: none;
}
.theme-dark .card-title,
.theme-dark .setting-label {
color: #f2f5fa;
}
.theme-dark .card-subtitle,
.theme-dark .setting-desc,
.theme-dark .footer-tip {
color: #aeb9ca;
}
.theme-dark .setting-value {
color: #d7deea;
}
.theme-dark .setting-row,
.theme-dark .action-list .setting-row {
border-color: #364152;
}
</style>