fix: resolve multiple miniprogram bugs and extend admin console
- fix manual bind stuck at BLE scan, redirect to bind-success - add 15s timeout and stopScan to ble-connect - add stopScan method to ble.js - add back-to-home button on bind-success page - fix field name in subscribe-plans (plan -> plan_type) - fix history.js to handle created_at field - fix index.js to handle device name field - add subscription route alias in request.js - extend admin cloud function from 1 to 9 actions - add mock mode to admin-console request.js - remove unused discover page and legacy record function Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
这个提交包含在:
@@ -101,7 +101,7 @@ export default {
|
|||||||
date_from: this.dateFrom,
|
date_from: this.dateFrom,
|
||||||
date_to: this.dateTo
|
date_to: this.dateTo
|
||||||
}
|
}
|
||||||
const data = await get('/api/v1/admin/logs', params)
|
const data = await get('/api/v1/admin/records', params)
|
||||||
this.records = data.records || []
|
this.records = data.records || []
|
||||||
this.total = data.total || 0
|
this.total = data.total || 0
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|||||||
@@ -1,6 +1,89 @@
|
|||||||
const BASE_URL = 'https://api.lightmask.com'
|
const BASE_URL = 'https://api.lightmask.com'
|
||||||
|
const USE_MOCK = true
|
||||||
|
|
||||||
|
const MOCK_DATA = {
|
||||||
|
'/api/v1/admin/devices/AABBCCDDEEFF0011': {
|
||||||
|
device_id: 'AABBCCDDEEFF0011', bound_user: 'user_001', battery: 85, fw_version: '1.0.0',
|
||||||
|
activated_at: '2025-03-15T10:00:00Z', status: 2, total_usage: '45h', last_online: '2025-04-22T14:30:00Z',
|
||||||
|
binding_history: [
|
||||||
|
{ nickname: '张小姐', bound_at: '2025-03-15T10:00:00Z', unbound_at: null, status: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/api/v1/admin/dashboard': {
|
||||||
|
device_count: 12,
|
||||||
|
user_count: 86,
|
||||||
|
treatment_count: 1234,
|
||||||
|
subscription_count: 52
|
||||||
|
},
|
||||||
|
'/api/v1/admin/devices': {
|
||||||
|
records: [
|
||||||
|
{ device_id: 'AABBCCDDEEFF0011', bound_user: 'user_001', battery: 85, fw_version: '1.0.0', activated_at: '2025-03-15T10:00:00Z', status: 2 },
|
||||||
|
{ device_id: '1122334455667788', bound_user: 'user_002', battery: 60, fw_version: '1.0.0', activated_at: '2025-03-20T14:00:00Z', status: 2 },
|
||||||
|
{ device_id: 'A1B2C3D4E5F60011', bound_user: null, battery: null, fw_version: '1.0.0', activated_at: '2025-03-10T08:00:00Z', status: 1 }
|
||||||
|
],
|
||||||
|
total: 3
|
||||||
|
},
|
||||||
|
'/api/v1/admin/users': {
|
||||||
|
records: [
|
||||||
|
{ _id: 'u1', openid: 'oXXXX1', nickname: '张小姐', phone: '138****1234', created_at: '2025-03-01T08:00:00Z', subscription_status: 'yearly' },
|
||||||
|
{ _id: 'u2', openid: 'oXXXX2', nickname: '李女士', phone: '139****5678', created_at: '2025-03-05T10:00:00Z', subscription_status: 'monthly' },
|
||||||
|
{ _id: 'u3', openid: 'oXXXX3', nickname: '王先生', phone: '137****9012', created_at: '2025-03-10T12:00:00Z', subscription_status: 'trial' }
|
||||||
|
],
|
||||||
|
total: 3
|
||||||
|
},
|
||||||
|
'/api/v1/admin/users/u1': {
|
||||||
|
_id: 'u1', openid: 'oXXXX1', nickname: '张小姐', phone: '138****1234',
|
||||||
|
created_at: '2025-03-01T08:00:00Z', subscription_type: 'yearly', subscription_status: 'active',
|
||||||
|
subscription_expire: '2026-03-01T08:00:00Z', treatment_count: 28, total_duration: '14h',
|
||||||
|
total_spent: 899, devices: [{ device_id: 'AABBCCDDEEFF0011', device_name: '我的光面膜' }],
|
||||||
|
recent_treatments: [
|
||||||
|
{ started_at: '2025-04-20T10:00:00Z', total_duration_ms: 1200000, device_id: 'AABBCCDDEEFF0011' },
|
||||||
|
{ started_at: '2025-04-18T09:00:00Z', total_duration_ms: 900000, device_id: 'AABBCCDDEEFF0011' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/api/v1/admin/subscriptions': {
|
||||||
|
records: [
|
||||||
|
{ id: 's1', user_id: 'user_001', plan: 'yearly', amount: 899, started_at: '2025-03-01T00:00:00Z', expired_at: '2026-03-01T00:00:00Z', status: 1 },
|
||||||
|
{ id: 's2', user_id: 'user_002', plan: 'monthly', amount: 99, started_at: '2025-04-01T00:00:00Z', expired_at: '2025-05-01T00:00:00Z', status: 1 },
|
||||||
|
{ id: 's3', user_id: 'user_003', plan: 'trial', amount: '-', started_at: '2025-03-10T00:00:00Z', expired_at: '2025-03-17T00:00:00Z', status: 3 }
|
||||||
|
],
|
||||||
|
total: 3,
|
||||||
|
stats: { monthly_count: 15, yearly_count: 30, trial_count: 7, monthly_revenue: 45890 }
|
||||||
|
},
|
||||||
|
'/api/v1/admin/records': {
|
||||||
|
records: [
|
||||||
|
{ started_at: '2025-04-20T10:00:00Z', total_duration_ms: 1200000, device_id: 'AABBCCDDEEFF0011', openid: 'oXXXX1' },
|
||||||
|
{ started_at: '2025-04-19T15:00:00Z', total_duration_ms: 900000, device_id: '1122334455667788', openid: 'oXXXX2' }
|
||||||
|
],
|
||||||
|
total: 2
|
||||||
|
},
|
||||||
|
'/api/v1/admin/logs': {
|
||||||
|
records: [
|
||||||
|
{ created_at: '2025-04-20T10:05:00Z', action: 'treatment_complete', detail: '用户张小姐完成护理', openid: 'oXXXX1' },
|
||||||
|
{ created_at: '2025-04-20T09:00:00Z', action: 'device_bind', detail: '设备AABBCCDDEEFF0011绑定', openid: 'oXXXX1' }
|
||||||
|
],
|
||||||
|
total: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMockData(url, data) {
|
||||||
|
if (url.startsWith('/api/v1/admin/users/') && !url.includes('users?')) {
|
||||||
|
const id = url.split('/').pop()
|
||||||
|
return MOCK_DATA['/api/v1/admin/users/u1'] || { _id: id, nickname: '未知用户' }
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(MOCK_DATA)) {
|
||||||
|
if (url.includes(key)) return MOCK_DATA[key]
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
function request(options) {
|
function request(options) {
|
||||||
|
if (USE_MOCK) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => resolve(getMockData(options.url, options.data)), 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const token = uni.getStorageSync('admin_token')
|
const token = uni.getStorageSync('admin_token')
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
var db = require('../common/db')
|
|
||||||
var RESPONSE = require('../common/response').RESPONSE
|
|
||||||
var ERROR_CODES = require('../common/response').ERROR_CODES
|
|
||||||
|
|
||||||
exports.main_handler = async function (event, context) {
|
|
||||||
try {
|
|
||||||
var token = extractToken(event)
|
|
||||||
if (!token) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.UNAUTHORIZED.code, ERROR_CODES.UNAUTHORIZED.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await verifyToken(token)
|
|
||||||
if (!user) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.TOKEN_EXPIRED.code, ERROR_CODES.TOKEN_EXPIRED.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = parseBody(event)
|
|
||||||
|
|
||||||
switch (event.path || event.action) {
|
|
||||||
case '/record/sync':
|
|
||||||
return await syncRecord(user, body)
|
|
||||||
case '/record/list':
|
|
||||||
return await listRecords(user, body)
|
|
||||||
case '/record/detail':
|
|
||||||
return await getRecordDetail(user, body)
|
|
||||||
default:
|
|
||||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '未知操作')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.INTERNAL_ERROR.code, err.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function syncRecord(user, body) {
|
|
||||||
if (!body.device_id || !body.started_at || !body.ended_at) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '参数不完整')
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbConfig = getDbConfig()
|
|
||||||
|
|
||||||
var sessionResult = await db.query(
|
|
||||||
'INSERT INTO sessions (user_id, device_id, status, started_at, ended_at, created_at, updated_at) ' +
|
|
||||||
'VALUES (?, ?, 2, ?, ?, NOW(), NOW())',
|
|
||||||
[user.id, body.device_id, body.started_at, body.ended_at],
|
|
||||||
dbConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
var sessionId = sessionResult.insertId
|
|
||||||
|
|
||||||
await db.query(
|
|
||||||
'INSERT INTO treatment_records ' +
|
|
||||||
'(session_id, user_id, device_id, duration_seconds, mode, result_summary, sync_status, synced_at, started_at, ended_at, created_at, updated_at) ' +
|
|
||||||
'VALUES (?, ?, ?, ?, ?, ?, 2, NOW(), ?, ?, NOW(), NOW())',
|
|
||||||
[
|
|
||||||
sessionId,
|
|
||||||
user.id,
|
|
||||||
body.device_id,
|
|
||||||
body.duration_seconds || null,
|
|
||||||
body.mode || null,
|
|
||||||
body.result_summary || null,
|
|
||||||
body.started_at,
|
|
||||||
body.ended_at
|
|
||||||
],
|
|
||||||
dbConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
return RESPONSE.success({ session_id: sessionId })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listRecords(user, body) {
|
|
||||||
var dbConfig = getDbConfig()
|
|
||||||
var limit = parseInt(body.limit) || 20
|
|
||||||
var offset = parseInt(body.offset) || 0
|
|
||||||
|
|
||||||
var records = await db.query(
|
|
||||||
'SELECT * FROM treatment_records WHERE user_id = ? ORDER BY started_at DESC LIMIT ? OFFSET ?',
|
|
||||||
[user.id, limit, offset],
|
|
||||||
dbConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
return RESPONSE.success({ list: records })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getRecordDetail(user, body) {
|
|
||||||
if (!body.record_id) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '缺少记录ID')
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbConfig = getDbConfig()
|
|
||||||
|
|
||||||
var records = await db.query(
|
|
||||||
'SELECT * FROM treatment_records WHERE id = ? AND user_id = ? LIMIT 1',
|
|
||||||
[body.record_id, user.id],
|
|
||||||
dbConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
if (records.length === 0) {
|
|
||||||
return RESPONSE.error(ERROR_CODES.NOT_FOUND.code, ERROR_CODES.NOT_FOUND.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return RESPONSE.success(records[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractToken(event) {
|
|
||||||
var header = event.headers || {}
|
|
||||||
var auth = header['Authorization'] || header['authorization'] || ''
|
|
||||||
if (auth.startsWith('Bearer ')) {
|
|
||||||
return auth.substring(7)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verifyToken(token) {
|
|
||||||
return { id: 1, openid: 'placeholder' }
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseBody(event) {
|
|
||||||
if (event.body) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(event.body)
|
|
||||||
} catch (e) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return event.queryString || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDbConfig() {
|
|
||||||
return {
|
|
||||||
host: process.env.DB_HOST || 'localhost',
|
|
||||||
port: parseInt(process.env.DB_PORT || '3306'),
|
|
||||||
user: process.env.DB_USER || 'root',
|
|
||||||
password: process.env.DB_PASSWORD || '',
|
|
||||||
database: process.env.DB_NAME || 'hox'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,184 @@
|
|||||||
const cloud = require('wx-server-sdk')
|
const cloud = require('wx-server-sdk')
|
||||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||||
const db = cloud.database()
|
const db = cloud.database()
|
||||||
|
const _ = db.command
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
exports.main = async (event, context) => {
|
exports.main = async (event, context) => {
|
||||||
const { action } = event
|
const { action, data } = event
|
||||||
|
|
||||||
if (action === 'dashboard') {
|
if (action === 'dashboard') {
|
||||||
const deviceCount = await db.collection('bindings').where({ status: 'active' }).count()
|
const [deviceCount, userCount, treatmentCount, subCount] = await Promise.all([
|
||||||
const userCount = await db.collection('users').count()
|
db.collection('bindings').where({ status: 'active' }).count(),
|
||||||
const treatmentCount = await db.collection('treatment_records').count()
|
db.collection('users').count(),
|
||||||
|
db.collection('treatment_records').count(),
|
||||||
|
db.collection('subscriptions').where({ status: 'active' }).count()
|
||||||
|
])
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
data: {
|
data: {
|
||||||
device_count: deviceCount.total,
|
device_count: deviceCount.total,
|
||||||
user_count: userCount.total,
|
user_count: userCount.total,
|
||||||
treatment_count: treatmentCount.total
|
treatment_count: treatmentCount.total,
|
||||||
|
subscription_count: subCount.total
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === 'devices') {
|
||||||
|
const { page = 1, page_size = PAGE_SIZE, keyword } = data || {}
|
||||||
|
let query = db.collection('bindings')
|
||||||
|
const conditions = { status: 'active' }
|
||||||
|
if (keyword) {
|
||||||
|
conditions.device_id = db.RegExp({ regexp: keyword, options: 'i' })
|
||||||
|
}
|
||||||
|
query = query.where(conditions)
|
||||||
|
const [totalRes, records] = await Promise.all([
|
||||||
|
query.count(),
|
||||||
|
query.orderBy('bind_time', 'desc').skip((page - 1) * page_size).limit(page_size).get()
|
||||||
|
])
|
||||||
|
const devices = records.data.map(b => ({
|
||||||
|
device_id: b.device_id,
|
||||||
|
bound_user: b.openid ? b.openid.slice(-8) : '-',
|
||||||
|
battery: null,
|
||||||
|
fw_version: '1.0.0',
|
||||||
|
activated_at: b.bind_time,
|
||||||
|
status: 2
|
||||||
|
}))
|
||||||
|
return { code: 0, data: { records: devices, total: totalRes.total } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'device_detail') {
|
||||||
|
const { device_id } = data
|
||||||
|
const bindings = await db.collection('bindings').where({ device_id, status: 'active' }).get()
|
||||||
|
if (bindings.data.length === 0) return { code: -1, message: '设备不存在' }
|
||||||
|
const b = bindings.data[0]
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
device_id: b.device_id,
|
||||||
|
bound_user: b.openid ? b.openid.slice(-8) : '-',
|
||||||
|
battery: null,
|
||||||
|
fw_version: '1.0.0',
|
||||||
|
activated_at: b.bind_time,
|
||||||
|
status: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'device_unbind') {
|
||||||
|
const { device_id } = data
|
||||||
|
await db.collection('bindings').where({ device_id, status: 'active' }).update({
|
||||||
|
data: { status: 'inactive', unbind_time: db.serverDate() }
|
||||||
|
})
|
||||||
|
return { code: 0, data: {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'users') {
|
||||||
|
const { page = 1, page_size = PAGE_SIZE, keyword } = data || {}
|
||||||
|
let conditions = {}
|
||||||
|
if (keyword) {
|
||||||
|
conditions = _.or([
|
||||||
|
{ openid: db.RegExp({ regexp: keyword, options: 'i' }) },
|
||||||
|
{ nickname: db.RegExp({ regexp: keyword, options: 'i' }) }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const [totalRes, records] = await Promise.all([
|
||||||
|
db.collection('users').where(conditions).count(),
|
||||||
|
db.collection('users').where(conditions).orderBy('created_at', 'desc').skip((page - 1) * page_size).limit(page_size).get()
|
||||||
|
])
|
||||||
|
return { code: 0, data: { records: records.data, total: totalRes.total } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'user_detail') {
|
||||||
|
const { user_id } = data
|
||||||
|
const userRes = await db.collection('users').doc(user_id).get()
|
||||||
|
const user = userRes.data
|
||||||
|
const [bindings, subs, treatments] = await Promise.all([
|
||||||
|
db.collection('bindings').where({ openid: user.openid, status: 'active' }).get(),
|
||||||
|
db.collection('subscriptions').where({ openid: user.openid }).orderBy('start_time', 'desc').limit(1).get(),
|
||||||
|
db.collection('treatment_records').where({ openid: user.openid }).orderBy('created_at', 'desc').limit(5).get()
|
||||||
|
])
|
||||||
|
const devices = bindings.data.map(b => ({ device_id: b.device_id, device_name: '我的光面膜' }))
|
||||||
|
const sub = subs.data[0] || {}
|
||||||
|
const treatmentCount = await db.collection('treatment_records').where({ openid: user.openid }).count()
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
...user,
|
||||||
|
user_id: user._id,
|
||||||
|
devices,
|
||||||
|
subscription_type: sub.plan_type || 'none',
|
||||||
|
subscription_status: sub.status || 'none',
|
||||||
|
subscription_expire: sub.end_time || null,
|
||||||
|
treatment_count: treatmentCount.total,
|
||||||
|
recent_treatments: treatments.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'subscriptions') {
|
||||||
|
const { page = 1, page_size = PAGE_SIZE, tab } = data || {}
|
||||||
|
let conditions = {}
|
||||||
|
if (tab && tab !== 'all') {
|
||||||
|
if (tab === 'expired') {
|
||||||
|
conditions = { status: 'expired' }
|
||||||
|
} else {
|
||||||
|
conditions = { plan_type: tab, status: 'active' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [totalRes, records] = await Promise.all([
|
||||||
|
db.collection('subscriptions').where(conditions).count(),
|
||||||
|
db.collection('subscriptions').where(conditions).orderBy('start_time', 'desc').skip((page - 1) * page_size).limit(page_size).get()
|
||||||
|
])
|
||||||
|
const monthlyCount = await db.collection('subscriptions').where({ plan_type: 'monthly', status: 'active' }).count()
|
||||||
|
const yearlyCount = await db.collection('subscriptions').where({ plan_type: 'yearly', status: 'active' }).count()
|
||||||
|
const trialCount = await db.collection('subscriptions').where({ plan_type: 'trial', status: 'active' }).count()
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
records: records.data.map(s => ({
|
||||||
|
...s,
|
||||||
|
id: s._id,
|
||||||
|
user_id: s.openid ? s.openid.slice(-8) : '-',
|
||||||
|
plan: s.plan_type,
|
||||||
|
amount: s.price || '-',
|
||||||
|
started_at: s.start_time,
|
||||||
|
expired_at: s.end_time,
|
||||||
|
status: s.status === 'active' ? 1 : 3
|
||||||
|
})),
|
||||||
|
total: totalRes.total,
|
||||||
|
stats: {
|
||||||
|
monthly_count: monthlyCount.total,
|
||||||
|
yearly_count: yearlyCount.total,
|
||||||
|
trial_count: trialCount.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'records') {
|
||||||
|
const { page = 1, page_size = PAGE_SIZE, user_id } = data || {}
|
||||||
|
let conditions = {}
|
||||||
|
if (user_id) {
|
||||||
|
const userRes = await db.collection('users').doc(user_id).get()
|
||||||
|
conditions = { openid: userRes.data.openid }
|
||||||
|
}
|
||||||
|
const [totalRes, records] = await Promise.all([
|
||||||
|
db.collection('treatment_records').where(conditions).count(),
|
||||||
|
db.collection('treatment_records').where(conditions).orderBy('created_at', 'desc').skip((page - 1) * page_size).limit(page_size).get()
|
||||||
|
])
|
||||||
|
return { code: 0, data: { records: records.data, total: totalRes.total } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'logs') {
|
||||||
|
const { page = 1, page_size = PAGE_SIZE } = data || {}
|
||||||
|
const [totalRes, records] = await Promise.all([
|
||||||
|
db.collection('operation_logs').count(),
|
||||||
|
db.collection('operation_logs').orderBy('created_at', 'desc').skip((page - 1) * page_size).limit(page_size).get()
|
||||||
|
])
|
||||||
|
return { code: 0, data: { records: records.data, total: totalRes.total } }
|
||||||
|
}
|
||||||
|
|
||||||
return { code: -1, message: '未知操作' }
|
return { code: -1, message: '未知操作' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ Page({
|
|||||||
|
|
||||||
onStart: function () {
|
onStart: function () {
|
||||||
wx.redirectTo({ url: '/pages/wear-check/wear-check' })
|
wx.redirectTo({ url: '/pages/wear-check/wear-check' })
|
||||||
|
},
|
||||||
|
|
||||||
|
onGoHome: function () {
|
||||||
|
wx.reLaunch({ url: '/pages/index/index' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,5 +19,6 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="btn-primary" bindtap="onStart">开始使用</button>
|
<button class="btn-primary" bindtap="onStart">开始使用</button>
|
||||||
|
<button class="btn-secondary" bindtap="onGoHome">返回首页</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
var ble = require('../../services/ble')
|
var ble = require('../../services/ble')
|
||||||
var app = getApp()
|
var app = getApp()
|
||||||
|
var SCAN_TIMEOUT = 15000
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
@@ -19,19 +20,32 @@ Page({
|
|||||||
this.startBleConnect()
|
this.startBleConnect()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUnload: function () {
|
||||||
|
clearTimeout(this._scanTimer)
|
||||||
|
ble.stopScan()
|
||||||
|
},
|
||||||
|
|
||||||
startBleConnect: function () {
|
startBleConnect: function () {
|
||||||
var self = this
|
var self = this
|
||||||
self.setData({ state: 'scanning', error: '' })
|
self.setData({ state: 'scanning', error: '' })
|
||||||
|
|
||||||
|
self._scanTimer = setTimeout(function () {
|
||||||
|
ble.stopScan()
|
||||||
|
self.setData({ state: 'error', error: '搜索超时,请确认设备已开机并在附近' })
|
||||||
|
}, SCAN_TIMEOUT)
|
||||||
|
|
||||||
ble.startScan({
|
ble.startScan({
|
||||||
onFound: function (device) {
|
onFound: function (device) {
|
||||||
|
clearTimeout(self._scanTimer)
|
||||||
self.setData({ state: 'connecting' })
|
self.setData({ state: 'connecting' })
|
||||||
},
|
},
|
||||||
onConnected: function (device) {
|
onConnected: function (device) {
|
||||||
|
clearTimeout(self._scanTimer)
|
||||||
self.setData({ state: 'binding' })
|
self.setData({ state: 'binding' })
|
||||||
self.doBind()
|
self.doBind()
|
||||||
},
|
},
|
||||||
onError: function (err) {
|
onError: function (err) {
|
||||||
|
clearTimeout(self._scanTimer)
|
||||||
self.setData({ state: 'error', error: err.msg || '连接失败' })
|
self.setData({ state: 'error', error: err.msg || '连接失败' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
Page({
|
|
||||||
data: {
|
|
||||||
articles: []
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoad: function () {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"navigationBarTitleText": "发现"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<view class="container">
|
|
||||||
<view class="section-title">发现</view>
|
|
||||||
<view class="text-center text-muted mt-20">暂无内容</view>
|
|
||||||
</view>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -54,7 +54,7 @@ Page({
|
|||||||
r.duration_text = durationMin + '分钟'
|
r.duration_text = durationMin + '分钟'
|
||||||
r.region_names = ble.getRegionName(r.regions || 0).join('、')
|
r.region_names = ble.getRegionName(r.regions || 0).join('、')
|
||||||
r.wavelength_name = ble.getWavelengthName(r.wavelength || 2)
|
r.wavelength_name = ble.getWavelengthName(r.wavelength || 2)
|
||||||
r.date_text = self.formatDate(r.start_time)
|
r.date_text = self.formatDate(r.start_time || r.created_at)
|
||||||
|
|
||||||
if (r.start_time && new Date(r.start_time) >= monthStart) {
|
if (r.start_time && new Date(r.start_time) >= monthStart) {
|
||||||
monthCount++
|
monthCount++
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Page({
|
|||||||
self.setData({ hasDevice: devices.length > 0 })
|
self.setData({ hasDevice: devices.length > 0 })
|
||||||
if (devices.length > 0) {
|
if (devices.length > 0) {
|
||||||
self.setData({
|
self.setData({
|
||||||
deviceName: devices[0].device_name || '光子美容仪',
|
deviceName: devices[0].name || devices[0].device_id || '我的光面膜',
|
||||||
deviceInfo: devices[0]
|
deviceInfo: devices[0]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,45 @@ Page({
|
|||||||
wx.navigateTo({ url: '/pages/scan/scan' })
|
wx.navigateTo({ url: '/pages/scan/scan' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onManageDevice: function () {
|
||||||
|
var self = this
|
||||||
|
wx.showActionSheet({
|
||||||
|
itemList: ['解绑当前设备'],
|
||||||
|
success: function (res) {
|
||||||
|
if (res.tapIndex === 0) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '确认解绑',
|
||||||
|
content: '解绑后将无法使用该设备,确定要解绑吗?',
|
||||||
|
success: function (modalRes) {
|
||||||
|
if (modalRes.confirm) {
|
||||||
|
self.doUnbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
doUnbind: function () {
|
||||||
|
var self = this
|
||||||
|
wx.showLoading({ title: '解绑中...' })
|
||||||
|
ble.disconnect()
|
||||||
|
http.post('/api/v1/device/unbind', {}).then(function () {
|
||||||
|
wx.hideLoading()
|
||||||
|
self.setData({
|
||||||
|
hasDevice: false,
|
||||||
|
deviceName: '',
|
||||||
|
deviceInfo: null,
|
||||||
|
connected: false
|
||||||
|
})
|
||||||
|
wx.showToast({ title: '已解绑', icon: 'success' })
|
||||||
|
}).catch(function (err) {
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.showToast({ title: err.message || '解绑失败', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
onStartTreatment: function () {
|
onStartTreatment: function () {
|
||||||
if (!this.data.subscription || this.data.subscription.status === 0) {
|
if (!this.data.subscription || this.data.subscription.status === 0) {
|
||||||
wx.navigateTo({ url: '/pages/subscribe-prompt/subscribe-prompt' })
|
wx.navigateTo({ url: '/pages/subscribe-prompt/subscribe-prompt' })
|
||||||
|
|||||||
@@ -20,6 +20,6 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="btn-primary" bindtap="onStartTreatment">开始护理</button>
|
<button class="btn-primary" bindtap="onStartTreatment">开始护理</button>
|
||||||
<button class="btn-secondary" bindtap="onAddDevice">设备管理</button>
|
<button class="btn-secondary" bindtap="onManageDevice">设备管理</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -47,16 +47,22 @@ Page({
|
|||||||
|
|
||||||
bindDevice: function (deviceId) {
|
bindDevice: function (deviceId) {
|
||||||
var self = this
|
var self = this
|
||||||
|
self.setData({ scanning: true, error: '' })
|
||||||
http.post('/api/v1/device/bind', { device_id: deviceId }).then(function (data) {
|
http.post('/api/v1/device/bind', { device_id: deviceId }).then(function (data) {
|
||||||
self.setData({ scanning: false })
|
self.setData({ scanning: false })
|
||||||
wx.navigateTo({
|
wx.redirectTo({
|
||||||
url: '/pages/ble-connect/ble-connect?device_id=' + deviceId + '&bind_token=' + data.bind_token
|
url: '/pages/bind-success/bind-success?device_id=' + deviceId
|
||||||
})
|
})
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
self.setData({
|
self.setData({ scanning: false })
|
||||||
scanning: false,
|
if (err && err.code === 2001) {
|
||||||
error: err.message || '绑定失败'
|
wx.showToast({ title: '已绑定设备', icon: 'none' })
|
||||||
})
|
setTimeout(function () {
|
||||||
|
wx.navigateBack()
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
self.setData({ error: err.message || '绑定失败' })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ Page({
|
|||||||
self.setData({ purchasing: true })
|
self.setData({ purchasing: true })
|
||||||
|
|
||||||
http.post('/api/v1/subscription/purchase', {
|
http.post('/api/v1/subscription/purchase', {
|
||||||
plan: self.data.selected,
|
plan_type: self.data.selected,
|
||||||
payment_method: 'wechat'
|
payment_method: 'wechat'
|
||||||
}).then(function (data) {
|
}).then(function (data) {
|
||||||
return http.post('/api/v1/subscription/verify', {
|
return http.post('/api/v1/subscription/verify', {
|
||||||
order_id: data.order_id,
|
order_id: data.order_id,
|
||||||
plan: self.data.selected
|
plan_type: self.data.selected
|
||||||
})
|
})
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
self.setData({ purchasing: false })
|
self.setData({ purchasing: false })
|
||||||
|
|||||||
@@ -538,6 +538,11 @@ function unbindDevice(userId) {
|
|||||||
return writeCommand(CMD.UNBIND, payload)
|
return writeCommand(CMD.UNBIND, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stopScan() {
|
||||||
|
wx.stopBluetoothDevicesDiscovery({})
|
||||||
|
wx.offBluetoothDeviceFound()
|
||||||
|
}
|
||||||
|
|
||||||
function disconnect() {
|
function disconnect() {
|
||||||
if (_deviceId) {
|
if (_deviceId) {
|
||||||
wx.closeBLEConnection({ deviceId: _deviceId })
|
wx.closeBLEConnection({ deviceId: _deviceId })
|
||||||
@@ -589,6 +594,7 @@ module.exports = {
|
|||||||
isConnected: isConnected,
|
isConnected: isConnected,
|
||||||
getDeviceId: getDeviceId,
|
getDeviceId: getDeviceId,
|
||||||
startScan: startScan,
|
startScan: startScan,
|
||||||
|
stopScan: stopScan,
|
||||||
connect: connect,
|
connect: connect,
|
||||||
disconnect: disconnect,
|
disconnect: disconnect,
|
||||||
on: on,
|
on: on,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ var FUNC_MAP = {
|
|||||||
'/api/v1/device/unbind': { fn: 'device', action: 'unbind' },
|
'/api/v1/device/unbind': { fn: 'device', action: 'unbind' },
|
||||||
'/api/v1/device/list': { fn: 'device', action: 'list' },
|
'/api/v1/device/list': { fn: 'device', action: 'list' },
|
||||||
'/api/v1/device/detail': { fn: 'device', action: 'detail' },
|
'/api/v1/device/detail': { fn: 'device', action: 'detail' },
|
||||||
|
'/api/v1/subscription': { fn: 'subscription', action: 'status' },
|
||||||
'/api/v1/subscription/status': { fn: 'subscription', action: 'status' },
|
'/api/v1/subscription/status': { fn: 'subscription', action: 'status' },
|
||||||
'/api/v1/subscription/purchase': { fn: 'subscription', action: 'purchase' },
|
'/api/v1/subscription/purchase': { fn: 'subscription', action: 'purchase' },
|
||||||
'/api/v1/subscription/verify': { fn: 'subscription', action: 'verify' },
|
'/api/v1/subscription/verify': { fn: 'subscription', action: 'verify' },
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户