fix: improve UX and fix admin console display issues
Miniprogram: - Add back buttons to all custom-nav pages - Add debug/mock buttons (BLE connect, wear check, treatment) controlled by __DEV__ flag (hidden in prod) Admin console: - Fix log page using nonexistent fields (operator_type, target_type) now correctly reads admin_id/user_id/action/detail from API - Fix subscription date fields (started_at→start_time, expired_at→expire_time) - Wire up subscription detail/extend/renew action buttons - Wire up device detail "查看完整日志" button - Add "创建订阅" button to subscription toolbar - Fix subscription status mapping (2=expired, 3=cancelled) Docs: - Add detailed architecture docs for server, miniprogram, admin console
这个提交包含在:
@@ -95,7 +95,7 @@
|
||||
|
||||
<view class="action-row">
|
||||
<button class="btn-primary" @click="onCommand('query')">发送指令</button>
|
||||
<button class="btn-default">查看完整日志</button>
|
||||
<button class="btn-default" @click="onViewLogs">查看完整日志</button>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@@ -154,6 +154,9 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
onViewLogs() {
|
||||
uni.redirectTo({ url: '/pages/log/index?device_id=' + this.deviceId })
|
||||
},
|
||||
onCheckUpdate() {
|
||||
uni.showToast({ title: '正在检查更新...', icon: 'none' })
|
||||
},
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
|
||||
<view class="page-card">
|
||||
<view class="log-list">
|
||||
<view class="log-item" v-for="item in logs" :key="item.id">
|
||||
<view class="log-item" v-for="item in logs" :key="item.log_id">
|
||||
<text class="log-time">{{ formatDate(item.created_at) }}</text>
|
||||
<view class="log-content">
|
||||
<text :class="logTypeBadge(item.operator_type)">{{ logTypeText(item.operator_type) }}</text>
|
||||
<text :class="actorBadge(item)">{{ actorLabel(item) }}</text>
|
||||
<text class="log-text">
|
||||
<text class="log-actor" :class="logActorClass(item.operator_type)">{{ item.operator_type === 1 ? '管理员' : '系统' }}</text>
|
||||
对 <text class="log-target">{{ item.target_type }} {{ item.target_id || '' }}</text>
|
||||
执行了 <text class="log-action">{{ item.action }}</text>
|
||||
<text class="log-action">{{ actionLabel(item.action) }}</text>
|
||||
<text class="log-detail" v-if="item.detail"> — {{ item.detail }}</text>
|
||||
</text>
|
||||
<text class="log-meta" v-if="item.ip">IP: {{ item.ip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -60,6 +60,11 @@ export default {
|
||||
return Math.ceil(this.total / this.pageSize) || 1
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options && options.device_id) {
|
||||
this.filters.device_id = options.device_id
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.loadLogs()
|
||||
},
|
||||
@@ -84,20 +89,24 @@ export default {
|
||||
this.page = p
|
||||
this.loadLogs()
|
||||
},
|
||||
logTypeText(type) {
|
||||
if (type === 1) return '管理操作'
|
||||
if (type === 2) return '系统事件'
|
||||
return '用户操作'
|
||||
actorLabel(item) {
|
||||
if (item.admin_id) return '管理员 #' + item.admin_id
|
||||
if (item.user_id) return '用户 #' + item.user_id
|
||||
return '系统'
|
||||
},
|
||||
logTypeBadge(type) {
|
||||
if (type === 1) return 'badge badge-pink'
|
||||
if (type === 2) return 'badge badge-blue'
|
||||
return 'badge badge-green'
|
||||
actorBadge(item) {
|
||||
if (item.admin_id) return 'badge badge-pink'
|
||||
if (item.user_id) return 'badge badge-green'
|
||||
return 'badge badge-blue'
|
||||
},
|
||||
logActorClass(type) {
|
||||
if (type === 1) return 'actor-admin'
|
||||
if (type === 2) return 'actor-system'
|
||||
return 'actor-user'
|
||||
actionLabel(action) {
|
||||
var map = {
|
||||
admin_login: '管理员登录', admin_device_create: '创建设备', admin_device_unbind: '后台解绑',
|
||||
admin_device_command: '下发指令', user_register: '用户注册', user_login: '用户登录',
|
||||
device_bind: '设备绑定', device_unbind: '设备解绑', treatment_sync: '护理记录同步',
|
||||
subscription_verify: '订阅核销'
|
||||
}
|
||||
return map[action] || action
|
||||
},
|
||||
formatDate: formatDateUtil,
|
||||
async onExport() {
|
||||
@@ -170,29 +179,18 @@ export default {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.log-actor {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.actor-admin {
|
||||
color: #E6508C;
|
||||
}
|
||||
|
||||
.actor-system {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.actor-user {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.log-target {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-action {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-detail {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.log-meta {
|
||||
font-size: 12px;
|
||||
color: #bbb;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<AdminLayout currentPage="/pages/subscription/index">
|
||||
<view class="toolbar">
|
||||
<view class="header-actions">
|
||||
<button class="btn-primary btn-sm" @click="showCreate = true">创建订阅</button>
|
||||
<button class="btn-default btn-sm" @click="onExport">导出报表</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -53,16 +54,15 @@
|
||||
<text class="t-td flex2">{{ item.nickname || item.user_id }}</text>
|
||||
<text class="t-td flex1">{{ planText(item.plan) }}</text>
|
||||
<text class="t-td flex1">¥{{ item.amount || '-' }}</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.started_at) }}</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.expired_at) }}</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.start_time) }}</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.expire_time) }}</text>
|
||||
<text class="t-td flex1">
|
||||
<text :class="statusBadge(item.status)">{{ statusText(item.status) }}</text>
|
||||
</text>
|
||||
<text class="t-td flex1">
|
||||
<text class="action-link">详情</text>
|
||||
<text class="action-link" v-if="item.status === 1">延期</text>
|
||||
<text class="action-link" v-if="item.status === 2">开通</text>
|
||||
<text class="action-link" v-if="item.status === 3">续费</text>
|
||||
<text class="action-link" @click="onDetail(item)">详情</text>
|
||||
<text class="action-link" v-if="item.status === 1" @click="onExtend(item)">延期</text>
|
||||
<text class="action-link" v-if="item.status === 2 || item.status === 3" @click="onRenew(item)">续费</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -166,16 +166,49 @@ export default {
|
||||
this.creating = false
|
||||
}
|
||||
},
|
||||
onDetail(item) {
|
||||
if (item.user_id) {
|
||||
uni.navigateTo({ url: '/pages/user-detail/index?user_id=' + item.user_id })
|
||||
}
|
||||
},
|
||||
onExtend(item) {
|
||||
var self = this
|
||||
uni.showModal({
|
||||
title: '延期订阅',
|
||||
content: '为用户 #' + item.user_id + ' 延期 30 天?',
|
||||
success: async function (res) {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await post('/api/v1/admin/subscriptions', {
|
||||
user_id: String(item.user_id),
|
||||
plan: item.plan || 'monthly',
|
||||
days: 30
|
||||
})
|
||||
uni.showToast({ title: '延期成功', icon: 'success' })
|
||||
self.loadSubscriptions()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onRenew(item) {
|
||||
this.createForm.user_id = String(item.user_id || '')
|
||||
this.createForm.plan = item.plan || 'monthly'
|
||||
this.createForm.days = 30
|
||||
this.showCreate = true
|
||||
},
|
||||
planText(plan) {
|
||||
const map = { monthly: '月卡', quarterly: '季卡', yearly: '年卡', trial: '试用' }
|
||||
return map[plan] || plan || '-'
|
||||
},
|
||||
statusText(status) {
|
||||
const map = { 1: '生效中', 2: '试用中', 3: '已过期' }
|
||||
const map = { 1: '生效中', 2: '已过期', 3: '已取消' }
|
||||
return map[status] || '-'
|
||||
},
|
||||
statusBadge(status) {
|
||||
const map = { 1: 'badge badge-success', 2: 'badge badge-warning', 3: 'badge badge-error' }
|
||||
const map = { 1: 'badge badge-success', 2: 'badge badge-error', 3: 'badge badge-default' }
|
||||
return map[status] || 'badge badge-default'
|
||||
},
|
||||
formatDate: formatDateShort,
|
||||
@@ -188,7 +221,7 @@ export default {
|
||||
exportCSV('subscriptions_' + new Date().toISOString().slice(0, 10) + '.csv',
|
||||
['用户', '订阅类型', '订单金额', '开始日期', '到期日期', '状态'],
|
||||
records.map(function (r) {
|
||||
return [r.nickname || r.user_id || '', planMap[r.plan] || r.plan || '', r.amount || '', r.started_at ? r.started_at.slice(0, 10) : '', r.expired_at ? r.expired_at.slice(0, 10) : '', statusMap[r.status] || '']
|
||||
return [r.nickname || r.user_id || '', planMap[r.plan] || r.plan || '', r.amount || '', r.start_time ? String(r.start_time).slice(0, 10) : '', r.expire_time ? String(r.expire_time).slice(0, 10) : '', statusMap[r.status] || '']
|
||||
})
|
||||
)
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
|
||||
在新工单中引用
屏蔽一个用户