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
这个提交包含在:
Guoguo
2026-04-28 18:32:26 -07:00
父节点 91d5937d8e
当前提交 c5f6033ccf
修改 30 个文件,包含 1509 行新增59 行删除
+42 -9
查看文件
@@ -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' })