feat: add user deactivation and vendor BLE protocol
这个提交包含在:
@@ -84,6 +84,14 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<view class="page-card" v-if="user">
|
||||
<view class="page-header">
|
||||
<text class="page-title">👥 用户详情 - {{ user.nickname || user.user_id }}</text>
|
||||
<view class="header-actions">
|
||||
<button class="btn-default btn-sm" v-if="user.devices && user.devices.length > 0" @click="onUnbindAll">解绑全部设备</button>
|
||||
<button class="btn-danger btn-sm" v-if="user.status !== 3" @click="onDeactivate">注销用户</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="user-profile">
|
||||
@@ -12,7 +16,10 @@
|
||||
<view class="profile-info">
|
||||
<text class="user-name">{{ user.nickname || '-' }}</text>
|
||||
<text class="user-phone">{{ user.phone || '-' }}</text>
|
||||
<text class="badge badge-success">{{ subStatusText(user.subscription_status) }}</text>
|
||||
<view class="profile-badges">
|
||||
<text :class="statusBadge(user.status)">{{ statusText(user.status) }}</text>
|
||||
<text :class="user.subscription_status === 1 ? 'badge badge-success' : 'badge badge-default'">{{ subStatusText(user.subscription_status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -35,6 +42,10 @@
|
||||
<view class="page-card" v-if="user">
|
||||
<view class="card-title">基本信息</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="info-label">用户状态</text>
|
||||
<text class="info-value"><text :class="statusBadge(user.status)">{{ statusText(user.status) }}</text></text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">绑定设备</text>
|
||||
<text class="info-value">{{ user.devices && user.devices.length > 0 ? user.devices[0].device_name || user.devices[0].device_id : '无' }}</text>
|
||||
@@ -54,6 +65,29 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-card" v-if="user && user.devices && user.devices.length > 0">
|
||||
<view class="card-title">绑定设备</view>
|
||||
<view class="data-table">
|
||||
<view class="t-header">
|
||||
<view class="t-row">
|
||||
<text class="t-th flex2">设备编号</text>
|
||||
<text class="t-th flex2">设备名称</text>
|
||||
<text class="t-th flex1">操作</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-body">
|
||||
<view class="t-row" v-for="device in user.devices" :key="device.device_id">
|
||||
<text class="t-td flex2">{{ device.device_id }}</text>
|
||||
<text class="t-td flex2">{{ device.device_name || '-' }}</text>
|
||||
<text class="t-td flex1">
|
||||
<text class="action-link" @click="onDeviceDetail(device)">详情</text>
|
||||
<text class="action-link danger-link" @click="onUnbindDevice(device)">解绑</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-card" v-if="user && user.recent_treatments">
|
||||
<view class="card-title">护理记录</view>
|
||||
<view class="data-table">
|
||||
@@ -80,7 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../utils/request'
|
||||
import { get, post } from '../utils/request'
|
||||
import { formatDateShort } from '../utils/format'
|
||||
|
||||
export default {
|
||||
@@ -115,6 +149,68 @@ export default {
|
||||
goRecords() {
|
||||
this.$emit('navigate', 'record', { user_id: this.userId })
|
||||
},
|
||||
onDeviceDetail(device) {
|
||||
this.$emit('navigate', 'device-detail', { device_id: device.device_id })
|
||||
},
|
||||
onUnbindDevice(device) {
|
||||
var self = this
|
||||
uni.showModal({
|
||||
title: '确认解绑',
|
||||
content: '确定要解绑设备 ' + device.device_id + ' 吗?',
|
||||
success: async function (res) {
|
||||
if (!res.confirm) return
|
||||
try {
|
||||
await post('/api/v1/admin/users/' + self.userId + '/unbind', { device_id: device.device_id })
|
||||
uni.showToast({ title: '解绑成功', icon: 'success' })
|
||||
self.loadUser()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '解绑失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onUnbindAll() {
|
||||
var self = this
|
||||
uni.showModal({
|
||||
title: '确认解绑',
|
||||
content: '确定要解绑该用户当前绑定的全部设备吗?',
|
||||
success: async function (res) {
|
||||
if (!res.confirm) return
|
||||
try {
|
||||
await post('/api/v1/admin/users/' + self.userId + '/unbind', {})
|
||||
uni.showToast({ title: '解绑成功', icon: 'success' })
|
||||
self.loadUser()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '解绑失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onDeactivate() {
|
||||
var self = this
|
||||
uni.showModal({
|
||||
title: '确认注销',
|
||||
content: '注销后该用户将无法继续登录,当前绑定设备也会被解绑。确定继续吗?',
|
||||
success: async function (res) {
|
||||
if (!res.confirm) return
|
||||
try {
|
||||
await post('/api/v1/admin/users/' + self.userId + '/deactivate', {})
|
||||
uni.showToast({ title: '已注销', icon: 'success' })
|
||||
self.loadUser()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '注销失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
statusText(status) {
|
||||
const map = { 1: '正常', 2: '禁用', 3: '已注销' }
|
||||
return map[status] || '未知'
|
||||
},
|
||||
statusBadge(status) {
|
||||
const map = { 1: 'badge badge-success', 2: 'badge badge-warning', 3: 'badge badge-error' }
|
||||
return map[status] || 'badge badge-default'
|
||||
},
|
||||
subStatusText(status) {
|
||||
return status === 1 ? '已订阅' : '未订阅'
|
||||
},
|
||||
@@ -144,6 +240,12 @@ export default {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
@@ -187,7 +289,9 @@ export default {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.badge {
|
||||
.profile-badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@@ -264,4 +368,8 @@ export default {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.danger-link {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<text class="t-th flex1">绑定设备</text>
|
||||
<text class="t-th flex1">护理次数</text>
|
||||
<text class="t-th flex1">订阅状态</text>
|
||||
<text class="t-th flex1">用户状态</text>
|
||||
<text class="t-th flex2">注册时间</text>
|
||||
<text class="t-th flex1">操作</text>
|
||||
</view>
|
||||
@@ -41,6 +42,9 @@
|
||||
<text class="t-td flex1">
|
||||
<text :class="subBadge(item.subscription_status)">{{ subStatusText(item.subscription_status) }}</text>
|
||||
</text>
|
||||
<text class="t-td flex1">
|
||||
<text :class="userStatusBadge(item.status)">{{ userStatusText(item.status) }}</text>
|
||||
</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.created_at) }}</text>
|
||||
<text class="t-td flex1">
|
||||
<text class="action-link" @click="onDetail(item.user_id)">详情</text>
|
||||
@@ -114,15 +118,24 @@ export default {
|
||||
subBadge(status) {
|
||||
return status === 1 ? 'badge badge-success' : 'badge badge-default'
|
||||
},
|
||||
userStatusText(status) {
|
||||
const map = { 1: '正常', 2: '禁用', 3: '已注销' }
|
||||
return map[status] || '未知'
|
||||
},
|
||||
userStatusBadge(status) {
|
||||
const map = { 1: 'badge badge-success', 2: 'badge badge-warning', 3: 'badge badge-error' }
|
||||
return map[status] || 'badge badge-default'
|
||||
},
|
||||
formatDate: formatDateShort,
|
||||
async onExport() {
|
||||
try {
|
||||
const data = await get('/api/v1/admin/users', { page: 1, page_size: 9999, keyword: this.keyword })
|
||||
const records = data.records || []
|
||||
exportCSV('users_' + new Date().toISOString().slice(0, 10) + '.csv',
|
||||
['用户ID', '昵称', '手机号', '绑定设备数', '护理次数', '订阅状态', '注册时间'],
|
||||
['用户ID', '昵称', '手机号', '绑定设备数', '护理次数', '订阅状态', '用户状态', '注册时间'],
|
||||
records.map(function (r) {
|
||||
return [r.user_id, r.nickname || '', r.phone || '', r.device_count || 0, r.treatment_count || 0, r.subscription_status === 1 ? '已订阅' : '未订阅', r.created_at ? r.created_at.slice(0, 10) : '']
|
||||
var userStatus = { 1: '正常', 2: '禁用', 3: '已注销' }[r.status] || '未知'
|
||||
return [r.user_id, r.nickname || '', r.phone || '', r.device_count || 0, r.treatment_count || 0, r.subscription_status === 1 ? '已订阅' : '未订阅', userStatus, r.created_at ? r.created_at.slice(0, 10) : '']
|
||||
})
|
||||
)
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
|
||||
在新工单中引用
屏蔽一个用户