完善生产查询与登录权限配置
This commit is contained in:
@@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<view class="about-page" :class="`theme-${themeMode}`">
|
||||
<view class="hero-card">
|
||||
<view class="hero-logo">
|
||||
<u-icon name="info-circle-fill" color="#ffffff" size="46" />
|
||||
</view>
|
||||
<view class="hero-content">
|
||||
<text class="hero-tag">APP 原型</text>
|
||||
<text class="hero-title">关于我们</text>
|
||||
<text class="hero-subtitle">扫描、提报、维修与统计的一体化现场作业助手</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<text class="section-title">产品信息</text>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">产品名称</text>
|
||||
<text class="info-value">ScanApp</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">适用场景</text>
|
||||
<text class="info-value">扫码录入 / 异常处理 / 维修协同</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">当前版本</text>
|
||||
<text class="info-value">v{{ appVersion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<text class="section-title">协议与资质</text>
|
||||
<view class="nav-list">
|
||||
<view v-for="item in policyItems" :key="item.title" class="nav-row" :class="{ tappable: item.readable }" @tap="item.readable && handlePolicyTap(item)">
|
||||
<text class="nav-title">{{ item.title }}</text>
|
||||
<view class="nav-right">
|
||||
<text v-if="item.value" class="nav-value">{{ item.value }}</text>
|
||||
<u-icon v-if="item.readable" class="nav-arrow" name="arrow-right" color="#c1c7d0" size="24" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<text class="section-title">联系与支持</text>
|
||||
<view class="contact-stack">
|
||||
<view v-for="item in contactItems" :key="item.label" class="contact-block">
|
||||
<text class="contact-label">{{ item.label }}</text>
|
||||
<view class="email-row">
|
||||
<text class="email-value">{{ item.email || '未配置' }}</text>
|
||||
<view v-if="item.email" class="copy-btn" @tap="copyEmail(item.email)">复制</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCurrentAppVersion } from '@/api/app-update.js'
|
||||
import { getConfigByCode } from '@/api/system-config.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
appVersion: getCurrentAppVersion(),
|
||||
policyItems: [
|
||||
{ title: '用户服务协议', value: '', content: '', readable: true },
|
||||
{ title: '隐私政策', value: '', content: '', readable: true },
|
||||
{ title: '备案号', value: '', content: '', readable: false }
|
||||
],
|
||||
contactItems: [
|
||||
{ label: '违法和不良信息举报邮箱', email: '' },
|
||||
{ label: '商务合作', email: '' }
|
||||
],
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadAboutConfig()
|
||||
},
|
||||
onShow() {
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: this.themeMode === 'dark' ? '#ffffff' : '#000000',
|
||||
backgroundColor: this.themeMode === 'dark' ? '#151923' : '#eef6ff'
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async loadAboutConfig() {
|
||||
try {
|
||||
const config = await getConfigByCode('app_about_setting')
|
||||
this.policyItems = [
|
||||
{ title: '用户服务协议', value: config.user_service_agreement ? '查看' : '', content: config.user_service_agreement || '', readable: true },
|
||||
{ title: '隐私政策', value: config.privacy_policy ? '查看' : '', content: config.privacy_policy || '', readable: true },
|
||||
{ title: '备案号', value: config.icp_number || '', content: '', readable: false }
|
||||
]
|
||||
this.contactItems = [
|
||||
{ label: '违法和不良信息举报邮箱', email: config.report_email || '' },
|
||||
{ label: '商务合作', email: config.business_contact || '' }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error('[scanapp] 获取关于我们配置失败', error)
|
||||
}
|
||||
},
|
||||
handlePolicyTap(item) {
|
||||
if (!item.content) {
|
||||
uni.showToast({ title: '暂未配置', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.setStorageSync('scanapp_policy_detail', {
|
||||
title: item.title,
|
||||
content: item.content
|
||||
})
|
||||
uni.navigateTo({ url: '/pages/settings/policy' })
|
||||
},
|
||||
copyEmail(email) {
|
||||
uni.setClipboardData({
|
||||
data: email,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已复制', icon: 'success' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.about-page {
|
||||
min-height: 100vh;
|
||||
padding: 28rpx 24rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #eef6ff 0%, #f4f6fb 36%, #f4f6fb 100%);
|
||||
}
|
||||
|
||||
.hero-card,
|
||||
.section-card {
|
||||
padding: 34rpx;
|
||||
border-radius: 28rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.75);
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 35, 61, 0.06);
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
min-width: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.hero-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 28rpx;
|
||||
background: linear-gradient(135deg, #1677ff 0%, #35bfa3 100%);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.hero-tag,
|
||||
.hero-subtitle,
|
||||
.info-label,
|
||||
.feature-desc,
|
||||
.contact-label,
|
||||
.contact-value,
|
||||
.nav-title,
|
||||
.nav-value,
|
||||
.email-value {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
color: #1677ff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-title,
|
||||
.section-title,
|
||||
.feature-title {
|
||||
display: block;
|
||||
color: #17233d;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
margin-top: 10rpx;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
margin-top: 10rpx;
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.info-row,
|
||||
.contact-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 18rpx 0;
|
||||
border-bottom: 1rpx solid #edf1f6;
|
||||
}
|
||||
|
||||
.info-value,
|
||||
.contact-value {
|
||||
min-width: 0;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.info-row:last-child,
|
||||
.contact-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label,
|
||||
.contact-label {
|
||||
color: #7d8798;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.info-value,
|
||||
.contact-value {
|
||||
color: #17233d;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 22rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
margin-top: 8rpx;
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.nav-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #edf1f6;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
color: #17233d;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: none;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.nav-value {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nav-arrow {
|
||||
flex: none;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.nav-value {
|
||||
color: #5f6b7a;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.contact-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22rpx;
|
||||
}
|
||||
|
||||
.contact-block + .contact-block {
|
||||
padding-top: 6rpx;
|
||||
}
|
||||
|
||||
.contact-label {
|
||||
margin-bottom: 14rpx;
|
||||
color: #17233d;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.email-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 22rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.email-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.email-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: #8f98a8;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
flex: none;
|
||||
padding: 0 8rpx;
|
||||
color: #7d8798;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tappable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.theme-dark.about-page {
|
||||
background: #151923;
|
||||
}
|
||||
|
||||
.theme-dark .policy-fullscreen {
|
||||
background: #151923;
|
||||
}
|
||||
|
||||
.theme-dark .policy-header {
|
||||
border-color: #364152;
|
||||
}
|
||||
|
||||
.theme-dark .policy-back,
|
||||
.theme-dark .policy-header-title,
|
||||
.theme-dark .policy-title {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .policy-content {
|
||||
color: #c4cede;
|
||||
}
|
||||
|
||||
.theme-dark .hero-card,
|
||||
.theme-dark .section-card {
|
||||
border-color: #364152;
|
||||
background: #222936;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.theme-dark .feature-item,
|
||||
.theme-dark .email-row {
|
||||
background: #1c2431;
|
||||
}
|
||||
|
||||
.theme-dark .hero-title,
|
||||
.theme-dark .section-title,
|
||||
.theme-dark .feature-title,
|
||||
.theme-dark .info-value,
|
||||
.theme-dark .contact-value,
|
||||
.theme-dark .nav-title,
|
||||
.theme-dark .contact-label {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .hero-subtitle,
|
||||
.theme-dark .info-label,
|
||||
.theme-dark .feature-desc,
|
||||
.theme-dark .nav-value,
|
||||
.theme-dark .email-value,
|
||||
.theme-dark .copy-btn {
|
||||
color: #aeb9ca;
|
||||
}
|
||||
|
||||
.theme-dark .info-row,
|
||||
.theme-dark .contact-item,
|
||||
.theme-dark .nav-row {
|
||||
border-color: #364152;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<view class="feedback-detail-page" :class="`theme-${themeMode}`">
|
||||
<view v-if="loading" class="loading-state">正在加载反馈详情…</view>
|
||||
<view v-else-if="feedback" class="detail-card">
|
||||
<view class="detail-header">
|
||||
<view>
|
||||
<text class="detail-type">{{ getTypeLabel(feedback.type) }}</text>
|
||||
<text class="detail-time">{{ feedback.created_date || '时间未知' }}</text>
|
||||
</view>
|
||||
<u-icon name="chat" color="#ff8a3d" size="40" />
|
||||
</view>
|
||||
|
||||
<view class="detail-section">
|
||||
<text class="detail-label">问题描述</text>
|
||||
<text class="detail-content">{{ feedback.content || '/' }}</text>
|
||||
</view>
|
||||
<view class="detail-section">
|
||||
<text class="detail-label">联系方式</text>
|
||||
<text class="detail-content">{{ feedback.contact || '未填写' }}</text>
|
||||
</view>
|
||||
<view v-if="imageList.length" class="detail-section">
|
||||
<text class="detail-label">截图附件</text>
|
||||
<view class="image-list">
|
||||
<image v-for="image in imageList" :key="image" :src="image" mode="aspectFill" @tap="previewImage(image)" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="feedback.reply_content" class="reply-card">
|
||||
<view class="reply-header">
|
||||
<text class="reply-title">管理员回复</text>
|
||||
<text class="reply-time">{{ feedback.reply_date || '' }}</text>
|
||||
</view>
|
||||
<text class="reply-content">{{ feedback.reply_content }}</text>
|
||||
<text v-if="feedback.reply_name" class="reply-admin">回复人:{{ feedback.reply_name }}</text>
|
||||
</view>
|
||||
<view v-else class="pending-reply">该反馈暂未收到回复,请耐心等待。</view>
|
||||
</view>
|
||||
<u-empty v-else mode="data" text="反馈记录不存在" icon="data" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyFeedbackDetail } from '@/api/feedback.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
feedback: null,
|
||||
feedbackId: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageList() {
|
||||
return Array.isArray(this.feedback?.images)
|
||||
? this.feedback.images.filter((image) => typeof image === 'string' && image)
|
||||
: []
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.feedbackId = String(options?.id || '')
|
||||
this.loadFeedbackDetail()
|
||||
},
|
||||
methods: {
|
||||
getTypeLabel(type) {
|
||||
return {
|
||||
bug: '功能异常',
|
||||
suggestion: '体验建议',
|
||||
correction: '内容纠错'
|
||||
}[type] || '问题反馈'
|
||||
},
|
||||
async loadFeedbackDetail() {
|
||||
if (!this.feedbackId) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
try {
|
||||
this.feedback = await getMyFeedbackDetail(this.feedbackId)
|
||||
} catch (error) {
|
||||
console.error('[scanapp] 获取反馈详情失败', error)
|
||||
this.feedback = null
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
previewImage(current) {
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls: this.imageList
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.feedback-detail-page {
|
||||
min-height: 100vh;
|
||||
padding: 28rpx 24rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f4f6fb;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 80rpx 0;
|
||||
color: #7d8798;
|
||||
font-size: 22rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 34rpx;
|
||||
border-radius: 28rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 35, 61, 0.06);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 28rpx;
|
||||
border-bottom: 1rpx solid #edf0f5;
|
||||
}
|
||||
|
||||
.detail-type,
|
||||
.detail-time,
|
||||
.detail-label,
|
||||
.detail-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.detail-type {
|
||||
color: #ff8a3d;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
margin-top: 10rpx;
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
padding-top: 28rpx;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
margin-top: 12rpx;
|
||||
color: #17233d;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.image-list image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
margin-right: 14rpx;
|
||||
margin-bottom: 14rpx;
|
||||
border-radius: 18rpx;
|
||||
}
|
||||
|
||||
.reply-card {
|
||||
margin-top: 32rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #fff8f2;
|
||||
}
|
||||
|
||||
.reply-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.reply-title {
|
||||
color: #d86c2d;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.reply-time,
|
||||
.reply-admin {
|
||||
color: #8b735f;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
color: #3e3128;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.reply-admin {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.pending-reply {
|
||||
margin-top: 32rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 18rpx;
|
||||
color: #7d8798;
|
||||
background: #f3f5f8;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.theme-dark.feedback-detail-page {
|
||||
background: #151923;
|
||||
}
|
||||
|
||||
.theme-dark .detail-card {
|
||||
background: #222936;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.theme-dark .detail-header {
|
||||
border-color: #364152;
|
||||
}
|
||||
|
||||
.theme-dark .detail-content {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .detail-label,
|
||||
.theme-dark .detail-time,
|
||||
.theme-dark .loading-state {
|
||||
color: #aeb9ca;
|
||||
}
|
||||
|
||||
.theme-dark .reply-card {
|
||||
background: #34291f;
|
||||
}
|
||||
|
||||
.theme-dark .reply-content {
|
||||
color: #f2e6da;
|
||||
}
|
||||
|
||||
.theme-dark .reply-time,
|
||||
.theme-dark .reply-admin,
|
||||
.theme-dark .pending-reply {
|
||||
color: #c7b8a8;
|
||||
}
|
||||
|
||||
.theme-dark .pending-reply {
|
||||
background: #1c2431;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,647 @@
|
||||
<template>
|
||||
<view class="feedback-page" :class="`theme-${themeMode}`">
|
||||
<view class="hero-card">
|
||||
<view>
|
||||
<text class="hero-tag">意见收集</text>
|
||||
<text class="hero-title">问题反馈</text>
|
||||
<text class="hero-subtitle">提交使用建议、问题描述或异常截图,我们会尽快跟进</text>
|
||||
</view>
|
||||
<view class="hero-icon">
|
||||
<u-icon name="email" color="#ffffff" size="44" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<text class="section-title">反馈内容</text>
|
||||
</view>
|
||||
|
||||
<view class="field-card">
|
||||
<text class="field-label">反馈类型</text>
|
||||
<view class="chip-row">
|
||||
<view
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
class="chip"
|
||||
:class="{ 'is-active': form.type === item.value }"
|
||||
@tap="form.type = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-card">
|
||||
<text class="field-label">问题描述</text>
|
||||
<view class="textarea-wrap">
|
||||
<textarea v-model="form.content" placeholder="请描述遇到的问题、操作步骤或建议" placeholder-class="textarea-placeholder" maxlength="500" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field-card">
|
||||
<text class="field-label">联系方式(选填)</text>
|
||||
<u-input v-model="form.contact" placeholder="手机号 / 微信 / 邮箱" :border="false" />
|
||||
</view>
|
||||
|
||||
<view class="field-card">
|
||||
<text class="field-label">截图附件</text>
|
||||
<view class="upload-row">
|
||||
<view v-if="form.images.length < 3" class="upload-box" @tap="pickImage" :class="{ 'is-disabled': uploading || form.images.length >= 3 }">
|
||||
<u-icon name="plus" color="#ff9d42" size="34" />
|
||||
<text>{{ uploading ? '上传中' : '添加截图' }}</text>
|
||||
</view>
|
||||
<view v-for="(img, index) in form.images" :key="img" class="upload-preview">
|
||||
<image :src="img" mode="aspectFill" />
|
||||
<text class="upload-label">图片{{ index + 1 }}</text>
|
||||
<view class="upload-delete" @tap.stop="removeImage(index)">
|
||||
<u-icon name="close" color="#ffffff" size="20" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips-card">
|
||||
<text class="tips-title">提交说明</text>
|
||||
<text class="tips-text">1. 请优先说明页面名称和操作路径;2. 如果是扫码异常,请附上单号或二维码截图;3. 反馈会保存在本机历史记录中。</text>
|
||||
</view>
|
||||
|
||||
<view class="bottom-actions">
|
||||
<view class="secondary-button" @tap="resetForm">清空</view>
|
||||
<view class="primary-button" :class="{ 'is-disabled': submitting }" @tap="submitFeedback">{{ submitting ? '提交中' : '提交反馈' }}</view>
|
||||
</view>
|
||||
|
||||
<view class="history-card">
|
||||
<view class="history-header">
|
||||
<view>
|
||||
<text class="section-title">我的反馈</text>
|
||||
<text class="history-subtitle">仅展示当前账号已提交的问题</text>
|
||||
</view>
|
||||
<view class="refresh-button" :class="{ 'is-disabled': historyLoading }" @tap="loadFeedbackList">
|
||||
<u-icon name="reload" color="#ff8a3d" size="28" />
|
||||
<text>刷新</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="historyLoading" class="history-loading">正在加载反馈记录…</view>
|
||||
<view v-else-if="feedbackList.length" class="feedback-list">
|
||||
<view v-for="item in feedbackList" :key="item.id" class="feedback-record" @tap="goFeedbackDetail(item.id)">
|
||||
<view class="record-main">
|
||||
<view class="record-title-row">
|
||||
<text class="record-type">{{ getTypeLabel(item.type) }}</text>
|
||||
<text class="record-time">{{ item.created_date || '时间未知' }}</text>
|
||||
</view>
|
||||
<text class="record-content">{{ item.content }}</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#9aa5b5" size="28" />
|
||||
</view>
|
||||
</view>
|
||||
<u-empty v-else mode="list" text="暂无已提交的反馈" icon="list" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyFeedbackList, submitFeedback as createFeedback } from '@/api/feedback.js'
|
||||
import { uploadImage } from '@/api/upload.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploading: false,
|
||||
submitting: false,
|
||||
historyLoading: false,
|
||||
feedbackList: [],
|
||||
form: {
|
||||
type: 'bug',
|
||||
content: '',
|
||||
contact: '',
|
||||
images: []
|
||||
},
|
||||
typeOptions: [
|
||||
{ label: '功能异常', value: 'bug' },
|
||||
{ label: '体验建议', value: 'suggestion' },
|
||||
{ label: '内容纠错', value: 'correction' }
|
||||
]
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.loadFeedbackList()
|
||||
},
|
||||
methods: {
|
||||
getTypeLabel(type) {
|
||||
return this.typeOptions.find((item) => item.value === type)?.label || '问题反馈'
|
||||
},
|
||||
async loadFeedbackList() {
|
||||
if (this.historyLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.historyLoading = true
|
||||
try {
|
||||
const result = await getMyFeedbackList({ page: 1, limit: 10 })
|
||||
this.feedbackList = Array.isArray(result?.items) ? result.items : []
|
||||
} catch (error) {
|
||||
console.error('[scanapp] 获取反馈记录失败', error)
|
||||
this.feedbackList = []
|
||||
} finally {
|
||||
this.historyLoading = false
|
||||
}
|
||||
},
|
||||
goFeedbackDetail(id) {
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/settings/feedback-detail?id=${encodeURIComponent(id)}`
|
||||
})
|
||||
},
|
||||
pickImage() {
|
||||
if (this.uploading || this.submitting || this.form.images.length >= 3) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.chooseImage({
|
||||
count: 3 - this.form.images.length,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: ({ tempFilePaths = [] }) => {
|
||||
this.form.images = [...this.form.images, ...tempFilePaths].slice(0, 3)
|
||||
}
|
||||
})
|
||||
},
|
||||
removeImage(index) {
|
||||
if (this.uploading || this.submitting) return
|
||||
this.form.images.splice(index, 1)
|
||||
},
|
||||
resetForm() {
|
||||
if (this.submitting || this.uploading) {
|
||||
return
|
||||
}
|
||||
this.form.content = ''
|
||||
this.form.contact = ''
|
||||
this.form.images = []
|
||||
this.form.type = 'bug'
|
||||
},
|
||||
async submitFeedback() {
|
||||
if (this.submitting || this.uploading) {
|
||||
return
|
||||
}
|
||||
if (!this.form.content.trim()) {
|
||||
uni.showToast({ title: '请输入问题描述', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
this.submitting = true
|
||||
try {
|
||||
this.uploading = true
|
||||
const uploadedImages = []
|
||||
for (const filePath of this.form.images) {
|
||||
uploadedImages.push(await uploadImage(filePath, 'feedback'))
|
||||
}
|
||||
await createFeedback({
|
||||
type: this.form.type,
|
||||
content: this.form.content.trim(),
|
||||
contact: this.form.contact.trim(),
|
||||
images: uploadedImages,
|
||||
source: 'app'
|
||||
})
|
||||
uni.showToast({ title: '反馈已提交', icon: 'none' })
|
||||
this.form.content = ''
|
||||
this.form.contact = ''
|
||||
this.form.images = []
|
||||
this.form.type = 'bug'
|
||||
await this.loadFeedbackList()
|
||||
} catch (error) {
|
||||
console.error('[scanapp] 提交反馈失败', error)
|
||||
} finally {
|
||||
this.uploading = false
|
||||
this.submitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.feedback-page {
|
||||
min-height: 100vh;
|
||||
padding: 28rpx 24rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #eef6ff 0%, #f4f6fb 36%, #f4f6fb 100%);
|
||||
}
|
||||
|
||||
.hero-card,
|
||||
.section-card,
|
||||
.tips-card {
|
||||
padding: 34rpx;
|
||||
border-radius: 28rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.75);
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 35, 61, 0.06);
|
||||
}
|
||||
|
||||
.section-card,
|
||||
.tips-card {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hero-tag,
|
||||
.section-subtitle,
|
||||
.tips-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
color: #ff8a3d;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-title,
|
||||
.section-title {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: #17233d;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero-subtitle,
|
||||
.section-subtitle {
|
||||
margin-top: 10rpx;
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hero-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 24rpx;
|
||||
background: linear-gradient(135deg, #ff9d42 0%, #ff7048 100%);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.field-card + .field-card {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.field-card {
|
||||
padding: 24rpx;
|
||||
border-radius: 22rpx;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
color: #17233d;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.chip-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chip-row .chip {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.chip {
|
||||
padding: 16rpx 22rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #e9edf4;
|
||||
color: #5f6b7a;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip.is-active {
|
||||
background: linear-gradient(135deg, #ff9d42 0%, #ff7d47 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.textarea-wrap {
|
||||
padding: 18rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 220rpx;
|
||||
color: #17233d;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.upload-row > * {
|
||||
margin-right: 14rpx;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.upload-box,
|
||||
.upload-preview {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 144rpx;
|
||||
height: 144rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-box {
|
||||
flex-direction: column;
|
||||
border: 2rpx dashed #ffb47e;
|
||||
background: #fff8f2;
|
||||
color: #ff8a3d;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.upload-box text {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.upload-preview image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.upload-label {
|
||||
position: absolute;
|
||||
right: 8rpx;
|
||||
bottom: 8rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
font-size: 18rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.upload-delete {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
display: block;
|
||||
color: #17233d;
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
margin-top: 12rpx;
|
||||
color: #7d8798;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.bottom-actions .primary-button {
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
|
||||
.bottom-actions .secondary-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.secondary-button,
|
||||
.primary-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 84rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
width: 32%;
|
||||
background: #f0f3f8;
|
||||
color: #56647a;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #ff9d42 0%, #ff7048 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.upload-box.is-disabled,
|
||||
.primary-button.is-disabled,
|
||||
.refresh-button.is-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.history-card {
|
||||
margin-top: 28rpx;
|
||||
padding: 34rpx;
|
||||
border-radius: 28rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 35, 61, 0.06);
|
||||
}
|
||||
|
||||
.history-header,
|
||||
.feedback-record,
|
||||
.record-title-row,
|
||||
.refresh-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.history-header {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.history-subtitle {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: #7d8798;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
padding: 12rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff8f2;
|
||||
color: #ff8a3d;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.refresh-button text {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.history-loading {
|
||||
padding: 40rpx 0;
|
||||
color: #7d8798;
|
||||
font-size: 22rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feedback-list {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.feedback-record {
|
||||
justify-content: space-between;
|
||||
padding: 22rpx 0;
|
||||
border-bottom: 1rpx solid #edf0f5;
|
||||
}
|
||||
|
||||
.feedback-record:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.record-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.record-title-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.record-type {
|
||||
color: #17233d;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
margin-left: 20rpx;
|
||||
color: #7d8798;
|
||||
font-size: 18rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-content {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
margin-right: 20rpx;
|
||||
color: #5f6b7a;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.theme-dark.feedback-page {
|
||||
background: #151923;
|
||||
}
|
||||
|
||||
.theme-dark .hero-card,
|
||||
.theme-dark .section-card,
|
||||
.theme-dark .tips-card,
|
||||
.theme-dark .history-card {
|
||||
border-color: #364152;
|
||||
background: #222936;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.theme-dark .field-card,
|
||||
.theme-dark .textarea-wrap,
|
||||
.theme-dark .upload-box {
|
||||
border-color: #364152;
|
||||
background: #1c2431;
|
||||
}
|
||||
|
||||
.theme-dark .hero-title,
|
||||
.theme-dark .section-title,
|
||||
.theme-dark .field-label,
|
||||
.theme-dark .tips-title,
|
||||
.theme-dark textarea {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .hero-subtitle,
|
||||
.theme-dark .section-subtitle,
|
||||
.theme-dark .tips-text,
|
||||
.theme-dark .history-subtitle,
|
||||
.theme-dark .history-loading,
|
||||
.theme-dark .record-time,
|
||||
.theme-dark .record-content,
|
||||
.theme-dark .chip,
|
||||
.theme-dark .upload-box {
|
||||
color: #aeb9ca;
|
||||
}
|
||||
|
||||
.theme-dark .record-type {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .feedback-record {
|
||||
border-color: #364152;
|
||||
}
|
||||
|
||||
.theme-dark .refresh-button {
|
||||
background: #34291f;
|
||||
}
|
||||
|
||||
.theme-dark .chip {
|
||||
background: #1a2230;
|
||||
}
|
||||
|
||||
.theme-dark .chip.is-active {
|
||||
background: linear-gradient(135deg, #a85d2f 0%, #8b4d35 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.theme-dark textarea::placeholder,
|
||||
.theme-dark :deep(.u-input__input)::placeholder {
|
||||
color: #8996aa;
|
||||
}
|
||||
|
||||
.theme-dark .secondary-button {
|
||||
background: #293344;
|
||||
color: #d7deea;
|
||||
}
|
||||
|
||||
.theme-dark .primary-button {
|
||||
background: linear-gradient(135deg, #ff9d42 0%, #ff7048 100%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,708 @@
|
||||
<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>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<view class="policy-page" :class="`theme-${themeMode}`">
|
||||
<scroll-view scroll-y class="policy-scroll">
|
||||
<view class="policy-paper">
|
||||
<text class="policy-title">{{ title }}</text>
|
||||
<text class="policy-content">{{ content }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const POLICY_STORAGE_KEY = 'scanapp_policy_detail'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
const policy = uni.getStorageSync(POLICY_STORAGE_KEY) || {}
|
||||
this.title = policy.title || '协议详情'
|
||||
this.content = policy.content || ''
|
||||
uni.setNavigationBarTitle({ title: this.title })
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: this.themeMode === 'dark' ? '#ffffff' : '#000000',
|
||||
backgroundColor: this.themeMode === 'dark' ? '#151923' : '#ffffff'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.policy-page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.policy-scroll {
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.policy-paper {
|
||||
padding: 44rpx 36rpx 80rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.policy-title {
|
||||
display: block;
|
||||
margin-bottom: 34rpx;
|
||||
color: #17233d;
|
||||
font-size: 38rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
display: block;
|
||||
color: #3f4b5d;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.9;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.theme-dark.policy-page {
|
||||
background: #151923;
|
||||
}
|
||||
|
||||
.theme-dark .policy-title {
|
||||
color: #f2f5fa;
|
||||
}
|
||||
|
||||
.theme-dark .policy-content {
|
||||
color: #c4cede;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user