fix: comprehensive security, quality and consistency fixes
Server: - Block startup with default JWT secrets in production - Make subscription verify admin-only (no payment integration yet) - Add device ownership validation on command/result, event, treatment/sync - Remove admin token from request body fallback - Add pageParams boundary protection (pageSize capped at 100) - Fix COS getObjectUrl to use callback-based Promise - Add settings key whitelist matching frontend fields - Add user existence check before subscription creation - Fix firmware always returning has_update:true - Replace hardcoded trial subscription with actual DB query - Extract shared utilities (limitClause, toMysqlDate, formatDate) Miniprogram: - Replace fake PD random data with placeholder - Mark client-timer treatment completions with source field - Disable mock.js - Fix BLE listener leaks (save refs, cleanup in onUnload) - Fix ble.off clearing all listeners (pass specific callback) - Add BLE disconnect detection via onBLEConnectionStateChange - Fix subscription status type consistency (number not string) - Fix scan callback accumulation in ble.js - Fix history stats accumulation across pages - Fix subscribe-success/treatment-done hardcoded values - Fix profile subscription view logic - Replace purchase flow with admin-contact modal - Add error logging in command-sync report Admin console: - Fix AdminLayout logout (require->import, logout->clearToken) - Remove all mock data from production request.js - Replace dashboard fake data with real API calls - Replace monthly_revenue with subscription_count - Fix subscription stats fallback (|| -> ??) - Add token expiry tracking (7 days) - Unify device status map and subscription status text - Fix user page record link navigation - Fix subscription createForm.user_id type handling - Add error feedback in all empty catch blocks - Remove unused remember checkbox and uview-plus dependency - Extract common CSS to shared stylesheet (-900 lines) - Extract formatDate to shared utils/format.js - Show real admin name in layout header
这个提交包含在:
@@ -41,6 +41,7 @@
|
||||
<script>
|
||||
import { get } from '../../utils/request'
|
||||
import { exportCSV } from '../../utils/export'
|
||||
import { formatDate as formatDateUtil } from '../../utils/format'
|
||||
import AdminLayout from '../../components/AdminLayout.vue'
|
||||
|
||||
export default {
|
||||
@@ -71,7 +72,9 @@ export default {
|
||||
const data = await get('/api/v1/admin/logs', params)
|
||||
this.logs = data.records || []
|
||||
this.total = data.total || 0
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
onSearch() {
|
||||
this.page = 1
|
||||
@@ -96,9 +99,7 @@ export default {
|
||||
if (type === 2) return 'actor-system'
|
||||
return 'actor-user'
|
||||
},
|
||||
formatDate(d) {
|
||||
return d ? d.slice(0, 19).replace('T', ' ') : '-'
|
||||
},
|
||||
formatDate: formatDateUtil,
|
||||
async onExport() {
|
||||
try {
|
||||
var params = { page: 1, page_size: 9999 }
|
||||
@@ -121,63 +122,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #E6508C;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
}
|
||||
@import '../../styles/common.css';
|
||||
|
||||
.log-list {
|
||||
margin-top: 8px;
|
||||
@@ -206,10 +151,6 @@ export default {
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@@ -218,11 +159,6 @@ export default {
|
||||
color: #E6508C;
|
||||
}
|
||||
|
||||
.badge-blue {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.badge-green {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
@@ -259,42 +195,4 @@ export default {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-page {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-page.active {
|
||||
background: #E6508C;
|
||||
color: #fff;
|
||||
border-color: #E6508C;
|
||||
}
|
||||
|
||||
.btn-page[disabled] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
在新工单中引用
屏蔽一个用户